diff --git a/dip-pasta-masternode-shares.md b/dip-pasta-masternode-shares.md new file mode 100644 index 00000000..3f405f01 --- /dev/null +++ b/dip-pasta-masternode-shares.md @@ -0,0 +1,456 @@ +
+  DIP: pasta-masternode-shares
+  Title: Masternode Reward Shares
+  Author(s): Pasta
+  Special-Thanks:
+  Comments-Summary: No comments yet.
+  Status: Draft
+  Type: Standard
+  Created: 2026-06-05
+  License: MIT License
+
+ +## Table of Contents + +1. [Abstract](#abstract) +2. [Motivation](#motivation) +3. [Prior Work](#prior-work) +4. [Specification](#specification) + 1. [Terminology](#terminology) + 2. [Provider Transaction Version](#provider-transaction-version) + 3. [Masternode Payout Share](#masternode-payout-share) + 4. [Registering a Masternode](#registering-a-masternode) + 5. [Updating Masternode Payout Shares](#updating-masternode-payout-shares) + 6. [Masternode List State](#masternode-list-state) + 7. [Masternode Reward Payments](#masternode-reward-payments) + 8. [Validation Rules](#validation-rules) + 9. [RPC and JSON Interfaces](#rpc-and-json-interfaces) + 10. [Special Transaction Filtering](#special-transaction-filtering) +5. [Deployment and Compatibility](#deployment-and-compatibility) +6. [Rationale](#rationale) +7. [Test Cases](#test-cases) +8. [Security Considerations](#security-considerations) +9. [Copyright](#copyright) + +## Abstract + +This DIP extends [DIP-0003: Deterministic Masternode Lists](dip-0003.md) by +allowing the owner-controlled portion of a masternode reward to be split across +multiple payout scripts. A masternode may specify between 1 and 8 distinct owner +payout entries, each with a basis-point reward share. A masternode with one +owner uses the same structure as a masternode with multiple owners: one payout +entry with a reward value of 10000. + +The existing operator reward mechanism is preserved. If a non-zero operator +reward was configured and an operator payout script is set, the operator reward +is subtracted from the masternode reward first. The remaining owner reward is +then split across the configured owner payout entries. + +## Motivation + +Dash masternodes currently support at most two automatic reward recipients: the +owner payout address and the operator payout address. This is useful when an +owner delegates operation to a hosting provider, but it does not support direct, +protocol-enforced reward distribution among multiple masternode share owners. + +Without protocol support, shared masternode ownership requires an off-chain +agreement. One participant receives the masternode reward and then distributes +funds manually or through an external service. This introduces custodial risk, +operational delay, accounting complexity, and disputes when the receiving party +fails to distribute payments correctly. + +This DIP allows the blockchain itself to enforce automatic reward distribution +for the owner-controlled portion of masternode rewards. The registrar owner key +continues to control masternode registration metadata, including the payout +split, so this proposal provides protocol-enforced automatic payouts while +preserving the existing owner-update model from DIP-0003. + +## Prior Work + +* [DIP-0002: Special Transactions](dip-0002.md) +* [DIP-0003: Deterministic Masternode Lists](dip-0003.md) +* [DIP-0004: Simplified Verification of Deterministic Masternode Lists](dip-0004.md) + +## Specification + +### Terminology + +This DIP uses the following terms: + +| Term | Definition | +| --- | --- | +| Owner reward | The masternode reward amount remaining after subtracting any operator reward. | +| Payout entry | One owner payout script and its reward share. | +| Payout list | The ordered list of owner payout entries for a masternode. | +| Reward share | A `uint16_t` basis-point value. A value of 10000 represents 100 percent. | + +### Provider Transaction Version + +This DIP defines a new provider transaction payload version: + +| Name | Value | Description | +| --- | --- | --- | +| `MultiPayout` | 4 | Provider transaction payload version with a unified owner payout list. | + +Version 4 provider registration and registrar update transactions replace the +single `scriptPayout` field with a unified `payouts` field. The unified field is +used for all owner payout configurations, including the single-owner case. + +Version 4 applies to the following special transactions: + +| Transaction | Special transaction type | Version 4 change | +| --- | --- | --- | +| ProRegTx | 1 | Replaces `scriptPayout` with `payouts`. | +| ProUpRegTx | 3 | Replaces `scriptPayout` with `payouts`. | + +This DIP does not change the existing ProUpServTx operator payout field. The +operator payout remains a separate operator-controlled service update field. + +### Masternode Payout Share + +Each owner payout entry has the following structure: + +| Field | Type | Size | Description | +| --- | --- | --- | --- | +| reward | uint16_t | 2 | Basis-point share of the owner reward. | +| scriptPayoutSize | compactSize uint | 1-9 | Size of the payout script. | +| scriptPayout | Script | Variable | Owner payout script. | + +The `reward` field is expressed in basis points. Implementations must interpret +10000 as 100 percent, 5000 as 50 percent, and 100 as 1 percent. + +The payout list has the following structure: + +| Field | Type | Size | Description | +| --- | --- | --- | --- | +| payoutsCount | uint8_t | 1 | Number of payout entries. Must be from 1 to 8. | +| payouts | MasternodePayoutShare[] | Variable | Ordered owner payout entries. | + +The ordering of payout entries is consensus-significant for reward rounding. +The last payout entry receives the rounding remainder as specified in +[Masternode Reward Payments](#masternode-reward-payments). + +### Registering a Masternode + +For version 4 ProRegTx payloads, the ProRegTx structure defined in DIP-0003 is +modified by replacing `scriptPayout` with `payouts`. + +| Field | Type | Size | Description | +| --- | --- | --- | --- | +| version | uint16_t | 2 | Provider transaction version number. Set to 4 for this DIP. | +| type | uint16_t | 2 | Masternode type. | +| mode | uint16_t | 2 | Masternode mode. | +| collateralOutpoint | COutpoint | 36 | The collateral outpoint. | +| netInfo | NetInfo | Variable | Masternode service addressing information. | +| KeyIdOwner | CKeyID | 20 | The public key hash used for owner-related signing. | +| PubKeyOperator | BLSPubKey | 48 | The public key used for operator-related signing. | +| KeyIdVoting | CKeyID | 20 | The public key hash used for voting. | +| operatorReward | uint16_t | 2 | Operator reward in basis points, from 0 to 10000. | +| payoutsCount | uint8_t | 1 | Number of owner payout entries. | +| payouts | MasternodePayoutShare[] | Variable | Owner payout entries. | +| inputsHash | uint256 | 32 | The SHA256 hash of all transaction input outpoints. | +| platformNodeID | uint160 | 20 | Node ID derived from the Platform P2P public key. Present only for Evo masternodes. | +| payloadSigSize | compactSize uint | 1-9 | Size of the signature. | +| payloadSig | unsigned char[] | Variable | Signature required for external collateral ownership proof. | + +For a masternode with one owner, the payout list contains exactly one entry with +`reward = 10000`. + +For version 4 Evo masternode registrations, Platform P2P and HTTPS addressing +data is encoded in `netInfo` using the extended-address provider transaction +format. Separate `platformP2PPort` and `platformHTTPPort` fields are not +serialized in version 4 payloads. + +For external collaterals, the collateral ownership sign string must include the +new payout list representation instead of the previous single payout string. The +payout list string is produced by concatenating each payout entry in serialized +order as: + +```text +: +``` + +Entries are joined with commas. `` is the Dash address corresponding +to the entry's `scriptPayout` when the script is a standard address script, or +the hex representation of the script otherwise. The ProRegTx sign string +therefore becomes: + +```text +|||| +``` + +### Updating Masternode Payout Shares + +For version 4 ProUpRegTx payloads, the ProUpRegTx structure defined in DIP-0003 +is modified by replacing `scriptPayout` with `payouts`. + +| Field | Type | Size | Description | +| --- | --- | --- | --- | +| version | uint16_t | 2 | Provider update registrar transaction version number. Set to 4 for this DIP. | +| proTXHash | uint256 | 32 | The hash of the initial ProRegTx. | +| mode | uint16_t | 2 | Masternode mode. | +| PubKeyOperator | BLSPubKey | 48 | The public key used for operator-related signing. | +| KeyIdVoting | CKeyID | 20 | The public key hash used for voting. | +| payoutsCount | uint8_t | 1 | Number of owner payout entries. | +| payouts | MasternodePayoutShare[] | Variable | Owner payout entries. | +| inputsHash | uint256 | 32 | The SHA256 hash of all transaction input outpoints. | +| payloadSigSize | compactSize uint | 1-9 | Size of the signature. | +| payloadSig | unsigned char[] | Variable | Signature of the ProUpRegTx fields, signed by the owner. | + +The masternode owner may update the payout list through ProUpRegTx. A payout +list update does not revive a PoSe-banned masternode, matching existing +ProUpRegTx behavior. + +### Masternode List State + +Deterministic masternode state must store the owner payout list for version 4 +masternodes. The previous single `scriptPayout` state field is replaced by the +unified `payouts` field for version 4 state. + +State diffs must report payout-list changes as a single logical field. A +version 4 state diff that changes any payout entry, payout order, payout script, +or reward share must include the full replacement payout list. + +For compatibility with pre-version-4 masternodes, implementations may internally +represent legacy `scriptPayout` as a one-entry payout list with `reward = 10000`. +This compatibility representation must not change the serialization of legacy +provider transaction payloads or legacy deterministic masternode state. + +### Masternode Reward Payments + +Masternode reward payment construction proceeds in the following order: + +1. Calculate the block's masternode reward using the existing rules. +2. Apply any Platform credit pool reallocation using the existing rules. +3. If the selected masternode has a non-zero `operatorReward` and a non-empty + operator payout script, calculate: + +```text +operatorAmount = floor(masternodeReward * operatorReward / 10000) +``` + +4. Subtract `operatorAmount` from `masternodeReward`. The result is the owner + reward. +5. For each owner payout entry except the last, calculate: + +```text +entryAmount = floor(ownerReward * reward / 10000) +``` + +6. The last owner payout entry receives: + +```text +ownerReward - sum(previous owner payout entry amounts) +``` + +7. Create one coinbase output for each owner payout entry with a non-zero + calculated amount. +8. If `operatorAmount` is non-zero, create the operator payout output using + the existing operator payout script. + +The final owner payout entry receives the remainder to guarantee that the total +owner reward is paid exactly even when integer division truncates earlier +entries. + +Coinbase validation must require every expected masternode payout output by +amount and script. As with the current masternode payment implementation, the +relative order of outputs in the coinbase transaction is not consensus +significant. + +### Validation Rules + +A version 4 payout list is invalid if any of the following conditions are true: + +1. `payoutsCount` is less than 1. +2. `payoutsCount` is greater than 8. +3. Any payout entry has `reward` less than 100. +4. Any payout entry has `reward` greater than 10000. +5. The sum of all `reward` fields is not exactly 10000. +6. Any `scriptPayout` is not a P2PKH or P2SH script. +7. The same payout script appears more than once in the payout list. +8. A P2PKH payout destination equals the owner key ID. +9. A P2PKH payout destination equals the voting key ID. +10. A P2PKH payout destination equals the collateral key ID when the collateral + output destination can be extracted as a P2PKH destination. + +The 100-basis-point minimum is a consensus anti-dust bound. It prevents +intentionally tiny micro-owner entries from creating nuisance coinbase outputs. +It is not a guarantee that every possible future block reward will remain above +policy dust thresholds forever as the block reward declines. + +All existing ProRegTx and ProUpRegTx validation rules from DIP-0003 continue to +apply unless explicitly replaced by this DIP. + +### RPC and JSON Interfaces + +RPCs that create ProRegTx or ProUpRegTx transactions must accept a unified +`payouts` array for version 4 transactions. Each entry contains an address and +a basis-point reward: + +```json +[ + { + "address": "X...", + "reward": 10000 + } +] +``` + +```json +[ + { + "address": "X...", + "reward": 7000 + }, + { + "address": "X...", + "reward": 3000 + } +] +``` + +JSON output for version 4 masternodes and provider transaction payloads must +expose owner payouts through a `payouts` array. This applies to at least: + +* Raw transaction JSON for ProRegTx and ProUpRegTx. +* `protx info`. +* `protx diff`. +* `protx listdiff`. +* Extended Simplified Masternode List JSON. + +The `payoutAddress` field is not part of the normative JSON shape for version 4 +masternodes. A single-owner version 4 masternode still reports a one-entry +`payouts` array with `reward = 10000`. + +RPCs that report required or actual masternode payments must expose every +generated masternode payout output. This applies to at least: + +* `masternode winners`. +* `masternode payments`. +* `getblocktemplate`. + +The existing operator payout reporting remains separate when an operator payout +output is generated. + +### Special Transaction Filtering + +Special transaction filters and bloom filters must include every script in a +version 4 payout list. A filter that matches any owner payout script in the list +must match the containing ProRegTx or ProUpRegTx in the same manner that a match +on the current single `scriptPayout` field does. + +## Deployment and Compatibility + +This DIP is intended to deploy with Dash Core's v24 network upgrade +(`DEPLOYMENT_V24`). Version 4 ProRegTx and ProUpRegTx payloads are only valid +after v24 activates. + +Before v24 activation: + +* Provider transaction versions greater than the current maximum valid version + remain invalid. +* Legacy ProRegTx and ProUpRegTx payload serialization is unchanged. +* Legacy masternode reward payment behavior is unchanged. + +After v24 activation: + +* New version 4 registrations may use 1 to 8 owner payout entries. +* Version 4 registrar updates may change the payout list. +* Existing legacy masternodes remain valid and continue to use their existing + single owner payout script. +* Existing operator reward behavior remains unchanged. + +Implementations should treat legacy single-payout masternodes and version 4 +one-entry payout-list masternodes as equivalent for reward payment purposes, but +must preserve their distinct serialized provider transaction payloads. + +## Rationale + +### Unified payout list + +Using a payout list for both single-owner and multi-owner masternodes avoids a +special case where one payout is represented differently from several payouts. +This makes the new version's semantics clear: every owner reward recipient is a +payout entry. + +### Maximum of 8 owner payout entries + +A maximum of 8 owner payout entries provides enough room for common shared +masternode arrangements while limiting the number of additional coinbase +outputs, deterministic masternode state size, and Simplified Masternode List +metadata. Larger limits would support finer ownership fragmentation, but would +increase worst-case coinbase and state growth for every paid block involving a +highly fragmented masternode. + +### Basis-point reward shares + +Basis points match the existing `operatorReward` precision and allow the payout +list to reuse a familiar representation. Requiring the shares to sum to exactly +10000 avoids ambiguous normalization rules. + +### Minimum reward share + +The minimum owner payout share of 100 basis points prevents registrations that +would intentionally create very small recurring coinbase outputs. This limit +also follows from the maximum payout count: if all 8 entries are used, each +entry must represent a meaningful owner share. + +### Operator reward remains separate + +The operator reward serves a different role from owner payout shares. It is +configured by the owner as a maximum operator percentage, while the operator +sets the actual operator payout script through ProUpServTx. Keeping this +mechanism separate preserves hosting-provider delegation without making the +operator payout script part of the owner payout list. + +## Test Cases + +Implementations should include tests for the following cases: + +1. A version 4 ProRegTx with one payout entry and `reward = 10000` is valid. +2. A version 4 ProRegTx with 8 distinct payout entries whose rewards sum to + 10000 is valid. +3. A version 4 ProRegTx with 0 payout entries is invalid. +4. A version 4 ProRegTx with 9 payout entries is invalid. +5. A payout entry with `reward = 99` is invalid. +6. A payout list whose rewards sum to 9999 or 10001 is invalid. +7. A payout list containing duplicate scripts is invalid. +8. A payout list containing a non-P2PKH and non-P2SH script is invalid. +9. A payout script that reuses the owner, voting, or collateral key is invalid + where the corresponding key comparison is possible. +10. A ProUpRegTx can replace the payout list without reviving a PoSe-banned + masternode. +11. A masternode with an operator payout first subtracts the operator reward and + then splits the owner reward across the payout list. +12. Integer rounding pays the remainder to the last owner payout entry. +13. Coinbase validation rejects a block missing any expected owner payout output. +14. Coinbase validation rejects a block paying an expected payout script the + wrong amount. +15. `protx info`, `protx diff`, `protx listdiff`, raw transaction JSON, and + extended SML JSON expose the unified `payouts` array. +16. `masternode payments`, `masternode winners`, and `getblocktemplate` expose + all generated masternode payout outputs. +17. Legacy single-payout masternodes continue to pay exactly as before. + +## Security Considerations + +This DIP removes the need for an off-chain distributor to manually forward the +owner portion of masternode rewards to multiple share owners. It does not remove +the registrar owner's authority to update masternode registration metadata. A +share arrangement that requires immutable payout rights must use additional +contractual, wallet, or protocol mechanisms outside the scope of this DIP. + +Payout scripts must not reuse owner, voting, or collateral keys for the same +reason that DIP-0003 restricts current payout key reuse: payout keys may be used +in less secure wallet environments, while owner, voting, and collateral keys +control higher-value masternode functions. + +The payout count and minimum share limits reduce the risk of using masternode +registrations to create large numbers of tiny recurring coinbase outputs. +However, the block reward declines over time, so no fixed basis-point minimum +can permanently guarantee that every future payout output will remain above all +policy dust thresholds. + +## Copyright + +Copyright (c) 2026 Dash Core Group, Inc. [Licensed under the MIT License](https://opensource.org/licenses/MIT) diff --git a/dip-pasta-yappr.md b/dip-pasta-yappr.md new file mode 100644 index 00000000..f8480b87 --- /dev/null +++ b/dip-pasta-yappr.md @@ -0,0 +1,563 @@ +
+  DIP: pasta-yappr
+  Title: Dash Platform Application Key Exchange and State Transition Signing
+  Author(s): Pasta
+  Special-Thanks:
+  Comments-Summary: No comments yet.
+  Status: Draft
+  Type: Applications
+  Created: 2026-03-10
+  License: MIT License
+
+ +# Table of Contents + +1. [Abstract](#abstract) +1. [Motivation](#motivation) +1. [Prior Work](#prior-work) +1. [Terminology](#terminology) +1. [Overview](#overview) +1. [Key Exchange Protocol](#key-exchange-protocol) + * [dash-key: URI Format](#dash-key-uri-format) + * [Binary Payload Layout](#binary-payload-layout) + * [Network Identifiers](#network-identifiers) + * [URI Validation Rules](#uri-validation-rules) + * [Cryptographic Flow](#cryptographic-flow) + * [Login Key Derivation](#login-key-derivation) + * [Shared Secret Derivation](#shared-secret-derivation) + * [Login Key Encryption](#login-key-encryption) + * [Application-Side Key Derivation](#application-side-key-derivation) +1. [Key Exchange Contract](#key-exchange-contract) + * [Document Schema](#document-schema) + * [Indices](#indices) + * [Polling](#polling) +1. [State Transition Signing Protocol](#state-transition-signing-protocol) + * [dash-st: URI Format](#dash-st-uri-format) + * [Query Parameters](#query-parameters) + * [Supported Encodings](#supported-encodings) + * [Wallet Validation and Signing](#wallet-validation-and-signing) +1. [First-Time Login Key Registration](#first-time-login-key-registration) +1. [Security Considerations](#security-considerations) +1. [Copyright](#copyright) + +# Abstract + +This DIP defines two complementary URI-based protocols that enable secure interaction between Dash +Platform applications and wallet software. The **Key Exchange Protocol** (`dash-key:`) allows web +applications to obtain deterministic login keys from a wallet via QR code scanning, using ECDH key +agreement and AES-256-GCM encryption with Dash Platform as the communication channel. The **State +Transition Signing Protocol** (`dash-st:`) allows applications to request that a wallet sign and +broadcast unsigned state transitions. Together, these protocols enable wallet-based authentication +for Platform applications without requiring users to manually handle private keys. + +# Motivation + +Dash Platform applications require users to authenticate with cryptographic keys tied to their +Platform identity. Current authentication methods require users to manually enter or manage private +keys, which is both error-prone and a poor user experience. Users accustomed to modern login flows +(e.g., scanning a QR code with a mobile device) expect a simpler mechanism. + +This DIP addresses the following problems: + +1. **Key management burden**: Users should not need to copy, paste, or store raw private keys to use + Platform applications. +2. **Deterministic authentication**: A wallet should derive the same login key for a given + application every time, enabling stateless re-authentication across devices and sessions. +3. **Per-application isolation**: Each application should receive a unique key, so compromise of one + application's key does not affect others. +4. **First-time onboarding**: New users need a way to register application-specific keys on their + identity without manually constructing state transitions. +5. **Decentralized communication**: The protocol should not rely on any centralized server; Dash + Platform itself serves as the communication channel between the application and the wallet. + +# Prior Work + +* [BIP-0032: Hierarchical Deterministic + Wallets](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) +* [BIP-0044: Multi-Account Hierarchy for Deterministic + Wallets](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) +* [DIP-0009: Coin Specific Feature Derivation + Paths](https://github.com/dashpay/dips/blob/master/dip-0009.md) +* [DIP-0011: Identities](https://github.com/dashpay/dips/blob/master/dip-0011.md) +* [DIP-0013: Identities in Hierarchical Deterministic + Wallets](https://github.com/dashpay/dips/blob/master/dip-0013.md) +* [DIP-0015: DashPay](https://github.com/dashpay/dips/blob/master/dip-0015.md) + +# Terminology + +| Term | Definition | +| ---- | ---------- | +| Application | A Dash Platform application (e.g., a web app) that needs to authenticate a user via their Platform identity | +| Wallet | Software that holds the user's HD wallet seed and Platform identity keys (e.g., Dash Evo Tool) | +| Login Key | A 32-byte deterministic key derived by the wallet for a specific application and identity | +| Ephemeral Keypair | A temporary secp256k1 keypair generated for a single key exchange session | +| Key Exchange Contract | A Dash Platform data contract that stores `loginKeyResponse` documents | +| Hash160 | The composition RIPEMD-160(SHA-256(data)), producing a 20-byte hash | + +# Overview + +The protocol operates in two phases. In the first phase, the application generates an ephemeral +keypair and encodes a key exchange request as a `dash-key:` URI, typically displayed as a QR code. +The user scans this with their wallet, which derives a deterministic login key, encrypts it using +ECDH-derived shared secret, and publishes the encrypted response to Dash Platform. The application +polls Platform for the response, decrypts the login key, and derives authentication and encryption +keys from it. + +If the user's identity does not yet have the necessary application keys registered, the application +enters a second phase: it constructs an unsigned `IdentityUpdateTransition` that adds the required +keys, encodes it as a `dash-st:` URI, and displays it as a QR code. The wallet signs and broadcasts +the transition to complete key registration. + +```text +APPLICATION DASH PLATFORM WALLET +═══════════ ═════════════ ══════ + + 1. Generate ephemeral keypair + (app_priv, app_pub) + 2. Encode app_pub + contractId + + label into dash-key: URI + 3. Display URI as QR code + 4. Scan QR / paste URI + Parse and validate payload + 5. User selects identity and + approves the request + 6. Derive login key from wallet + seed (BIP32 path + HKDF with + identity, contract, key index) + 7. Generate wallet ephemeral + keypair (wallet_priv, wallet_pub) + 8. Compute shared secret: + ECDH(wallet_priv, app_pub) → HKDF + 9. Encrypt login key with shared + secret using AES-256-GCM + 10. Create loginKeyResponse document + with wallet_pub + encrypted payload + ◄─────────────────── + 11. Document stored + on Platform +12. Poll Platform for document + matching contractId + + Hash160(app_pub) +13. Document found; extract + wallet_pub and encrypted payload +14. Compute same shared secret: + ECDH(app_priv, wallet_pub) → HKDF +15. Decrypt login key using + shared secret and AES-256-GCM +16. Derive auth key and encryption + key from login key via HKDF +17. Check if derived keys are + registered on the identity + ├─ YES: Login complete + └─ NO: First-time login; + continue to key registration + +18. Build unsigned IdentityUpdate + transition adding auth + + encryption keys +19. Encode as dash-st: URI + and display QR code + 20. Scan dash-st: QR + Review transition details + 21. User approves; wallet signs + with existing auth key + 22. Broadcast signed transition + ◄─────────────────── + 23. Identity updated + on Platform +24. Poll identity for new keys +25. Keys found; login complete +``` + +# Key Exchange Protocol + +## dash-key: URI Format + +A key exchange request is encoded as a URI with the following structure: + +```text +dash-key:?n=&v= +``` + +| Component | Description | +| --------- | ----------- | +| `dash-key:` | URI scheme prefix | +| `` | Base58-encoded binary payload (see below) | +| `n` | Network identifier (required) | +| `v` | Protocol version (optional). When present, the wallet MUST verify it matches the version byte in the payload. The current protocol version is `1`. | + +### Binary Payload Layout + +The Base58-decoded payload has the following structure: + +| Offset | Size (bytes) | Field | Description | +| ------ | ------------ | ----- | ----------- | +| 0 | 1 | version | Protocol version; must be `0x01` | +| 1 | 33 | appEphemeralPubKey | Compressed secp256k1 public key (prefix `0x02` or `0x03`) | +| 34 | 32 | contractId | Dash Platform data contract identifier for the application | +| 66 | 1 | labelLength | Length of the label field in bytes (0–64) | +| 67 | 0–64 | label | UTF-8 encoded application label | + +Minimum payload size: 67 bytes (no label). Maximum payload size: 131 bytes (64-byte label). + +### Network Identifiers + +| Value | Network | +| ----- | ------- | +| `m`, `mainnet`, `dash` | Mainnet | +| `t`, `testnet` | Testnet | +| `d`, `devnet` | Devnet | +| `r`, `regtest` | Regtest (local) | + +## URI Validation Rules + +A wallet implementation MUST validate the following before processing a `dash-key:` URI: + +1. The URI begins with the `dash-key:` prefix. +2. The Base58 payload decodes successfully. +3. The payload is at least 67 bytes. +4. The version byte at offset 0 equals `0x01`. Any other version MUST be rejected. +5. The ephemeral public key at offset 1 is a valid compressed secp256k1 point (first byte is `0x02` + or `0x03`). +6. The label length byte at offset 66 does not exceed 64. +7. The total payload length equals `67 + labelLength`. +8. The label bytes (if any) are valid UTF-8. +9. If the query parameter `v` is present, it matches the version byte in the payload. +10. The network parameter `n` matches the wallet's active network. + +## Cryptographic Flow + +### Login Key Derivation + +The wallet derives a deterministic login key from its HD seed for a given identity, contract, and +key index. The derivation proceeds in two stages. + +#### Stage 1: BIP32 Base Key Derivation + +Derive a private key at the following HD path: + +```text +m / 9' / coin_type' / 21' / account' +``` + +| Path Level | Value | +| ---------- | ----- | +| 9' | Feature purpose (per [DIP-0009](https://github.com/dashpay/dips/blob/master/dip-0009.md)) | +| coin_type' | `5` (mainnet) or `1` (testnet/devnet/regtest) per [BIP-0044](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) | +| 21' | Key exchange feature index | +| account' | Wallet account index for the selected identity (default `0`) | + +#### Stage 2: HKDF Key Derivation + +Apply HKDF-SHA256 ([RFC 5869](https://www.rfc-editor.org/rfc/rfc5869)) to the BIP32-derived private +key: + +| Parameter | Value | +| --------- | ----- | +| Hash | SHA-256 | +| IKM | 32-byte private key from Stage 1 | +| Salt | 32-byte identity identifier | +| Info | `contractId (32 bytes) ‖ keyIndex (4 bytes, little-endian)` | +| Output length | 32 bytes | + +The `keyIndex` is determined by querying Platform for an existing `loginKeyResponse` document owned +by this identity for the target contract. If a document exists, its stored `keyIndex` is reused. If +no document exists, `keyIndex` is `0`. This ensures the same login key is derived on every +authentication for the same identity and contract. + +### Shared Secret Derivation + +Both the application and the wallet independently derive the same shared secret using ECDH: + +1. Perform ECDH multiplication on secp256k1: multiply the local ephemeral private key by the + remote ephemeral public key. +2. Extract the x-coordinate of the resulting point (32 bytes). +3. Apply HKDF-SHA256: + +| Parameter | Value | +| --------- | ----- | +| Hash | SHA-256 | +| IKM | 32-byte x-coordinate from ECDH | +| Salt | `dash:key-exchange:v1` (UTF-8, 20 bytes) | +| Info | empty | +| Output length | 32 bytes | + +The output is the 32-byte symmetric key used for AES-256-GCM encryption. + +### Login Key Encryption + +After deriving the login key and the shared secret, the wallet encrypts the login key so it can be +safely published to Platform. Only the application — which holds the other half of the ECDH +ephemeral keypair — can decrypt it. + +The wallet generates a random 12-byte nonce and encrypts the 32-byte login key using AES-256-GCM +with the shared secret as the symmetric key. No associated data is used. + +The resulting encrypted payload is serialized as a single byte sequence: + +| Offset | Size (bytes) | Field | Description | +| ------ | ------------ | ----- | ----------- | +| 0 | 12 | nonce | Random nonce used for AES-256-GCM | +| 12 | 32 | ciphertext | Encrypted login key | +| 44 | 16 | tag | Message authentication code produced by AES-GCM; used during decryption to verify the ciphertext has not been modified | + +Total encrypted payload size: 60 bytes. This byte sequence is stored in the `encryptedPayload` +field of the `loginKeyResponse` document published to Platform. + +To decrypt, the application extracts the nonce from the first 12 bytes, uses its own copy of the +shared secret as the AES-256-GCM key, and decrypts the remaining 48 bytes (ciphertext + tag) to +recover the 32-byte login key. + +### Application-Side Key Derivation + +After decrypting the login key, the application derives two keys using HKDF-SHA256: + +**Authentication key:** + +| Parameter | Value | +| --------- | ----- | +| IKM | 32-byte login key | +| Salt | 32-byte identity identifier | +| Info | `auth` (UTF-8, 4 bytes) | +| Output length | 32 bytes | + +**Encryption key:** + +| Parameter | Value | +| --------- | ----- | +| IKM | 32-byte login key | +| Salt | 32-byte identity identifier | +| Info | `encryption` (UTF-8, 10 bytes) | +| Output length | 32 bytes | + +These derived keys are deterministic: the same login key and identity always produce the same +authentication and encryption keys. + +# Key Exchange Contract + +The key exchange protocol uses a Dash Platform data contract to communicate responses from the +wallet to the application. This contract is deployed once and shared by all applications using the +protocol. + +## Document Schema + +The contract defines a single document type: `loginKeyResponse`. + +| Field | Type | Size (bytes) | Description | +| ----- | ---- | ------------ | ----------- | +| contractId | Identifier | 32 | The target application's data contract identifier | +| appEphemeralPubKeyHash | Bytes | 20 | Hash160 of the application's ephemeral public key | +| walletEphemeralPubKey | Bytes | 33 | Wallet's compressed secp256k1 ephemeral public key | +| encryptedPayload | Bytes | 1–4096 | Encrypted login key (nonce ‖ ciphertext ‖ tag; typically 60 bytes) | +| keyIndex | Integer | 4 | The key index used in login key derivation | + +All fields are required. + +## Indices + +The contract defines two unique indices: + +### Index 1: byOwnerAndContract + +| Property | Order | +| -------- | ----- | +| $ownerId | ascending | +| contractId | ascending | + +This index ensures a wallet identity can have at most one response per target contract. Subsequent +key exchanges for the same identity and contract update the existing document. + +### Index 2: byContractAndEphemeralKey + +| Property | Order | +| -------- | ----- | +| contractId | ascending | +| appEphemeralPubKeyHash | ascending | + +This index enables the application to poll for a response using its ephemeral public key hash, +without needing to know the wallet's identity in advance. + +## Polling + +The application polls for a response by querying the key exchange contract with the following +conditions: + +* `contractId` equals the application's own contract identifier +* `appEphemeralPubKeyHash` equals Hash160 of the application's ephemeral public key + +The recommended polling interval is 3 seconds with a 120-second timeout. When a matching document is +found, the application extracts `walletEphemeralPubKey` and `encryptedPayload` to complete the +decryption. The wallet identity is discovered from the document's `$ownerId` field. + +## Document Lifecycle + +When a wallet processes a key exchange request: + +1. Query for an existing `loginKeyResponse` document matching `($ownerId, contractId)`. +2. If a document exists, update it with the new ephemeral key hash, wallet ephemeral public key, + encrypted payload, and key index. Bump the document revision. +3. If no document exists, create a new one with all required fields. +4. On revision conflict during update, retry up to 3 times with a 500ms delay between attempts, + re-querying the document on each attempt. + +# State Transition Signing Protocol + +The `dash-st:` protocol allows an application to request that a wallet sign and broadcast an +unsigned state transition. While general-purpose, its primary use in the key exchange flow is for +first-time key registration. + +## dash-st: URI Format + +```text +dash-st:?n=&v=[&id=][&k=][&l=