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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

DIP stands for Dash Improvement Proposal. Similar to Bitcoin's [BIPs](https://github.com/bitcoin/bips/), a DIP is a design document providing information to the Dash community, or describing a new feature for Dash or its processes or environment. The DIP should provide a concise technical specification of the feature and a rationale for the feature.

Because Dash is forked from the Bitcoin codebase, many of the BIPs can be applied to Dash as well (a list of the BIPs updated to include Dash-specific details can be found [here](https://github.com/dashevo/bips)). The purpose of the DIPs is not to duplicate those which exist as BIPs, but to introduce protocol upgrades or feature specifications which are unique to Dash.

Check failure on line 5 in README.md

View workflow job for this annotation

GitHub Actions / lint

Link text should be descriptive

README.md:5:174 MD059/descriptive-link-text Link text should be descriptive [Context: "[here]"] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md059.md

## Contributions

Expand Down Expand Up @@ -34,7 +34,7 @@
[15](dip-0015.md) | Applications | DashPay | Samuel Westrich, Eric Britten | Standard | Proposed
[16](dip-0016.md) | Applications | Headers First Synchronization on Simple Payment Verification Wallets | Samuel Westrich | Informational | Proposed
[17](dip-0017.md) | Consensus | Dash Platform Payment Addresses and HD Derivation | Samuel Westrich | Standard | Proposed
[18](dip-0018.md) | Consensus | Dash Platform Payment Address Encodings | Samuel Westrich | Standard | Proposed
[18](dip-0018.md) | Consensus | Dash Platform Payment Address Encodings | Samuel Westrich, thephez | Standard | Proposed
[20](dip-0020.md) | Consensus | Dash Opcode Updates | Mart Mangus | Standard | Final
[21](dip-0021.md) | Consensus | LLMQ DKG Data Sharing | dustinface | Standard | Final
[22](dip-0022.md) | Consensus | Making InstantSend Deterministic using Quorum Cycles | Samuel Westrich, UdjinM6 | Standard | Final
Expand Down
250 changes: 167 additions & 83 deletions dip-0018.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<pre>
DIP: 0018
Title: Dash Platform Payment Address Encodings
Author(s): Samuel Westrich
Author(s): Samuel Westrich, thephez
Special-Thanks: Dash Platform Team
Comments-Summary: No comments yet.
Status: Draft
Expand Down Expand Up @@ -31,145 +31,229 @@
1. [Test Vectors](#test-vectors)
1. [Copyright](#copyright)

# Abstract
## Abstract

This DIP specifies the address encoding formats for Dash Platform payments. It defines Base58Check parameters for Platform pay-to-public-key-hash (P2PKH) addresses derived via [DIP-17](dip-0017.md) and introduces Platform pay-to-script-hash (P2SH) addresses. Distinct mainnet and testnet prefixes prevent confusion with Dash Core chain addresses.
This DIP specifies the bech32m address encoding formats for Dash Platform payments. It defines the human-readable part (HRP), data layout, and checksum rules for Platform pay-to-public-key-hash (P2PKH) addresses derived via [DIP-17](dip-0017.md), and introduces Platform pay-to-script-hash (P2SH) addresses. Distinct HRPs for Platform prevent confusion with Dash Core chain addresses and with other bech32-based formats.

# Motivation
## Motivation

Platform payment keys are derived under [DIP-17](dip-0017.md). To interoperate between wallets, hardware wallets, and services, a standard encoding with explicit network prefixes and checksum rules is required. Script-hash addresses are also needed for multisig and other script-based Platform payments.
Platform payment keys are derived under [DIP-17](dip-0017.md). To interoperate between wallets, hardware wallets, and services, a standard encoding with explicit network separation is required. This DIP adopts bech32m, which provides strong error detection, produces compact QR codes, and excludes ambiguous characters.

# Prior Work
## Prior Work

* [DIP-0017: Dash Platform Payment Addresses and HD Derivation](https://github.com/dashpay/dips/blob/master/dip-0017.md)
* [BIP-0173: bech32 format](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)
* [BIP-0350: bech32m format for modern checksums](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)

# Specification
## Specification

## Address Types
### Address Types

* **Platform P2PKH (D-address):** HASH160 of a compressed secp256k1 public key derived per [DIP-17](dip-0017.md).
* **Platform P2SH:** HASH160 of a Platform script (e.g., multisig or policy script) for receiving Platform payments to scripts.
* **Platform P2PKH:** `HASH160(pubkey)` where `pubkey` is a compressed secp256k1 public key derived per [DIP-17](dip-0017.md).
* **Platform P2SH:** `HASH160(script)` where `script` is the raw byte serialization of a Platform script (e.g., multisig or policy script) being paid to.

## Encoding
### Encoding

Base58Check is used for all Platform address types.
#### Encoding Algorithm Summary

Payloads:
Encoding a Dash Platform address uses the bech32m format defined in [BIP-350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki), with the general encoding rules inherited from [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki).

* P2PKH payload: `HASH160(pubkey)` where `pubkey` is compressed secp256k1 (33 bytes).
* P2SH payload: `HASH160(script)` where `script` is the raw byte serialization of the Platform script being paid to.
Given:

Encoding algorithm (for either address type):
* `hrp`: the network human-readable prefix (e.g., `dashevo`, `tdashevo`)
* `type_byte`: `0x00` for P2PKH or `0x01` for P2SH
* `hash160`: a 20-byte `HASH160(pubkey or script)` value

1. Prepend the network-specific version byte for the address type.
2. Compute checksum = first 4 bytes of `SHA256(SHA256(version || payload))`.
3. Concatenate `version || payload || checksum`.
4. Base58Check-encode the 25-byte result.
The address MUST be encoded as follows:

Decoding reverses these steps and verifies checksum and version.
1. Form the 21-byte payload: `payload = type_byte || hash160`
2. Convert the payload from 8-bit bytes to 5-bit groups using the standard `convertbits()` procedure described in [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki), with padding enabled
3. Compute the bech32m checksum using the algorithm defined in [BIP-350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki#appendix-checksum-design--properties), including HRP expansion and polymod evaluation using the bech32 generator constants
4. Append the checksum to the data and map the resulting 5-bit values to characters using the bech32 alphabet defined in [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) (`qpzry9x8gf2tvdw0s3jn54khce6mua7l`)
5. Produce the final address string: `hrp + "1" + encoded_data`

## Network Parameters
Decoders MUST reverse these steps and MUST verify:

| Address type | Mainnet version | Expected prefix | Testnet/Devnet/Regtest version | Expected prefix |
| ------------ | --------------- | --------------- | ------------------------------ | ---------------- |
| Platform P2PKH | `0x1e` | `D` | `0x5a` | `d` |
| Platform P2SH | `0x38` | `P` | `0x75` | `p` |
* 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`.

These prefixes are distinct from Dash Core chain (`X`/`7`/`y`/`8`) and from each other.
#### Structure

## Validation
All Platform addresses are encoded as:

An address is valid for a network if:
```text
<HRP> "1" <data-part>
```

* `<HRP>` is network-specific (see table).
* `<data-part>` contains:
* one type byte (`0x00` P2PKH, `0x01` 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).

#### Rules

* MUST be lowercase when encoded and displayed.
* Decoders MUST reject mixed-case input.
* Uppercase MAY be accepted but MUST normalize to lowercase before storage/display.
* Attempting to validate or decode Dash Platform addresses using legacy bech32 rules (BIP-173 checksum constant) MUST fail.

#### Character set

The bech32m encoding used in this specification requires a fixed, 32-character base-32 alphabet. The data portion of all encoded addresses MUST use the same character set as Bitcoin and [Bitcoin Cash](https://reference.cash/protocol/blockchain/encoding/cashaddr):

```text
qpzry9x8gf2tvdw0s3jn54khce6mua7l
```

### Network Parameters

The following values define the canonical human-readable prefixes (HRPs) and type-byte assignments for Dash Platform addresses. These values are fixed and MUST be used exactly as specified.

| Network | HRP |
| ------- | -------- |
| Mainnet | `dashevo` |
| Testnet / Devnet / Regtest | `tdashevo` |

Type byte meaning:

| Address Type | Type byte |
| -------------- | --------- |
| Platform P2PKH | `0x00` |
| Platform P2SH | `0x01` |

### Validation

A Platform address is valid if:

1. Base58 decoding yields 25 bytes.
2. The checksum matches the first 4 bytes of `SHA256(SHA256(version || payload))`.
3. The version byte equals the network’s P2PKH or P2SH Platform prefix.
4. Wallets MUST reject Platform addresses when constructing Dash Core chain scripts and SHOULD present a clear warning if a user attempts to mix layers.
1. It is lowercase or uppercase (but not mixed).
2. HRP matches expected network.
3. bech32m checksum verifies.
4. Payload decodes to exactly 21 bytes.
5. `payload[0]` is `0x00` or `0x01`.

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

### Wallet and Hardware Wallet Behavior

* Wallets MUST use the P2PKH encoding above for public keys derived per [DIP-17](dip-0017.md).
* Wallets MUST use the P2SH encoding for Platform scripts intended to receive Platform funds.
* Hardware wallets MUST whitelist the version bytes above and display “Dash Platform address” or “Dash Platform script address” as appropriate.
* 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.

## Rationale

# Rationale
Bech32m was chosen over Base58Check because it:

* **Base58Check:** Aligns with existing Dash address UX and reduces user confusion compared to introducing a new encoding.
* **Distinct prefixes:** `D/d` for P2PKH and `P/p` for P2SH avoid collisions with Core chain addresses and with each other.
* **Script support:** P2SH enables multisig and policy scripts on Platform without overloading the P2PKH prefix.
* Improves checksum strength
* Is QR efficient
* Avoids ambiguous characters
* Clearly separates networks using HRPs
* Future-proofs script or address extensions

# Backwards Compatibility
## 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.

# Reference Implementation
## Reference Implementation

Note: The following pseudocode covers the encoding and decoding parts of DIP-18 only, not the wallet-UI or signing device behaviors.

```text
function encode_platform_address(payload, type, network):
# payload: 20-byte HASH160
function encode_platform_address(hash160, type, network):
# type: "p2pkh" or "p2sh"
# network: "mainnet" or "testnet"
if len(hash160) != 20:
error("invalid hash160 length")

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

hrp = {
"mainnet": "dashevo",
"testnet": "tdashevo",
}.get(network) or error()

payload = [type_byte] || hash160
data = convertbits(payload, 8, 5, pad=true)
return bech32m_encode(hrp, data)

function decode_platform_address(addr):
if mixed_case(addr): error("mixed case not allowed")

addr = to_lowercase(addr)

# bech32m_decode MUST:
# - verify bech32m checksum (BIP-350)
# - validate character set
# - return (hrp, data_without_checksum)
hrp, data = bech32m_decode(addr)

if type == "p2pkh":
version = 0x1e if network == "mainnet" else 0x5a
else if type == "p2sh":
version = 0x38 if network == "mainnet" else 0x75
# Infer network from HRP
network = {
"dashevo": "mainnet",
"tdashevo": "testnet",
}.get(hrp)

if network is null:
error("unknown hrp / network")

payload = convertbits(data, 5, 8, pad=false)
if len(payload) != 21:
error("invalid payload length")

type_byte = payload[0]
hash160 = payload[1:21]

if type_byte == 0x00:
addr_type = "p2pkh"
else if type_byte == 0x01:
addr_type = "p2sh"
else:
error("unknown type")

data = version || payload
checksum = SHA256(SHA256(data))[0:4]
return Base58Encode(data || checksum)

function decode_platform_address(addr, network):
raw = Base58Decode(addr)
assert len(raw) == 25
version = raw[0]
payload = raw[1:21]
checksum = raw[21:25]
expect = SHA256(SHA256(raw[0:21]))[0:4]
assert checksum == expect

valid_p2pkh = (version == 0x1e and network == "mainnet") or (version == 0x5a and network != "mainnet")
valid_p2sh = (version == 0x38 and network == "mainnet") or (version == 0x75 and network != "mainnet")
assert valid_p2pkh or valid_p2sh

return payload, ("p2pkh" if valid_p2pkh else "p2sh")
error("unknown type byte")

return network, addr_type, hash160
```

# Security Considerations
A Python implementation is available at [`dip-0018/bech32.py`](dip-0018/bech32.py). It uses the [BIP-350 reference code](https://github.com/sipa/bech32/blob/master/ref/python/segwit_addr.py) by Pieter Wuille and validates against the [provided test vectors](#test-vectors).

## Security Considerations

* Checksums detect mistyped addresses; distinct prefixes reduce layer-mixing mistakes.
* Hardware wallet whitelisting of prefixes mitigates key-path confusion.
* P2SH scripts must be fully validated by wallets before signing or displaying to prevent malicious script substitution.

# Privacy Considerations
## Privacy Considerations

* Base58Check addresses are unshielded; privacy relies on HD key rotation per [DIP-17](dip-0017.md) and script hygiene.
* Privacy relies on HD key rotation per [DIP-17](dip-0017.md) and script hygiene.
* P2SH script hashes reveal neither full script nor participant keys but can still be correlated if reused; wallets SHOULD discourage P2SH reuse.

# Test Vectors
## Test Vectors

Mnemonic (shared with [DIP-17](dip-0017.md)): `abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`
Passphrase: `""`

## P2PKH examples (payloads from DIP-17)
The HASH160 payloads in the following tables are derived from the mnemonic and paths specified in [DIP-17](dip-0017.md). Implementations MAY use those derivation vectors to perform end-to-end tests (mnemonic → key → pubkey → HASH160 → address).

### P2PKH examples

| Vector | Payload (HASH160) | Mainnet (`dashevo`) | Testnet (`tdashevo`) |
| ------ | ------------------------------------------ | ------------------------------------------------ | ------------------------------------------------- |
| 1 | `f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525` | `dashevo1qrma5z3ttj75la4m93xcndna9ullamq9y5smxxxm` | `tdashevo1qrma5z3ttj75la4m93xcndna9ullamq9y5aawfeu` |
| 2 | `a5ff0046217fd1c7d238e3e146cc5bfd90832a7e` | `dashevo1qzjl7qzxy9lar37j8r37z3kvt07epqe20clcut89` | `tdashevo1qzjl7qzxy9lar37j8r37z3kvt07epqe20cj75ycz` |
| 3 | `6d92674fd64472a3dfcfc3ebcfed7382bf699d7b` | `dashevo1qpkeye606ez89g7lelp7hnldwwpt76va0vcv050v` | `tdashevo1qpkeye606ez89g7lelp7hnldwwpt76va0v428mst` |

| Vector | Path (mainnet / testnet) | account' | key_class' | index | HASH160(pubkey) | Mainnet P2PKH | Testnet P2PKH |
| ------ | ----------------------- | -------- | ---------- | ----- | --------------- | ------------- | -------------- |
| 1 | m/9'/5'/17'/0'/0'/0 / m/9'/1'/17'/0'/0'/0 | 0' | 0' | 0 | f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525 | DTjceJiEqrNkCsSizK65fojEANTKoQMtsR | dSqV2orinasFpYAMGQTLy6uYpW9Dnge563 |
| 2 | m/9'/5'/17'/0'/0'/1 / m/9'/1'/17'/0'/0'/1 | 0' | 0' | 1 | a5ff0046217fd1c7d238e3e146cc5bfd90832a7e | DLGoWhHfAyFcJafRgt2fFN7fxgLqNrfCXm | dZoepEc46ivSfm3VYr8mJeA4hZXYytgkKZ |
| 3 | m/9'/5'/17'/0'/1'/0 / m/9'/1'/17'/0'/1'/0 | 0' | 1' | 0 | 6d92674fd64472a3dfcfc3ebcfed7382bf699d7b | DF8TaTy7YrLdGYqq6cwapSUqdM3qJxLQbo | dFQSkGujaeDNwWTQDDVfbrurQ9ChXXYDov |
### P2SH example

## P2SH example
Payload: `43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636`

* Script (hex): `76a914000102030405060708090a0b0c0d0e0f101112131488ac` (standard HASH160-to-pubkey script for illustration)
* HASH160(script): `43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636`
* Mainnet P2SH: `Pe8D1pMrEnWsmuj5zCEBhHTcsFE51Asp8k`
* Testnet P2SH: `pBk15SYRYnnKfMENUnYdGw4cG1wcRmSdoh`
* Mainnet: `dashevo1q9pl5xpu70aka8nacc4kj2htflydspzkxckndrac`
* Testnet: `tdashevo1q9pl5xpu70aka8nacc4kj2htflydspzkxcm49vzl`

# Copyright
## Copyright

Copyright (c) 2025 Dash Core Group, Inc. [Licensed under the MIT License](https://opensource.org/licenses/MIT)
Loading
Loading