Skip to content

Commit e58bdf3

Browse files
thephezclaude
andauthored
feat(dip-18): add distinct type byte prefixes (#176)
* feat(dip-18): use type bytes 0xb0/0x80 for P2PKH/P2SH Change type bytes to produce distinct address prefixes: - P2PKH: 0xb0 -> addresses start with "k" - P2SH: 0x80 -> addresses start with "s" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(dip-18): document type byte visual identification and reserved ranges - Explain why type byte values were chosen - Add reserved type byte ranges to prevent future collisions - Add type_byte_calc.py utility for finding type bytes that map to desired characters --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7073bf1 commit e58bdf3

3 files changed

Lines changed: 128 additions & 27 deletions

File tree

dip-0018.md

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Encoding a Dash Platform address uses the bech32m format defined in [BIP-350](ht
6161
Given:
6262

6363
* `hrp`: the network human-readable prefix (e.g., `evo`, `tevo`)
64-
* `type_byte`: `0x00` for P2PKH or `0x01` for P2SH
64+
* `type_byte`: `0xb0` for P2PKH or `0x80` for P2SH
6565
* `hash160`: a 20-byte `HASH160(pubkey or script)` value
6666

6767
The address MUST be encoded as follows:
@@ -77,7 +77,7 @@ Decoders MUST reverse these steps and MUST verify:
7777
* Checksum validity (per [BIP-350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki#appendix-checksum-design--properties)),
7878
* HRP correctness for the target network,
7979
* Data-part length requirements,
80-
* The type byte is either `0x00` or `0x01`.
80+
* The type byte is either `0xb0` or `0x80`.
8181

8282
#### Structure
8383

@@ -89,7 +89,7 @@ All Platform addresses are encoded as:
8989

9090
* `<HRP>` is network-specific (see table).
9191
* `<data-part>` contains:
92-
* one type byte (`0x00` P2PKH, `0x01` P2SH), followed by
92+
* one type byte (`0xb0` P2PKH, `0x80` P2SH), followed by
9393
* 20-byte HASH160 payload encoded as 5-bit groups via bech32 rules.
9494

9595
The checksum MUST be calculated using the [bech32m algorithm as defined in BIP-350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki#bech32m).
@@ -120,10 +120,28 @@ The following values define the canonical human-readable prefixes (HRPs) and typ
120120

121121
Type byte meaning:
122122

123-
| Address Type | Type byte |
124-
| -------------- | --------- |
125-
| Platform P2PKH | `0x00` |
126-
| Platform P2SH | `0x01` |
123+
| Address Type | Type byte | First data character |
124+
| -------------- | --------- | -------------------- |
125+
| Platform P2PKH | `0xb0` | `k` |
126+
| Platform P2SH | `0x80` | `s` |
127+
128+
The type bytes `0xb0` and `0x80` were specifically chosen so that the first character after the `1` separator is always `k` for P2PKH and `s` for P2SH. This provides immediate visual identification of address type:
129+
130+
* P2PKH addresses always match pattern `evo1k...` (mainnet) or `tevo1k...` (testnet)
131+
* P2SH addresses always match pattern `evo1s...` (mainnet) or `tevo1s...` (testnet)
132+
133+
This mapping is guaranteed because the bech32 `convertbits()` function deterministically converts the first 5 bits of the type byte to the first data character. The high 5 bits of `0xb0` (binary `10110...`) map to index 22 in the bech32 alphabet (`k`), while the high 5 bits of `0x80` (binary `10000...`) map to index 16 (`s`).
134+
135+
#### Reserved Type Byte Ranges
136+
137+
To preserve visual identification, future address types MUST NOT reuse the first-character mappings of existing types. The first data character is determined by the high 5 bits of the type byte:
138+
139+
| High 5 bits | First char | Reserved for | Type byte range |
140+
| ----------- | ---------- | --------------- | --------------- |
141+
| `10110` | `k` | P2PKH | `0xb0``0xb7` |
142+
| `10000` | `s` | P2SH | `0x80``0x87` |
143+
144+
Future address types SHOULD select type bytes that produce unique first characters not already in use. The reference script [`dip-0018/type_byte_calc.py`](dip-0018/type_byte_calc.py) can be used to find type bytes that map to a desired character.
127145

128146
### Validation
129147

@@ -133,7 +151,7 @@ A Platform address is valid if:
133151
2. HRP matches expected network.
134152
3. bech32m checksum verifies.
135153
4. Payload decodes to exactly 21 bytes.
136-
5. `payload[0]` is `0x00` or `0x01`.
154+
5. `payload[0]` is `0xb0` or `0x80`.
137155

138156
Wallets MUST reject Platform addresses when constructing Dash Core chain scripts and SHOULD present a clear warning if a user attempts to mix layers.
139157

@@ -144,10 +162,12 @@ Wallets MUST reject Platform addresses when constructing Dash Core chain scripts
144162
* Wallets MUST treat HRP as the network selector.
145163
* Software wallets SHOULD label Platform balances separately from Core chain balances and SHOULD avoid auto-pasting Platform addresses into Core chain contexts.
146164
* Wallets SHOULD derive payloads via [DIP-17](dip-0017.md) and then encode using these rules; no alternative prefixes are allowed.
147-
* Hardware wallets MUST validate the HRP to confirm network identity and MUST enforce the type byte (`0x00` or `0x01`). Devices MUST display a user-facing descriptor: “Dash Platform address” for P2PKH and “Dash Platform script address” for P2SH.
165+
* Hardware wallets MUST validate the HRP to confirm network identity and MUST enforce the type byte (`0xb0` or `0x80`). Devices MUST display a user-facing descriptor: “Dash Platform address” for P2PKH and “Dash Platform script address” for P2SH.
148166

149167
## Rationale
150168

169+
### Why Bech32m?
170+
151171
Bech32m was chosen over Base58Check because it:
152172

153173
* Improves checksum strength
@@ -156,6 +176,14 @@ Bech32m was chosen over Base58Check because it:
156176
* Clearly separates networks using HRPs
157177
* Future-proofs script or address extensions
158178

179+
### Why a Type Byte?
180+
181+
Platform addresses embed a type byte as the first byte of the payload. This approach was chosen because:
182+
183+
* Platform addresses distinguish between *address types* (P2PKH vs P2SH) for wallet display purposes, not script interpretation versions for consensus (e.g., P2WPKH/P2WSH vs Taproot in Bitcoin). Both address types use identical HASH160 payloads with the same cryptographic properties.
184+
* A full byte in the payload is straightforward to encode and decode without special handling separate from the hash data.
185+
* Specific type byte values improve human identification of address type by guaranteeing that P2PKH and P2SH addresses have a unique starting characters.
186+
159187
## Backwards Compatibility
160188

161189
No impact on Core chain addresses. Platform P2PKH/P2SH prefixes are new and cannot be misinterpreted as existing Dash formats. Seeds and derivation ([DIP-17](dip-0017.md)) are unchanged.
@@ -171,7 +199,7 @@ function encode_platform_address(hash160, type, network):
171199
if len(hash160) != 20:
172200
error("invalid hash160 length")
173201
174-
type_byte = 0x00 if type=="p2pkh" else 0x01 if type=="p2sh" else error()
202+
type_byte = 0xb0 if type=="p2pkh" else 0x80 if type=="p2sh" else error()
175203
176204
hrp = {
177205
"mainnet": "evo",
@@ -209,9 +237,9 @@ function decode_platform_address(addr):
209237
type_byte = payload[0]
210238
hash160 = payload[1:21]
211239
212-
if type_byte == 0x00:
240+
if type_byte == 0xb0:
213241
addr_type = "p2pkh"
214-
else if type_byte == 0x01:
242+
else if type_byte == 0x80:
215243
addr_type = "p2sh"
216244
else:
217245
error("unknown type byte")
@@ -243,16 +271,16 @@ The HASH160 payloads in the following tables are derived from the mnemonic and p
243271

244272
| Vector | Payload (HASH160) | Mainnet (`evo`) | Testnet (`tevo`) |
245273
| ------ | ------------------------------------------ | ------------------------------------------------ | ------------------------------------------------- |
246-
| 1 | `f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525` | `evo1qrma5z3ttj75la4m93xcndna9ullamq9y57vzaqm` | `tevo1qrma5z3ttj75la4m93xcndna9ullamq9y5ch5x7d` |
247-
| 2 | `a5ff0046217fd1c7d238e3e146cc5bfd90832a7e` | `evo1qzjl7qzxy9lar37j8r37z3kvt07epqe20c30csp9` | `tevo1qzjl7qzxy9lar37j8r37z3kvt07epqe20ch5wtln` |
248-
| 3 | `6d92674fd64472a3dfcfc3ebcfed7382bf699d7b` | `evo1qpkeye606ez89g7lelp7hnldwwpt76va0vkmt0fv` | `tevo1qpkeye606ez89g7lelp7hnldwwpt76va0vsqa5h6` |
274+
| 1 | `f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525` | `evo1krma5z3ttj75la4m93xcndna9ullamq9y59dj9x7` | `tevo1krma5z3ttj75la4m93xcndna9ullamq9y5rky7cg` |
275+
| 2 | `a5ff0046217fd1c7d238e3e146cc5bfd90832a7e` | `evo1kzjl7qzxy9lar37j8r37z3kvt07epqe20c2wgg8q` | `tevo1kzjl7qzxy9lar37j8r37z3kvt07epqe20cv47nek` |
276+
| 3 | `6d92674fd64472a3dfcfc3ebcfed7382bf699d7b` | `evo1kpkeye606ez89g7lelp7hnldwwpt76va0vd6mh0f` | `tevo1kpkeye606ez89g7lelp7hnldwwpt76va0vtpdv3l` |
249277

250278
### P2SH example
251279

252280
Payload: `43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636`
253281

254-
* Mainnet: `evo1q9pl5xpu70aka8nacc4kj2htflydspzkxccyfcmc`
255-
* Testnet: `tevo1q9pl5xpu70aka8nacc4kj2htflydspzkxc7llr9w`
282+
* Mainnet: `evo1sppl5xpu70aka8nacc4kj2htflydspzkxctaevg5`
283+
* Testnet: `tevo1sppl5xpu70aka8nacc4kj2htflydspzkxcdx0hkz`
256284

257285
## Copyright
258286

dip-0018/bech32.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ def convertbits(data, frombits, tobits, pad=True):
155155
HRP_TO_NETWORK = {v: k for k, v in NETWORK_TO_HRP.items()}
156156

157157
TYPE_TO_BYTE = {
158-
"p2pkh": 0x00,
159-
"p2sh": 0x01,
158+
"p2pkh": 0xb0,
159+
"p2sh": 0x80,
160160
}
161161

162162
BYTE_TO_TYPE = {v: k for k, v in TYPE_TO_BYTE.items()}
@@ -309,34 +309,34 @@ def format_path(path: list[int]) -> str:
309309
"6bca392f43453b7bc33a9532b69221ce74906a8815281637e0c9d0bee35361fe",
310310
"03de102ed1fc43cbdb16af02e294945ffaed8e0595d3072f4c592ae80816e6859e",
311311
"f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525",
312-
"evo1qrma5z3ttj75la4m93xcndna9ullamq9y57vzaqm",
313-
"tevo1qrma5z3ttj75la4m93xcndna9ullamq9y5ch5x7d",
312+
"evo1krma5z3ttj75la4m93xcndna9ullamq9y59dj9x7",
313+
"tevo1krma5z3ttj75la4m93xcndna9ullamq9y5rky7cg",
314314
),
315315
# Vector 2: m/9'/5'/17'/0'/0'/1
316316
(
317317
[9 + H, 5 + H, 17 + H, 0 + H, 0 + H, 1],
318318
"eef58ce73383f63d5062f281ed0c1e192693c170fbc0049662a73e48a1981523",
319319
"02269ff766fcd04184bc314f5385a04498df215ce1e7193cec9a607f69bc8954da",
320320
"a5ff0046217fd1c7d238e3e146cc5bfd90832a7e",
321-
"evo1qzjl7qzxy9lar37j8r37z3kvt07epqe20c30csp9",
322-
"tevo1qzjl7qzxy9lar37j8r37z3kvt07epqe20ch5wtln",
321+
"evo1kzjl7qzxy9lar37j8r37z3kvt07epqe20c2wgg8q",
322+
"tevo1kzjl7qzxy9lar37j8r37z3kvt07epqe20cv47nek",
323323
),
324324
# Vector 3: m/9'/5'/17'/0'/1'/0 (key_class' = 1')
325325
(
326326
[9 + H, 5 + H, 17 + H, 0 + H, 1 + H, 0],
327327
"cc05b4389712a2e724566914c256217685d781503d7cc05af6642e60260830db",
328328
"0317a3ed70c141cffafe00fa8bf458cec119f6fc039a7ba9a6b7303dc65b27bed3",
329329
"6d92674fd64472a3dfcfc3ebcfed7382bf699d7b",
330-
"evo1qpkeye606ez89g7lelp7hnldwwpt76va0vkmt0fv",
331-
"tevo1qpkeye606ez89g7lelp7hnldwwpt76va0vsqa5h6",
330+
"evo1kpkeye606ez89g7lelp7hnldwwpt76va0vd6mh0f",
331+
"tevo1kpkeye606ez89g7lelp7hnldwwpt76va0vtpdv3l",
332332
),
333333
]
334334

335335
# DIP-18 P2SH vector (address encoding only, no derivation path)
336336
P2SH_VECTOR = (
337337
"43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636",
338-
"evo1q9pl5xpu70aka8nacc4kj2htflydspzkxccyfcmc",
339-
"tevo1q9pl5xpu70aka8nacc4kj2htflydspzkxc7llr9w",
338+
"evo1sppl5xpu70aka8nacc4kj2htflydspzkxctaevg5",
339+
"tevo1sppl5xpu70aka8nacc4kj2htflydspzkxcdx0hkz",
340340
)
341341

342342

dip-0018/type_byte_calc.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env python3
2+
"""Calculate type bytes that produce specific bech32 characters."""
3+
4+
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
5+
6+
def convertbits(data, frombits, tobits, pad=True):
7+
"""General power-of-2 base conversion."""
8+
acc = 0
9+
bits = 0
10+
ret = []
11+
maxv = (1 << tobits) - 1
12+
max_acc = (1 << (frombits + tobits - 1)) - 1
13+
for value in data:
14+
if value < 0 or (value >> frombits):
15+
return None
16+
acc = ((acc << frombits) | value) & max_acc
17+
bits += frombits
18+
while bits >= tobits:
19+
bits -= tobits
20+
ret.append((acc >> bits) & maxv)
21+
if pad:
22+
if bits:
23+
ret.append((acc << (tobits - bits)) & maxv)
24+
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
25+
return None
26+
return ret
27+
28+
def find_type_byte_for_char(target_char):
29+
"""Find type bytes where the first encoded character is target_char."""
30+
target_index = CHARSET.index(target_char)
31+
print(f"\nLooking for type bytes that produce '{target_char}' (index {target_index})")
32+
print("-" * 60)
33+
34+
# Use a sample hash (all zeros for simplicity)
35+
sample_hash = [0] * 20
36+
37+
results = []
38+
for type_byte in range(256):
39+
payload = [type_byte] + sample_hash
40+
converted = convertbits(payload, 8, 5)
41+
if converted and CHARSET[converted[0]] == target_char:
42+
results.append(type_byte)
43+
44+
print(f"Type bytes that produce '{target_char}': {results}")
45+
print(f"Hex values: {[hex(b) for b in results]}")
46+
return results
47+
48+
def show_encoding_for_type_byte(type_byte):
49+
"""Show the full encoding details for a given type byte."""
50+
sample_hash = [0] * 20
51+
payload = [type_byte] + sample_hash
52+
converted = convertbits(payload, 8, 5)
53+
54+
print(f"\nType byte: {type_byte} (0x{type_byte:02x}, binary: {type_byte:08b})")
55+
print(f"First few 5-bit values: {converted[:5]}")
56+
print(f"First few chars: {''.join(CHARSET[v] for v in converted[:5])}")
57+
58+
# Find type bytes for desired characters
59+
print("=" * 60)
60+
print("Finding type bytes for specific first characters")
61+
print("=" * 60)
62+
63+
for char in ['q', 'k', 's', 'x', '7']:
64+
find_type_byte_for_char(char)
65+
66+
print("\n" + "=" * 60)
67+
print("Encoding details for current and proposed type bytes")
68+
print("=" * 60)
69+
70+
# Check proposed combinations
71+
print("\n--- Checking specific values ---")
72+
for tb in [0x30, 0xE0, 0xA0, 0x80, 48, 224]:
73+
show_encoding_for_type_byte(tb)

0 commit comments

Comments
 (0)