Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions dip-0018.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Encoding a Dash Platform address uses the bech32m format defined in [BIP-350](ht
Given:

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

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

#### Structure

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

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

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).
Expand Down Expand Up @@ -120,10 +120,28 @@ The following values define the canonical human-readable prefixes (HRPs) and typ

Type byte meaning:

| Address Type | Type byte |
| -------------- | --------- |
| Platform P2PKH | `0x00` |
| Platform P2SH | `0x01` |
| Address Type | Type byte | First data character |
| -------------- | --------- | -------------------- |
| Platform P2PKH | `0xb0` | `k` |
| Platform P2SH | `0x80` | `s` |

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:

* P2PKH addresses always match pattern `evo1k...` (mainnet) or `tevo1k...` (testnet)
* P2SH addresses always match pattern `evo1s...` (mainnet) or `tevo1s...` (testnet)

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`).

#### Reserved Type Byte Ranges

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:

| High 5 bits | First char | Reserved for | Type byte range |
| ----------- | ---------- | --------------- | --------------- |
| `10110` | `k` | P2PKH | `0xb0`–`0xb7` |
| `10000` | `s` | P2SH | `0x80`–`0x87` |

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.

### Validation

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

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

Expand All @@ -144,10 +162,12 @@ Wallets MUST reject Platform addresses when constructing Dash Core chain scripts
* Wallets MUST treat HRP as the network selector.
* Software wallets SHOULD label Platform balances separately from Core chain balances and SHOULD avoid auto-pasting Platform addresses into Core chain contexts.
* Wallets SHOULD derive payloads via [DIP-17](dip-0017.md) and then encode using these rules; no alternative prefixes are allowed.
* 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.
* 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.

## Rationale

### Why Bech32m?

Bech32m was chosen over Base58Check because it:

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

### Why a Type Byte?

Platform addresses embed a type byte as the first byte of the payload. This approach was chosen because:

* 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.
* A full byte in the payload is straightforward to encode and decode without special handling separate from the hash data.
* Specific type byte values improve human identification of address type by guaranteeing that P2PKH and P2SH addresses have a unique starting characters.

## Backwards Compatibility

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.
Expand All @@ -171,7 +199,7 @@ function encode_platform_address(hash160, type, network):
if len(hash160) != 20:
error("invalid hash160 length")

type_byte = 0x00 if type=="p2pkh" else 0x01 if type=="p2sh" else error()
type_byte = 0xb0 if type=="p2pkh" else 0x80 if type=="p2sh" else error()

hrp = {
"mainnet": "evo",
Expand Down Expand Up @@ -209,9 +237,9 @@ function decode_platform_address(addr):
type_byte = payload[0]
hash160 = payload[1:21]

if type_byte == 0x00:
if type_byte == 0xb0:
addr_type = "p2pkh"
else if type_byte == 0x01:
else if type_byte == 0x80:
addr_type = "p2sh"
else:
error("unknown type byte")
Expand Down Expand Up @@ -243,16 +271,16 @@ The HASH160 payloads in the following tables are derived from the mnemonic and p

| Vector | Payload (HASH160) | Mainnet (`evo`) | Testnet (`tevo`) |
| ------ | ------------------------------------------ | ------------------------------------------------ | ------------------------------------------------- |
| 1 | `f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525` | `evo1qrma5z3ttj75la4m93xcndna9ullamq9y57vzaqm` | `tevo1qrma5z3ttj75la4m93xcndna9ullamq9y5ch5x7d` |
| 2 | `a5ff0046217fd1c7d238e3e146cc5bfd90832a7e` | `evo1qzjl7qzxy9lar37j8r37z3kvt07epqe20c30csp9` | `tevo1qzjl7qzxy9lar37j8r37z3kvt07epqe20ch5wtln` |
| 3 | `6d92674fd64472a3dfcfc3ebcfed7382bf699d7b` | `evo1qpkeye606ez89g7lelp7hnldwwpt76va0vkmt0fv` | `tevo1qpkeye606ez89g7lelp7hnldwwpt76va0vsqa5h6` |
| 1 | `f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525` | `evo1krma5z3ttj75la4m93xcndna9ullamq9y59dj9x7` | `tevo1krma5z3ttj75la4m93xcndna9ullamq9y5rky7cg` |
| 2 | `a5ff0046217fd1c7d238e3e146cc5bfd90832a7e` | `evo1kzjl7qzxy9lar37j8r37z3kvt07epqe20c2wgg8q` | `tevo1kzjl7qzxy9lar37j8r37z3kvt07epqe20cv47nek` |
| 3 | `6d92674fd64472a3dfcfc3ebcfed7382bf699d7b` | `evo1kpkeye606ez89g7lelp7hnldwwpt76va0vd6mh0f` | `tevo1kpkeye606ez89g7lelp7hnldwwpt76va0vtpdv3l` |

### P2SH example

Payload: `43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636`

* Mainnet: `evo1q9pl5xpu70aka8nacc4kj2htflydspzkxccyfcmc`
* Testnet: `tevo1q9pl5xpu70aka8nacc4kj2htflydspzkxc7llr9w`
* Mainnet: `evo1sppl5xpu70aka8nacc4kj2htflydspzkxctaevg5`
* Testnet: `tevo1sppl5xpu70aka8nacc4kj2htflydspzkxcdx0hkz`

## Copyright

Expand Down
20 changes: 10 additions & 10 deletions dip-0018/bech32.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ def convertbits(data, frombits, tobits, pad=True):
HRP_TO_NETWORK = {v: k for k, v in NETWORK_TO_HRP.items()}

TYPE_TO_BYTE = {
"p2pkh": 0x00,
"p2sh": 0x01,
"p2pkh": 0xb0,
"p2sh": 0x80,
}

BYTE_TO_TYPE = {v: k for k, v in TYPE_TO_BYTE.items()}
Expand Down Expand Up @@ -309,34 +309,34 @@ def format_path(path: list[int]) -> str:
"6bca392f43453b7bc33a9532b69221ce74906a8815281637e0c9d0bee35361fe",
"03de102ed1fc43cbdb16af02e294945ffaed8e0595d3072f4c592ae80816e6859e",
"f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525",
"evo1qrma5z3ttj75la4m93xcndna9ullamq9y57vzaqm",
"tevo1qrma5z3ttj75la4m93xcndna9ullamq9y5ch5x7d",
"evo1krma5z3ttj75la4m93xcndna9ullamq9y59dj9x7",
"tevo1krma5z3ttj75la4m93xcndna9ullamq9y5rky7cg",
),
# Vector 2: m/9'/5'/17'/0'/0'/1
(
[9 + H, 5 + H, 17 + H, 0 + H, 0 + H, 1],
"eef58ce73383f63d5062f281ed0c1e192693c170fbc0049662a73e48a1981523",
"02269ff766fcd04184bc314f5385a04498df215ce1e7193cec9a607f69bc8954da",
"a5ff0046217fd1c7d238e3e146cc5bfd90832a7e",
"evo1qzjl7qzxy9lar37j8r37z3kvt07epqe20c30csp9",
"tevo1qzjl7qzxy9lar37j8r37z3kvt07epqe20ch5wtln",
"evo1kzjl7qzxy9lar37j8r37z3kvt07epqe20c2wgg8q",
"tevo1kzjl7qzxy9lar37j8r37z3kvt07epqe20cv47nek",
),
# Vector 3: m/9'/5'/17'/0'/1'/0 (key_class' = 1')
(
[9 + H, 5 + H, 17 + H, 0 + H, 1 + H, 0],
"cc05b4389712a2e724566914c256217685d781503d7cc05af6642e60260830db",
"0317a3ed70c141cffafe00fa8bf458cec119f6fc039a7ba9a6b7303dc65b27bed3",
"6d92674fd64472a3dfcfc3ebcfed7382bf699d7b",
"evo1qpkeye606ez89g7lelp7hnldwwpt76va0vkmt0fv",
"tevo1qpkeye606ez89g7lelp7hnldwwpt76va0vsqa5h6",
"evo1kpkeye606ez89g7lelp7hnldwwpt76va0vd6mh0f",
"tevo1kpkeye606ez89g7lelp7hnldwwpt76va0vtpdv3l",
),
]

# DIP-18 P2SH vector (address encoding only, no derivation path)
P2SH_VECTOR = (
"43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636",
"evo1q9pl5xpu70aka8nacc4kj2htflydspzkxccyfcmc",
"tevo1q9pl5xpu70aka8nacc4kj2htflydspzkxc7llr9w",
"evo1sppl5xpu70aka8nacc4kj2htflydspzkxctaevg5",
"tevo1sppl5xpu70aka8nacc4kj2htflydspzkxcdx0hkz",
)


Expand Down
73 changes: 73 additions & 0 deletions dip-0018/type_byte_calc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Calculate type bytes that produce specific bech32 characters."""

CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"

def convertbits(data, frombits, tobits, pad=True):
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret

def find_type_byte_for_char(target_char):
"""Find type bytes where the first encoded character is target_char."""
target_index = CHARSET.index(target_char)
print(f"\nLooking for type bytes that produce '{target_char}' (index {target_index})")
print("-" * 60)

# Use a sample hash (all zeros for simplicity)
sample_hash = [0] * 20

results = []
for type_byte in range(256):
payload = [type_byte] + sample_hash
converted = convertbits(payload, 8, 5)
if converted and CHARSET[converted[0]] == target_char:
results.append(type_byte)

print(f"Type bytes that produce '{target_char}': {results}")
print(f"Hex values: {[hex(b) for b in results]}")
return results

def show_encoding_for_type_byte(type_byte):
"""Show the full encoding details for a given type byte."""
sample_hash = [0] * 20
payload = [type_byte] + sample_hash
converted = convertbits(payload, 8, 5)

print(f"\nType byte: {type_byte} (0x{type_byte:02x}, binary: {type_byte:08b})")
print(f"First few 5-bit values: {converted[:5]}")
print(f"First few chars: {''.join(CHARSET[v] for v in converted[:5])}")

# Find type bytes for desired characters
print("=" * 60)
print("Finding type bytes for specific first characters")
print("=" * 60)

for char in ['q', 'k', 's', 'x', '7']:
find_type_byte_for_char(char)

print("\n" + "=" * 60)
print("Encoding details for current and proposed type bytes")
print("=" * 60)

# Check proposed combinations
print("\n--- Checking specific values ---")
for tb in [0x30, 0xE0, 0xA0, 0x80, 48, 224]:
show_encoding_for_type_byte(tb)