From 222cbcb01ce4cab3195b558d2e1565895fab5ecd Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 05:46:26 -0500 Subject: [PATCH 01/18] docs: add draft DIP for Decentralized Masternode Shares Introduces a draft DIP describing shared-collateral masternodes with 2-8 participants, ProTxVersion 5 (SharedCollateral), per-participant consent for registration and shared-registrar updates, consensus enforcement of a dissolution covenant on the shared collateral output, and a new CProDisTx (TRANSACTION_PROVIDER_DISSOLVE) with strict output/penalty rules. Filed under the alias name per DIP editor policy. Co-Authored-By: Claude Opus 4.7 (1M context) --- dip-decentralized-masternode-shares.md | 1264 ++++++++++++++++++++++++ 1 file changed, 1264 insertions(+) create mode 100644 dip-decentralized-masternode-shares.md diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md new file mode 100644 index 00000000..1166e978 --- /dev/null +++ b/dip-decentralized-masternode-shares.md @@ -0,0 +1,1264 @@ +
+  DIP: TBD
+  Title: Decentralized Masternode Shares
+  Author(s): Pasta
+  Comments-Summary: No comments yet.
+  Status: Draft
+  Type: Standard
+  Created: 2026-06-20
+  Requires: DIP-0002, DIP-0003, DIP-0023, DIP-0026
+  License: MIT License
+
+ +## Table of Contents + +1. [Abstract](#abstract) +2. [Motivation](#motivation) +3. [Prior Work](#prior-work) +4. [Relationship to DIP-0026](#relationship-to-dip-0026) +5. [Specification](#specification) + 1. [Terminology](#terminology) + 2. [Parameters](#parameters) + 3. [Provider Transaction Version](#provider-transaction-version) + 4. [Shared Collateral Output](#shared-collateral-output) + 5. [Collateral Share](#collateral-share) + 6. [Shared Registration (ProRegTx v5)](#shared-registration-proregtx-v5) + 7. [Registration Consent Digest](#registration-consent-digest) + 8. [Shared Update Transactions](#shared-update-transactions) + 9. [Authorization Tiers](#authorization-tiers) + 10. [Reward Distribution](#reward-distribution) + 11. [Dissolution (ProDisTx)](#dissolution-prodistx) + 12. [Collateral Spend Enforcement](#collateral-spend-enforcement) + 13. [Deterministic Masternode State](#deterministic-masternode-state) + 14. [Simplified Masternode List and Filters](#simplified-masternode-list-and-filters) + 15. [Governance](#governance) +6. [Deployment and Compatibility](#deployment-and-compatibility) +7. [Rationale](#rationale) +8. [Test Cases](#test-cases) +9. [Implementation Notes](#implementation-notes) +10. [Security Considerations](#security-considerations) +11. [Open Issues](#open-issues) +12. [Copyright](#copyright) + +## Abstract + +This DIP extends [DIP-0003: Deterministic Masternode Lists](dip-0003.md) by +defining a new provider transaction payload version, `SharedCollateral` (`5`), +that allows between 2 and 8 mutually distrusting participants to jointly fund, +operate, and exit a single Regular or Evolution masternode under +protocol-enforced consent. The shared masternode is funded by an internal +collateral output that is locked by consensus to a dissolution covenant. Owner +rewards are split natively in the coinbase by recorded share amounts. Each +participant may unilaterally dissolve the masternode at any time, subject to a +participant-chosen penalty and the standard transaction fee, and the full set of +participants may dissolve unanimously with no penalty. Updates to fields that +affect all participants require consent from every participant; the operator +role remains unchanged. + +Shared collateral is a strict superset of the [DIP-0026: Multi-Party +Payouts](dip-0026.md) reward-splitting mechanism. DIP-0026 leaves the +registrar owner in unilateral control of the payout list. This DIP introduces +a separate provider payload version so that share amounts, refund destinations, +and the collateral itself are bound to per-participant consent and cannot be +redirected by any single owner, operator, miner, or compromised update path. + +## Motivation + +Dash masternodes require 1000 DASH of collateral for a Regular masternode and +4000 DASH for an Evolution masternode. Many users wish to combine smaller +holdings to operate a single masternode and receive a proportional share of +rewards. Without protocol support, shared ownership requires an off-chain +custodian who controls the collateral, the registration keys, and the +distribution of rewards. This is a custodial relationship: the custodian can +abscond with collateral, freeze a participant out of rewards, refuse to exit, +or be compelled to do any of the above. + +DIP-0026 makes the recurring reward split trustless, but a single registrar +owner can still rewrite the payout list, the collateral remains under one +party's control, and there is no consensus-enforced exit path. To make shared +masternodes fully trustless, the protocol must also enforce: + +* atomic, multi-party formation of the masternode and its collateral output; +* a collateral covenant that allows funds to leave only through a valid + dissolution transaction; +* per-participant refund obligations on exit; +* per-share reward shares tied to recorded collateral contributions; +* an authorization model that distinguishes individual, shared-registrar, and + operator-controlled fields; and +* a unilateral exit path so that an unresponsive or hostile counterparty + cannot freeze any participant's principal. + +This DIP specifies those rules. The initial scope is deliberately narrow: +internal collateral only, immutable share amounts and participant set, +immutable refund scripts, immutable participant owner keys, per-share +reward-script self-update, unanimous shared-registrar updates, +operator-authorized service and operator-payout updates, and unilateral or +unanimous dissolution. Partial exit, participant replacement, external +collateral, owner-key rotation, refund-script mutation, relock transactions, +and on-chain fractional governance are out of scope and may be addressed by +future DIPs. + +## Prior Work + +* [DIP-0002: Special Transactions](dip-0002.md) +* [DIP-0003: Deterministic Masternode Lists](dip-0003.md) +* [DIP-0023: Enhanced Hard Fork Mechanism](dip-0023.md) +* [DIP-0026: Multi-Party Payouts](dip-0026.md) +* [DIP-0028: Evolution Masternodes](dip-0028.md) + +## Relationship to DIP-0026 + +DIP-0026 introduces provider transaction payload version `4` (`MultiPayout`). +Under DIP-0026 v4, the registrar owner remains the sole signer for ProRegTx +and ProUpRegTx, and may freely rewrite the payout list at any time. + +This DIP does not modify DIP-0026 v4 semantics. Shared masternodes use a new +provider transaction payload version, `5` (`SharedCollateral`), with the +following distinctions: + +| Property | DIP-0026 v4 | This DIP, v5 | +| --- | --- | --- | +| Ownership model | Single registrar owner | 2 to 8 participants | +| Payout list authority | Registrar owner via ProUpRegTx | Per-share self-update; share amounts immutable | +| Collateral lock | Single owner's UTXO | Shared collateral covenant | +| Exit path | Owner spends collateral | Valid `ProDisTx` only | +| Voting / operator updates | Owner | Unanimous participant consent | +| Refund on exit | None enforced | Per-participant refund script enforced | + +A v5 payload MUST NOT be reinterpreted as a v4 payload, and a v4 payload MUST +NOT be reinterpreted as v5. A single-owner masternode using v3 or v4 cannot +be upgraded to v5 in place: shared collateral is established only by a new +v5 registration, and a shared masternode is wound down only by a valid +`ProDisTx`. There is no in-place downgrade from v5 to v3 or v4. + +## Specification + +### Terminology + +| Term | Definition | +| --- | --- | +| Participant | One of the 2 to 8 owners of a shared masternode, identified by a participant owner key. | +| Share | One participant's recorded collateral amount, refund script, reward script, and owner key. | +| Share table | The ordered list of `CollateralShare` entries in a shared registration. | +| Shared collateral | The internal collateral output of a shared masternode, locked to the shared-collateral script template. | +| Shared collateral script template | The fixed serialized output script used by every shared collateral output (see [Shared Collateral Output](#shared-collateral-output)). | +| Registration consent digest | The domain-separated digest each participant signs to authorize a shared registration. | +| Dissolution authorization digest | The domain-separated digest signed to authorize a `ProDisTx`. | +| Unilateral dissolution | A dissolution authorized by a single participant ("the actor") with a penalty redistributed to non-actors. | +| Unanimous dissolution | A dissolution authorized by every participant with no penalty. | +| Actor | The participant chosen by `actorIndex` who pays the transaction fee and, in unilateral mode, the penalty. | +| Early period | The window of `earlyPeriodBlocks` blocks after registration during which `earlyPenalty` applies to unilateral dissolution. | + +### Parameters + +| Constant | Value | Description | +| --- | ---: | --- | +| `SHARED_MIN_PARTICIPANTS` | 2 | Minimum number of shares. | +| `SHARED_MAX_PARTICIPANTS` | 8 | Maximum number of shares, matching DIP-0026 payout-count limits. | +| `SHARED_MIN_SHARE_DUFFS` | 1000000000 (10 DASH) | Minimum value of any `shares[i].amount`. | +| `SHARED_MAX_EARLY_PERIOD_BLOCKS` | 1051200 (~2 years at 60 s blocks) | Maximum value of `earlyPeriodBlocks`. | + +`SHARED_MIN_SHARE_DUFFS` exists to keep recurring per-share coinbase outputs +above policy dust thresholds at present block rewards while permitting modest +participation levels. Wallets SHOULD warn participants that the block reward +declines over time and very small shares may produce dust-class outputs in +the future. + +Shared collateral amounts MUST satisfy: + +```text +sum(shares[i].amount) == GetMnType(nType).collat_amount +``` + +where `GetMnType(nType).collat_amount` is 1000 DASH for `Regular` or 4000 DASH +for `Evo`, as defined in DIP-0003 Appendix B and DIP-0028. A registration that +does not exactly sum to the required collateral is invalid. + +Penalty parameters MUST satisfy: + +```text +0 <= standardPenalty <= earlyPenalty +earlyPenalty < min(shares[i].amount) +standardPenalty < min(shares[i].amount) +``` + +The strict `<` ensures that even the smallest share has a positive remainder +for the unilateral actor after the penalty is paid, so a unilateral +dissolution by any participant always returns a non-zero amount to the actor +before fees. + +### Provider Transaction Version + +This DIP introduces one new provider transaction payload version and three +new special transaction types. + +| Name | Value | Applies to | +| --- | ---: | --- | +| `ProTxVersion::SharedCollateral` | 5 | `ProRegTx`, `ProUpShareTx`, `ProUpSharedRegTx`, `ProDisTx` | + +| Name | Value | Description | +| --- | ---: | --- | +| `TRANSACTION_PROVIDER_DISSOLVE` | 10 | `ProDisTx` payload (this DIP). | +| `TRANSACTION_PROVIDER_UPDATE_SHARE` | 11 | `ProUpShareTx` payload (this DIP). | +| `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR` | 12 | `ProUpSharedRegTx` payload (this DIP). | + +The numeric assignments `10`, `11`, and `12` are tentative and MUST be the +next three free DIP-0002 special transaction types at the time of merge. If +another DIP claims any of these values first, this DIP is updated to use the +next free values. The names (`PROVIDER_DISSOLVE`, +`PROVIDER_UPDATE_SHARE`, `PROVIDER_UPDATE_SHARED_REGISTRAR`) are normative. + +ProRegTx (type `1`) is reused at payload version 5 for shared registration. +The base ProUpRegTx (type `3`) and ProUpServTx (type `2`) continue to operate +under DIP-0003 rules for non-shared masternodes; both are forbidden for shared +masternodes as specified in [Authorization Tiers](#authorization-tiers). + +### Shared Collateral Output + +A shared registration creates exactly one internal collateral output, here +called the **shared collateral output**. The output value MUST equal +`GetMnType(nType).collat_amount`. + +The shared collateral output uses a single, fixed serialized script, +`SHARED_COLLATERAL_SCRIPT`, with the following normative properties: + +1. It is a fixed byte sequence specified by this DIP; every shared collateral + output on every network uses the identical script bytes. The shared + collateral output is identified by script-template equality, not by + per-masternode commitments. +2. It contains no participant-specific data. In particular, it does not embed + any participant owner key, refund script, reward script, share amount, or + `proTxHash`. The unique identity of a shared collateral output is its + outpoint; the per-masternode binding is provided by the deterministic + masternode state. +3. It is recognizable by full nodes using a single template comparison, and + it is recognizable as non-standard by older nodes that do not understand + shared collateral. +4. After activation, consensus rejects every transaction that spends an + output bearing `SHARED_COLLATERAL_SCRIPT` unless the spend is a valid + `ProDisTx` for the masternode whose collateral outpoint matches the spent + outpoint. This rejection is enforced at the mempool acceptance layer, at + block connection, and during deterministic masternode list processing + (see [Collateral Spend Enforcement](#collateral-spend-enforcement)). +5. Before activation, miners and relays MUST treat any output with this + script as non-standard, so it cannot be created on the live network in + advance of activation. After activation, only a v5 ProRegTx may create + an output with this script. + +The recommended template is a P2SH of an `OP_TRUE`-equivalent redeem script, +chosen so that the output is recognizable by template equality and so that no +existing wallet would construct an equivalent output by accident. The exact +bytes of `SHARED_COLLATERAL_SCRIPT` are assigned during implementation review, +constrained to the properties above; any implementation choice MUST be +identical across all consensus implementations and MUST be encoded into the +chain parameters used for activation. The DIP is amended with the assigned +bytes before mainnet activation. + +Rationale: a P2SH anyone-can-spend template makes the script-level +satisfaction trivial so that a valid `ProDisTx` can construct a sensible +`scriptSig`, while consensus rules above script evaluation reject all +spending paths except dissolution. Embedding per-masternode commitments +(such as `proTxHash` or participant keys) into the output script was +considered and rejected because it (i) would conflict with future +relock-style updates of participant keys and (ii) would require either large +output scripts or a separate commitment scheme that would still depend on +deterministic masternode state to verify. + +### Collateral Share + +```text +CollateralShare { + amount : CAmount (8 bytes, little-endian, in duffs) + refundScript : CScript (compact-size length-prefixed) + rewardScript : CScript (compact-size length-prefixed) + ownerKey : CKeyID (20 bytes) + joinSig : vector (compact-size length-prefixed, 65 bytes when present) +} +``` + +| Field | Mutability | Authorization to change | +| --- | --- | --- | +| `amount` | Immutable | Set at registration; cannot be changed. | +| `refundScript` | Immutable | Set at registration; cannot be changed. | +| `rewardScript` | Mutable per-share | Participant owner key of that share, via `ProUpShareTx`. | +| `ownerKey` | Immutable | Set at registration; cannot be rotated in this DIP. | +| `joinSig` | Set only in `ProRegTx` payload | Validated at registration; not stored in deterministic state. | + +Field rules at registration: + +1. `amount` MUST be at least `SHARED_MIN_SHARE_DUFFS`. +2. `refundScript` MUST be a standard `P2PKH` or `P2SH` script. +3. `rewardScript` MUST be a standard `P2PKH` or `P2SH` script. An empty + `rewardScript` is interpreted at the consensus layer as `refundScript`. +4. `ownerKey` MUST be distinct from every other `ownerKey` in the share + table and from `keyIDVoting`. +5. `ownerKey` MUST be distinct from every active owner key recorded for any + other registered masternode at the registration height. The + unique-property index defined in DIP-0003 is extended to track every + `shares[i].ownerKey` for v5 masternodes (see [Deterministic Masternode + State](#deterministic-masternode-state)). +6. `refundScript` MUST NOT be duplicated within the share table. +7. `rewardScript` MAY be duplicated within the share table. +8. No `refundScript` and no `rewardScript` may be a P2PKH script paying to + `keyIDOwner` (for v5 the field is absent — see below), `keyIDVoting`, or + to a key ID that equals any `shares[i].ownerKey`. The intent matches + the DIP-0026 key-reuse rule: these scripts are spent in lower-trust + wallet contexts and MUST NOT reuse keys that control masternode state. +9. `joinSig` is a 65-byte compact ECDSA signature by `ownerKey` over the + registration consent digest defined in [Registration Consent + Digest](#registration-consent-digest). Any other length or any + signature that does not verify under `ownerKey` is invalid. + +### Shared Registration (ProRegTx v5) + +A v5 ProRegTx has the same general layout as the v4 ProRegTx from DIP-0026 +with the following differences. Field positions for a v5 payload are +specified normatively below; this list is exhaustive and replaces the v4 +ProRegTx layout for `nVersion == 5`. + +| Field | Type | Notes | +| --- | --- | --- | +| `nVersion` | `uint16_t` | MUST be `5`. | +| `nType` | `MnType` (`uint16_t`) | `Regular` or `Evo`. | +| `nMode` | `uint16_t` | MUST be `0`. | +| `collateralOutpoint.hash` | `uint256` | MUST be the null hash. | +| `collateralOutpoint.n` | `uint32_t` | Index of the shared collateral output within this transaction. | +| `netInfo` | DIP-0003/0028 net info | As for v4. | +| `pubKeyOperator` | BLS public key (basic scheme) | As for v4. | +| `keyIDVoting` | `CKeyID` | As for v4. | +| `nOperatorReward` | `uint16_t` | Basis points; as for v4. | +| `shares` | `CollateralShare[]` | 2 to 8 entries; per [Collateral Share](#collateral-share). | +| `earlyPeriodBlocks` | `uint32_t` | `0` to `SHARED_MAX_EARLY_PERIOD_BLOCKS`. | +| `earlyPenalty` | `CAmount` (8 bytes) | Duffs. | +| `standardPenalty` | `CAmount` (8 bytes) | Duffs. | +| `platformNodeID`, ports (Evo only) | as DIP-0028 | As for v4. | +| `inputsHash` | `uint256` | `CalcTxInputsHash(tx)`. | +| `vchSig` | `vector` | MUST be empty for v5 (no external collateral signature). | + +There is no `keyIDOwner` field in a v5 ProRegTx. The role of the single owner +key in DIP-0003 is performed in v5 by the collection of `shares[*].ownerKey`. + +There is no `scriptPayout` or `payouts` field in a v5 ProRegTx. Owner rewards +are derived directly from the share table as specified in [Reward +Distribution](#reward-distribution). + +A v5 ProRegTx is invalid if any of the following conditions hold: + +1. `nVersion != 5`. +2. `nType` is not `Regular` or `Evo`. +3. `nMode != 0`. +4. `collateralOutpoint.hash` is not the null hash. +5. `collateralOutpoint.n` does not point to an output of this transaction. +6. The output at `collateralOutpoint.n` does not pay + `GetMnType(nType).collat_amount` to `SHARED_COLLATERAL_SCRIPT`. +7. The transaction creates more than one output paying + `SHARED_COLLATERAL_SCRIPT`. +8. `shares.size()` is less than `SHARED_MIN_PARTICIPANTS` or greater than + `SHARED_MAX_PARTICIPANTS`. +9. Any share fails its per-field validation rules in [Collateral + Share](#collateral-share). +10. `sum(shares[i].amount) != GetMnType(nType).collat_amount`. +11. Penalty parameter constraints in [Parameters](#parameters) are not + satisfied. +12. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. +13. `vchSig` is non-empty. +14. `inputsHash != CalcTxInputsHash(tx)`. +15. For any share `i`, `shares[i].joinSig` does not verify against + `shares[i].ownerKey` over the registration consent digest defined + below. + +Rules 14 and 15 are evaluated in order: `inputsHash` mismatch and the +recomputed `outputsHash` mismatch (see below) are checked before any +ECDSA signature verification. + +### Registration Consent Digest + +Each participant signs an explicit consent digest that commits to the full +effect of the registration transaction, independent of any +sighash-mode behavior of the funding-input scripts. + +```text +SharedRegConsentHash = SHA256d( + "DashSharedMNReg" || + LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || + inputsHash || outputsHash || + LE16(payload.nVersion) || + LE16(payload.nType) || LE16(payload.nMode) || + LE32(payload.collateralOutpoint.n) || + netInfoSerialized || + platformFieldsSerialized || // present iff nType == Evo + pubKeyOperatorSerialized || + keyIDVoting || + LE16(nOperatorReward) || + LE8(shares.size()) || + sharesWithoutJoinSigs || + LE32(earlyPeriodBlocks) || + LE64(earlyPenalty) || + LE64(standardPenalty) +) +``` + +Where: + +* `LE8`, `LE16`, `LE32`, `LE64` denote little-endian encodings of the + indicated width. +* `SHA256d(x)` is the Dash double-SHA-256 used for transaction hashes. +* `inputsHash` is `CalcTxInputsHash(tx)`. Consensus MUST recompute this + from the transaction and compare it to the `payload.inputsHash` field + before signature verification; mismatch is invalid. +* `outputsHash` is the SHA-256d of the concatenation of every output of + `tx` in order, each serialized as `(value, scriptPubKey)` using the + standard Dash transaction-output serialization. Consensus MUST recompute + this from the transaction; there is no `outputsHash` field on a + ProRegTx, but the recomputed value is used inside this digest. +* `netInfoSerialized` and `platformFieldsSerialized` use the same encoding + as in the provider transaction payload itself. +* `pubKeyOperatorSerialized` is the basic-scheme BLS public key + serialization (48 bytes). +* `sharesWithoutJoinSigs` is the concatenation of every `CollateralShare` + in share order with the `joinSig` field omitted (treated as a zero-length + byte string) so that signatures are not self-referential. + +The domain separator `"DashSharedMNReg"` is the byte sequence of those +ASCII characters with no trailing NUL. + +Funding-input signatures are still required to authorize spending of each +participant's funding UTXOs, but consensus MUST NOT rely on those +signatures to demonstrate consent to shared-masternode parameters: Dash +inherits Bitcoin sighash modes including `SIGHASH_NONE`, `SIGHASH_SINGLE`, +and `SIGHASH_ANYONECANPAY`, any of which permits later modification of +fields a participant would otherwise believe they had committed to. + +### Shared Update Transactions + +Two new special transaction types specialize updates for shared +masternodes. The base `ProUpRegTx` (type `3`) is invalid for v5 masternodes, +because its single-owner authorization model is incompatible with the +shared-registrar tier defined here. The base `ProUpServTx` (type `2`) +remains valid for v5 masternodes with operator authorization (see +[Authorization Tiers](#authorization-tiers)). + +#### ProUpShareTx (type 11) + +A `ProUpShareTx` updates exactly one share's mutable fields. In this DIP +the only mutable per-share field is `rewardScript`. + +```text +CProUpShareTx { + nVersion : uint16_t // MUST be 5 + proTxHash : uint256 + shareIndex : uint16_t // index into shares[] + newRewardScript : CScript + inputsHash : uint256 + sig : vector // 65 bytes +} +``` + +Validation: + +1. The masternode identified by `proTxHash` MUST exist and MUST be a v5 + masternode. +2. `shareIndex` MUST be less than `shares.size()` in the current state. +3. `newRewardScript` MUST be a standard `P2PKH` or `P2SH` script. An empty + `newRewardScript` is interpreted as `shares[shareIndex].refundScript`. +4. `newRewardScript` MUST satisfy the same key-reuse restrictions as + registration: it MUST NOT be a P2PKH paying to any + `shares[i].ownerKey` and MUST NOT pay to `keyIDVoting`. +5. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. +6. `sig` MUST be a valid ECDSA compact signature by + `shares[shareIndex].ownerKey` over: + + ```text + SHA256d("DashSharedMNUpShare" || + proTxHash || LE16(shareIndex) || newRewardScript || inputsHash) + ``` + +A valid `ProUpShareTx` replaces `shares[shareIndex].rewardScript` in the +deterministic masternode state. It does not revive a PoSe-banned masternode +and does not affect any other field. + +#### ProUpSharedRegTx (type 12) + +A `ProUpSharedRegTx` updates fields that affect all participants. In this +DIP the mutable shared-registrar fields are `pubKeyOperator`, `keyIDVoting`, +and `nOperatorReward`. + +```text +CProUpSharedRegTx { + nVersion : uint16_t // MUST be 5 + proTxHash : uint256 + pubKeyOperator : CBLSPublicKey // basic scheme + keyIDVoting : CKeyID + nOperatorReward : uint16_t + inputsHash : uint256 + vchSigs : vector> // exactly shares.size() entries, in share order +} +``` + +Validation: + +1. The masternode identified by `proTxHash` MUST exist and MUST be a v5 + masternode. +2. `pubKeyOperator` MUST be a valid basic-scheme BLS public key and MUST + NOT collide with the operator key of any other registered masternode. +3. `keyIDVoting` MUST NOT equal any `shares[i].ownerKey`. +4. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. +5. `vchSigs.size()` MUST equal `shares.size()`. +6. For each `i` in `[0, shares.size())`, `vchSigs[i]` MUST be a valid + ECDSA compact signature by `shares[i].ownerKey` over: + + ```text + SHA256d("DashSharedMNUpSharedReg" || + proTxHash || pubKeyOperatorSerialized || + keyIDVoting || LE16(nOperatorReward) || inputsHash) + ``` + +7. Signature verification proceeds in share order. Any missing, extra, or + out-of-order signature is invalid. + +A valid `ProUpSharedRegTx` replaces `pubKeyOperator`, `keyIDVoting`, and +`nOperatorReward` in the deterministic masternode state. It does not revive +a PoSe-banned masternode and does not affect share contents, collateral, or +service fields. Operator-controlled fields (`scriptOperatorPayout`, +`netInfo`, Platform identifiers) are unchanged by this transaction; they +continue to be updated by the operator using `ProUpServTx`. + +### Authorization Tiers + +| Field or action | Authorization | Transaction | +| --- | --- | --- | +| Unilateral dissolution | One signature: `shares[actorIndex].ownerKey` | `ProDisTx` (mode `0`) | +| Unanimous dissolution | One signature per participant in share order | `ProDisTx` (mode `1`) | +| `pubKeyOperator` | One signature per participant in share order | `ProUpSharedRegTx` | +| `keyIDVoting` | One signature per participant in share order | `ProUpSharedRegTx` | +| `nOperatorReward` | One signature per participant in share order | `ProUpSharedRegTx` | +| `rewardScript` for share `i` | `shares[i].ownerKey` | `ProUpShareTx` | +| `refundScript` for share `i` | Immutable | — | +| `ownerKey` for share `i` | Immutable | — | +| `shares[i].amount`, share count, penalties, `earlyPeriodBlocks` | Immutable | — | +| `netInfo` (addresses) | Operator BLS key | `ProUpServTx` | +| `scriptOperatorPayout` | Operator BLS key | `ProUpServTx` | +| Platform service endpoints (Evo) | Operator BLS key | `ProUpServTx` | +| PoSe revocation | Operator BLS key | `ProUpRevTx` | + +`ProUpRegTx` (type `3`) is invalid against a v5 masternode. `ProUpServTx` and +`ProUpRevTx` continue to use operator authorization as in DIP-0003 and +DIP-0028. The operator can stop service or revoke the masternode but cannot +redirect owner rewards or spend the shared collateral. + +### Reward Distribution + +Owner-reward distribution for a v5 masternode reuses the DIP-0026 pipeline +but weights by share amount rather than by basis points. + +1. Compute the masternode reward and apply the Platform credit-pool + reallocation as in current rules. +2. If `nOperatorReward != 0` and the operator has set + `scriptOperatorPayout`, compute + `operatorAmount = floor(masternodeReward * nOperatorReward / 10000)` + and subtract from `masternodeReward` to obtain `ownerReward`. Otherwise + `operatorAmount = 0` and `ownerReward = masternodeReward`. +3. Distribute `ownerReward` across `shares` using the helper + `DistributeByWeight(ownerReward, shares[i].amount)` defined below. +4. For each share `i` whose computed reward is non-zero, create one + coinbase output paying that amount to: + * `shares[i].rewardScript`, if `shares[i].rewardScript` is non-empty; + * `shares[i].refundScript`, otherwise. +5. If `operatorAmount != 0`, create the operator payout output to + `scriptOperatorPayout` using current rules. + +```text +DistributeByWeight(total, weights): + sum_w = sum(weights) // MUST be > 0 + out[i] = floor(total * weights[i] / sum_w) for each i + remainder = total - sum(out[i]) + // remainder is distributed in share order, one duff per index, until exhausted + for i in 0..weights.size(): + if remainder == 0: break + out[i] += 1 + remainder -= 1 + return out +``` + +Coinbase validation requires every expected per-share reward output by exact +amount and script. As in DIP-0026, the relative order of outputs within the +coinbase is not consensus-significant. + +The `DistributeByWeight` helper is also used in [Dissolution +(ProDisTx)](#dissolution-prodistx) for penalty redistribution. + +### Dissolution (ProDisTx) + +A `ProDisTx` is the only transaction that can spend a shared collateral +output after activation. It is special transaction type +`TRANSACTION_PROVIDER_DISSOLVE` (`10`). + +```text +CProDisTx { + nVersion : uint16_t // MUST be 5 + proTxHash : uint256 + mode : uint8_t // 0 = unilateral, 1 = unanimous + actorIndex : uint16_t + inputsHash : uint256 + outputsHash : uint256 + vchSigs : vector> +} +``` + +#### Transaction shape + +A valid dissolution transaction satisfies all of: + +1. It has exactly one input, and that input spends the shared collateral + outpoint of the masternode identified by `proTxHash`. No additional + funding inputs are permitted in this initial scope. +2. Its outputs are the refund outputs defined below, in share order, with + no additional outputs of any kind. Change outputs are not permitted. +3. Its `nLockTime` MAY be non-zero. The dissolution authorization digest + commits to `nLockTime`, so consensus enforces whatever value was signed. + +#### Authorization digest + +```text +SharedDisHash = SHA256d( + "DashSharedMNDissolve" || + LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || + inputsHash || outputsHash || + LE16(payload.nVersion) || + proTxHash || + LE8(mode) || LE16(actorIndex) +) +``` + +Where `inputsHash` is `CalcTxInputsHash(tx)` and `outputsHash` is the +SHA-256d of the concatenated serialized outputs of `tx`, both recomputed +from the actual transaction. Consensus MUST recompute both hashes and +reject mismatches with the payload's `inputsHash` and `outputsHash` +fields before any signature is verified. + +#### Period and penalty + +Let `H` be the height at which the dissolution is connected, and let +`state` be the deterministic masternode state for `proTxHash` at the parent +block of `H`. Define: + +```text +early = (H - state.nRegisteredHeight) < state.earlyPeriodBlocks +penalty = (mode == 1) ? 0 : (early ? state.earlyPenalty : state.standardPenalty) +``` + +#### Signature cardinality + +For `mode == 0` (unilateral): + +1. `actorIndex < state.shares.size()`. +2. `vchSigs.size() == 1`. +3. `vchSigs[0]` MUST verify under `state.shares[actorIndex].ownerKey` over + `SharedDisHash`. + +For `mode == 1` (unanimous): + +1. `actorIndex < state.shares.size()`. +2. `vchSigs.size() == state.shares.size()`. +3. For each `i`, `vchSigs[i]` MUST verify under `state.shares[i].ownerKey` + over `SharedDisHash`. Signatures are processed in share order; any + missing, extra, or out-of-order signature is invalid. + +#### Output covenant + +Let `a = actorIndex`, `N = shares.size()`, and `C = +state.shares[i].amount` for the actor and non-actors. Define the +non-actor bonus distribution: + +```text +weights[i] = (i == a) ? 0 : state.shares[i].amount +bonus = DistributeByWeight(penalty, weights) +``` + +The outputs of the dissolution transaction MUST be exactly, in order: + +* For each `i` in `[0, N)`: + * If `i != a`: + * Output `i` MUST pay exactly `state.shares[i].amount + bonus[i]` + to `state.shares[i].refundScript`. + * If `i == a`: + * Output `a` MUST pay to `state.shares[a].refundScript`. + * Its value MUST be at most + `state.shares[a].amount - penalty`. + * Its value MUST be non-negative. + +No other outputs of any kind are permitted. In particular, OP_RETURN +outputs, change outputs, and operator outputs are not permitted. + +For `mode == 1` (unanimous), `penalty` is zero, so `bonus[i] == 0` for +every `i`, each non-actor output pays exactly `state.shares[i].amount` to +`state.shares[i].refundScript`, and the actor output pays at most +`state.shares[a].amount`. + +#### Fee accounting + +The difference between the spent shared collateral value and the sum of +all outputs is the transaction fee: + +```text +fee = state.shared_collateral_value - sum(outputs.value) +``` + +Because every non-actor output is exact and the actor output is bounded +above by `state.shares[a].amount - penalty`, this fee can be taken only +from the actor's share. The fee MUST be non-negative; consensus enforces +this by the actor-output upper bound and the no-extra-output rule. + +A zero-fee dissolution is consensus-valid. Relay and mining policy MAY +require a non-zero fee at the standard minimum relay fee rate; wallets +SHOULD construct dissolutions paying the current standard fee so that +miners include them. + +#### Same-block ordering + +A `ProDisTx` MAY appear in the same block as the v5 `ProRegTx` that +created the spent shared collateral output, provided every other +shared-collateral consensus rule holds. Block validation MUST track shared +collateral outputs created earlier in the same block (see [Collateral +Spend Enforcement](#collateral-spend-enforcement)). + +#### State effect + +Connecting a valid `ProDisTx` removes the masternode identified by +`proTxHash` from the deterministic masternode list as a normal +collateral-spend removal would. The masternode entry MUST NOT be removed +until the corresponding `ProDisTx` has been validated. + +### Collateral Spend Enforcement + +Provider transaction `CheckSpecialTx` validation alone is insufficient to +enforce the shared collateral covenant: an ordinary transaction that +spends the shared collateral output but bears no special-transaction +payload would, under DIP-0003 rules, simply remove the masternode by +collateral spend. Implementations MUST also enforce the rules below +outside `CheckSpecialTx`. + +1. **Mempool acceptance.** Before accepting any transaction into the + mempool, scan its inputs. For each input that spends an output in the + UTXO set bearing `SHARED_COLLATERAL_SCRIPT`, reject the transaction + unless it is a valid `ProDisTx` for the masternode identified by that + outpoint in the current deterministic masternode list. +2. **Block connection, prior blocks.** Before applying the deterministic + masternode list update for a connected block, scan every non-coinbase + transaction in that block for inputs that spend outputs in the UTXO + set bearing `SHARED_COLLATERAL_SCRIPT`. Reject the block unless every + such input is the input of a valid `ProDisTx` for the matching + masternode. +3. **Block connection, same-block.** Maintain a per-block index of shared + collateral outputs created by earlier transactions in the same block, + keyed by `(txid, vout, proTxHash)`. For every later non-coinbase + transaction in the same block, reject inputs that spend such outputs + unless the spending transaction is a valid `ProDisTx` referencing the + corresponding `proTxHash`. The matching `ProDisTx` MUST appear strictly + after the registration in the block ordering. +4. **Deterministic masternode list removal.** Replace the + "remove on collateral spend" rule from DIP-0003 with the following for + v5 masternodes: a v5 masternode MUST NOT be removed until a + corresponding valid `ProDisTx` for that `proTxHash` has been processed. + Any spend of the masternode's shared collateral output is otherwise + invalid. +5. **Block disconnection and reorg.** Disconnecting a block that + contained a `ProDisTx` MUST restore the v5 masternode entry to the + exact pre-dissolution deterministic masternode state. State diffs MUST + capture the full share vector and shared-registration parameters as a + single replacement (see [Deterministic Masternode + State](#deterministic-masternode-state)) so that reorg replay is + deterministic. + +These rules are evaluated before script-level evaluation: even if +`SHARED_COLLATERAL_SCRIPT` is satisfied by a trivial witness, consensus +rejects the spend unless it is a valid `ProDisTx` for the corresponding +masternode. + +### Deterministic Masternode State + +The deterministic masternode state defined in DIP-0003 is extended for +v5 masternodes. The pre-v5 state serialization is unchanged. + +For `nVersion >= ProTxVersion::SharedCollateral`, the masternode state +includes the following additional fields: + +| Field | Type | Notes | +| --- | --- | --- | +| `shares` | `CollateralShare[]` (without `joinSig`) | 2 to 8 entries; order preserved from registration. | +| `earlyPeriodBlocks` | `uint32_t` | Frozen at registration. | +| `earlyPenalty` | `CAmount` | Frozen at registration. | +| `standardPenalty` | `CAmount` | Frozen at registration. | + +The fields `scriptPayout` and `payouts` are unused for v5 masternodes and +MUST be serialized as empty. `keyIDOwner` is unused for v5 masternodes. + +State diffs MUST be version-gated: + +1. The state-diff bitfield gains a new field bit for the share vector. + Any change to any `shares[i].rewardScript` produces a state diff in + which the share vector is fully replaced. Implementations MAY also + choose to encode per-share rewardScript diffs more compactly; if so, + the encoding MUST be invertible byte-for-byte from the resulting + state. +2. State diffs for v5 fields are present only when the masternode is + v5; for pre-v5 masternodes the corresponding bits MUST NOT appear. +3. Snapshot serialization MUST round-trip across restart, reorg replay, + and historic block validation without loss. + +Unique-property indexes MUST be extended: + +* Every `shares[i].ownerKey` participates in the owner-key uniqueness + index used to reject reuse across masternodes at registration time. + Pre-v5 masternodes contribute their single `keyIDOwner`; v5 + masternodes contribute every participant owner key. +* The voting-key uniqueness index applies to `keyIDVoting` as in + DIP-0003. +* The operator-key uniqueness index applies to `pubKeyOperator` as in + DIP-0003. + +A v5 masternode is created only by a valid v5 ProRegTx and removed only +by a valid v5 `ProDisTx` for the same `proTxHash`. Pre-v5 masternodes +continue to be created and removed as in DIP-0003. + +### Simplified Masternode List and Filters + +`CSimplifiedMNListEntry::CalcHash`, as used by DIP-0004 simplified +masternode list verification, MUST NOT include shares, refund scripts, +reward scripts, penalty parameters, or any other v5-only field. Light +clients receive no SML commitment to shared-collateral metadata. This +matches the treatment of payout metadata adopted by DIP-0026. Diagnostic +RPCs and extended JSON output MAY expose v5 fields. + +Special-transaction and bloom filtering, as defined in DIP-0003 for +`ProRegTx`/`ProUpRegTx` payout scripts, is extended for v5 masternodes: + +* For `ProRegTx` v5, the filter MUST match every `shares[i].refundScript` + and every non-empty `shares[i].rewardScript`, and every + `shares[i].ownerKey`. +* For `ProUpShareTx`, the filter MUST match `newRewardScript`. +* For `ProUpSharedRegTx`, the filter MUST match `keyIDVoting`. +* For `ProDisTx`, the filter MUST match every output's `scriptPubKey`, + matching existing transaction-output filter semantics. + +These filter extensions apply only to v5 special transactions after +activation. Pre-v5 special transactions retain DIP-0003 filtering. + +### Governance + +Dash governance assigns one vote per masternode, signed by the voting key +recorded in deterministic masternode state. A v5 masternode retains one +`keyIDVoting` and therefore one vote. + +`keyIDVoting` MAY be updated only by `ProUpSharedRegTx`, which requires +unanimous participant signatures. There is no protocol-level mechanism +for fractional voting among participants; any coordination of +participant preferences for governance votes is off-chain and outside the +scope of this DIP. + +## Deployment and Compatibility + +Activation of this DIP is gated by a future deployment defined under +[DIP-0023](dip-0023.md). The exact deployment name and signaling window +are assigned during release engineering. This DIP does not claim a +specific Dash Core release; release engineering MUST verify the live +deployment state of any candidate fork bit before assigning activation. + +Before activation: + +* Any provider transaction payload with `nVersion == 5` is invalid. +* Special transaction types `10`, `11`, and `12` are invalid. +* Any output with `SHARED_COLLATERAL_SCRIPT` is treated as non-standard + by relay and mining policy and MUST NOT be created on chain. + +After activation: + +* v5 ProRegTx, `ProUpShareTx`, `ProUpSharedRegTx`, and `ProDisTx` are + valid as specified above. +* DIP-0003 single-owner masternodes (v1, v2, v3) and DIP-0026 + multi-payout masternodes (v4) continue to operate unchanged. +* No upgrade path is defined from v1/v2/v3/v4 to v5. A shared masternode + is established only by a new v5 ProRegTx; a v5 masternode is wound + down only by a `ProDisTx`. +* No downgrade path is defined from v5 to v1/v2/v3/v4. + +A v5 masternode and a non-v5 masternode never appear as the same +`proTxHash`; deterministic masternode state is version-gated as +specified in [Deterministic Masternode +State](#deterministic-masternode-state). + +## Rationale + +### Strict superset of DIP-0026 + +DIP-0026 fixes the operational pain of off-chain reward distribution but +does not constrain the registrar owner, the collateral UTXO, or the exit +path. The most common use cases that motivate DIP-0026 (services that +hold the collateral on behalf of multiple beneficiaries) remain +custodial under v4. This DIP addresses that gap by binding share +amounts, refund destinations, and the collateral itself to per-participant +consent. + +### Distinct provider payload version + +Reusing v4 semantics for shared collateral would either redefine +registrar-controlled fields under a multi-signer rule or carry both a +basis-point payout list and a share table on every v4 payload. Either +choice complicates the v4 deserialization rules and the deterministic +masternode state machine. Allocating a separate v5 keeps each version +self-describing and lets nodes that have implemented v4 reject v5 by +version check until activation. + +### Internal collateral only + +External shared collateral would require either a multi-signature +collateral UTXO controlled off-chain (defeating the goal of trustless +exit) or a covenant attached to a previously created output (out of +scope for the existing UTXO format). Constraining v5 to internal +collateral makes the covenant model tractable and ensures that every +shared collateral output is created by a v5 ProRegTx whose consent +digest committed every participant. + +### Immutable share parameters + +Mutable share amounts would let an attacker who compromised one owner +key dilute another participant's stake without unanimous consent. +Mutable refund scripts would create the same risk for the eventual exit +destination. Both are therefore immutable in this DIP. `rewardScript` +is mutable per share because reward destinations are paid in lower-trust +wallet contexts and benefit from key rotation, and because a stale +reward script affects only the participant who owns it. + +### Immutable participant owner keys + +If the shared collateral output embedded participant owner keys, owner-key +rotation would invalidate the output script or require a relock +transaction that simultaneously spent the old collateral and created a +new one while preserving the masternode entry. Both options were +considered out of scope for the initial DIP. The simpler choice is +specified here: participant owner keys are immutable, and the collateral +output script contains no participant-specific data. + +### Operator authority unchanged + +Service availability and operator payout are owned by the operator +under DIP-0003. Moving those fields to unanimous participant consent +would prevent legitimate operator-driven operational changes (such as IP +or port reassignment) without coordinating every participant for routine +maintenance, while not actually improving the security model (the +operator can already withhold service unilaterally). Operator authority +is therefore unchanged. + +### Explicit registration consent signatures + +Funding-input signatures cannot be relied on to authenticate consent to +shared-masternode parameters: Bitcoin/Dash sighash modes +`SIGHASH_NONE`, `SIGHASH_SINGLE`, and `SIGHASH_ANYONECANPAY` each permit +later modification of fields a participant would otherwise believe +they had signed. Each participant therefore signs an explicit +domain-separated consent digest that covers the inputs, outputs, payload +fields, share table, penalty parameters, and version fields. + +### Dissolution covenant commits to outputs + +A signature over only the payload would leave the actor remainder, fee, +and even the refund destinations open to substitution at relay or mining +time without invalidating the payload signature. The dissolution +authorization digest therefore commits to the full transaction effect: +`nVersion`, `nType`, `nLockTime`, recomputed `inputsHash` and +`outputsHash`, and the payload's `proTxHash`, `mode`, and `actorIndex`. + +### Penalty bounds + +The strict `<` bounds on `earlyPenalty` and `standardPenalty` ensure +that even the smallest participant retains a positive remainder after a +unilateral exit. This bounds the worst-case griefing cost and avoids +edge cases where the unilateral actor would have to produce a zero-value +output that violates the output covenant. + +### Mempool and block enforcement + +Spend rejection cannot live only in `CheckSpecialTx`: ordinary +transactions that spend the shared collateral output would not invoke +`CheckSpecialTx` at all, and the DIP-0003 "remove on collateral spend" +rule would otherwise quietly drop the masternode. The required mempool +and block-validation hooks are made explicit. + +### Same-block dissolution + +Allowing a `ProDisTx` in the same block as the corresponding v5 +ProRegTx lets a participant exit immediately after registration, which +is important for use cases where coordination off-chain proved +unsatisfactory. It also matches the existing behavior that a regular +collateral can be spent in the same block as its ProRegTx. + +### One vote per masternode + +Fractional governance would require either a new vote-aggregation +scheme or a per-share signing tier on every governance message. Both are +out of scope. Preserving the one-masternode-one-vote rule keeps shared +masternodes compatible with the existing governance infrastructure and +defers fractional voting to a future DIP. + +## Test Cases + +Implementations SHOULD include at minimum the following tests. Test +values for `SHARED_COLLATERAL_SCRIPT` are taken from the activation +chain parameters. + +### Helper + +1. `DistributeByWeight(10000, [2500, 2500, 2500, 2500])` returns + `[2500, 2500, 2500, 2500]`. +2. `DistributeByWeight(10001, [2500, 2500, 2500, 2500])` returns + `[2501, 2500, 2500, 2500]`. +3. `DistributeByWeight(10, [0, 1, 1])` returns `[0, 5, 5]`. +4. `DistributeByWeight(7, [1, 1, 1, 1, 1, 1, 1, 1])` returns one duff + to each of the first seven indices and zero to the eighth. + +### Registration + +1. A v5 ProRegTx with two shares whose amounts sum to 1000 DASH, both + shares signed correctly, is valid. +2. A v5 ProRegTx with eight shares summing to 1000 DASH, all signed, is + valid. +3. A v5 ProRegTx with one share is invalid. +4. A v5 ProRegTx with nine shares is invalid. +5. A v5 ProRegTx whose share amounts sum to 999.99 DASH is invalid. +6. A v5 ProRegTx with a duplicate participant owner key is invalid. +7. A v5 ProRegTx with a duplicate refund script is invalid. +8. A v5 ProRegTx with a refund script paying to a participant owner key + is invalid. +9. A v5 ProRegTx whose collateral output uses a P2PKH script (not + `SHARED_COLLATERAL_SCRIPT`) is invalid. +10. A v5 ProRegTx whose `vchSig` is non-empty is invalid. +11. A v5 ProRegTx whose `joinSig` for share `i` was produced under a + different `outputsHash` is invalid. +12. A v5 ProRegTx whose `joinSig` for share `i` was produced under a + different penalty value is invalid. + +### Reward Splitting + +1. A v5 masternode with shares `[500, 500]` DASH receives two coinbase + outputs of equal value to each `rewardScript`. +2. A v5 masternode with shares `[300, 300, 400]` DASH receives three + coinbase outputs proportional to the shares, with rounding rules per + `DistributeByWeight`. +3. A v5 masternode with `nOperatorReward = 1000` subtracts the operator + amount first, then splits the remainder by share. +4. A coinbase missing any expected per-share output is invalid. +5. A coinbase with an extra unexpected output for a v5 masternode is + invalid. + +### Updates + +1. A `ProUpShareTx` signed by `shares[0].ownerKey` updating + `shares[0].rewardScript` is valid. +2. A `ProUpShareTx` for share `0` signed by `shares[1].ownerKey` is + invalid. +3. A `ProUpShareTx` setting `newRewardScript` to a P2PKH paying + `shares[0].ownerKey` is invalid. +4. A `ProUpSharedRegTx` with `vchSigs.size() == shares.size()`, signed + by every share in order, updating `nOperatorReward`, is valid. +5. A `ProUpSharedRegTx` missing one signature is invalid. +6. A `ProUpSharedRegTx` with an extra signature is invalid. +7. A `ProUpSharedRegTx` whose signatures are presented out of share + order is invalid. +8. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. + +### Dissolution + +1. A unilateral `ProDisTx` after the early period, with exactly one + signature by `shares[a].ownerKey`, penalty equal to + `standardPenalty`, and exact non-actor outputs, is valid. +2. A unilateral `ProDisTx` during the early period uses `earlyPenalty` + instead of `standardPenalty`. +3. A unanimous `ProDisTx` with `shares.size()` signatures in share + order and zero penalty is valid. +4. A unilateral `ProDisTx` whose actor output exceeds + `shares[a].amount - penalty` is invalid. +5. A unilateral `ProDisTx` whose non-actor output `i` pays more or less + than `shares[i].amount + bonus[i]` is invalid. +6. A unilateral `ProDisTx` whose non-actor output `i` pays to a script + other than `shares[i].refundScript` is invalid. +7. A unilateral `ProDisTx` with an extra output (e.g. OP_RETURN) is + invalid. +8. A unilateral `ProDisTx` with an extra input is invalid. +9. A unanimous `ProDisTx` missing one participant signature is invalid. +10. A `ProDisTx` whose `outputsHash` does not match the recomputed + transaction `outputsHash` is invalid; the signature check is not + reached. +11. A normal transaction (`nVersion < 3` or `nType == 0`) that spends a + shared collateral output is rejected by mempool and by block + validation. +12. A non-dissolution special transaction whose input spends a shared + collateral output is rejected. +13. A v5 ProRegTx and a unilateral `ProDisTx` for the same `proTxHash` + in the same block, in that order, are accepted; the masternode is + created and then removed within the block. + +### Reorg + +1. Disconnecting a block containing a v5 ProRegTx fully removes the + masternode entry, including all share state. +2. Disconnecting a block containing a `ProUpShareTx` restores the + prior `rewardScript`. +3. Disconnecting a block containing a `ProDisTx` restores the v5 + masternode entry with its full share state. +4. A reorg that replays a `ProUpSharedRegTx` followed by a `ProDisTx` + in a different order produces the same final state. + +## Implementation Notes + +These notes are non-normative guidance for implementers. + +* The deterministic masternode state-diff bitfield must reserve a new bit + for the share vector. Implementations should follow the bit-allocation + conventions already used in `CDeterministicMNStateDiff` so that + pre-v5 snapshots continue to round-trip exactly. +* `CheckSpecialTx` should treat shared collateral outputs as a separate + spend-rejection rule rather than as part of the per-payload checks; the + spend rejection applies to *every* transaction in mempool and block + contexts, not only to special transactions. +* The same-block index of new shared collateral outputs introduced by + v5 ProRegTx earlier in the block can be implemented as a + small `std::unordered_map` that is + populated as block transactions are validated and consulted by every + later transaction's input scan. +* Coinbase construction should reuse the existing payout-pipeline hook + added by DIP-0026 (PR 184) and append one output per v5 share with the + computed amount and target script. +* The wallet RPC surface should expose: a coordinator-style flow that + builds an unsigned v5 ProRegTx given each participant's funding inputs + and share parameters; per-participant `joinSig` production over the + consent digest; and a `dissolvemasternode` RPC that builds a + `ProDisTx`, computes the signed dissolution digest, and either + collects the actor's signature (unilateral) or the full set of + signatures (unanimous). +* RPC output for `protx info` for a v5 masternode should expose the + share table, penalty parameters, and the canonical + `SHARED_COLLATERAL_SCRIPT` template for diagnostic purposes. + +## Security Considerations + +### Trust model + +A v5 masternode requires only that consensus is honest. No participant +trusts any other participant with custody of funds. Specifically: + +* Collateral cannot be spent except through a valid `ProDisTx` for the + matching masternode, enforced by mempool and block consensus. +* Unilateral dissolution lets any participant exit at any time without + the cooperation of any other participant; the only cost is the + configured penalty (paid to non-actors) and the transaction fee + (paid from the actor's share). +* Per-share reward outputs go to a participant-controlled script set at + registration; the registrar cannot rewrite them. +* Unanimous registrar updates require every participant's signature. + +### Operator compromise + +The operator can withhold service, but cannot redirect owner rewards, +spend collateral, or alter share state. Service withholding can cause a +PoSe ban and thereby reduce or stop owner reward accrual. Participants +can mitigate this by replacing the operator via `ProUpSharedRegTx` +(unanimous) or by dissolving the masternode. Participants choosing an +operator SHOULD treat operator selection as a unanimous-consent decision. + +### One-participant compromise + +If one participant's owner key is compromised, the attacker can: + +* update that participant's `rewardScript` to a controlled script, + redirecting only that participant's future rewards; +* sign a unilateral `ProDisTx`, exiting the masternode with the + compromised participant's share minus the penalty (paid to the + non-actor honest participants) and the transaction fee. + +The attacker cannot: + +* redirect any other participant's reward share; +* redirect any participant's refund on exit (refund scripts are + immutable); +* spend collateral except through a valid dissolution; +* alter `keyIDVoting`, `pubKeyOperator`, or `nOperatorReward` (those + require unanimous consent). + +This bounds the loss from a single compromised key to that +participant's share plus the loss of future reward share for the +remaining lifetime of the masternode. + +### Penalty griefing + +If `earlyPenalty` and `standardPenalty` are both zero, a malicious +participant can dissolve the masternode immediately after registration +at no cost to themselves, returning the other participants' principal +but disrupting service. Participants SHOULD agree on non-trivial penalty +values that compensate for the cost of redeploying the masternode. +Wallets SHOULD warn when a participant signs a registration with +zero-valued penalties. + +### Future-block reward dust + +The block reward declines over time. A future block reward could be +small enough that the per-share coinbase output for the smallest share +falls below policy dust thresholds. `SHARED_MIN_SHARE_DUFFS` is chosen +to keep per-share rewards safely above current dust thresholds at +present reward levels; it does not guarantee that the smallest share's +reward output will remain non-dust forever. Wallets SHOULD warn during +registration when any share is small enough that anticipated future +rewards could approach dust. + +### Light client guarantees + +Shared-collateral metadata is not committed to by SML hashes. SPV +clients cannot verify v5 share state without a full node or a +DIP-0004-extending future proof. SPV-level features that depend on +v5 metadata (filter matching of refund and reward scripts, for example) +require the full node serving the client to be honest about v5 state. + +### Replay across chains + +The registration consent digest and the dissolution authorization digest +include the payload's `nVersion`, the transaction's `nVersion`, +`nType`, and `nLockTime`, the recomputed `inputsHash` and `outputsHash`, +and the masternode `proTxHash`. They do not include a chain identifier. +Cross-chain replay protection relies on the existing Dash policy that +chains use distinct genesis blocks and therefore distinct `proTxHash` +values for any v5 registration. A future DIP MAY add an explicit chain +identifier to these digests if cross-chain replay protection becomes a +requirement. + +## Open Issues + +The following implementation details are deferred and MUST be resolved +before activation: + +1. **Exact bytes of `SHARED_COLLATERAL_SCRIPT`.** The recommended + template family is a P2SH of an `OP_TRUE`-equivalent redeem + script. The chosen bytes are encoded into chain parameters and + included in this DIP before mainnet activation. +2. **Final numeric values of `TRANSACTION_PROVIDER_DISSOLVE`, + `TRANSACTION_PROVIDER_UPDATE_SHARE`, and + `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR`.** Tentatively + `10`, `11`, `12`. If another DIP claims any of these values + first, this DIP shifts to the next free values. +3. **Final value of `SHARED_MIN_SHARE_DUFFS`.** Tentatively + `1000000000` (10 DASH). Subject to dust-policy review and + feedback from wallet implementers. +4. **Activation deployment name.** Subject to release engineering + confirmation that no candidate fork bit has already been consumed. + +The following protocol-level extensions are out of scope for this DIP +and may be addressed by future DIPs: + +1. External shared collateral. +2. Participant replacement without full dissolution and re-registration. +3. Owner-key rotation, with or without a relock transaction. +4. Refund-script mutation under unanimous consent. +5. Extra fee inputs and explicit change outputs in `ProDisTx`. +6. Protocol-level fractional governance among participants. + +## Copyright + +Copyright (c) 2026 Dash Core Group, Inc. [Licensed under the MIT License](https://opensource.org/licenses/MIT). From 732d6863add8d332ca2ea1ce9b686571ecc83e39 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 05:51:28 -0500 Subject: [PATCH 02/18] docs(shared-mn): tighten covenant scope, dissolution, and weight helper * Replace 1051200 (~2 yr at 60 s) early-period ceiling with 420480 (~2 yr at nominal 2.5 min/block) and document wall-clock conversion. * Narrow the shared collateral covenant: consensus protects only active or same-block-pending v5 collateral outpoints, not every output whose scriptPubKey matches the template, so unrelated UTXOs with the same script are not retroactively locked. * Fix DistributeByWeight remainder distribution to skip zero-weight indices and require sum_w > 0, and add helper tests. * Allow the actor's dissolution output to be omitted when its value would be zero (e.g. when the fee consumes the full pre-fee remainder); reject stray zero-value actor outputs; specify output matching by walking the share table. * Special-case unanimous dissolution so DistributeByWeight is not invoked with an all-zero weight vector; bonus is set to zero directly. * Update test cases and penalty-bounds rationale. Co-Authored-By: Claude Opus 4.7 (1M context) --- dip-decentralized-masternode-shares.md | 271 ++++++++++++++++++------- 1 file changed, 195 insertions(+), 76 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 1166e978..1f40c1b8 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -156,7 +156,16 @@ v5 registration, and a shared masternode is wound down only by a valid | `SHARED_MIN_PARTICIPANTS` | 2 | Minimum number of shares. | | `SHARED_MAX_PARTICIPANTS` | 8 | Maximum number of shares, matching DIP-0026 payout-count limits. | | `SHARED_MIN_SHARE_DUFFS` | 1000000000 (10 DASH) | Minimum value of any `shares[i].amount`. | -| `SHARED_MAX_EARLY_PERIOD_BLOCKS` | 1051200 (~2 years at 60 s blocks) | Maximum value of `earlyPeriodBlocks`. | +| `SHARED_MAX_EARLY_PERIOD_BLOCKS` | 420480 (~2 years at nominal 2.5 min/block) | Maximum value of `earlyPeriodBlocks`. | + +`SHARED_MAX_EARLY_PERIOD_BLOCKS` is given in blocks at Dash's nominal block +interval of 2.5 minutes (150 s), yielding approximately +`365 * 24 * 60 / 2.5 * 2 = 420480` blocks for a two-year ceiling. Realized block +times vary, so the wall-clock equivalent drifts with hashrate; activation may +refine this constant to the actual measured cap selected at deployment time. +Implementations and tooling that prefer a wall-clock interface SHOULD convert +the user-facing value to blocks at the nominal 2.5 min/block rate and reject +any input that exceeds `SHARED_MAX_EARLY_PERIOD_BLOCKS` once converted. `SHARED_MIN_SHARE_DUFFS` exists to keep recurring per-share coinbase outputs above policy dust thresholds at present block rewards while permitting modest @@ -223,27 +232,44 @@ The shared collateral output uses a single, fixed serialized script, `SHARED_COLLATERAL_SCRIPT`, with the following normative properties: 1. It is a fixed byte sequence specified by this DIP; every shared collateral - output on every network uses the identical script bytes. The shared - collateral output is identified by script-template equality, not by - per-masternode commitments. + output on every network uses the identical script bytes. Script-template + equality is the registration-time selector used to identify which output + of a v5 ProRegTx is the shared collateral output; it is NOT, on its own, + the consensus identifier of a protected collateral. 2. It contains no participant-specific data. In particular, it does not embed any participant owner key, refund script, reward script, share amount, or - `proTxHash`. The unique identity of a shared collateral output is its - outpoint; the per-masternode binding is provided by the deterministic - masternode state. + `proTxHash`. The per-masternode binding between an outpoint and a + `proTxHash` is provided by the deterministic masternode state, not by the + script itself. 3. It is recognizable by full nodes using a single template comparison, and it is recognizable as non-standard by older nodes that do not understand shared collateral. -4. After activation, consensus rejects every transaction that spends an - output bearing `SHARED_COLLATERAL_SCRIPT` unless the spend is a valid - `ProDisTx` for the masternode whose collateral outpoint matches the spent - outpoint. This rejection is enforced at the mempool acceptance layer, at - block connection, and during deterministic masternode list processing - (see [Collateral Spend Enforcement](#collateral-spend-enforcement)). -5. Before activation, miners and relays MUST treat any output with this +4. After activation, consensus protects **shared collateral outpoints** — the + set of UTXOs that are either (a) recorded as the collateral outpoint of an + active v5 masternode in the deterministic masternode list, or (b) created + by a valid v5 ProRegTx earlier in the same block currently being + validated. Any spend of an outpoint in this set is rejected unless the + spending transaction is a valid `ProDisTx` for the corresponding + masternode. Outpoints in this set are referred to below as + "active-or-pending shared collateral outpoints" and the rejection is + enforced at mempool acceptance, at block connection, and during + deterministic masternode list processing (see [Collateral Spend + Enforcement](#collateral-spend-enforcement)). +5. Ordinary UTXOs that happen to pay `SHARED_COLLATERAL_SCRIPT` but are NOT + active-or-pending shared collateral outpoints are NOT bound by the + dissolution covenant. They behave as ordinary outputs at script-evaluation + time, subject to whatever spending conditions the recommended template + imposes (for example, the `OP_TRUE` redeem-script template makes them + anyone-can-spend). Implementations MUST NOT retroactively lock arbitrary + pre-existing outputs or unrelated outputs created outside the v5 + registration flow. +6. Before activation, miners and relays MUST treat any output with this script as non-standard, so it cannot be created on the live network in - advance of activation. After activation, only a v5 ProRegTx may create - an output with this script. + advance of activation. After activation, the only way for a transaction + to register an outpoint into the protected set is a valid v5 ProRegTx; + relay and mining policy MUST continue to treat any other output bearing + `SHARED_COLLATERAL_SCRIPT` as non-standard to discourage accidental + creation of stranded anyone-can-spend outputs. The recommended template is a P2SH of an `OP_TRUE`-equivalent redeem script, chosen so that the output is recognizable by template equality and so that no @@ -569,17 +595,28 @@ but weights by share amount rather than by basis points. ```text DistributeByWeight(total, weights): - sum_w = sum(weights) // MUST be > 0 + sum_w = sum(weights) + require sum_w > 0 // reject all-zero weights out[i] = floor(total * weights[i] / sum_w) for each i remainder = total - sum(out[i]) - // remainder is distributed in share order, one duff per index, until exhausted + // remainder is distributed deterministically to indices with positive + // weights only, in ascending index order, one duff per such index, + // until exhausted. Indices with weight 0 are skipped so that no duff is + // ever credited to a zero-weight recipient. for i in 0..weights.size(): if remainder == 0: break + if weights[i] == 0: continue out[i] += 1 remainder -= 1 return out ``` +`DistributeByWeight` is invalid for an all-zero weight vector and callers +MUST NOT invoke it in that case; for unanimous dissolution, where the +penalty itself is zero, the bonus is taken to be all-zero directly without +calling `DistributeByWeight` (see [Dissolution +(ProDisTx)](#dissolution-prodistx)). + Coinbase validation requires every expected per-share reward output by exact amount and script. As in DIP-0026, the relative order of outputs within the coinbase is not consensus-significant. @@ -666,34 +703,69 @@ For `mode == 1` (unanimous): #### Output covenant -Let `a = actorIndex`, `N = shares.size()`, and `C = -state.shares[i].amount` for the actor and non-actors. Define the -non-actor bonus distribution: +Let `a = actorIndex` and `N = shares.size()`. Define the non-actor bonus +distribution: ```text -weights[i] = (i == a) ? 0 : state.shares[i].amount -bonus = DistributeByWeight(penalty, weights) +if mode == 1: + // unanimous: penalty is zero, bonus is trivially zero everywhere. + // DistributeByWeight is NOT invoked. + bonus[i] = 0 for all i in [0, N) +else: + // unilateral: redistribute the penalty pro rata among non-actors. + // At least one non-actor weight is positive because N >= 2 and every + // share amount is positive, so sum_w > 0 and DistributeByWeight is + // well-defined. + weights[i] = (i == a) ? 0 : state.shares[i].amount + bonus = DistributeByWeight(penalty, weights) ``` -The outputs of the dissolution transaction MUST be exactly, in order: +Define the per-share dissolution value: -* For each `i` in `[0, N)`: - * If `i != a`: - * Output `i` MUST pay exactly `state.shares[i].amount + bonus[i]` - to `state.shares[i].refundScript`. - * If `i == a`: - * Output `a` MUST pay to `state.shares[a].refundScript`. - * Its value MUST be at most - `state.shares[a].amount - penalty`. - * Its value MUST be non-negative. +```text +value[i] = (i == a) ? actorValue + : state.shares[i].amount + bonus[i] +``` -No other outputs of any kind are permitted. In particular, OP_RETURN -outputs, change outputs, and operator outputs are not permitted. +where `actorValue` is the value chosen by the constructor of the +transaction, subject to: -For `mode == 1` (unanimous), `penalty` is zero, so `bonus[i] == 0` for -every `i`, each non-actor output pays exactly `state.shares[i].amount` to -`state.shares[i].refundScript`, and the actor output pays at most -`state.shares[a].amount`. +```text +0 <= actorValue <= state.shares[a].amount - penalty +``` + +The outputs of the dissolution transaction MUST be exactly, in order +matching the share table with the actor slot optionally omitted: + +* Walk the share table in ascending `i` from `0` to `N - 1`. For each + `i`: + * If `i != a`, the transaction MUST contain the next output at this + position, paying exactly `state.shares[i].amount + bonus[i]` to + `state.shares[i].refundScript`. + * If `i == a` and `actorValue > 0`, the transaction MUST contain the + next output at this position, paying exactly `actorValue` to + `state.shares[a].refundScript`. + * If `i == a` and `actorValue == 0`, the actor's output MUST be + omitted entirely. No dust, OP_RETURN, or placeholder output stands + in for the actor. + +Non-actor outputs always appear in share order and are exact; the +actor's output appears in its share-order slot when present and is +absent otherwise. No outputs other than those defined above are +permitted. In particular, OP_RETURN outputs, change outputs, and +operator outputs are not permitted. + +Consensus matches outputs by walking the share table in share order and +either consuming the next output (for non-actor slots and for the actor +slot when present) or skipping the slot entirely (only for the actor +when `actorValue` would be zero). Any deviation — extra output, missing +non-actor output, wrong refund script, wrong amount, or a stray actor +output when the value would be zero — is invalid. + +For `mode == 1` (unanimous), `penalty` is zero, every non-actor output +pays exactly `state.shares[i].amount` to `state.shares[i].refundScript`, +and the actor output pays `actorValue` with `0 <= actorValue <= +state.shares[a].amount` (or is omitted when `actorValue == 0`). #### Fee accounting @@ -733,35 +805,53 @@ until the corresponding `ProDisTx` has been validated. Provider transaction `CheckSpecialTx` validation alone is insufficient to enforce the shared collateral covenant: an ordinary transaction that -spends the shared collateral output but bears no special-transaction -payload would, under DIP-0003 rules, simply remove the masternode by -collateral spend. Implementations MUST also enforce the rules below -outside `CheckSpecialTx`. +spends the shared collateral outpoint of an active v5 masternode but +bears no special-transaction payload would, under DIP-0003 rules, simply +remove the masternode by collateral spend. Implementations MUST also +enforce the rules below outside `CheckSpecialTx`. + +The protected set is the set of **active-or-pending shared collateral +outpoints**, defined as: + +* every collateral outpoint recorded against an active v5 masternode in + the deterministic masternode list at the parent of the block (or + mempool tip) being validated, plus +* every collateral outpoint created by a valid v5 ProRegTx that has been + processed earlier in the block currently being validated and that has + not itself already been dissolved earlier in that block. + +A UTXO whose `scriptPubKey` equals `SHARED_COLLATERAL_SCRIPT` but whose +outpoint is not in this set is NOT protected by the covenant and is not +the subject of the rules below. The rules deliberately key off the +recorded outpoint identity, not raw script equality, to avoid +retroactively locking pre-existing or unrelated outputs that happen to +match the template. 1. **Mempool acceptance.** Before accepting any transaction into the - mempool, scan its inputs. For each input that spends an output in the - UTXO set bearing `SHARED_COLLATERAL_SCRIPT`, reject the transaction - unless it is a valid `ProDisTx` for the masternode identified by that - outpoint in the current deterministic masternode list. + mempool, scan its inputs. For each input that spends an + active-or-pending shared collateral outpoint, reject the transaction + unless it is a valid `ProDisTx` for the masternode whose collateral + outpoint matches the spent outpoint in the current deterministic + masternode list. 2. **Block connection, prior blocks.** Before applying the deterministic masternode list update for a connected block, scan every non-coinbase - transaction in that block for inputs that spend outputs in the UTXO - set bearing `SHARED_COLLATERAL_SCRIPT`. Reject the block unless every - such input is the input of a valid `ProDisTx` for the matching - masternode. -3. **Block connection, same-block.** Maintain a per-block index of shared - collateral outputs created by earlier transactions in the same block, - keyed by `(txid, vout, proTxHash)`. For every later non-coinbase - transaction in the same block, reject inputs that spend such outputs - unless the spending transaction is a valid `ProDisTx` referencing the - corresponding `proTxHash`. The matching `ProDisTx` MUST appear strictly - after the registration in the block ordering. + transaction in that block for inputs that spend collateral outpoints + of active v5 masternodes in the parent-state deterministic masternode + list. Reject the block unless every such input is the input of a + valid `ProDisTx` for the matching masternode. +3. **Block connection, same-block.** Maintain a per-block index of + shared collateral outputs created by valid v5 ProRegTx earlier in the + same block, keyed by `(txid, vout, proTxHash)`. For every later + non-coinbase transaction in the same block, reject inputs that spend + such outputs unless the spending transaction is a valid `ProDisTx` + referencing the corresponding `proTxHash`. The matching `ProDisTx` + MUST appear strictly after the registration in the block ordering. 4. **Deterministic masternode list removal.** Replace the "remove on collateral spend" rule from DIP-0003 with the following for v5 masternodes: a v5 masternode MUST NOT be removed until a corresponding valid `ProDisTx` for that `proTxHash` has been processed. - Any spend of the masternode's shared collateral output is otherwise - invalid. + Any spend of the masternode's shared collateral outpoint by a + transaction that is not such a `ProDisTx` is invalid. 5. **Block disconnection and reorg.** Disconnecting a block that contained a `ProDisTx` MUST restore the v5 masternode entry to the exact pre-dissolution deterministic masternode state. State diffs MUST @@ -770,10 +860,13 @@ outside `CheckSpecialTx`. State](#deterministic-masternode-state)) so that reorg replay is deterministic. -These rules are evaluated before script-level evaluation: even if -`SHARED_COLLATERAL_SCRIPT` is satisfied by a trivial witness, consensus -rejects the spend unless it is a valid `ProDisTx` for the corresponding -masternode. +These rules are evaluated before script-level evaluation for inputs that +spend active-or-pending shared collateral outpoints: even if the +underlying redeem script is trivially satisfiable, consensus rejects the +spend of a protected outpoint unless the spend is a valid `ProDisTx` for +the corresponding masternode. Spends of unprotected UTXOs that happen to +bear the same script are not subjected to the covenant and are +script-evaluated normally. ### Deterministic Masternode State @@ -971,10 +1064,15 @@ authorization digest therefore commits to the full transaction effect: ### Penalty bounds The strict `<` bounds on `earlyPenalty` and `standardPenalty` ensure -that even the smallest participant retains a positive remainder after a -unilateral exit. This bounds the worst-case griefing cost and avoids -edge cases where the unilateral actor would have to produce a zero-value -output that violates the output covenant. +that even the smallest participant retains a positive remainder +*before fees* after a unilateral exit, so that no consent flow can +configure a penalty that exceeds the actor's principal. This bounds the +worst-case griefing cost. The output covenant separately permits the +actor's output to be omitted entirely when the transaction fee consumes +the full pre-fee remainder, so the strict `<` rule on penalties is not +required to keep the post-fee actor remainder positive; that flexibility +exists so that the actor can cover an arbitrary fee at relay time +without violating the covenant. ### Mempool and block enforcement @@ -1015,6 +1113,11 @@ chain parameters. 3. `DistributeByWeight(10, [0, 1, 1])` returns `[0, 5, 5]`. 4. `DistributeByWeight(7, [1, 1, 1, 1, 1, 1, 1, 1])` returns one duff to each of the first seven indices and zero to the eighth. +5. `DistributeByWeight(3, [0, 1, 0, 1, 0])` returns `[0, 2, 0, 1, 0]`: + the remainder skips zero-weight indices and is credited only to + positive-weight indices in ascending order. +6. `DistributeByWeight(5, [0, 0, 0])` is invalid (all-zero weights) and + MUST be rejected by the helper. ### Registration @@ -1084,18 +1187,34 @@ chain parameters. 7. A unilateral `ProDisTx` with an extra output (e.g. OP_RETURN) is invalid. 8. A unilateral `ProDisTx` with an extra input is invalid. -9. A unanimous `ProDisTx` missing one participant signature is invalid. -10. A `ProDisTx` whose `outputsHash` does not match the recomputed +9. A unilateral `ProDisTx` whose actor output is omitted because the + fee equals `shares[a].amount - penalty` (so the actor remainder + after the fee is zero) is valid, and its output list is the + non-actor outputs in share order with no actor slot. +10. A unilateral `ProDisTx` that includes an actor output paying zero + duffs to `shares[a].refundScript` is invalid; the actor output MUST + be omitted rather than paid as a zero-value output. +11. A unanimous `ProDisTx` whose actor output is omitted (because the + fee equals `shares[a].amount`) is valid; every non-actor output + pays exactly `shares[i].amount`. +12. A unanimous `ProDisTx` missing one participant signature is + invalid. +13. A `ProDisTx` whose `outputsHash` does not match the recomputed transaction `outputsHash` is invalid; the signature check is not reached. -11. A normal transaction (`nVersion < 3` or `nType == 0`) that spends a - shared collateral output is rejected by mempool and by block - validation. -12. A non-dissolution special transaction whose input spends a shared - collateral output is rejected. -13. A v5 ProRegTx and a unilateral `ProDisTx` for the same `proTxHash` +14. A normal transaction (`nVersion < 3` or `nType == 0`) that spends + the collateral outpoint of an active v5 masternode is rejected by + mempool and by block validation. +15. A non-dissolution special transaction whose input spends the + collateral outpoint of an active v5 masternode is rejected. +16. A v5 ProRegTx and a unilateral `ProDisTx` for the same `proTxHash` in the same block, in that order, are accepted; the masternode is created and then removed within the block. +17. An ordinary transaction whose input spends an unrelated UTXO that + happens to pay `SHARED_COLLATERAL_SCRIPT` but is NOT a recorded + active or same-block-pending shared collateral outpoint is NOT + rejected by the covenant; whether it succeeds depends only on + ordinary script evaluation of the underlying redeem script. ### Reorg From 98884bdf97b436a7763705744349baf4ae27a375 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 05:52:25 -0500 Subject: [PATCH 03/18] docs(shared-mn): clarify draft covenant wording --- dip-decentralized-masternode-shares.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 1f40c1b8..c7896080 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -264,9 +264,10 @@ The shared collateral output uses a single, fixed serialized script, pre-existing outputs or unrelated outputs created outside the v5 registration flow. 6. Before activation, miners and relays MUST treat any output with this - script as non-standard, so it cannot be created on the live network in - advance of activation. After activation, the only way for a transaction - to register an outpoint into the protected set is a valid v5 ProRegTx; + script as non-standard, so ordinary relay and mining policy discourages + creating such outputs before activation. After activation, the only way + for a transaction to register an outpoint into the protected set is a + valid v5 ProRegTx; relay and mining policy MUST continue to treat any other output bearing `SHARED_COLLATERAL_SCRIPT` as non-standard to discourage accidental creation of stranded anyone-can-spend outputs. From 4cb5c2f40f4526aa07ce37509a1aea83b318b5e2 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 05:59:39 -0500 Subject: [PATCH 04/18] docs(shared-mn): add chain-id digest binding and tighten version wording Address compatibility/versioning review on the draft Shared Masternode DIP: * Bind SharedRegConsentHash and SharedDisHash to chainGenesisHash so consensus digests are network-scoped; signers MUST verify chain context before signing. Rewrite the cross-chain replay security consideration accordingly: proTxHash alone is not sufficient. * Clarify that ProTxVersion::SharedCollateral = 5 is the v5 ProRegTx payload version and the shared-MN state tag; the new ProDisTx, ProUpShareTx, and ProUpSharedRegTx payloads carry their own independent nVersion starting at 1. Reserve future provider payload versions (6+) for single-owner ProRegTx changes; v5 is not a generic mode flag. * Clean up ProUpRegTx/ProUpServTx wording: ProUpRegTx is invalid against a v5 masternode; ProUpServTx remains operator-authorized for service/operator-payout fields only and MUST NOT touch owner or shared-collateral fields. * Spell out the SML/filter trust boundary: SML hashes MUST NOT include v5-only fields, full nodes validate v5 data from reconstructed state, filter matches exist for client discovery only and are not consensus commitments. A future DIP-0004 extension would be required for SPV commitments. Mirrors the non-commitment direction taken for DIP-0026 payout metadata. * Mark the DIP explicitly non-final for activation until concrete SHARED_COLLATERAL_SCRIPT bytes are assigned and inserted, and strengthen the open issue requiring re-check of the tentative 10/11/12 special transaction type numbers against the authoritative DIP-0002 / Dash Core allocations before merge or activation, with mandatory renumbering on conflict. Co-Authored-By: Claude Opus 4.7 (1M context) --- dip-decentralized-masternode-shares.md | 147 ++++++++++++++++++++----- 1 file changed, 118 insertions(+), 29 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index c7896080..7c90e514 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -198,12 +198,25 @@ before fees. ### Provider Transaction Version -This DIP introduces one new provider transaction payload version and three -new special transaction types. +This DIP introduces one new ProRegTx payload version and three new special +transaction types, each with its own independent payload version. | Name | Value | Applies to | | --- | ---: | --- | -| `ProTxVersion::SharedCollateral` | 5 | `ProRegTx`, `ProUpShareTx`, `ProUpSharedRegTx`, `ProDisTx` | +| `ProTxVersion::SharedCollateral` | 5 | `ProRegTx` payload version for shared-collateral registration and the corresponding shared masternode state. | + +`ProTxVersion::SharedCollateral = 5` is the provider transaction payload +version of a v5 `ProRegTx` and is also the version tag carried in the +deterministic masternode state for masternodes registered by such a +transaction. It does NOT apply as the payload version of the new special +transaction types introduced below; those payloads carry their own, +independent `nVersion` field that starts at `1` (see each payload +definition). + +Provider payload version `5` is reserved for shared-collateral semantics. +Future protocol changes that affect the single-owner `ProRegTx` flow MUST +use the next available provider payload version (`6` or later) rather than +reusing `5` as a generic mode flag. | Name | Value | Description | | --- | ---: | --- | @@ -211,16 +224,25 @@ new special transaction types. | `TRANSACTION_PROVIDER_UPDATE_SHARE` | 11 | `ProUpShareTx` payload (this DIP). | | `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR` | 12 | `ProUpSharedRegTx` payload (this DIP). | -The numeric assignments `10`, `11`, and `12` are tentative and MUST be the -next three free DIP-0002 special transaction types at the time of merge. If -another DIP claims any of these values first, this DIP is updated to use the -next free values. The names (`PROVIDER_DISSOLVE`, -`PROVIDER_UPDATE_SHARE`, `PROVIDER_UPDATE_SHARED_REGISTRAR`) are normative. +The numeric assignments `10`, `11`, and `12` are tentative. Before this DIP +is merged or activated, the assignments MUST be checked against the +authoritative DIP-0002 registry and the current Dash Core +`TRANSACTION_*` allocation in `primitives/transaction.h`. If any of these +values is already consumed by another DIP or pending allocation at that +time, this DIP MUST be updated to use the next free values. The names +(`PROVIDER_DISSOLVE`, `PROVIDER_UPDATE_SHARE`, +`PROVIDER_UPDATE_SHARED_REGISTRAR`) are normative; this DIP does not +publish an authoritative table of unrelated special transaction type +allocations. ProRegTx (type `1`) is reused at payload version 5 for shared registration. -The base ProUpRegTx (type `3`) and ProUpServTx (type `2`) continue to operate -under DIP-0003 rules for non-shared masternodes; both are forbidden for shared -masternodes as specified in [Authorization Tiers](#authorization-tiers). +The base `ProUpRegTx` (type `3`) is invalid against a v5 masternode and is +unaffected for non-shared masternodes. The base `ProUpServTx` (type `2`) +continues to operate under DIP-0003 / DIP-0028 rules for non-shared +masternodes AND remains valid against a v5 masternode for the +operator-authorized fields enumerated in [Authorization +Tiers](#authorization-tiers); `ProUpServTx` MUST NOT attempt to modify any +owner-controlled or shared-collateral field. ### Shared Collateral Output @@ -281,6 +303,13 @@ identical across all consensus implementations and MUST be encoded into the chain parameters used for activation. The DIP is amended with the assigned bytes before mainnet activation. +This DIP is therefore NOT final for activation: it remains a Draft until +the concrete `SHARED_COLLATERAL_SCRIPT` byte sequence is assigned and +inserted here. The draft is suitable for review of the consensus rules and +covenant model, but no implementation may treat the script as fixed until +this section is updated with the assigned bytes (see [Open +Issues](#open-issues)). + Rationale: a P2SH anyone-can-spend template makes the script-level satisfaction trivial so that a valid `ProDisTx` can construct a sensible `scriptSig`, while consensus rules above script evaluation reject all @@ -407,6 +436,7 @@ sighash-mode behavior of the funding-input scripts. ```text SharedRegConsentHash = SHA256d( "DashSharedMNReg" || + chainGenesisHash || LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || inputsHash || outputsHash || LE16(payload.nVersion) || @@ -430,6 +460,13 @@ Where: * `LE8`, `LE16`, `LE32`, `LE64` denote little-endian encodings of the indicated width. * `SHA256d(x)` is the Dash double-SHA-256 used for transaction hashes. +* `chainGenesisHash` is the 32-byte block hash of the genesis block of the + network on which the registration is being authorized (mainnet, testnet, + devnet, regtest, or any future fork). Consensus uses the genesis hash of + the chain that is validating the transaction. Each participant signer + MUST independently verify the network / chain context of the + registration before producing `joinSig`; a signature produced against + one network's `chainGenesisHash` MUST NOT verify on any other network. * `inputsHash` is `CalcTxInputsHash(tx)`. Consensus MUST recompute this from the transaction and compare it to the `payload.inputsHash` field before signature verification; mismatch is invalid. @@ -472,7 +509,7 @@ the only mutable per-share field is `rewardScript`. ```text CProUpShareTx { - nVersion : uint16_t // MUST be 5 + nVersion : uint16_t // MUST be 1 (payload version of this new special tx type) proTxHash : uint256 shareIndex : uint16_t // index into shares[] newRewardScript : CScript @@ -481,6 +518,11 @@ CProUpShareTx { } ``` +`nVersion` here is the independent payload version of the new +`TRANSACTION_PROVIDER_UPDATE_SHARE` special transaction; it is NOT the +provider payload version `ProTxVersion::SharedCollateral = 5` of the v5 +`ProRegTx`. The two version namespaces are separate. + Validation: 1. The masternode identified by `proTxHash` MUST exist and MUST be a v5 @@ -512,7 +554,7 @@ and `nOperatorReward`. ```text CProUpSharedRegTx { - nVersion : uint16_t // MUST be 5 + nVersion : uint16_t // MUST be 1 (payload version of this new special tx type) proTxHash : uint256 pubKeyOperator : CBLSPublicKey // basic scheme keyIDVoting : CKeyID @@ -522,6 +564,10 @@ CProUpSharedRegTx { } ``` +As with `ProUpShareTx`, this `nVersion` is the independent payload version +of the new `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR` special +transaction and is unrelated to `ProTxVersion::SharedCollateral = 5`. + Validation: 1. The masternode identified by `proTxHash` MUST exist and MUST be a v5 @@ -633,7 +679,7 @@ output after activation. It is special transaction type ```text CProDisTx { - nVersion : uint16_t // MUST be 5 + nVersion : uint16_t // MUST be 1 (payload version of this new special tx type) proTxHash : uint256 mode : uint8_t // 0 = unilateral, 1 = unanimous actorIndex : uint16_t @@ -643,6 +689,10 @@ CProDisTx { } ``` +`nVersion` here is the independent payload version of the new +`TRANSACTION_PROVIDER_DISSOLVE` special transaction and is unrelated to +`ProTxVersion::SharedCollateral = 5`. + #### Transaction shape A valid dissolution transaction satisfies all of: @@ -660,6 +710,7 @@ A valid dissolution transaction satisfies all of: ```text SharedDisHash = SHA256d( "DashSharedMNDissolve" || + chainGenesisHash || LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || inputsHash || outputsHash || LE16(payload.nVersion) || @@ -672,7 +723,14 @@ Where `inputsHash` is `CalcTxInputsHash(tx)` and `outputsHash` is the SHA-256d of the concatenated serialized outputs of `tx`, both recomputed from the actual transaction. Consensus MUST recompute both hashes and reject mismatches with the payload's `inputsHash` and `outputsHash` -fields before any signature is verified. +fields before any signature is verified. `chainGenesisHash` is the +32-byte genesis block hash of the network validating the transaction, +identical to its use in [Registration Consent +Digest](#registration-consent-digest); a dissolution signed against one +network's `chainGenesisHash` MUST NOT verify on any other. Each signer +MUST independently verify the network / chain context before producing +its `vchSigs` entry. `payload.nVersion` here is the dissolution payload +version (currently `1`), not the v5 ProRegTx provider payload version. #### Period and penalty @@ -920,9 +978,24 @@ continue to be created and removed as in DIP-0003. `CSimplifiedMNListEntry::CalcHash`, as used by DIP-0004 simplified masternode list verification, MUST NOT include shares, refund scripts, reward scripts, penalty parameters, or any other v5-only field. Light -clients receive no SML commitment to shared-collateral metadata. This -matches the treatment of payout metadata adopted by DIP-0026. Diagnostic -RPCs and extended JSON output MAY expose v5 fields. +clients receive no SML commitment to shared-collateral metadata. +Diagnostic RPCs and extended JSON output MAY expose v5 fields. + +The trust boundary this creates is intentional and mirrors the +direction DIP-0026 took for multi-payout metadata: shared-collateral +fields are not committed to by SML hashes, so light clients cannot +independently verify share amounts, refund scripts, reward scripts, +penalty parameters, or any other v5-only field from an SML proof. A +future extension to DIP-0004 would be required before SPV clients +could verify shared-collateral terms without trusting a serving full +node. + +Full nodes are unaffected by this boundary: a full node validates +every v5 field from the deterministic masternode state that it +reconstructs by replaying blocks, exactly as it does for pre-v5 +masternode state. The SML/filter limitation applies only to clients +that depend on SML hashes or filter matches as their source of +truth. Special-transaction and bloom filtering, as defined in DIP-0003 for `ProRegTx`/`ProUpRegTx` payout scripts, is extended for v5 masternodes: @@ -935,6 +1008,12 @@ Special-transaction and bloom filtering, as defined in DIP-0003 for * For `ProDisTx`, the filter MUST match every output's `scriptPubKey`, matching existing transaction-output filter semantics. +These filter extensions exist solely for client-side discovery and +relay; a filter match is NOT a consensus commitment to the matched +data, and any light client that uses a filter match as authoritative +evidence of v5 state still depends on the honesty of the serving full +node. + These filter extensions apply only to v5 special transactions after activation. Pre-v5 special transactions retain DIP-0003 filtering. @@ -1339,15 +1418,19 @@ require the full node serving the client to be honest about v5 state. ### Replay across chains -The registration consent digest and the dissolution authorization digest -include the payload's `nVersion`, the transaction's `nVersion`, -`nType`, and `nLockTime`, the recomputed `inputsHash` and `outputsHash`, -and the masternode `proTxHash`. They do not include a chain identifier. -Cross-chain replay protection relies on the existing Dash policy that -chains use distinct genesis blocks and therefore distinct `proTxHash` -values for any v5 registration. A future DIP MAY add an explicit chain -identifier to these digests if cross-chain replay protection becomes a -requirement. +Both the registration consent digest and the dissolution authorization +digest commit explicitly to `chainGenesisHash`, the genesis block hash of +the network on which the transaction is being authorized. The consensus +digest domain therefore includes the network's genesis hash, and any +v5 registration or dissolution signed against one network's +`chainGenesisHash` cannot be replayed onto any other network or fork +because the digest verified by consensus on the target network would +differ. Signers MUST verify the network / chain context (which genesis +hash they are signing against) before producing any `joinSig` or +dissolution signature; relying on `proTxHash` alone is insufficient for +cross-chain replay protection because `proTxHash` collisions across +networks or short-lived forks cannot be ruled out by digest construction +alone and would otherwise expose unsuspecting signers to replay. ## Open Issues @@ -1361,8 +1444,14 @@ before activation: 2. **Final numeric values of `TRANSACTION_PROVIDER_DISSOLVE`, `TRANSACTION_PROVIDER_UPDATE_SHARE`, and `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR`.** Tentatively - `10`, `11`, `12`. If another DIP claims any of these values - first, this DIP shifts to the next free values. + `10`, `11`, `12`. Before merge and before activation, these + assignments MUST be re-checked against the authoritative DIP-0002 + registry and the live Dash Core `TRANSACTION_*` enum (as defined in + `primitives/transaction.h` on the targeted release branch). If any + value is already taken or pending allocation, this DIP MUST be + renumbered to the next free values rather than be merged with a + conflicting allocation. This DIP intentionally does not embed an + authoritative table of unrelated allocations. 3. **Final value of `SHARED_MIN_SHARE_DUFFS`.** Tentatively `1000000000` (10 DASH). Subject to dust-policy review and feedback from wallet implementers. From 897f687b2d94e49d4f9de79f7fceb44f88cb2457 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:04:29 -0500 Subject: [PATCH 05/18] docs(shared-mn): forbid same-block dissolution, require canonical share-vector diffs Address consensus review blockers ahead of activation-final: * Deterministic MN state diffs: remove the MAY-compact alternative and require a single canonical encoding - full replacement of the share vector on any share-field change. * Same-block ProRegTx + ProDisTx: forbid dissolution in the same block as the v5 registration. The protected set is split into an active set (allows ProDisTx) and a same-block pending set (rejects every spend, including ProDisTx). Update covenant rules, rationale, implementation notes, and test cases accordingly. * Activation language: tighten draft-status wording so the DIP is reviewable but explicitly not activation-final until script bytes and DIP-0023 deployment parameters are assigned. No specific Dash Core release version is claimed. --- dip-decentralized-masternode-shares.md | 262 ++++++++++++++++--------- 1 file changed, 168 insertions(+), 94 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 7c90e514..910cccbf 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -266,21 +266,30 @@ The shared collateral output uses a single, fixed serialized script, 3. It is recognizable by full nodes using a single template comparison, and it is recognizable as non-standard by older nodes that do not understand shared collateral. -4. After activation, consensus protects **shared collateral outpoints** — the - set of UTXOs that are either (a) recorded as the collateral outpoint of an - active v5 masternode in the deterministic masternode list, or (b) created - by a valid v5 ProRegTx earlier in the same block currently being - validated. Any spend of an outpoint in this set is rejected unless the - spending transaction is a valid `ProDisTx` for the corresponding - masternode. Outpoints in this set are referred to below as - "active-or-pending shared collateral outpoints" and the rejection is - enforced at mempool acceptance, at block connection, and during - deterministic masternode list processing (see [Collateral Spend - Enforcement](#collateral-spend-enforcement)). -5. Ordinary UTXOs that happen to pay `SHARED_COLLATERAL_SCRIPT` but are NOT - active-or-pending shared collateral outpoints are NOT bound by the - dissolution covenant. They behave as ordinary outputs at script-evaluation - time, subject to whatever spending conditions the recommended template +4. After activation, consensus protects two distinct classes of UTXOs: + * (a) **Active shared collateral outpoints** — outpoints recorded as + the collateral of an active v5 masternode in the deterministic + masternode list at the parent state of the block (or mempool tip) + being validated. A spend of an outpoint in this class is rejected + unless the spending transaction is a valid `ProDisTx` for the + corresponding masternode. + * (b) **Same-block pending shared collateral outputs** — shared + collateral outputs created by a valid v5 ProRegTx earlier in the + block currently being validated. A spend of an output in this + class later in the same block is rejected unconditionally: ordinary + spends are rejected because the outpoint represents a freshly + created masternode commitment, and `ProDisTx` is rejected because + the registration is not yet part of the parent deterministic state + a dissolution would consult. Dissolution of such a masternode is + permitted only in a strictly later block, after the registration + has been recorded in the deterministic masternode list. + Rejection is enforced at mempool acceptance, at block connection, and + during deterministic masternode list processing (see [Collateral + Spend Enforcement](#collateral-spend-enforcement)). +5. Ordinary UTXOs that happen to pay `SHARED_COLLATERAL_SCRIPT` but are + NOT in either protected class above are NOT bound by the dissolution + covenant. They behave as ordinary outputs at script-evaluation time, + subject to whatever spending conditions the recommended template imposes (for example, the `OP_TRUE` redeem-script template makes them anyone-can-spend). Implementations MUST NOT retroactively lock arbitrary pre-existing outputs or unrelated outputs created outside the v5 @@ -303,12 +312,17 @@ identical across all consensus implementations and MUST be encoded into the chain parameters used for activation. The DIP is amended with the assigned bytes before mainnet activation. -This DIP is therefore NOT final for activation: it remains a Draft until -the concrete `SHARED_COLLATERAL_SCRIPT` byte sequence is assigned and -inserted here. The draft is suitable for review of the consensus rules and -covenant model, but no implementation may treat the script as fixed until -this section is updated with the assigned bytes (see [Open -Issues](#open-issues)). +This DIP is therefore NOT final for activation. It remains a Draft until +the concrete `SHARED_COLLATERAL_SCRIPT` byte sequence is assigned, the +special transaction type numbers are confirmed against the live Dash +Core allocation, and the DIP-0023 deployment parameters are assigned. +The draft is suitable for review of the consensus rules and the +covenant model, but no implementation may treat the script bytes or the +deployment parameters as fixed until this section and [Open +Issues](#open-issues) are updated with the assigned values. Critical +consensus details that depend on those exact bytes (template equality +checks, mempool / mining policy rejection, and chain-parameter +encodings) MUST NOT be considered finalized in this revision. Rationale: a P2SH anyone-can-spend template makes the script-level satisfaction trivial so that a valid `ProDisTx` can construct a sensible @@ -847,11 +861,19 @@ miners include them. #### Same-block ordering -A `ProDisTx` MAY appear in the same block as the v5 `ProRegTx` that -created the spent shared collateral output, provided every other -shared-collateral consensus rule holds. Block validation MUST track shared -collateral outputs created earlier in the same block (see [Collateral -Spend Enforcement](#collateral-spend-enforcement)). +A `ProDisTx` MUST NOT appear in the same block as the v5 `ProRegTx` +that creates the shared collateral output it would spend. Dissolution +operates against the deterministic masternode state at the parent +block, so the registering masternode is not yet part of that state +until the registering block has been connected. A `ProDisTx` whose +input refers to a shared collateral output created earlier in the +same block is therefore invalid even if every other covenant rule +would otherwise hold; dissolution is permitted only in a strictly +later block. Block validation MUST track shared collateral outputs +created earlier in the same block solely so that any spend of those +outputs later in the same block — ordinary or `ProDisTx` — can be +rejected (see [Collateral Spend +Enforcement](#collateral-spend-enforcement)). #### State effect @@ -869,63 +891,75 @@ bears no special-transaction payload would, under DIP-0003 rules, simply remove the masternode by collateral spend. Implementations MUST also enforce the rules below outside `CheckSpecialTx`. -The protected set is the set of **active-or-pending shared collateral -outpoints**, defined as: - -* every collateral outpoint recorded against an active v5 masternode in - the deterministic masternode list at the parent of the block (or - mempool tip) being validated, plus -* every collateral outpoint created by a valid v5 ProRegTx that has been - processed earlier in the block currently being validated and that has - not itself already been dissolved earlier in that block. +The protected set has two parts with different rules: + +* **Active set.** Every collateral outpoint recorded against an active + v5 masternode in the deterministic masternode list at the parent of + the block (or mempool tip) being validated. A spend of an outpoint + in the active set is rejected unless it is a valid `ProDisTx` for + the matching masternode. +* **Same-block pending set.** Every shared collateral outpoint created + by a valid v5 ProRegTx earlier in the block currently being + validated. A spend of an outpoint in the same-block pending set is + rejected unconditionally: ordinary spends are rejected because the + outpoint represents an active masternode commitment, and `ProDisTx` + spends are rejected because the registration is not yet part of the + parent deterministic state. Dissolution of such a masternode is + permitted only in a strictly later block. A UTXO whose `scriptPubKey` equals `SHARED_COLLATERAL_SCRIPT` but whose -outpoint is not in this set is NOT protected by the covenant and is not +outpoint is in neither set is NOT protected by the covenant and is not the subject of the rules below. The rules deliberately key off the recorded outpoint identity, not raw script equality, to avoid retroactively locking pre-existing or unrelated outputs that happen to match the template. 1. **Mempool acceptance.** Before accepting any transaction into the - mempool, scan its inputs. For each input that spends an - active-or-pending shared collateral outpoint, reject the transaction - unless it is a valid `ProDisTx` for the masternode whose collateral - outpoint matches the spent outpoint in the current deterministic - masternode list. + mempool, scan its inputs. For each input that spends an outpoint in + the active set, reject the transaction unless it is a valid + `ProDisTx` for the masternode whose collateral outpoint matches the + spent outpoint in the current deterministic masternode list. The + same-block pending set is not consulted at mempool acceptance time; + it is computed per block by block-connection logic. 2. **Block connection, prior blocks.** Before applying the deterministic masternode list update for a connected block, scan every non-coinbase - transaction in that block for inputs that spend collateral outpoints - of active v5 masternodes in the parent-state deterministic masternode - list. Reject the block unless every such input is the input of a - valid `ProDisTx` for the matching masternode. + transaction in that block for inputs that spend outpoints in the + active set (collateral outpoints of active v5 masternodes in the + parent-state deterministic masternode list). Reject the block unless + every such input is the input of a valid `ProDisTx` for the matching + masternode. 3. **Block connection, same-block.** Maintain a per-block index of - shared collateral outputs created by valid v5 ProRegTx earlier in the - same block, keyed by `(txid, vout, proTxHash)`. For every later - non-coinbase transaction in the same block, reject inputs that spend - such outputs unless the spending transaction is a valid `ProDisTx` - referencing the corresponding `proTxHash`. The matching `ProDisTx` - MUST appear strictly after the registration in the block ordering. + shared collateral outputs created by valid v5 ProRegTx earlier in + the same block, keyed by `(txid, vout, proTxHash)`. For every later + non-coinbase transaction in the same block, reject any input that + spends such an output. Both ordinary spends and `ProDisTx` spends + are rejected: a `ProDisTx` whose input refers to a shared collateral + output created in the same block is invalid even if every other + covenant rule would otherwise hold. 4. **Deterministic masternode list removal.** Replace the - "remove on collateral spend" rule from DIP-0003 with the following for - v5 masternodes: a v5 masternode MUST NOT be removed until a - corresponding valid `ProDisTx` for that `proTxHash` has been processed. - Any spend of the masternode's shared collateral outpoint by a - transaction that is not such a `ProDisTx` is invalid. + "remove on collateral spend" rule from DIP-0003 with the following + for v5 masternodes: a v5 masternode MUST NOT be removed until a + corresponding valid `ProDisTx` for that `proTxHash` has been + processed in a strictly later block than the block that contained + the registering v5 ProRegTx. Any spend of the masternode's shared + collateral outpoint by a transaction that is not such a `ProDisTx` + is invalid. 5. **Block disconnection and reorg.** Disconnecting a block that contained a `ProDisTx` MUST restore the v5 masternode entry to the - exact pre-dissolution deterministic masternode state. State diffs MUST - capture the full share vector and shared-registration parameters as a - single replacement (see [Deterministic Masternode + exact pre-dissolution deterministic masternode state. State diffs + MUST capture the full share vector and shared-registration + parameters as a single replacement (see [Deterministic Masternode State](#deterministic-masternode-state)) so that reorg replay is deterministic. -These rules are evaluated before script-level evaluation for inputs that -spend active-or-pending shared collateral outpoints: even if the -underlying redeem script is trivially satisfiable, consensus rejects the -spend of a protected outpoint unless the spend is a valid `ProDisTx` for -the corresponding masternode. Spends of unprotected UTXOs that happen to -bear the same script are not subjected to the covenant and are -script-evaluated normally. +These rules are evaluated before script-level evaluation for inputs +that spend outpoints in either protected set: even if the underlying +redeem script is trivially satisfiable, consensus rejects the spend of +an active-set outpoint unless the spend is a valid `ProDisTx` for the +corresponding masternode, and rejects every spend of a same-block +pending outpoint unconditionally. Spends of unprotected UTXOs that +happen to bear the same script are not subjected to the covenant and +are script-evaluated normally. ### Deterministic Masternode State @@ -948,11 +982,15 @@ MUST be serialized as empty. `keyIDOwner` is unused for v5 masternodes. State diffs MUST be version-gated: 1. The state-diff bitfield gains a new field bit for the share vector. - Any change to any `shares[i].rewardScript` produces a state diff in - which the share vector is fully replaced. Implementations MAY also - choose to encode per-share rewardScript diffs more compactly; if so, - the encoding MUST be invertible byte-for-byte from the resulting - state. + Any change to any field of any `shares[i]` (including a + per-share `rewardScript` change from a `ProUpShareTx`) MUST produce + a state diff in which the entire share vector is fully replaced. + This full-replacement encoding is the single canonical + representation of a v5 share-state diff: compact diffs, per-share + field diffs, and any other alternative encoding of share-vector + changes are NOT permitted. Implementations MUST emit and accept + share-vector changes only as a full replacement of the share + vector. 2. State diffs for v5 fields are present only when the masternode is v5; for pre-v5 masternodes the corresponding bits MUST NOT appear. 3. Snapshot serialization MUST round-trip across restart, reorg replay, @@ -1031,11 +1069,22 @@ scope of this DIP. ## Deployment and Compatibility -Activation of this DIP is gated by a future deployment defined under -[DIP-0023](dip-0023.md). The exact deployment name and signaling window -are assigned during release engineering. This DIP does not claim a -specific Dash Core release; release engineering MUST verify the live -deployment state of any candidate fork bit before assigning activation. +**Draft status.** This DIP is a Draft. Activation parameters — the +concrete byte sequence of `SHARED_COLLATERAL_SCRIPT`, the special +transaction type numbers, and the DIP-0023 deployment name and +signaling window — are NOT finalized in this revision. The consensus +model and covenant rules below are written to be reviewable as a +draft and MUST NOT be treated as activation-ready until those +parameters are assigned and this DIP is updated accordingly (see +[Open Issues](#open-issues)). + +Activation of this DIP is gated by an intended future deployment +defined under [DIP-0023](dip-0023.md). The exact deployment name and +signaling window are assigned during release engineering. This DIP +does not claim any specific Dash Core release for activation; release +engineering MUST verify the live deployment state of any candidate +fork bit before assigning activation, and the activation target is +subject to DIP editor assignment. Before activation: @@ -1162,13 +1211,21 @@ transactions that spend the shared collateral output would not invoke rule would otherwise quietly drop the masternode. The required mempool and block-validation hooks are made explicit. -### Same-block dissolution - -Allowing a `ProDisTx` in the same block as the corresponding v5 -ProRegTx lets a participant exit immediately after registration, which -is important for use cases where coordination off-chain proved -unsatisfactory. It also matches the existing behavior that a regular -collateral can be spent in the same block as its ProRegTx. +### Same-block dissolution forbidden + +A `ProDisTx` is invalid in the same block as the v5 ProRegTx that +creates the shared collateral output it would spend. Dissolution +operates against the deterministic masternode state at the parent +block, so allowing same-block dissolution would require either a +mid-block speculative state machine or a duplicated validation path +that reads the in-progress block under construction. Forbidding the +same-block case keeps state transitions atomic at block boundaries, +matches how DIP-0003 ProUp*Tx are evaluated against the parent state, +and aligns the protected-set rule with a single canonical +share-vector diff (see [Deterministic Masternode +State](#deterministic-masternode-state)). A participant who wishes to +exit immediately after registration can do so in the next block at no +additional cost beyond standard fees. ### One vote per masternode @@ -1287,14 +1344,27 @@ chain parameters. mempool and by block validation. 15. A non-dissolution special transaction whose input spends the collateral outpoint of an active v5 masternode is rejected. -16. A v5 ProRegTx and a unilateral `ProDisTx` for the same `proTxHash` - in the same block, in that order, are accepted; the masternode is - created and then removed within the block. -17. An ordinary transaction whose input spends an unrelated UTXO that +16. A normal (non-`ProDisTx`) transaction later in the same block as a + v5 ProRegTx that spends the just-created shared collateral output + is rejected at block connection. +17. A unilateral `ProDisTx` for the same `proTxHash` in the same block + as the v5 ProRegTx that creates its shared collateral output is + invalid, regardless of ordering within the block and regardless of + whether every other covenant rule holds. +18. A unanimous `ProDisTx` for the same `proTxHash` in the same block + as the v5 ProRegTx that creates its shared collateral output is + invalid on the same basis as the unilateral case. +19. A `ProDisTx` (unilateral or unanimous) that spends the shared + collateral outpoint of a v5 masternode whose v5 ProRegTx was + confirmed in a strictly earlier block, and that otherwise + satisfies every covenant rule, is valid; the masternode is + removed when this block is connected. +20. An ordinary transaction whose input spends an unrelated UTXO that happens to pay `SHARED_COLLATERAL_SCRIPT` but is NOT a recorded - active or same-block-pending shared collateral outpoint is NOT - rejected by the covenant; whether it succeeds depends only on - ordinary script evaluation of the underlying redeem script. + active shared collateral outpoint and is NOT a same-block pending + shared collateral outpoint is NOT rejected by the covenant; + whether it succeeds depends only on ordinary script evaluation of + the underlying redeem script. ### Reorg @@ -1320,10 +1390,14 @@ These notes are non-normative guidance for implementers. spend rejection applies to *every* transaction in mempool and block contexts, not only to special transactions. * The same-block index of new shared collateral outputs introduced by - v5 ProRegTx earlier in the block can be implemented as a - small `std::unordered_map` that is - populated as block transactions are validated and consulted by every - later transaction's input scan. + v5 ProRegTx earlier in the block can be implemented as a small + `std::unordered_set` (or + `std::unordered_map` if the + diagnostic value is wanted) that is populated as block transactions + are validated and consulted by every later transaction's input scan. + Any spend of an outpoint in this set within the same block is + rejected, including by `ProDisTx`; same-block dissolution is not a + legal path. * Coinbase construction should reuse the existing payout-pipeline hook added by DIP-0026 (PR 184) and append one output per v5 share with the computed amount and target script. From 38dd0114ee3b412e74ae69f88bec873dfbac56ef Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:11:59 -0500 Subject: [PATCH 06/18] docs(shared-mn): fix dissolution sequence and validation gaps --- dip-decentralized-masternode-shares.md | 89 +++++++++++++++----------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 910cccbf..4b15bf6c 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -396,7 +396,7 @@ ProRegTx layout for `nVersion == 5`. | `netInfo` | DIP-0003/0028 net info | As for v4. | | `pubKeyOperator` | BLS public key (basic scheme) | As for v4. | | `keyIDVoting` | `CKeyID` | As for v4. | -| `nOperatorReward` | `uint16_t` | Basis points; as for v4. | +| `nOperatorReward` | `uint16_t` | Basis points; MUST be from 0 to 10000. | | `shares` | `CollateralShare[]` | 2 to 8 entries; per [Collateral Share](#collateral-share). | | `earlyPeriodBlocks` | `uint32_t` | `0` to `SHARED_MAX_EARLY_PERIOD_BLOCKS`. | | `earlyPenalty` | `CAmount` (8 bytes) | Duffs. | @@ -428,12 +428,13 @@ A v5 ProRegTx is invalid if any of the following conditions hold: 9. Any share fails its per-field validation rules in [Collateral Share](#collateral-share). 10. `sum(shares[i].amount) != GetMnType(nType).collat_amount`. -11. Penalty parameter constraints in [Parameters](#parameters) are not +11. `nOperatorReward > 10000`. +12. Penalty parameter constraints in [Parameters](#parameters) are not satisfied. -12. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. -13. `vchSig` is non-empty. -14. `inputsHash != CalcTxInputsHash(tx)`. -15. For any share `i`, `shares[i].joinSig` does not verify against +13. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. +14. `vchSig` is non-empty. +15. `inputsHash != CalcTxInputsHash(tx)`. +16. For any share `i`, `shares[i].joinSig` does not verify against `shares[i].ownerKey` over the registration consent digest defined below. @@ -589,9 +590,10 @@ Validation: 2. `pubKeyOperator` MUST be a valid basic-scheme BLS public key and MUST NOT collide with the operator key of any other registered masternode. 3. `keyIDVoting` MUST NOT equal any `shares[i].ownerKey`. -4. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. -5. `vchSigs.size()` MUST equal `shares.size()`. -6. For each `i` in `[0, shares.size())`, `vchSigs[i]` MUST be a valid +4. `nOperatorReward <= 10000`. +5. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. +6. `vchSigs.size()` MUST equal `shares.size()`. +7. For each `i` in `[0, shares.size())`, `vchSigs[i]` MUST be a valid ECDSA compact signature by `shares[i].ownerKey` over: ```text @@ -600,7 +602,7 @@ Validation: keyIDVoting || LE16(nOperatorReward) || inputsHash) ``` -7. Signature verification proceeds in share order. Any missing, extra, or +8. Signature verification proceeds in share order. Any missing, extra, or out-of-order signature is invalid. A valid `ProUpSharedRegTx` replaces `pubKeyOperator`, `keyIDVoting`, and @@ -726,6 +728,7 @@ SharedDisHash = SHA256d( "DashSharedMNDissolve" || chainGenesisHash || LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || + LE32(tx.vin[0].nSequence) || inputsHash || outputsHash || LE16(payload.nVersion) || proTxHash || @@ -735,9 +738,12 @@ SharedDisHash = SHA256d( Where `inputsHash` is `CalcTxInputsHash(tx)` and `outputsHash` is the SHA-256d of the concatenated serialized outputs of `tx`, both recomputed -from the actual transaction. Consensus MUST recompute both hashes and -reject mismatches with the payload's `inputsHash` and `outputsHash` -fields before any signature is verified. `chainGenesisHash` is the +from the actual transaction. The digest also commits to the sequence of +the single dissolution input (`tx.vin[0].nSequence`) so that changing the +sequence cannot disable or alter the signed `nLockTime` semantics. +Consensus MUST recompute both hashes and reject mismatches with the +payload's `inputsHash` and `outputsHash` fields before any signature is +verified. `chainGenesisHash` is the 32-byte genesis block hash of the network validating the transaction, identical to its use in [Registration Consent Digest](#registration-consent-digest); a dissolution signed against one @@ -1002,8 +1008,10 @@ Unique-property indexes MUST be extended: index used to reject reuse across masternodes at registration time. Pre-v5 masternodes contribute their single `keyIDOwner`; v5 masternodes contribute every participant owner key. -* The voting-key uniqueness index applies to `keyIDVoting` as in - DIP-0003. +* No network-wide uniqueness index is added for `keyIDVoting`. As in + DIP-0003, the voting key may be delegated and reused; v5 validation only + forbids `keyIDVoting` from equaling any participant owner key for the same + masternode. * The operator-key uniqueness index applies to `pubKeyOperator` as in DIP-0003. @@ -1262,19 +1270,20 @@ chain parameters. shares signed correctly, is valid. 2. A v5 ProRegTx with eight shares summing to 1000 DASH, all signed, is valid. -3. A v5 ProRegTx with one share is invalid. -4. A v5 ProRegTx with nine shares is invalid. -5. A v5 ProRegTx whose share amounts sum to 999.99 DASH is invalid. -6. A v5 ProRegTx with a duplicate participant owner key is invalid. -7. A v5 ProRegTx with a duplicate refund script is invalid. -8. A v5 ProRegTx with a refund script paying to a participant owner key +3. A v5 ProRegTx with `nOperatorReward > 10000` is invalid. +4. A v5 ProRegTx with one share is invalid. +5. A v5 ProRegTx with nine shares is invalid. +6. A v5 ProRegTx whose share amounts sum to 999.99 DASH is invalid. +7. A v5 ProRegTx with a duplicate participant owner key is invalid. +8. A v5 ProRegTx with a duplicate refund script is invalid. +9. A v5 ProRegTx with a refund script paying to a participant owner key is invalid. -9. A v5 ProRegTx whose collateral output uses a P2PKH script (not +10. A v5 ProRegTx whose collateral output uses a P2PKH script (not `SHARED_COLLATERAL_SCRIPT`) is invalid. -10. A v5 ProRegTx whose `vchSig` is non-empty is invalid. -11. A v5 ProRegTx whose `joinSig` for share `i` was produced under a - different `outputsHash` is invalid. +11. A v5 ProRegTx whose `vchSig` is non-empty is invalid. 12. A v5 ProRegTx whose `joinSig` for share `i` was produced under a + different `outputsHash` is invalid. +13. A v5 ProRegTx whose `joinSig` for share `i` was produced under a different penalty value is invalid. ### Reward Splitting @@ -1299,12 +1308,14 @@ chain parameters. 3. A `ProUpShareTx` setting `newRewardScript` to a P2PKH paying `shares[0].ownerKey` is invalid. 4. A `ProUpSharedRegTx` with `vchSigs.size() == shares.size()`, signed - by every share in order, updating `nOperatorReward`, is valid. -5. A `ProUpSharedRegTx` missing one signature is invalid. -6. A `ProUpSharedRegTx` with an extra signature is invalid. -7. A `ProUpSharedRegTx` whose signatures are presented out of share + by every share in order, updating `nOperatorReward` to a value no + greater than 10000, is valid. +5. A `ProUpSharedRegTx` setting `nOperatorReward > 10000` is invalid. +6. A `ProUpSharedRegTx` missing one signature is invalid. +7. A `ProUpSharedRegTx` with an extra signature is invalid. +8. A `ProUpSharedRegTx` whose signatures are presented out of share order is invalid. -8. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. +9. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. ### Dissolution @@ -1336,30 +1347,32 @@ chain parameters. pays exactly `shares[i].amount`. 12. A unanimous `ProDisTx` missing one participant signature is invalid. -13. A `ProDisTx` whose `outputsHash` does not match the recomputed +13. A `ProDisTx` whose input sequence is changed after signing is invalid, + including when the sequence change would alter `nLockTime` behavior. +14. A `ProDisTx` whose `outputsHash` does not match the recomputed transaction `outputsHash` is invalid; the signature check is not reached. -14. A normal transaction (`nVersion < 3` or `nType == 0`) that spends +15. A normal transaction (`nVersion < 3` or `nType == 0`) that spends the collateral outpoint of an active v5 masternode is rejected by mempool and by block validation. -15. A non-dissolution special transaction whose input spends the +16. A non-dissolution special transaction whose input spends the collateral outpoint of an active v5 masternode is rejected. -16. A normal (non-`ProDisTx`) transaction later in the same block as a +17. A normal (non-`ProDisTx`) transaction later in the same block as a v5 ProRegTx that spends the just-created shared collateral output is rejected at block connection. -17. A unilateral `ProDisTx` for the same `proTxHash` in the same block +18. A unilateral `ProDisTx` for the same `proTxHash` in the same block as the v5 ProRegTx that creates its shared collateral output is invalid, regardless of ordering within the block and regardless of whether every other covenant rule holds. -18. A unanimous `ProDisTx` for the same `proTxHash` in the same block +19. A unanimous `ProDisTx` for the same `proTxHash` in the same block as the v5 ProRegTx that creates its shared collateral output is invalid on the same basis as the unilateral case. -19. A `ProDisTx` (unilateral or unanimous) that spends the shared +20. A `ProDisTx` (unilateral or unanimous) that spends the shared collateral outpoint of a v5 masternode whose v5 ProRegTx was confirmed in a strictly earlier block, and that otherwise satisfies every covenant rule, is valid; the masternode is removed when this block is connected. -20. An ordinary transaction whose input spends an unrelated UTXO that +21. An ordinary transaction whose input spends an unrelated UTXO that happens to pay `SHARED_COLLATERAL_SCRIPT` but is NOT a recorded active shared collateral outpoint and is NOT a same-block pending shared collateral outpoint is NOT rejected by the covenant; From a92f22fd626f4711ef4f619f241462e1d07138a3 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:24:18 -0500 Subject: [PATCH 07/18] docs(shared-mn): tighten v5 update authorization rules --- dip-decentralized-masternode-shares.md | 89 ++++++++++++++++++-------- 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 4b15bf6c..e1b7a517 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -412,33 +412,43 @@ There is no `scriptPayout` or `payouts` field in a v5 ProRegTx. Owner rewards are derived directly from the share table as specified in [Reward Distribution](#reward-distribution). +Unless explicitly replaced by this section, the base ProRegTx validation rules +from DIP-0003, DIP-0026, and DIP-0028 continue to apply to v5 registrations. +This includes network-info validation, Evo Platform-field validation, +operator-key validity, and operator-key uniqueness. + A v5 ProRegTx is invalid if any of the following conditions hold: 1. `nVersion != 5`. 2. `nType` is not `Regular` or `Evo`. 3. `nMode != 0`. -4. `collateralOutpoint.hash` is not the null hash. -5. `collateralOutpoint.n` does not point to an output of this transaction. -6. The output at `collateralOutpoint.n` does not pay +4. `netInfo` fails the applicable DIP-0003 network-info validation rules. +5. For Evo masternodes, any Platform field fails the applicable DIP-0028 + validation rules. +6. `pubKeyOperator` is not a valid basic-scheme BLS public key or collides + with the operator key of any other registered masternode. +7. `collateralOutpoint.hash` is not the null hash. +8. `collateralOutpoint.n` does not point to an output of this transaction. +9. The output at `collateralOutpoint.n` does not pay `GetMnType(nType).collat_amount` to `SHARED_COLLATERAL_SCRIPT`. -7. The transaction creates more than one output paying - `SHARED_COLLATERAL_SCRIPT`. -8. `shares.size()` is less than `SHARED_MIN_PARTICIPANTS` or greater than - `SHARED_MAX_PARTICIPANTS`. -9. Any share fails its per-field validation rules in [Collateral - Share](#collateral-share). -10. `sum(shares[i].amount) != GetMnType(nType).collat_amount`. -11. `nOperatorReward > 10000`. -12. Penalty parameter constraints in [Parameters](#parameters) are not +10. The transaction creates more than one output paying + `SHARED_COLLATERAL_SCRIPT`. +11. `shares.size()` is less than `SHARED_MIN_PARTICIPANTS` or greater than + `SHARED_MAX_PARTICIPANTS`. +12. Any share fails its per-field validation rules in [Collateral + Share](#collateral-share). +13. `sum(shares[i].amount) != GetMnType(nType).collat_amount`. +14. `nOperatorReward > 10000`. +15. Penalty parameter constraints in [Parameters](#parameters) are not satisfied. -13. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. -14. `vchSig` is non-empty. -15. `inputsHash != CalcTxInputsHash(tx)`. -16. For any share `i`, `shares[i].joinSig` does not verify against +16. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. +17. `vchSig` is non-empty. +18. `inputsHash != CalcTxInputsHash(tx)`. +19. For any share `i`, `shares[i].joinSig` does not verify against `shares[i].ownerKey` over the registration consent digest defined below. -Rules 14 and 15 are evaluated in order: `inputsHash` mismatch and the +Rules 17 and 18 are evaluated in order: `inputsHash` mismatch and the recomputed `outputsHash` mismatch (see below) are checked before any ECDSA signature verification. @@ -554,9 +564,17 @@ Validation: ```text SHA256d("DashSharedMNUpShare" || - proTxHash || LE16(shareIndex) || newRewardScript || inputsHash) + chainGenesisHash || + LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || + inputsHash || + LE16(payload.nVersion) || + proTxHash || LE16(shareIndex) || newRewardScript) ``` + `chainGenesisHash` is the 32-byte genesis block hash of the network + validating the transaction. `payload.nVersion` is the ProUpShareTx payload + version (currently `1`). + A valid `ProUpShareTx` replaces `shares[shareIndex].rewardScript` in the deterministic masternode state. It does not revive a PoSe-banned masternode and does not affect any other field. @@ -590,19 +608,30 @@ Validation: 2. `pubKeyOperator` MUST be a valid basic-scheme BLS public key and MUST NOT collide with the operator key of any other registered masternode. 3. `keyIDVoting` MUST NOT equal any `shares[i].ownerKey`. -4. `nOperatorReward <= 10000`. -5. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. -6. `vchSigs.size()` MUST equal `shares.size()`. -7. For each `i` in `[0, shares.size())`, `vchSigs[i]` MUST be a valid +4. No current `refundScript` and no current effective `rewardScript` may be a + P2PKH script paying to `keyIDVoting`. For this check, an empty + `rewardScript` is interpreted as the corresponding `refundScript`. +5. `nOperatorReward <= 10000`. +6. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. +7. `vchSigs.size()` MUST equal `shares.size()`. +8. For each `i` in `[0, shares.size())`, `vchSigs[i]` MUST be a valid ECDSA compact signature by `shares[i].ownerKey` over: ```text SHA256d("DashSharedMNUpSharedReg" || + chainGenesisHash || + LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || + inputsHash || + LE16(payload.nVersion) || proTxHash || pubKeyOperatorSerialized || - keyIDVoting || LE16(nOperatorReward) || inputsHash) + keyIDVoting || LE16(nOperatorReward)) ``` -8. Signature verification proceeds in share order. Any missing, extra, or + `chainGenesisHash` is the 32-byte genesis block hash of the network + validating the transaction. `payload.nVersion` is the ProUpSharedRegTx + payload version (currently `1`). + +9. Signature verification proceeds in share order. Any missing, extra, or out-of-order signature is invalid. A valid `ProUpSharedRegTx` replaces `pubKeyOperator`, `keyIDVoting`, and @@ -1311,11 +1340,15 @@ chain parameters. by every share in order, updating `nOperatorReward` to a value no greater than 10000, is valid. 5. A `ProUpSharedRegTx` setting `nOperatorReward > 10000` is invalid. -6. A `ProUpSharedRegTx` missing one signature is invalid. -7. A `ProUpSharedRegTx` with an extra signature is invalid. -8. A `ProUpSharedRegTx` whose signatures are presented out of share +6. A `ProUpSharedRegTx` setting `keyIDVoting` to a key hash already used by + any current refund script or effective reward script is invalid. +7. A `ProUpSharedRegTx` missing one signature is invalid. +8. A `ProUpSharedRegTx` with an extra signature is invalid. +9. A `ProUpSharedRegTx` whose signatures are presented out of share order is invalid. -9. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. +10. A `ProUpSharedRegTx` signature produced for another network's + `chainGenesisHash` is invalid. +11. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. ### Dissolution From 55e795017bf0b198cf4de6b4d63f19f43ba60e67 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:24:36 -0500 Subject: [PATCH 08/18] docs(shared-mn): clarify registration output digest handling --- dip-decentralized-masternode-shares.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index e1b7a517..83cd6f7b 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -448,9 +448,10 @@ A v5 ProRegTx is invalid if any of the following conditions hold: `shares[i].ownerKey` over the registration consent digest defined below. -Rules 17 and 18 are evaluated in order: `inputsHash` mismatch and the -recomputed `outputsHash` mismatch (see below) are checked before any -ECDSA signature verification. +Rules 17 and 18 are evaluated before any ECDSA signature verification. +Consensus computes `outputsHash` from the transaction outputs when building +the registration consent digest; there is no payload `outputsHash` field in +a v5 ProRegTx. ### Registration Consent Digest From f268635de389e35a575331ad3bf62ea98432c01a Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:27:32 -0500 Subject: [PATCH 09/18] docs(shared-mn): list proposed special transaction types --- dip-0002/special-transactions.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dip-0002/special-transactions.md b/dip-0002/special-transactions.md index f1b8a743..79819970 100644 --- a/dip-0002/special-transactions.md +++ b/dip-0002/special-transactions.md @@ -4,8 +4,8 @@ The transaction type is described based on proposed DIPs. Here is a table of current proposed types and their associated DIP. Future DIPs may introduce more types. -*Note:* This table refers to the _payload_ version which relates only to the special transaction -payload and is distinct from the _transaction_ version. +*Note:* This table refers to the *payload* version which relates only to the special transaction +payload and is distinct from the *transaction* version. | Type | Transaction Type | DIP Number and Name | Payload Version | State | | ---- | ---------------- | ------------------- | --------------- | ----- | @@ -18,3 +18,6 @@ payload and is distinct from the _transaction_ version. | 7 | Masternode Hard Fork Signal | [DIP 023: Enhanced Hard Fork Mechanism](https://github.com/dashpay/dips/blob/master/dip-0023.md) | 1 | Active | | 8 | Asset Lock | [DIP 027: Dash Core Credit Pool](https://github.com/dashpay/dips/blob/master/dip-0027.md) | 1 | Active | | 9 | Asset Unlock | [DIP 027: Dash Core Credit Pool](https://github.com/dashpay/dips/blob/master/dip-0027.md) | 1 | Active | +| 10 | Provider Dissolution Transaction (ProDisTx) | [DIP TBD: Decentralized Masternode Shares](../dip-decentralized-masternode-shares.md) | 1 | Proposed | +| 11 | Provider Update Share Transaction (ProUpShareTx) | [DIP TBD: Decentralized Masternode Shares](../dip-decentralized-masternode-shares.md) | 1 | Proposed | +| 12 | Provider Update Shared Registrar Transaction (ProUpSharedRegTx) | [DIP TBD: Decentralized Masternode Shares](../dip-decentralized-masternode-shares.md) | 1 | Proposed | From 3c1ea09d185edfa813ee81baeadd886bce00d376 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:32:59 -0500 Subject: [PATCH 10/18] docs(shared-mn): remove final consensus ambiguities --- dip-decentralized-masternode-shares.md | 50 ++++++++++++++++---------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 83cd6f7b..43a7c4fe 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -358,8 +358,9 @@ Field rules at registration: 1. `amount` MUST be at least `SHARED_MIN_SHARE_DUFFS`. 2. `refundScript` MUST be a standard `P2PKH` or `P2SH` script. -3. `rewardScript` MUST be a standard `P2PKH` or `P2SH` script. An empty - `rewardScript` is interpreted at the consensus layer as `refundScript`. +3. `rewardScript` MUST be empty or a standard `P2PKH` or `P2SH` script. An + empty `rewardScript` is interpreted at the consensus layer as + `refundScript`. 4. `ownerKey` MUST be distinct from every other `ownerKey` in the share table and from `keyIDVoting`. 5. `ownerKey` MUST be distinct from every active owner key recorded for any @@ -464,7 +465,7 @@ SharedRegConsentHash = SHA256d( "DashSharedMNReg" || chainGenesisHash || LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || - inputsHash || outputsHash || + inputsHash || sequencesHash || outputsHash || LE16(payload.nVersion) || LE16(payload.nType) || LE16(payload.nMode) || LE32(payload.collateralOutpoint.n) || @@ -496,6 +497,11 @@ Where: * `inputsHash` is `CalcTxInputsHash(tx)`. Consensus MUST recompute this from the transaction and compare it to the `payload.inputsHash` field before signature verification; mismatch is invalid. +* `sequencesHash` is the SHA-256d of the concatenation of every input's + `nSequence` in transaction-input order, each encoded as `LE32(nSequence)`. + Consensus MUST recompute this from the transaction before signature + verification. This prevents a coordinator from changing finality, RBF, or + `nLockTime` behavior after participants produce `joinSig`. * `outputsHash` is the SHA-256d of the concatenation of every output of `tx` in order, each serialized as `(value, scriptPubKey)` using the standard Dash transaction-output serialization. Consensus MUST recompute @@ -554,8 +560,9 @@ Validation: 1. The masternode identified by `proTxHash` MUST exist and MUST be a v5 masternode. 2. `shareIndex` MUST be less than `shares.size()` in the current state. -3. `newRewardScript` MUST be a standard `P2PKH` or `P2SH` script. An empty - `newRewardScript` is interpreted as `shares[shareIndex].refundScript`. +3. `newRewardScript` MUST be empty or a standard `P2PKH` or `P2SH` script. + An empty `newRewardScript` is interpreted as + `shares[shareIndex].refundScript`. 4. `newRewardScript` MUST satisfy the same key-reuse restrictions as registration: it MUST NOT be a P2PKH paying to any `shares[i].ownerKey` and MUST NOT pay to `keyIDVoting`. @@ -750,6 +757,8 @@ A valid dissolution transaction satisfies all of: no additional outputs of any kind. Change outputs are not permitted. 3. Its `nLockTime` MAY be non-zero. The dissolution authorization digest commits to `nLockTime`, so consensus enforces whatever value was signed. +4. `mode` MUST be either `0` (unilateral) or `1` (unanimous). Any other + value is invalid. #### Authorization digest @@ -820,7 +829,7 @@ if mode == 1: // unanimous: penalty is zero, bonus is trivially zero everywhere. // DistributeByWeight is NOT invoked. bonus[i] = 0 for all i in [0, N) -else: +else if mode == 0: // unilateral: redistribute the penalty pro rata among non-actors. // At least one non-actor weight is positive because N >= 2 and every // share amount is positive, so sum_w > 0 and DistributeByWeight is @@ -1002,8 +1011,10 @@ are script-evaluated normally. The deterministic masternode state defined in DIP-0003 is extended for v5 masternodes. The pre-v5 state serialization is unchanged. -For `nVersion >= ProTxVersion::SharedCollateral`, the masternode state -includes the following additional fields: +For `nVersion == ProTxVersion::SharedCollateral`, the masternode state +includes the following additional fields. A future provider payload version +MUST NOT be interpreted as shared collateral unless that future DIP explicitly +extends the shared-collateral version family and defines its state encoding. | Field | Type | Notes | | --- | --- | --- | @@ -1313,7 +1324,9 @@ chain parameters. 11. A v5 ProRegTx whose `vchSig` is non-empty is invalid. 12. A v5 ProRegTx whose `joinSig` for share `i` was produced under a different `outputsHash` is invalid. -13. A v5 ProRegTx whose `joinSig` for share `i` was produced under a +13. A v5 ProRegTx whose `joinSig` for share `i` was produced under different + input sequences is invalid. +14. A v5 ProRegTx whose `joinSig` for share `i` was produced under a different penalty value is invalid. ### Reward Splitting @@ -1381,32 +1394,33 @@ chain parameters. pays exactly `shares[i].amount`. 12. A unanimous `ProDisTx` missing one participant signature is invalid. -13. A `ProDisTx` whose input sequence is changed after signing is invalid, +13. A `ProDisTx` with `mode` other than `0` or `1` is invalid. +14. A `ProDisTx` whose input sequence is changed after signing is invalid, including when the sequence change would alter `nLockTime` behavior. -14. A `ProDisTx` whose `outputsHash` does not match the recomputed +15. A `ProDisTx` whose `outputsHash` does not match the recomputed transaction `outputsHash` is invalid; the signature check is not reached. -15. A normal transaction (`nVersion < 3` or `nType == 0`) that spends +16. A normal transaction (`nVersion < 3` or `nType == 0`) that spends the collateral outpoint of an active v5 masternode is rejected by mempool and by block validation. -16. A non-dissolution special transaction whose input spends the +17. A non-dissolution special transaction whose input spends the collateral outpoint of an active v5 masternode is rejected. -17. A normal (non-`ProDisTx`) transaction later in the same block as a +18. A normal (non-`ProDisTx`) transaction later in the same block as a v5 ProRegTx that spends the just-created shared collateral output is rejected at block connection. -18. A unilateral `ProDisTx` for the same `proTxHash` in the same block +19. A unilateral `ProDisTx` for the same `proTxHash` in the same block as the v5 ProRegTx that creates its shared collateral output is invalid, regardless of ordering within the block and regardless of whether every other covenant rule holds. -19. A unanimous `ProDisTx` for the same `proTxHash` in the same block +20. A unanimous `ProDisTx` for the same `proTxHash` in the same block as the v5 ProRegTx that creates its shared collateral output is invalid on the same basis as the unilateral case. -20. A `ProDisTx` (unilateral or unanimous) that spends the shared +21. A `ProDisTx` (unilateral or unanimous) that spends the shared collateral outpoint of a v5 masternode whose v5 ProRegTx was confirmed in a strictly earlier block, and that otherwise satisfies every covenant rule, is valid; the masternode is removed when this block is connected. -21. An ordinary transaction whose input spends an unrelated UTXO that +22. An ordinary transaction whose input spends an unrelated UTXO that happens to pay `SHARED_COLLATERAL_SCRIPT` but is NOT a recorded active shared collateral outpoint and is NOT a same-block pending shared collateral outpoint is NOT rejected by the covenant; From 9656cc38f419302245d5ac71f8cb4880ee2d0305 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:36:45 -0500 Subject: [PATCH 11/18] docs(shared-mn): require overflow-safe weight distribution --- dip-decentralized-masternode-shares.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 43a7c4fe..472505ec 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -712,9 +712,14 @@ DistributeByWeight(total, weights): ``` `DistributeByWeight` is invalid for an all-zero weight vector and callers -MUST NOT invoke it in that case; for unanimous dissolution, where the -penalty itself is zero, the bonus is taken to be all-zero directly without -calling `DistributeByWeight` (see [Dissolution +MUST NOT invoke it in that case. All intermediate arithmetic in +`DistributeByWeight`, including `total * weights[i]`, `sum_w`, and +`sum(out[i])`, MUST be evaluated exactly without overflow. Implementations +MUST use at least 128-bit integer intermediates or a mathematically equivalent +overflow-safe quotient/remainder algorithm that produces the same floor and +remainder results for every consensus-valid input. For unanimous dissolution, +where the penalty itself is zero, the bonus is taken to be all-zero directly +without calling `DistributeByWeight` (see [Dissolution (ProDisTx)](#dissolution-prodistx)). Coinbase validation requires every expected per-share reward output by exact From b64a580e0cc4cfe4ebde157f35c970b0a8081bcc Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 20 Jun 2026 06:53:39 -0500 Subject: [PATCH 12/18] docs(shared-mn): tighten shared collateral DIP draft --- README.md | 3 +- dip-decentralized-masternode-shares.md | 548 +++++++++++++------------ project-words.txt | 12 +- 3 files changed, 304 insertions(+), 259 deletions(-) diff --git a/README.md b/README.md index 3e8d1cc4..69353c41 100644 --- a/README.md +++ b/README.md @@ -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. +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](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. ## Contributions @@ -47,6 +47,7 @@ Number | Layer | Title | Owner | Type | Status [29](dip-0029.md) | Consensus | Randomness Beacon For LLMQ Selection | Virgile Bartolo | Standard | Proposed [30](dip-0030.md) | Consensus | Replay Attack Prevention and State Transition Nonces | Samuel Westrich | Standard | Proposed [31](dip-0031.md) | Consensus | Platform Proof of Service | Ivan Shumkov, Pasta | Standard | Proposed +[TBD](dip-decentralized-masternode-shares.md) | Consensus | Decentralized Masternode Shares | Pasta | Standard | Draft ## License diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 472505ec..0c84ddb0 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -6,7 +6,7 @@ Status: Draft Type: Standard Created: 2026-06-20 - Requires: DIP-0002, DIP-0003, DIP-0023, DIP-0026 + Requires: DIP-0002, DIP-0003, DIP-0023, DIP-0026, DIP-0028 License: MIT License @@ -109,8 +109,8 @@ future DIPs. ## Relationship to DIP-0026 DIP-0026 introduces provider transaction payload version `4` (`MultiPayout`). -Under DIP-0026 v4, the registrar owner remains the sole signer for ProRegTx -and ProUpRegTx, and may freely rewrite the payout list at any time. +Under DIP-0026 v4, the registrar owner remains the sole signer for ProRegTx and +ProUpRegTx, and may freely rewrite the payout list at any time. This DIP does not modify DIP-0026 v4 semantics. Shared masternodes use a new provider transaction payload version, `5` (`SharedCollateral`), with the @@ -154,18 +154,17 @@ v5 registration, and a shared masternode is wound down only by a valid | Constant | Value | Description | | --- | ---: | --- | | `SHARED_MIN_PARTICIPANTS` | 2 | Minimum number of shares. | -| `SHARED_MAX_PARTICIPANTS` | 8 | Maximum number of shares, matching DIP-0026 payout-count limits. | +| `SHARED_MAX_PARTICIPANTS` | 8 | Maximum number of shares. The limit is deliberately stricter than current legacy payout-list limits and is aligned with the pending DIP-0026 v4 design goal of bounding coinbase fan-out. | | `SHARED_MIN_SHARE_DUFFS` | 1000000000 (10 DASH) | Minimum value of any `shares[i].amount`. | -| `SHARED_MAX_EARLY_PERIOD_BLOCKS` | 420480 (~2 years at nominal 2.5 min/block) | Maximum value of `earlyPeriodBlocks`. | +| `SHARED_MAX_EARLY_PERIOD_BLOCKS` | 400305 (~2 years at 2.626 min/block) | Maximum value of `earlyPeriodBlocks`. | -`SHARED_MAX_EARLY_PERIOD_BLOCKS` is given in blocks at Dash's nominal block -interval of 2.5 minutes (150 s), yielding approximately -`365 * 24 * 60 / 2.5 * 2 = 420480` blocks for a two-year ceiling. Realized block -times vary, so the wall-clock equivalent drifts with hashrate; activation may -refine this constant to the actual measured cap selected at deployment time. +`SHARED_MAX_EARLY_PERIOD_BLOCKS` is given in blocks using Dash's measured +long-run block interval estimate of 2.626 minutes, yielding approximately +`365 * 24 * 60 / 2.626 * 2 = 400305` blocks for a two-year ceiling. Realized +block times vary, so the wall-clock equivalent drifts with hashrate. Implementations and tooling that prefer a wall-clock interface SHOULD convert -the user-facing value to blocks at the nominal 2.5 min/block rate and reject -any input that exceeds `SHARED_MAX_EARLY_PERIOD_BLOCKS` once converted. +the user-facing value to blocks at the 2.626 min/block rate and reject any input +that exceeds `SHARED_MAX_EARLY_PERIOD_BLOCKS` once converted. `SHARED_MIN_SHARE_DUFFS` exists to keep recurring per-share coinbase outputs above policy dust thresholds at present block rewards while permitting modest @@ -176,12 +175,17 @@ the future. Shared collateral amounts MUST satisfy: ```text -sum(shares[i].amount) == GetMnType(nType).collat_amount +for every i: + MoneyRange(shares[i].amount) + SHARED_MIN_SHARE_DUFFS <= shares[i].amount <= GetMnType(nType).collat_amount +sum_overflow_safe(shares[i].amount) == GetMnType(nType).collat_amount ``` where `GetMnType(nType).collat_amount` is 1000 DASH for `Regular` or 4000 DASH -for `Evo`, as defined in DIP-0003 Appendix B and DIP-0028. A registration that -does not exactly sum to the required collateral is invalid. +for `Evo`, as defined in DIP-0003 Appendix B and DIP-0028. The sum MUST be +computed with exact overflow-safe arithmetic; any overflow, non-`MoneyRange` +share amount, share amount above the required collateral amount, or non-exact +sum is invalid. Penalty parameters MUST satisfy: @@ -213,27 +217,17 @@ transaction types introduced below; those payloads carry their own, independent `nVersion` field that starts at `1` (see each payload definition). -Provider payload version `5` is reserved for shared-collateral semantics. -Future protocol changes that affect the single-owner `ProRegTx` flow MUST -use the next available provider payload version (`6` or later) rather than -reusing `5` as a generic mode flag. - | Name | Value | Description | | --- | ---: | --- | | `TRANSACTION_PROVIDER_DISSOLVE` | 10 | `ProDisTx` payload (this DIP). | | `TRANSACTION_PROVIDER_UPDATE_SHARE` | 11 | `ProUpShareTx` payload (this DIP). | | `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR` | 12 | `ProUpSharedRegTx` payload (this DIP). | -The numeric assignments `10`, `11`, and `12` are tentative. Before this DIP -is merged or activated, the assignments MUST be checked against the -authoritative DIP-0002 registry and the current Dash Core -`TRANSACTION_*` allocation in `primitives/transaction.h`. If any of these -values is already consumed by another DIP or pending allocation at that -time, this DIP MUST be updated to use the next free values. The names -(`PROVIDER_DISSOLVE`, `PROVIDER_UPDATE_SHARE`, -`PROVIDER_UPDATE_SHARED_REGISTRAR`) are normative; this DIP does not -publish an authoritative table of unrelated special transaction type -allocations. +Values `10`, `11`, and `12` are assigned by this DIP and are also registered in +[DIP-0002's special transaction type table](dip-0002/special-transactions.md). +If another accepted DIP consumes any of these values before this DIP is merged, +this DIP MUST be updated to use the next free values before merge; it MUST NOT +ship with conflicting type assignments. ProRegTx (type `1`) is reused at payload version 5 for shared registration. The base `ProUpRegTx` (type `3`) is invalid against a v5 masternode and is @@ -266,7 +260,7 @@ The shared collateral output uses a single, fixed serialized script, 3. It is recognizable by full nodes using a single template comparison, and it is recognizable as non-standard by older nodes that do not understand shared collateral. -4. After activation, consensus protects two distinct classes of UTXOs: +4. After activation, consensus and mempool policy protect the following shared collateral outpoint sets: * (a) **Active shared collateral outpoints** — outpoints recorded as the collateral of an active v5 masternode in the deterministic masternode list at the parent state of the block (or mempool tip) @@ -283,56 +277,44 @@ The shared collateral output uses a single, fixed serialized script, a dissolution would consult. Dissolution of such a masternode is permitted only in a strictly later block, after the registration has been recorded in the deterministic masternode list. + * (c) **Mempool pending shared collateral outputs** — shared + collateral outputs created by an unconfirmed valid v5 ProRegTx in + the mempool. A mempool spend of an output in this class is rejected + unconditionally until the registration confirms. Rejection is enforced at mempool acceptance, at block connection, and during deterministic masternode list processing (see [Collateral Spend Enforcement](#collateral-spend-enforcement)). 5. Ordinary UTXOs that happen to pay `SHARED_COLLATERAL_SCRIPT` but are - NOT in either protected class above are NOT bound by the dissolution + NOT in any protected class above are NOT bound by the dissolution covenant. They behave as ordinary outputs at script-evaluation time, subject to whatever spending conditions the recommended template imposes (for example, the `OP_TRUE` redeem-script template makes them anyone-can-spend). Implementations MUST NOT retroactively lock arbitrary pre-existing outputs or unrelated outputs created outside the v5 registration flow. -6. Before activation, miners and relays MUST treat any output with this - script as non-standard, so ordinary relay and mining policy discourages - creating such outputs before activation. After activation, the only way - for a transaction to register an outpoint into the protected set is a - valid v5 ProRegTx; +6. Before activation, upgraded relays and miners MUST treat any output with + this script as non-standard and SHOULD NOT mine it, so ordinary relay and + mining policy discourages creating such outputs before activation. After + activation, the only way for a transaction to register an outpoint into the + protected set is a valid v5 ProRegTx; relay and mining policy MUST continue to treat any other output bearing `SHARED_COLLATERAL_SCRIPT` as non-standard to discourage accidental creation of stranded anyone-can-spend outputs. -The recommended template is a P2SH of an `OP_TRUE`-equivalent redeem script, -chosen so that the output is recognizable by template equality and so that no -existing wallet would construct an equivalent output by accident. The exact -bytes of `SHARED_COLLATERAL_SCRIPT` are assigned during implementation review, -constrained to the properties above; any implementation choice MUST be -identical across all consensus implementations and MUST be encoded into the -chain parameters used for activation. The DIP is amended with the assigned -bytes before mainnet activation. - -This DIP is therefore NOT final for activation. It remains a Draft until -the concrete `SHARED_COLLATERAL_SCRIPT` byte sequence is assigned, the -special transaction type numbers are confirmed against the live Dash -Core allocation, and the DIP-0023 deployment parameters are assigned. -The draft is suitable for review of the consensus rules and the -covenant model, but no implementation may treat the script bytes or the -deployment parameters as fixed until this section and [Open -Issues](#open-issues) are updated with the assigned values. Critical -consensus details that depend on those exact bytes (template equality -checks, mempool / mining policy rejection, and chain-parameter -encodings) MUST NOT be considered finalized in this revision. - -Rationale: a P2SH anyone-can-spend template makes the script-level -satisfaction trivial so that a valid `ProDisTx` can construct a sensible -`scriptSig`, while consensus rules above script evaluation reject all -spending paths except dissolution. Embedding per-masternode commitments -(such as `proTxHash` or participant keys) into the output script was -considered and rejected because it (i) would conflict with future -relock-style updates of participant keys and (ii) would require either large -output scripts or a separate commitment scheme that would still depend on -deterministic masternode state to verify. +The shared collateral script is the single-byte script `OP_TRUE`: + +```text +SHARED_COLLATERAL_SCRIPT = 0x51 +``` + +This bare anyone-can-spend script is deliberately non-standard except when it +appears as the collateral output of a valid v5 ProRegTx after activation. +Script-level satisfaction is trivial, but consensus rules above script +evaluation reject every protected spend except a valid `ProDisTx`. Embedding +per-masternode commitments (such as `proTxHash` or participant keys) into the +output script was considered and rejected because it would either conflict with +future relock-style updates of participant keys or require a separate commitment +scheme that would still depend on deterministic masternode state to verify. ### Collateral Share @@ -342,7 +324,7 @@ CollateralShare { refundScript : CScript (compact-size length-prefixed) rewardScript : CScript (compact-size length-prefixed) ownerKey : CKeyID (20 bytes) - joinSig : vector (compact-size length-prefixed, 65 bytes when present) + joinSig : vector (compact-size length-prefixed, 65 bytes when present; CompactSize(0) in join-signature preimage) } ``` @@ -352,11 +334,12 @@ CollateralShare { | `refundScript` | Immutable | Set at registration; cannot be changed. | | `rewardScript` | Mutable per-share | Participant owner key of that share, via `ProUpShareTx`. | | `ownerKey` | Immutable | Set at registration; cannot be rotated in this DIP. | -| `joinSig` | Set only in `ProRegTx` payload | Validated at registration; not stored in deterministic state. | +| `joinSig` | Set only in `ProRegTx` payload | Validated at registration; not stored in deterministic state. In the registration join-signature digest, this field is serialized byte-exactly as an empty vector (`CompactSize(0)`), not omitted. | Field rules at registration: -1. `amount` MUST be at least `SHARED_MIN_SHARE_DUFFS`. +1. `amount` MUST satisfy `MoneyRange(amount)` and MUST be between + `SHARED_MIN_SHARE_DUFFS` and `GetMnType(nType).collat_amount`, inclusive. 2. `refundScript` MUST be a standard `P2PKH` or `P2SH` script. 3. `rewardScript` MUST be empty or a standard `P2PKH` or `P2SH` script. An empty `rewardScript` is interpreted at the consensus layer as @@ -369,7 +352,9 @@ Field rules at registration: `shares[i].ownerKey` for v5 masternodes (see [Deterministic Masternode State](#deterministic-masternode-state)). 6. `refundScript` MUST NOT be duplicated within the share table. -7. `rewardScript` MAY be duplicated within the share table. +7. Effective reward scripts MUST NOT be duplicated within the share table. For + this rule, an empty `rewardScript` is interpreted as `refundScript` before + duplicate detection. 8. No `refundScript` and no `rewardScript` may be a P2PKH script paying to `keyIDOwner` (for v5 the field is absent — see below), `keyIDVoting`, or to a key ID that equals any `shares[i].ownerKey`. The intent matches @@ -402,8 +387,8 @@ ProRegTx layout for `nVersion == 5`. | `earlyPeriodBlocks` | `uint32_t` | `0` to `SHARED_MAX_EARLY_PERIOD_BLOCKS`. | | `earlyPenalty` | `CAmount` (8 bytes) | Duffs. | | `standardPenalty` | `CAmount` (8 bytes) | Duffs. | -| `platformNodeID`, ports (Evo only) | as DIP-0028 | As for v4. | | `inputsHash` | `uint256` | `CalcTxInputsHash(tx)`. | +| `platformNodeID`, `platformNetInfo` (Evo only) | as DIP-0028 | Serialized after `inputsHash` and before `vchSig`, preserving the DIP-0028 insertion point. | | `vchSig` | `vector` | MUST be empty for v5 (no external collateral signature). | There is no `keyIDOwner` field in a v5 ProRegTx. The role of the single owner @@ -438,18 +423,22 @@ A v5 ProRegTx is invalid if any of the following conditions hold: `SHARED_MAX_PARTICIPANTS`. 12. Any share fails its per-field validation rules in [Collateral Share](#collateral-share). -13. `sum(shares[i].amount) != GetMnType(nType).collat_amount`. -14. `nOperatorReward > 10000`. -15. Penalty parameter constraints in [Parameters](#parameters) are not +13. Any `shares[i].amount` fails `MoneyRange`, is less than + `SHARED_MIN_SHARE_DUFFS`, or is greater than + `GetMnType(nType).collat_amount`. +14. The exact overflow-safe sum of all `shares[i].amount` values does not + equal `GetMnType(nType).collat_amount`, or the summation overflows. +15. `nOperatorReward > 10000`. +16. Penalty parameter constraints in [Parameters](#parameters) are not satisfied. -16. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. -17. `vchSig` is non-empty. -18. `inputsHash != CalcTxInputsHash(tx)`. -19. For any share `i`, `shares[i].joinSig` does not verify against +17. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. +18. `vchSig` is non-empty. +19. `inputsHash != CalcTxInputsHash(tx)`. +20. For any share `i`, `shares[i].joinSig` does not verify against `shares[i].ownerKey` over the registration consent digest defined below. -Rules 17 and 18 are evaluated before any ECDSA signature verification. +Rules 18 and 19 are evaluated before any ECDSA signature verification. Consensus computes `outputsHash` from the transaction outputs when building the registration consent digest; there is no payload `outputsHash` field in a v5 ProRegTx. @@ -489,11 +478,12 @@ Where: * `SHA256d(x)` is the Dash double-SHA-256 used for transaction hashes. * `chainGenesisHash` is the 32-byte block hash of the genesis block of the network on which the registration is being authorized (mainnet, testnet, - devnet, regtest, or any future fork). Consensus uses the genesis hash of - the chain that is validating the transaction. Each participant signer - MUST independently verify the network / chain context of the - registration before producing `joinSig`; a signature produced against - one network's `chainGenesisHash` MUST NOT verify on any other network. + devnet, regtest, or any future network with a distinct genesis block). + Consensus uses the genesis hash of the chain that is validating the + transaction. Each participant signer MUST independently verify the + network / chain context of the registration before producing `joinSig`; + a signature produced against one `chainGenesisHash` MUST NOT verify on a + network with a different genesis hash. * `inputsHash` is `CalcTxInputsHash(tx)`. Consensus MUST recompute this from the transaction and compare it to the `payload.inputsHash` field before signature verification; mismatch is invalid. @@ -512,8 +502,9 @@ Where: * `pubKeyOperatorSerialized` is the basic-scheme BLS public key serialization (48 bytes). * `sharesWithoutJoinSigs` is the concatenation of every `CollateralShare` - in share order with the `joinSig` field omitted (treated as a zero-length - byte string) so that signatures are not self-referential. + in share order, with each `joinSig` field serialized byte-exactly as an + empty vector (`CompactSize(0)`) so that signatures are not + self-referential. The field MUST NOT be omitted from the preimage. The domain separator `"DashSharedMNReg"` is the byte sequence of those ASCII characters with no trailing NUL. @@ -544,7 +535,7 @@ CProUpShareTx { nVersion : uint16_t // MUST be 1 (payload version of this new special tx type) proTxHash : uint256 shareIndex : uint16_t // index into shares[] - newRewardScript : CScript + newRewardScript : CScript // compact-size length-prefixed inputsHash : uint256 sig : vector // 65 bytes } @@ -557,35 +548,44 @@ provider payload version `ProTxVersion::SharedCollateral = 5` of the v5 Validation: -1. The masternode identified by `proTxHash` MUST exist and MUST be a v5 +1. `nVersion` MUST be `1`. +2. The masternode identified by `proTxHash` MUST exist and MUST be a v5 masternode. -2. `shareIndex` MUST be less than `shares.size()` in the current state. -3. `newRewardScript` MUST be empty or a standard `P2PKH` or `P2SH` script. +3. `shareIndex` MUST be less than `shares.size()` in the current state. +4. `newRewardScript` MUST be empty or a standard `P2PKH` or `P2SH` script. An empty `newRewardScript` is interpreted as `shares[shareIndex].refundScript`. -4. `newRewardScript` MUST satisfy the same key-reuse restrictions as +5. `newRewardScript` MUST satisfy the same key-reuse restrictions as registration: it MUST NOT be a P2PKH paying to any `shares[i].ownerKey` and MUST NOT pay to `keyIDVoting`. -5. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. -6. `sig` MUST be a valid ECDSA compact signature by +6. After interpreting an empty `newRewardScript` as + `shares[shareIndex].refundScript`, the resulting effective reward script + MUST NOT equal any other share's current effective reward script. +7. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. +8. `sig` MUST be a valid ECDSA compact signature by `shares[shareIndex].ownerKey` over: ```text SHA256d("DashSharedMNUpShare" || chainGenesisHash || LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || - inputsHash || + inputsHash || sequencesHash || outputsHash || LE16(payload.nVersion) || - proTxHash || LE16(shareIndex) || newRewardScript) + proTxHash || LE16(shareIndex) || newRewardScriptSerialized) ``` `chainGenesisHash` is the 32-byte genesis block hash of the network validating the transaction. `payload.nVersion` is the ProUpShareTx payload - version (currently `1`). + version (currently `1`). `sequencesHash` and `outputsHash` use the same + definitions as in [Registration Consent Digest](#registration-consent-digest) + and are recomputed from the actual transaction before signature + verification. `newRewardScriptSerialized` is the standard compact-size + length-prefixed `CScript` serialization used in the payload. A valid `ProUpShareTx` replaces `shares[shareIndex].rewardScript` in the deterministic masternode state. It does not revive a PoSe-banned masternode -and does not affect any other field. +and does not affect any other field. Duplicate effective reward scripts are +therefore invalid both at registration and after every per-share update. #### ProUpSharedRegTx (type 12) @@ -611,25 +611,27 @@ transaction and is unrelated to `ProTxVersion::SharedCollateral = 5`. Validation: -1. The masternode identified by `proTxHash` MUST exist and MUST be a v5 +1. `nVersion` MUST be `1`. +2. The masternode identified by `proTxHash` MUST exist and MUST be a v5 masternode. -2. `pubKeyOperator` MUST be a valid basic-scheme BLS public key and MUST +3. `pubKeyOperator` MUST be a valid basic-scheme BLS public key and MUST NOT collide with the operator key of any other registered masternode. -3. `keyIDVoting` MUST NOT equal any `shares[i].ownerKey`. -4. No current `refundScript` and no current effective `rewardScript` may be a +4. `keyIDVoting` MUST be non-null and MUST NOT equal any + `shares[i].ownerKey`. +5. No current `refundScript` and no current effective `rewardScript` may be a P2PKH script paying to `keyIDVoting`. For this check, an empty `rewardScript` is interpreted as the corresponding `refundScript`. -5. `nOperatorReward <= 10000`. -6. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. -7. `vchSigs.size()` MUST equal `shares.size()`. -8. For each `i` in `[0, shares.size())`, `vchSigs[i]` MUST be a valid +6. `nOperatorReward <= 10000`. +7. `inputsHash` MUST equal `CalcTxInputsHash(tx)`. +8. `vchSigs.size()` MUST equal `shares.size()`. +9. For each `i` in `[0, shares.size())`, `vchSigs[i]` MUST be a valid ECDSA compact signature by `shares[i].ownerKey` over: ```text SHA256d("DashSharedMNUpSharedReg" || chainGenesisHash || LE16(tx.nVersion) || LE16(tx.nType) || LE32(tx.nLockTime) || - inputsHash || + inputsHash || sequencesHash || outputsHash || LE16(payload.nVersion) || proTxHash || pubKeyOperatorSerialized || keyIDVoting || LE16(nOperatorReward)) @@ -637,17 +639,30 @@ Validation: `chainGenesisHash` is the 32-byte genesis block hash of the network validating the transaction. `payload.nVersion` is the ProUpSharedRegTx - payload version (currently `1`). + payload version (currently `1`). `sequencesHash` and `outputsHash` use the + same definitions as in [Registration Consent Digest](#registration-consent-digest) + and are recomputed from the actual transaction before signature + verification. -9. Signature verification proceeds in share order. Any missing, extra, or - out-of-order signature is invalid. +10. Signature verification proceeds in share order. Any missing, extra, or + out-of-order signature is invalid. A valid `ProUpSharedRegTx` replaces `pubKeyOperator`, `keyIDVoting`, and `nOperatorReward` in the deterministic masternode state. It does not revive -a PoSe-banned masternode and does not affect share contents, collateral, or -service fields. Operator-controlled fields (`scriptOperatorPayout`, -`netInfo`, Platform identifiers) are unchanged by this transaction; they -continue to be updated by the operator using `ProUpServTx`. +a PoSe-banned masternode and does not affect share contents or collateral. + +If `pubKeyOperator` is unchanged, existing operator-controlled service fields +(`scriptOperatorPayout`, `netInfo`, and Platform identifiers) are unchanged by +this transaction and continue to be updated by the operator using +`ProUpServTx`. + +If `pubKeyOperator` changes, all operator-controlled service fields MUST be +reset: `scriptOperatorPayout` becomes empty, `netInfo` becomes the empty +network-info value for the masternode's state version, Platform identifiers +become null/empty, and the masternode is marked PoSe-banned until the new +operator submits a valid `ProUpServTx`. This mirrors the existing +operator-key-reset behavior for non-v5 masternodes and prevents stale +operator payout or endpoint state from carrying across an operator change. ### Authorization Tiers @@ -697,21 +712,16 @@ but weights by share amount rather than by basis points. DistributeByWeight(total, weights): sum_w = sum(weights) require sum_w > 0 // reject all-zero weights - out[i] = floor(total * weights[i] / sum_w) for each i - remainder = total - sum(out[i]) - // remainder is distributed deterministically to indices with positive - // weights only, in ascending index order, one duff per such index, - // until exhausted. Indices with weight 0 are skipped so that no duff is - // ever credited to a zero-weight recipient. - for i in 0..weights.size(): - if remainder == 0: break - if weights[i] == 0: continue - out[i] += 1 - remainder -= 1 + remainder_index = greatest i where weights[i] > 0 + out[i] = floor(total * weights[i] / sum_w) for each i != remainder_index + out[remainder_index] = total - sum(out[i] for i != remainder_index) return out ``` -`DistributeByWeight` is invalid for an all-zero weight vector and callers +The final positive-weight entry receives the rounding remainder, matching the +shape of DIP-0026's final-entry remainder rule while allowing weights to be +collateral amounts rather than basis points. `DistributeByWeight` is invalid +for an all-zero weight vector and callers MUST NOT invoke it in that case. All intermediate arithmetic in `DistributeByWeight`, including `total * weights[i]`, `sum_w`, and `sum(out[i])`, MUST be evaluated exactly without overflow. Implementations @@ -755,14 +765,15 @@ CProDisTx { A valid dissolution transaction satisfies all of: -1. It has exactly one input, and that input spends the shared collateral +1. `nVersion` MUST be `1`. +2. It has exactly one input, and that input spends the shared collateral outpoint of the masternode identified by `proTxHash`. No additional funding inputs are permitted in this initial scope. -2. Its outputs are the refund outputs defined below, in share order, with +3. Its outputs are the refund outputs defined below, in share order, with no additional outputs of any kind. Change outputs are not permitted. -3. Its `nLockTime` MAY be non-zero. The dissolution authorization digest +4. Its `nLockTime` MAY be non-zero. The dissolution authorization digest commits to `nLockTime`, so consensus enforces whatever value was signed. -4. `mode` MUST be either `0` (unilateral) or `1` (unanimous). Any other +5. `mode` MUST be either `0` (unilateral) or `1` (unanimous). Any other value is invalid. #### Authorization digest @@ -787,14 +798,14 @@ the single dissolution input (`tx.vin[0].nSequence`) so that changing the sequence cannot disable or alter the signed `nLockTime` semantics. Consensus MUST recompute both hashes and reject mismatches with the payload's `inputsHash` and `outputsHash` fields before any signature is -verified. `chainGenesisHash` is the -32-byte genesis block hash of the network validating the transaction, -identical to its use in [Registration Consent -Digest](#registration-consent-digest); a dissolution signed against one -network's `chainGenesisHash` MUST NOT verify on any other. Each signer -MUST independently verify the network / chain context before producing -its `vchSigs` entry. `payload.nVersion` here is the dissolution payload -version (currently `1`), not the v5 ProRegTx provider payload version. +verified. `chainGenesisHash` is the 32-byte genesis block hash of the +network validating the transaction, identical to its use in [Registration +Consent Digest](#registration-consent-digest); a dissolution signed against +one `chainGenesisHash` MUST NOT verify on a network with a different +genesis hash. Each signer MUST independently verify the network / chain +context before producing its `vchSigs` entry. `payload.nVersion` here is +the dissolution payload version (currently `1`), not the v5 ProRegTx +provider payload version. #### Period and penalty @@ -896,7 +907,7 @@ The difference between the spent shared collateral value and the sum of all outputs is the transaction fee: ```text -fee = state.shared_collateral_value - sum(outputs.value) +fee = GetMnType(state.nType).collat_amount - sum(outputs.value) ``` Because every non-actor output is exact and the actor output is bounded @@ -941,7 +952,7 @@ bears no special-transaction payload would, under DIP-0003 rules, simply remove the masternode by collateral spend. Implementations MUST also enforce the rules below outside `CheckSpecialTx`. -The protected set has two parts with different rules: +The protected set has three parts with different rules: * **Active set.** Every collateral outpoint recorded against an active v5 masternode in the deterministic masternode list at the parent of @@ -956,6 +967,11 @@ The protected set has two parts with different rules: spends are rejected because the registration is not yet part of the parent deterministic state. Dissolution of such a masternode is permitted only in a strictly later block. +* **Mempool pending set.** Every shared collateral outpoint created by + an unconfirmed v5 ProRegTx accepted into the mempool. A mempool + transaction that spends an outpoint in this set is rejected + unconditionally until the registration confirms and the outpoint moves + into the active set. A UTXO whose `scriptPubKey` equals `SHARED_COLLATERAL_SCRIPT` but whose outpoint is in neither set is NOT protected by the covenant and is not @@ -968,9 +984,13 @@ match the template. mempool, scan its inputs. For each input that spends an outpoint in the active set, reject the transaction unless it is a valid `ProDisTx` for the masternode whose collateral outpoint matches the - spent outpoint in the current deterministic masternode list. The - same-block pending set is not consulted at mempool acceptance time; - it is computed per block by block-connection logic. + spent outpoint in the current deterministic masternode list. For + each input that spends an outpoint in the mempool pending set, reject + the transaction unconditionally, including if the transaction is a + `ProDisTx`. The same-block pending set is computed per block by + block-connection logic; the mempool pending set prevents miners from + assembling an invalid parent-plus-child package around an + unconfirmed v5 registration. 2. **Block connection, prior blocks.** Before applying the deterministic masternode list update for a connected block, scan every non-coinbase transaction in that block for inputs that spend outpoints in the @@ -1003,13 +1023,15 @@ match the template. deterministic. These rules are evaluated before script-level evaluation for inputs -that spend outpoints in either protected set: even if the underlying -redeem script is trivially satisfiable, consensus rejects the spend of -an active-set outpoint unless the spend is a valid `ProDisTx` for the -corresponding masternode, and rejects every spend of a same-block -pending outpoint unconditionally. Spends of unprotected UTXOs that -happen to bear the same script are not subjected to the covenant and -are script-evaluated normally. +that spend outpoints in the active set or same-block pending set: even +if the underlying redeem script is trivially satisfiable, consensus +rejects the spend of an active-set outpoint unless the spend is a valid +`ProDisTx` for the corresponding masternode, and rejects every spend of +a same-block pending outpoint unconditionally. Mempool policy likewise +rejects every spend of a mempool-pending outpoint before script-level +standardness or validity can make the child acceptable. Spends of +unprotected UTXOs that happen to bear the same script are not subjected +to the covenant and are script-evaluated normally. ### Deterministic Masternode State @@ -1029,7 +1051,11 @@ extends the shared-collateral version family and defines its state encoding. | `standardPenalty` | `CAmount` | Frozen at registration. | The fields `scriptPayout` and `payouts` are unused for v5 masternodes and -MUST be serialized as empty. `keyIDOwner` is unused for v5 masternodes. +MUST be serialized as empty. `keyIDOwner` is absent from the v5 payload and +MUST NOT participate in v5 validation or uniqueness indexes. If an +implementation's deterministic state object contains a legacy `keyIDOwner` +field, that field MUST be serialized as null/zero for v5 masternodes and MUST +be ignored whenever `isSharedCollateral` is true. State diffs MUST be version-gated: @@ -1092,13 +1118,12 @@ truth. Special-transaction and bloom filtering, as defined in DIP-0003 for `ProRegTx`/`ProUpRegTx` payout scripts, is extended for v5 masternodes: -* For `ProRegTx` v5, the filter MUST match every `shares[i].refundScript` - and every non-empty `shares[i].rewardScript`, and every - `shares[i].ownerKey`. -* For `ProUpShareTx`, the filter MUST match `newRewardScript`. -* For `ProUpSharedRegTx`, the filter MUST match `keyIDVoting`. -* For `ProDisTx`, the filter MUST match every output's `scriptPubKey`, - matching existing transaction-output filter semantics. +| Transaction | Filter elements | +| --- | --- | +| `ProRegTx` v5 | Every `shares[i].refundScript`; every non-empty `shares[i].rewardScript`; every `shares[i].ownerKey`; `keyIDVoting`; `pubKeyOperator`; the shared collateral outpoint. | +| `ProUpShareTx` | `proTxHash`; the current `shares[shareIndex].ownerKey`; `newRewardScript` when non-empty. | +| `ProUpSharedRegTx` | `proTxHash`; every current `shares[i].ownerKey`; `keyIDVoting`; `pubKeyOperator`. | +| `ProDisTx` | `proTxHash`; every signing participant owner key required by `mode`; every refund output `scriptPubKey`, matching existing transaction-output filter semantics. | These filter extensions exist solely for client-side discovery and relay; a filter match is NOT a consensus commitment to the matched @@ -1111,26 +1136,29 @@ activation. Pre-v5 special transactions retain DIP-0003 filtering. ### Governance -Dash governance assigns one vote per masternode, signed by the voting key -recorded in deterministic masternode state. A v5 masternode retains one -`keyIDVoting` and therefore one vote. +A v5 masternode retains one `keyIDVoting`, signed by the voting key recorded +in deterministic masternode state. This DIP does not introduce fractional +participant voting or per-share governance keys. + +Vote weight remains defined by existing masternode-type rules. A shared +`Regular` masternode has the regular masternode vote weight, while a shared +`Evo` masternode retains the DIP-0028 evonode vote weight of four relative to +a regular masternode. The single v5 `keyIDVoting` authorizes that whole +masternode-type vote weight. `keyIDVoting` MAY be updated only by `ProUpSharedRegTx`, which requires -unanimous participant signatures. There is no protocol-level mechanism -for fractional voting among participants; any coordination of -participant preferences for governance votes is off-chain and outside the -scope of this DIP. +unanimous participant signatures. There is no protocol-level mechanism for +fractional voting among participants; any coordination of participant +preferences for governance votes is off-chain and outside the scope of this +DIP. ## Deployment and Compatibility -**Draft status.** This DIP is a Draft. Activation parameters — the -concrete byte sequence of `SHARED_COLLATERAL_SCRIPT`, the special -transaction type numbers, and the DIP-0023 deployment name and -signaling window — are NOT finalized in this revision. The consensus -model and covenant rules below are written to be reviewable as a -draft and MUST NOT be treated as activation-ready until those -parameters are assigned and this DIP is updated accordingly (see -[Open Issues](#open-issues)). +**Draft status.** This DIP is a Draft. The DIP-0023 deployment name and +signaling window are NOT finalized in this revision. The consensus model and +covenant rules below are written to be reviewable as a draft and MUST NOT be +treated as activation-ready until deployment parameters are assigned and this +DIP is updated accordingly (see [Open Issues](#open-issues)). Activation of this DIP is gated by an intended future deployment defined under [DIP-0023](dip-0023.md). The exact deployment name and @@ -1144,8 +1172,10 @@ Before activation: * Any provider transaction payload with `nVersion == 5` is invalid. * Special transaction types `10`, `11`, and `12` are invalid. -* Any output with `SHARED_COLLATERAL_SCRIPT` is treated as non-standard - by relay and mining policy and MUST NOT be created on chain. +* Upgraded relay and mining policy MUST treat any output with + `SHARED_COLLATERAL_SCRIPT` as non-standard and SHOULD NOT mine it. + Pre-activation consensus validity for such outputs is otherwise unchanged + unless a separate deployment says otherwise. After activation: @@ -1281,32 +1311,30 @@ State](#deterministic-masternode-state)). A participant who wishes to exit immediately after registration can do so in the next block at no additional cost beyond standard fees. -### One vote per masternode +### One voting key per masternode Fractional governance would require either a new vote-aggregation scheme or a per-share signing tier on every governance message. Both are -out of scope. Preserving the one-masternode-one-vote rule keeps shared -masternodes compatible with the existing governance infrastructure and -defers fractional voting to a future DIP. +out of scope. Preserving one voting key per masternode while retaining +existing masternode-type vote weights keeps shared masternodes compatible +with the current governance infrastructure and defers fractional voting +to a future DIP. ## Test Cases -Implementations SHOULD include at minimum the following tests. Test -values for `SHARED_COLLATERAL_SCRIPT` are taken from the activation -chain parameters. +Implementations SHOULD include at minimum the following tests. ### Helper 1. `DistributeByWeight(10000, [2500, 2500, 2500, 2500])` returns `[2500, 2500, 2500, 2500]`. 2. `DistributeByWeight(10001, [2500, 2500, 2500, 2500])` returns - `[2501, 2500, 2500, 2500]`. + `[2500, 2500, 2500, 2501]`. 3. `DistributeByWeight(10, [0, 1, 1])` returns `[0, 5, 5]`. -4. `DistributeByWeight(7, [1, 1, 1, 1, 1, 1, 1, 1])` returns one duff - to each of the first seven indices and zero to the eighth. -5. `DistributeByWeight(3, [0, 1, 0, 1, 0])` returns `[0, 2, 0, 1, 0]`: - the remainder skips zero-weight indices and is credited only to - positive-weight indices in ascending order. +4. `DistributeByWeight(7, [1, 1, 1, 1, 1, 1, 1, 1])` returns seven duffs + to the final index and zero to the other indices. +5. `DistributeByWeight(3, [0, 1, 0, 1, 0])` returns `[0, 1, 0, 2, 0]`: + the final positive-weight index receives the remainder. 6. `DistributeByWeight(5, [0, 0, 0])` is invalid (all-zero weights) and MUST be rejected by the helper. @@ -1320,18 +1348,22 @@ chain parameters. 4. A v5 ProRegTx with one share is invalid. 5. A v5 ProRegTx with nine shares is invalid. 6. A v5 ProRegTx whose share amounts sum to 999.99 DASH is invalid. -7. A v5 ProRegTx with a duplicate participant owner key is invalid. -8. A v5 ProRegTx with a duplicate refund script is invalid. -9. A v5 ProRegTx with a refund script paying to a participant owner key - is invalid. -10. A v5 ProRegTx whose collateral output uses a P2PKH script (not - `SHARED_COLLATERAL_SCRIPT`) is invalid. -11. A v5 ProRegTx whose `vchSig` is non-empty is invalid. -12. A v5 ProRegTx whose `joinSig` for share `i` was produced under a +7. A v5 ProRegTx whose share summation overflows is invalid. +8. A v5 ProRegTx with any individual share amount outside `MoneyRange` or + greater than `GetMnType(nType).collat_amount` is invalid. +9. A v5 ProRegTx with a duplicate participant owner key is invalid. +10. A v5 ProRegTx with a duplicate refund script is invalid. +11. A v5 ProRegTx with duplicate effective reward scripts is invalid. +12. A v5 ProRegTx with a refund script paying to a participant owner key + is invalid. +13. A v5 ProRegTx whose collateral output uses a P2PKH script (not + `SHARED_COLLATERAL_SCRIPT`) is invalid. +14. A v5 ProRegTx whose `vchSig` is non-empty is invalid. +15. A v5 ProRegTx whose `joinSig` for share `i` was produced under a different `outputsHash` is invalid. -13. A v5 ProRegTx whose `joinSig` for share `i` was produced under different +16. A v5 ProRegTx whose `joinSig` for share `i` was produced under different input sequences is invalid. -14. A v5 ProRegTx whose `joinSig` for share `i` was produced under a +17. A v5 ProRegTx whose `joinSig` for share `i` was produced under a different penalty value is invalid. ### Reward Splitting @@ -1355,19 +1387,32 @@ chain parameters. invalid. 3. A `ProUpShareTx` setting `newRewardScript` to a P2PKH paying `shares[0].ownerKey` is invalid. -4. A `ProUpSharedRegTx` with `vchSigs.size() == shares.size()`, signed +4. A `ProUpShareTx` setting `newRewardScript` to another share's current + effective reward script is invalid. +5. A `ProUpShareTx` whose signature was produced under different input + sequences or transaction outputs is invalid. +6. A `ProUpShareTx` with `nVersion != 1` is invalid. +7. A `ProUpSharedRegTx` with `vchSigs.size() == shares.size()`, signed by every share in order, updating `nOperatorReward` to a value no greater than 10000, is valid. -5. A `ProUpSharedRegTx` setting `nOperatorReward > 10000` is invalid. -6. A `ProUpSharedRegTx` setting `keyIDVoting` to a key hash already used by - any current refund script or effective reward script is invalid. -7. A `ProUpSharedRegTx` missing one signature is invalid. -8. A `ProUpSharedRegTx` with an extra signature is invalid. -9. A `ProUpSharedRegTx` whose signatures are presented out of share - order is invalid. -10. A `ProUpSharedRegTx` signature produced for another network's +8. A `ProUpSharedRegTx` with `nVersion != 1` is invalid. +9. A `ProUpSharedRegTx` setting `keyIDVoting` to null is invalid. +10. A `ProUpSharedRegTx` setting `nOperatorReward > 10000` is invalid. +11. A `ProUpSharedRegTx` setting `keyIDVoting` to a key hash already used by + any current refund script or effective reward script is invalid. +12. A `ProUpSharedRegTx` whose signature was produced under different input + sequences or transaction outputs is invalid. +13. A `ProUpSharedRegTx` missing one signature is invalid. +14. A `ProUpSharedRegTx` with an extra signature is invalid. +15. A `ProUpSharedRegTx` whose signatures are presented out of share + order is invalid. +16. A `ProUpSharedRegTx` signature produced for another network's `chainGenesisHash` is invalid. -11. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. +17. A `ProUpSharedRegTx` that changes `pubKeyOperator` resets + `scriptOperatorPayout`, `netInfo`, and Platform identifiers and leaves the + masternode PoSe-banned until the new operator submits a valid + `ProUpServTx`. +18. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. ### Dissolution @@ -1378,54 +1423,55 @@ chain parameters. instead of `standardPenalty`. 3. A unanimous `ProDisTx` with `shares.size()` signatures in share order and zero penalty is valid. -4. A unilateral `ProDisTx` whose actor output exceeds +4. A `ProDisTx` with `nVersion != 1` is invalid. +5. A unilateral `ProDisTx` whose actor output exceeds `shares[a].amount - penalty` is invalid. -5. A unilateral `ProDisTx` whose non-actor output `i` pays more or less +6. A unilateral `ProDisTx` whose non-actor output `i` pays more or less than `shares[i].amount + bonus[i]` is invalid. -6. A unilateral `ProDisTx` whose non-actor output `i` pays to a script +7. A unilateral `ProDisTx` whose non-actor output `i` pays to a script other than `shares[i].refundScript` is invalid. -7. A unilateral `ProDisTx` with an extra output (e.g. OP_RETURN) is +8. A unilateral `ProDisTx` with an extra output (e.g. OP_RETURN) is invalid. -8. A unilateral `ProDisTx` with an extra input is invalid. -9. A unilateral `ProDisTx` whose actor output is omitted because the +9. A unilateral `ProDisTx` with an extra input is invalid. +10. A unilateral `ProDisTx` whose actor output is omitted because the fee equals `shares[a].amount - penalty` (so the actor remainder after the fee is zero) is valid, and its output list is the non-actor outputs in share order with no actor slot. -10. A unilateral `ProDisTx` that includes an actor output paying zero +11. A unilateral `ProDisTx` that includes an actor output paying zero duffs to `shares[a].refundScript` is invalid; the actor output MUST be omitted rather than paid as a zero-value output. -11. A unanimous `ProDisTx` whose actor output is omitted (because the +12. A unanimous `ProDisTx` whose actor output is omitted (because the fee equals `shares[a].amount`) is valid; every non-actor output pays exactly `shares[i].amount`. -12. A unanimous `ProDisTx` missing one participant signature is +13. A unanimous `ProDisTx` missing one participant signature is invalid. -13. A `ProDisTx` with `mode` other than `0` or `1` is invalid. -14. A `ProDisTx` whose input sequence is changed after signing is invalid, +14. A `ProDisTx` with `mode` other than `0` or `1` is invalid. +15. A `ProDisTx` whose input sequence is changed after signing is invalid, including when the sequence change would alter `nLockTime` behavior. -15. A `ProDisTx` whose `outputsHash` does not match the recomputed +16. A `ProDisTx` whose `outputsHash` does not match the recomputed transaction `outputsHash` is invalid; the signature check is not reached. -16. A normal transaction (`nVersion < 3` or `nType == 0`) that spends +17. A normal transaction (`nVersion < 3` or `nType == 0`) that spends the collateral outpoint of an active v5 masternode is rejected by mempool and by block validation. -17. A non-dissolution special transaction whose input spends the +18. A non-dissolution special transaction whose input spends the collateral outpoint of an active v5 masternode is rejected. -18. A normal (non-`ProDisTx`) transaction later in the same block as a +19. A normal (non-`ProDisTx`) transaction later in the same block as a v5 ProRegTx that spends the just-created shared collateral output is rejected at block connection. -19. A unilateral `ProDisTx` for the same `proTxHash` in the same block +20. A unilateral `ProDisTx` for the same `proTxHash` in the same block as the v5 ProRegTx that creates its shared collateral output is invalid, regardless of ordering within the block and regardless of whether every other covenant rule holds. -20. A unanimous `ProDisTx` for the same `proTxHash` in the same block +21. A unanimous `ProDisTx` for the same `proTxHash` in the same block as the v5 ProRegTx that creates its shared collateral output is invalid on the same basis as the unilateral case. -21. A `ProDisTx` (unilateral or unanimous) that spends the shared +22. A `ProDisTx` (unilateral or unanimous) that spends the shared collateral outpoint of a v5 masternode whose v5 ProRegTx was confirmed in a strictly earlier block, and that otherwise satisfies every covenant rule, is valid; the masternode is removed when this block is connected. -22. An ordinary transaction whose input spends an unrelated UTXO that +23. An ordinary transaction whose input spends an unrelated UTXO that happens to pay `SHARED_COLLATERAL_SCRIPT` but is NOT a recorded active shared collateral outpoint and is NOT a same-block pending shared collateral outpoint is NOT rejected by the covenant; @@ -1561,42 +1607,30 @@ require the full node serving the client to be honest about v5 state. Both the registration consent digest and the dissolution authorization digest commit explicitly to `chainGenesisHash`, the genesis block hash of the network on which the transaction is being authorized. The consensus -digest domain therefore includes the network's genesis hash, and any -v5 registration or dissolution signed against one network's -`chainGenesisHash` cannot be replayed onto any other network or fork -because the digest verified by consensus on the target network would -differ. Signers MUST verify the network / chain context (which genesis -hash they are signing against) before producing any `joinSig` or -dissolution signature; relying on `proTxHash` alone is insufficient for -cross-chain replay protection because `proTxHash` collisions across -networks or short-lived forks cannot be ruled out by digest construction -alone and would otherwise expose unsuspecting signers to replay. +digest domain therefore separates networks with different genesis hashes: +a v5 registration or dissolution signed against one `chainGenesisHash` +cannot be replayed onto a network with a different genesis hash because +the digest verified by consensus on the target network would differ. + +This does not provide replay separation between forks or deployments that +share the same genesis block. Signers MUST verify the intended network / +chain context before producing any `joinSig` or dissolution signature, and +implementations deploying v5 on same-genesis forks MUST add any additional +fork-specific replay protection they require. Relying on `proTxHash` alone +is insufficient for cross-chain replay protection because matching +transaction and state context across networks cannot be ruled out by +digest construction alone. ## Open Issues The following implementation details are deferred and MUST be resolved before activation: -1. **Exact bytes of `SHARED_COLLATERAL_SCRIPT`.** The recommended - template family is a P2SH of an `OP_TRUE`-equivalent redeem - script. The chosen bytes are encoded into chain parameters and - included in this DIP before mainnet activation. -2. **Final numeric values of `TRANSACTION_PROVIDER_DISSOLVE`, - `TRANSACTION_PROVIDER_UPDATE_SHARE`, and - `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR`.** Tentatively - `10`, `11`, `12`. Before merge and before activation, these - assignments MUST be re-checked against the authoritative DIP-0002 - registry and the live Dash Core `TRANSACTION_*` enum (as defined in - `primitives/transaction.h` on the targeted release branch). If any - value is already taken or pending allocation, this DIP MUST be - renumbered to the next free values rather than be merged with a - conflicting allocation. This DIP intentionally does not embed an - authoritative table of unrelated allocations. -3. **Final value of `SHARED_MIN_SHARE_DUFFS`.** Tentatively - `1000000000` (10 DASH). Subject to dust-policy review and - feedback from wallet implementers. -4. **Activation deployment name.** Subject to release engineering - confirmation that no candidate fork bit has already been consumed. +1. **Final value of `SHARED_MIN_SHARE_DUFFS`.** Currently + `1000000000` (10 DASH). Subject to dust-policy review and feedback from + wallet implementers. +2. **Activation deployment name.** Subject to release engineering confirmation + that no candidate fork bit has already been consumed. The following protocol-level extensions are out of scope for this DIP and may be addressed by future DIPs: diff --git a/project-words.txt b/project-words.txt index 85f03cf8..918c1ea2 100644 --- a/project-words.txt +++ b/project-words.txt @@ -120,6 +120,8 @@ lowtopup nocredits nouser thresholdreached +infinitedash +johndoe # Names Akimov @@ -162,4 +164,12 @@ Udjin Udjinm Virgile Westrich -Wray \ No newline at end of file +Wray +ANYONECANPAY +dissolvemasternode +Serv +SIGHASH +sighash +vout + +standardness From f6bd78aa67b7da38b676782cff38fefcd5fdcf1a Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sun, 21 Jun 2026 16:09:36 -0500 Subject: [PATCH 13/18] docs: clarify shared collateral registered set --- dip-decentralized-masternode-shares.md | 45 ++++++++++++++------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 0c84ddb0..ef78a23f 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -261,12 +261,12 @@ The shared collateral output uses a single, fixed serialized script, it is recognizable as non-standard by older nodes that do not understand shared collateral. 4. After activation, consensus and mempool policy protect the following shared collateral outpoint sets: - * (a) **Active shared collateral outpoints** — outpoints recorded as - the collateral of an active v5 masternode in the deterministic + * (a) **Registered shared collateral outpoints** — outpoints recorded + as the collateral of a registered v5 masternode in the deterministic masternode list at the parent state of the block (or mempool tip) - being validated. A spend of an outpoint in this class is rejected - unless the spending transaction is a valid `ProDisTx` for the - corresponding masternode. + being validated, including PoSe-banned entries that remain registered. + A spend of an outpoint in this class is rejected unless the spending + transaction is a valid `ProDisTx` for the corresponding masternode. * (b) **Same-block pending shared collateral outputs** — shared collateral outputs created by a valid v5 ProRegTx earlier in the block currently being validated. A spend of an output in this @@ -947,18 +947,19 @@ until the corresponding `ProDisTx` has been validated. Provider transaction `CheckSpecialTx` validation alone is insufficient to enforce the shared collateral covenant: an ordinary transaction that -spends the shared collateral outpoint of an active v5 masternode but +spends the shared collateral outpoint of a registered v5 masternode but bears no special-transaction payload would, under DIP-0003 rules, simply remove the masternode by collateral spend. Implementations MUST also enforce the rules below outside `CheckSpecialTx`. The protected set has three parts with different rules: -* **Active set.** Every collateral outpoint recorded against an active - v5 masternode in the deterministic masternode list at the parent of - the block (or mempool tip) being validated. A spend of an outpoint - in the active set is rejected unless it is a valid `ProDisTx` for - the matching masternode. +* **Registered set.** Every collateral outpoint recorded against a + registered v5 masternode in the deterministic masternode list at the + parent of the block (or mempool tip) being validated, including + PoSe-banned entries that remain registered until removed by a valid + `ProDisTx`. A spend of an outpoint in the registered set is rejected + unless it is a valid `ProDisTx` for the matching masternode. * **Same-block pending set.** Every shared collateral outpoint created by a valid v5 ProRegTx earlier in the block currently being validated. A spend of an outpoint in the same-block pending set is @@ -971,7 +972,7 @@ The protected set has three parts with different rules: an unconfirmed v5 ProRegTx accepted into the mempool. A mempool transaction that spends an outpoint in this set is rejected unconditionally until the registration confirms and the outpoint moves - into the active set. + into the registered set. A UTXO whose `scriptPubKey` equals `SHARED_COLLATERAL_SCRIPT` but whose outpoint is in neither set is NOT protected by the covenant and is not @@ -982,7 +983,7 @@ match the template. 1. **Mempool acceptance.** Before accepting any transaction into the mempool, scan its inputs. For each input that spends an outpoint in - the active set, reject the transaction unless it is a valid + the registered set, reject the transaction unless it is a valid `ProDisTx` for the masternode whose collateral outpoint matches the spent outpoint in the current deterministic masternode list. For each input that spends an outpoint in the mempool pending set, reject @@ -994,8 +995,9 @@ match the template. 2. **Block connection, prior blocks.** Before applying the deterministic masternode list update for a connected block, scan every non-coinbase transaction in that block for inputs that spend outpoints in the - active set (collateral outpoints of active v5 masternodes in the - parent-state deterministic masternode list). Reject the block unless + registered set (collateral outpoints of registered v5 masternodes, + including PoSe-banned entries, in the parent-state deterministic + masternode list). Reject the block unless every such input is the input of a valid `ProDisTx` for the matching masternode. 3. **Block connection, same-block.** Maintain a per-block index of @@ -1023,9 +1025,9 @@ match the template. deterministic. These rules are evaluated before script-level evaluation for inputs -that spend outpoints in the active set or same-block pending set: even +that spend outpoints in the registered set or same-block pending set: even if the underlying redeem script is trivially satisfiable, consensus -rejects the spend of an active-set outpoint unless the spend is a valid +rejects the spend of a registered-set outpoint unless the spend is a valid `ProDisTx` for the corresponding masternode, and rejects every spend of a same-block pending outpoint unconditionally. Mempool policy likewise rejects every spend of a mempool-pending outpoint before script-level @@ -1452,10 +1454,11 @@ Implementations SHOULD include at minimum the following tests. transaction `outputsHash` is invalid; the signature check is not reached. 17. A normal transaction (`nVersion < 3` or `nType == 0`) that spends - the collateral outpoint of an active v5 masternode is rejected by - mempool and by block validation. + the collateral outpoint of a registered v5 masternode, including a + PoSe-banned masternode that remains registered, is rejected by mempool + and by block validation. 18. A non-dissolution special transaction whose input spends the - collateral outpoint of an active v5 masternode is rejected. + collateral outpoint of a registered v5 masternode is rejected. 19. A normal (non-`ProDisTx`) transaction later in the same block as a v5 ProRegTx that spends the just-created shared collateral output is rejected at block connection. @@ -1473,7 +1476,7 @@ Implementations SHOULD include at minimum the following tests. removed when this block is connected. 23. An ordinary transaction whose input spends an unrelated UTXO that happens to pay `SHARED_COLLATERAL_SCRIPT` but is NOT a recorded - active shared collateral outpoint and is NOT a same-block pending + registered shared collateral outpoint and is NOT a same-block pending shared collateral outpoint is NOT rejected by the covenant; whether it succeeds depends only on ordinary script evaluation of the underlying redeem script. From dcd170bc0ba3ddefe4c3348370d47e52044d633e Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sun, 21 Jun 2026 16:27:57 -0500 Subject: [PATCH 14/18] docs: align early penalty window definition --- dip-decentralized-masternode-shares.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index ef78a23f..e68a59d6 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -147,7 +147,7 @@ v5 registration, and a shared masternode is wound down only by a valid | Unilateral dissolution | A dissolution authorized by a single participant ("the actor") with a penalty redistributed to non-actors. | | Unanimous dissolution | A dissolution authorized by every participant with no penalty. | | Actor | The participant chosen by `actorIndex` who pays the transaction fee and, in unilateral mode, the penalty. | -| Early period | The window of `earlyPeriodBlocks` blocks after registration during which `earlyPenalty` applies to unilateral dissolution. | +| Early period | The first `earlyPeriodBlocks` eligible dissolution blocks after registration during which `earlyPenalty` applies to unilateral dissolution. Same-block dissolution is not eligible; if `earlyPeriodBlocks == 0`, no block is early. | ### Parameters @@ -814,10 +814,16 @@ Let `H` be the height at which the dissolution is connected, and let block of `H`. Define: ```text -early = (H - state.nRegisteredHeight) < state.earlyPeriodBlocks +early = (state.earlyPeriodBlocks > 0) && + ((H - state.nRegisteredHeight) <= state.earlyPeriodBlocks) penalty = (mode == 1) ? 0 : (early ? state.earlyPenalty : state.standardPenalty) ``` +Because same-block dissolution is invalid, the first eligible dissolution +height is `state.nRegisteredHeight + 1`. Therefore `earlyPeriodBlocks == 1` +means only that first eligible block uses `earlyPenalty`, and +`earlyPeriodBlocks == 0` disables the early penalty window. + #### Signature cardinality For `mode == 0` (unilateral): From 7c91aa58feafa4cbd9aa3f903de8442da39ade59 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sun, 21 Jun 2026 16:49:09 -0500 Subject: [PATCH 15/18] docs: clarify reorg replay test ordering --- dip-decentralized-masternode-shares.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index e68a59d6..7802bbea 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -1495,8 +1495,9 @@ Implementations SHOULD include at minimum the following tests. prior `rewardScript`. 3. Disconnecting a block containing a `ProDisTx` restores the v5 masternode entry with its full share state. -4. A reorg that replays a `ProUpSharedRegTx` followed by a `ProDisTx` - in a different order produces the same final state. +4. Disconnecting and reconnecting a chain segment that contains a + `ProUpSharedRegTx` followed later by a `ProDisTx` in the original + chain order produces the same final state. ## Implementation Notes From 113b147ad18a2dc4548c5854467ee2a4ca6d5b30 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sun, 21 Jun 2026 17:15:12 -0500 Subject: [PATCH 16/18] docs: specify deferred share-vector encoding details --- dip-decentralized-masternode-shares.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 7802bbea..017e1490 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -383,7 +383,7 @@ ProRegTx layout for `nVersion == 5`. | `pubKeyOperator` | BLS public key (basic scheme) | As for v4. | | `keyIDVoting` | `CKeyID` | As for v4. | | `nOperatorReward` | `uint16_t` | Basis points; MUST be from 0 to 10000. | -| `shares` | `CollateralShare[]` | 2 to 8 entries; per [Collateral Share](#collateral-share). | +| `shares` | `CollateralShare[]` | CompactSize-prefixed vector of 2 to 8 entries; per [Collateral Share](#collateral-share). | | `earlyPeriodBlocks` | `uint32_t` | `0` to `SHARED_MAX_EARLY_PERIOD_BLOCKS`. | | `earlyPenalty` | `CAmount` (8 bytes) | Duffs. | | `standardPenalty` | `CAmount` (8 bytes) | Duffs. | @@ -1067,7 +1067,8 @@ be ignored whenever `isSharedCollateral` is true. State diffs MUST be version-gated: -1. The state-diff bitfield gains a new field bit for the share vector. +1. The state-diff bitfield gains a new field bit for the share vector + (exact bit value deferred; see [Open Issues](#open-issues)). Any change to any field of any `shares[i]` (including a per-share `rewardScript` change from a `ProUpShareTx`) MUST produce a state diff in which the entire share vector is fully replaced. @@ -1641,6 +1642,8 @@ before activation: wallet implementers. 2. **Activation deployment name.** Subject to release engineering confirmation that no candidate fork bit has already been consumed. +3. **State-diff bit value for the share vector.** The exact bit position for + the v5 share-vector full-replacement diff MUST be assigned before activation. The following protocol-level extensions are out of scope for this DIP and may be addressed by future DIPs: From 1b778bc15317055b614b33f3ec9cd1e5cd31bb0d Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sun, 21 Jun 2026 17:44:30 -0500 Subject: [PATCH 17/18] docs: use shared collateral discriminator --- dip-decentralized-masternode-shares.md | 853 +++++++++++++++---------- 1 file changed, 504 insertions(+), 349 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 017e1490..01d68ceb 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -19,10 +19,10 @@ 5. [Specification](#specification) 1. [Terminology](#terminology) 2. [Parameters](#parameters) - 3. [Provider Transaction Version](#provider-transaction-version) + 3. [Shared-Collateral Mode Discriminator](#shared-collateral-mode-discriminator) 4. [Shared Collateral Output](#shared-collateral-output) 5. [Collateral Share](#collateral-share) - 6. [Shared Registration (ProRegTx v5)](#shared-registration-proregtx-v5) + 6. [Shared Registration (ProRegTx v4, shared mode)](#shared-registration-proregtx-v4-shared-mode) 7. [Registration Consent Digest](#registration-consent-digest) 8. [Shared Update Transactions](#shared-update-transactions) 9. [Authorization Tiers](#authorization-tiers) @@ -43,24 +43,28 @@ ## Abstract This DIP extends [DIP-0003: Deterministic Masternode Lists](dip-0003.md) by -defining a new provider transaction payload version, `SharedCollateral` (`5`), -that allows between 2 and 8 mutually distrusting participants to jointly fund, -operate, and exit a single Regular or Evolution masternode under -protocol-enforced consent. The shared masternode is funded by an internal -collateral output that is locked by consensus to a dissolution covenant. Owner -rewards are split natively in the coinbase by recorded share amounts. Each -participant may unilaterally dissolve the masternode at any time, subject to a -participant-chosen penalty and the standard transaction fee, and the full set of -participants may dissolve unanimously with no penalty. Updates to fields that -affect all participants require consent from every participant; the operator -role remains unchanged. +defining a **shared-collateral mode** within the existing DIP-0026 v4 provider +transaction payload, selected by an explicit `isSharedCollateral` discriminator +flag. In shared-collateral mode, between 2 and 8 mutually distrusting +participants jointly fund, operate, and exit a single Regular or Evolution +masternode under protocol-enforced consent. The shared masternode is funded by +an internal collateral output that is locked by consensus to a dissolution +covenant. Owner rewards are split natively in the coinbase by recorded share +amounts. Each participant may unilaterally dissolve the masternode at any time, +subject to a participant-chosen penalty and the standard transaction fee, and +the full set of participants may dissolve unanimously with no penalty. Updates +to fields that affect all participants require consent from every participant; +the operator role remains unchanged. Shared collateral is a strict superset of the [DIP-0026: Multi-Party -Payouts](dip-0026.md) reward-splitting mechanism. DIP-0026 leaves the -registrar owner in unilateral control of the payout list. This DIP introduces -a separate provider payload version so that share amounts, refund destinations, -and the collateral itself are bound to per-participant consent and cannot be -redirected by any single owner, operator, miner, or compromised update path. +Payouts](dip-0026.md) reward-splitting mechanism. DIP-0026 v4 with +`isSharedCollateral == false` leaves the registrar owner in unilateral control +of the payout list and is unchanged by this DIP. This DIP introduces the +shared-collateral discriminator into the same v4 payload version so that, when +the discriminator is true, share amounts, refund destinations, and the +collateral itself are bound to per-participant consent and cannot be redirected +by any single owner, operator, miner, or compromised update path. No new +ProRegTx provider payload version is reserved. ## Motivation @@ -109,14 +113,15 @@ future DIPs. ## Relationship to DIP-0026 DIP-0026 introduces provider transaction payload version `4` (`MultiPayout`). -Under DIP-0026 v4, the registrar owner remains the sole signer for ProRegTx and -ProUpRegTx, and may freely rewrite the payout list at any time. +Under DIP-0026 v4 with `isSharedCollateral == false`, the registrar owner +remains the sole signer for ProRegTx and ProUpRegTx, and may freely rewrite +the payout list at any time. -This DIP does not modify DIP-0026 v4 semantics. Shared masternodes use a new -provider transaction payload version, `5` (`SharedCollateral`), with the -following distinctions: +This DIP does not modify DIP-0026 v4 non-shared semantics. Shared masternodes +reuse the same v4 provider transaction payload version with +`isSharedCollateral == true`, with the following distinctions: -| Property | DIP-0026 v4 | This DIP, v5 | +| Property | v4, `isSharedCollateral == false` (DIP-0026) | v4, `isSharedCollateral == true` (this DIP) | | --- | --- | --- | | Ownership model | Single registrar owner | 2 to 8 participants | | Payout list authority | Registrar owner via ProUpRegTx | Per-share self-update; share amounts immutable | @@ -125,11 +130,17 @@ following distinctions: | Voting / operator updates | Owner | Unanimous participant consent | | Refund on exit | None enforced | Per-participant refund script enforced | -A v5 payload MUST NOT be reinterpreted as a v4 payload, and a v4 payload MUST -NOT be reinterpreted as v5. A single-owner masternode using v3 or v4 cannot -be upgraded to v5 in place: shared collateral is established only by a new -v5 registration, and a shared masternode is wound down only by a valid -`ProDisTx`. There is no in-place downgrade from v5 to v3 or v4. +The two modes share `nVersion == 4` but are not interchangeable on the wire: +the discriminator selects mutually exclusive variant fields (see +[Shared-Collateral Mode Discriminator](#shared-collateral-mode-discriminator)), +so a payload serialized with `isSharedCollateral == true` MUST NOT be +reinterpreted as a non-shared v4 payload, and a non-shared v4 payload MUST NOT +be reinterpreted as shared. A single-owner masternode using v3 or non-shared +v4 cannot be converted to shared in place: shared collateral is established +only by a new v4 registration with `isSharedCollateral == true`, and a shared +masternode is wound down only by a valid `ProDisTx`. There is no in-place +conversion from a shared v4 masternode back to a non-shared v3 or v4 +masternode. ## Specification @@ -200,22 +211,52 @@ for the unilateral actor after the penalty is paid, so a unilateral dissolution by any participant always returns a non-zero amount to the actor before fees. -### Provider Transaction Version +### Shared-Collateral Mode Discriminator -This DIP introduces one new ProRegTx payload version and three new special -transaction types, each with its own independent payload version. +This DIP does NOT introduce a new ProRegTx provider payload version. +Shared-collateral registration reuses the existing DIP-0026 v4 ProRegTx +(`nVersion == 4`) and selects shared-collateral semantics by an explicit +discriminator field, `isSharedCollateral`. This DIP introduces three new +special transaction types, each with its own independent payload version, +to update and dissolve shared masternodes. -| Name | Value | Applies to | -| --- | ---: | --- | -| `ProTxVersion::SharedCollateral` | 5 | `ProRegTx` payload version for shared-collateral registration and the corresponding shared masternode state. | +The discriminator is a single `uint8_t` field in the v4 ProRegTx payload: -`ProTxVersion::SharedCollateral = 5` is the provider transaction payload -version of a v5 `ProRegTx` and is also the version tag carried in the -deterministic masternode state for masternodes registered by such a -transaction. It does NOT apply as the payload version of the new special +| Name | Type | Encoding | +| --- | --- | --- | +| `isSharedCollateral` | `uint8_t` | `0x00` for non-shared (DIP-0026 multi-payout) mode; `0x01` for shared-collateral mode. Any other value is invalid. | + +The discriminator selects mutually exclusive variant fields in the same v4 +payload: + +* `isSharedCollateral == false`: the v4 payload follows the unchanged + DIP-0026 multi-payout layout (single `keyIDOwner`, `scriptPayout` / + `payouts`, no share table, no shared collateral output, no penalty + parameters, no `earlyPeriodBlocks`). All DIP-0026 v4 semantics, validation, + state, and authorization apply verbatim and are unaffected by this DIP. +* `isSharedCollateral == true`: the v4 payload follows the shared-collateral + layout defined in [Shared Registration (ProRegTx v4, shared + mode)](#shared-registration-proregtx-v4-shared-mode) (no `keyIDOwner`, no + `scriptPayout`, no `payouts`, a `CollateralShare[]` table, an internal + collateral output that pays `SHARED_COLLATERAL_SCRIPT`, and per-masternode + penalty parameters). The shared-collateral variant is selected solely by + the discriminator; consensus MUST NOT infer the variant from any other + field length, script shape, or output count. + +The discriminator is the mode tag carried in the deterministic masternode +state: a masternode created by a v4 ProRegTx with `isSharedCollateral == true` +carries `state.isSharedCollateral == true` for the rest of its lifetime, and +all shared-collateral state fields and authorization rules in this DIP gate on +that flag. A masternode created by a v4 ProRegTx with +`isSharedCollateral == false` carries `state.isSharedCollateral == false` and +is governed by DIP-0026. + +The discriminator does NOT apply as the payload version of the new special transaction types introduced below; those payloads carry their own, independent `nVersion` field that starts at `1` (see each payload -definition). +definition). The independent payload versions of `ProDisTx`, +`ProUpShareTx`, and `ProUpSharedRegTx` evolve separately from the v4 +ProRegTx payload version. | Name | Value | Description | | --- | ---: | --- | @@ -229,11 +270,12 @@ If another accepted DIP consumes any of these values before this DIP is merged, this DIP MUST be updated to use the next free values before merge; it MUST NOT ship with conflicting type assignments. -ProRegTx (type `1`) is reused at payload version 5 for shared registration. -The base `ProUpRegTx` (type `3`) is invalid against a v5 masternode and is -unaffected for non-shared masternodes. The base `ProUpServTx` (type `2`) -continues to operate under DIP-0003 / DIP-0028 rules for non-shared -masternodes AND remains valid against a v5 masternode for the +ProRegTx (type `1`) is reused at payload version 4 with +`isSharedCollateral == true` for shared registration. The base `ProUpRegTx` +(type `3`) is invalid against a shared-collateral masternode +(`state.isSharedCollateral == true`) and is unaffected for non-shared +masternodes. The base `ProUpServTx` (type `2`) continues to operate under +DIP-0003 / DIP-0028 rules for non-shared masternodes AND remains valid against a shared-collateral masternode for the operator-authorized fields enumerated in [Authorization Tiers](#authorization-tiers); `ProUpServTx` MUST NOT attempt to modify any owner-controlled or shared-collateral field. @@ -250,8 +292,8 @@ The shared collateral output uses a single, fixed serialized script, 1. It is a fixed byte sequence specified by this DIP; every shared collateral output on every network uses the identical script bytes. Script-template equality is the registration-time selector used to identify which output - of a v5 ProRegTx is the shared collateral output; it is NOT, on its own, - the consensus identifier of a protected collateral. + of a v4 shared-collateral ProRegTx is the shared collateral output; it is + NOT, on its own, the consensus identifier of a protected collateral. 2. It contains no participant-specific data. In particular, it does not embed any participant owner key, refund script, reward script, share amount, or `proTxHash`. The per-masternode binding between an outpoint and a @@ -262,25 +304,28 @@ The shared collateral output uses a single, fixed serialized script, shared collateral. 4. After activation, consensus and mempool policy protect the following shared collateral outpoint sets: * (a) **Registered shared collateral outpoints** — outpoints recorded - as the collateral of a registered v5 masternode in the deterministic + as the collateral of a registered shared-collateral masternode + (`state.isSharedCollateral == true`) in the deterministic masternode list at the parent state of the block (or mempool tip) being validated, including PoSe-banned entries that remain registered. A spend of an outpoint in this class is rejected unless the spending transaction is a valid `ProDisTx` for the corresponding masternode. * (b) **Same-block pending shared collateral outputs** — shared - collateral outputs created by a valid v5 ProRegTx earlier in the - block currently being validated. A spend of an output in this - class later in the same block is rejected unconditionally: ordinary - spends are rejected because the outpoint represents a freshly - created masternode commitment, and `ProDisTx` is rejected because - the registration is not yet part of the parent deterministic state - a dissolution would consult. Dissolution of such a masternode is - permitted only in a strictly later block, after the registration - has been recorded in the deterministic masternode list. + collateral outputs created by a valid v4 ProRegTx with + `isSharedCollateral == true` earlier in the block currently being + validated. A spend of an output in this class later in the same + block is rejected unconditionally: ordinary spends are rejected + because the outpoint represents a freshly created masternode + commitment, and `ProDisTx` is rejected because the registration is + not yet part of the parent deterministic state a dissolution would + consult. Dissolution of such a masternode is permitted only in a + strictly later block, after the registration has been recorded in + the deterministic masternode list. * (c) **Mempool pending shared collateral outputs** — shared - collateral outputs created by an unconfirmed valid v5 ProRegTx in - the mempool. A mempool spend of an output in this class is rejected - unconditionally until the registration confirms. + collateral outputs created by an unconfirmed valid v4 ProRegTx with + `isSharedCollateral == true` in the mempool. A mempool spend of an + output in this class is rejected unconditionally until the + registration confirms. Rejection is enforced at mempool acceptance, at block connection, and during deterministic masternode list processing (see [Collateral Spend Enforcement](#collateral-spend-enforcement)). @@ -290,13 +335,13 @@ The shared collateral output uses a single, fixed serialized script, subject to whatever spending conditions the recommended template imposes (for example, the `OP_TRUE` redeem-script template makes them anyone-can-spend). Implementations MUST NOT retroactively lock arbitrary - pre-existing outputs or unrelated outputs created outside the v5 - registration flow. + pre-existing outputs or unrelated outputs created outside the + shared-collateral registration flow. 6. Before activation, upgraded relays and miners MUST treat any output with this script as non-standard and SHOULD NOT mine it, so ordinary relay and mining policy discourages creating such outputs before activation. After activation, the only way for a transaction to register an outpoint into the - protected set is a valid v5 ProRegTx; + protected set is a valid v4 ProRegTx with `isSharedCollateral == true`; relay and mining policy MUST continue to treat any other output bearing `SHARED_COLLATERAL_SCRIPT` as non-standard to discourage accidental creation of stranded anyone-can-spend outputs. @@ -308,7 +353,8 @@ SHARED_COLLATERAL_SCRIPT = 0x51 ``` This bare anyone-can-spend script is deliberately non-standard except when it -appears as the collateral output of a valid v5 ProRegTx after activation. +appears as the collateral output of a valid v4 ProRegTx with +`isSharedCollateral == true` after activation. Script-level satisfaction is trivial, but consensus rules above script evaluation reject every protected spend except a valid `ProDisTx`. Embedding per-masternode commitments (such as `proTxHash` or participant keys) into the @@ -349,15 +395,17 @@ Field rules at registration: 5. `ownerKey` MUST be distinct from every active owner key recorded for any other registered masternode at the registration height. The unique-property index defined in DIP-0003 is extended to track every - `shares[i].ownerKey` for v5 masternodes (see [Deterministic Masternode - State](#deterministic-masternode-state)). + `shares[i].ownerKey` for shared-collateral masternodes (see + [Deterministic Masternode State](#deterministic-masternode-state)). 6. `refundScript` MUST NOT be duplicated within the share table. 7. Effective reward scripts MUST NOT be duplicated within the share table. For this rule, an empty `rewardScript` is interpreted as `refundScript` before duplicate detection. 8. No `refundScript` and no `rewardScript` may be a P2PKH script paying to - `keyIDOwner` (for v5 the field is absent — see below), `keyIDVoting`, or - to a key ID that equals any `shares[i].ownerKey`. The intent matches + `keyIDVoting` or to a key ID that equals any `shares[i].ownerKey`. There + is no `keyIDOwner` field in shared-collateral mode — see [Shared + Registration (ProRegTx v4, shared + mode)](#shared-registration-proregtx-v4-shared-mode). The intent matches the DIP-0026 key-reuse rule: these scripts are spent in lower-trust wallet contexts and MUST NOT reuse keys that control masternode state. 9. `joinSig` is a 65-byte compact ECDSA signature by `ownerKey` over the @@ -365,23 +413,25 @@ Field rules at registration: Digest](#registration-consent-digest). Any other length or any signature that does not verify under `ownerKey` is invalid. -### Shared Registration (ProRegTx v5) +### Shared Registration (ProRegTx v4, shared mode) -A v5 ProRegTx has the same general layout as the v4 ProRegTx from DIP-0026 -with the following differences. Field positions for a v5 payload are -specified normatively below; this list is exhaustive and replaces the v4 -ProRegTx layout for `nVersion == 5`. +A v4 ProRegTx with `isSharedCollateral == true` uses the field layout below. +Field positions for the shared-collateral variant are specified normatively +here; this list is exhaustive and defines the v4 ProRegTx layout for +`isSharedCollateral == true`. The v4 layout for `isSharedCollateral == false` +is unchanged from DIP-0026 and is NOT defined by this DIP. | Field | Type | Notes | | --- | --- | --- | -| `nVersion` | `uint16_t` | MUST be `5`. | +| `nVersion` | `uint16_t` | MUST be `4`. | | `nType` | `MnType` (`uint16_t`) | `Regular` or `Evo`. | | `nMode` | `uint16_t` | MUST be `0`. | +| `isSharedCollateral` | `uint8_t` | MUST be `0x01` to select the shared-collateral variant defined in this section. `0x00` selects the unchanged DIP-0026 multi-payout variant and is not governed by this DIP. Any other value is invalid. | | `collateralOutpoint.hash` | `uint256` | MUST be the null hash. | | `collateralOutpoint.n` | `uint32_t` | Index of the shared collateral output within this transaction. | -| `netInfo` | DIP-0003/0028 net info | As for v4. | -| `pubKeyOperator` | BLS public key (basic scheme) | As for v4. | -| `keyIDVoting` | `CKeyID` | As for v4. | +| `netInfo` | DIP-0003/0028 net info | As for non-shared v4. | +| `pubKeyOperator` | BLS public key (basic scheme) | As for non-shared v4. | +| `keyIDVoting` | `CKeyID` | As for non-shared v4. | | `nOperatorReward` | `uint16_t` | Basis points; MUST be from 0 to 10000. | | `shares` | `CollateralShare[]` | CompactSize-prefixed vector of 2 to 8 entries; per [Collateral Share](#collateral-share). | | `earlyPeriodBlocks` | `uint32_t` | `0` to `SHARED_MAX_EARLY_PERIOD_BLOCKS`. | @@ -389,59 +439,66 @@ ProRegTx layout for `nVersion == 5`. | `standardPenalty` | `CAmount` (8 bytes) | Duffs. | | `inputsHash` | `uint256` | `CalcTxInputsHash(tx)`. | | `platformNodeID`, `platformNetInfo` (Evo only) | as DIP-0028 | Serialized after `inputsHash` and before `vchSig`, preserving the DIP-0028 insertion point. | -| `vchSig` | `vector` | MUST be empty for v5 (no external collateral signature). | +| `vchSig` | `vector` | MUST be empty in shared-collateral mode (no external collateral signature). | -There is no `keyIDOwner` field in a v5 ProRegTx. The role of the single owner -key in DIP-0003 is performed in v5 by the collection of `shares[*].ownerKey`. +There is no `keyIDOwner` field in a shared-collateral v4 ProRegTx. The role +of the single owner key in DIP-0003 is performed in shared-collateral mode by +the collection of `shares[*].ownerKey`. The non-shared v4 ProRegTx retains +its `keyIDOwner` field unchanged. -There is no `scriptPayout` or `payouts` field in a v5 ProRegTx. Owner rewards -are derived directly from the share table as specified in [Reward -Distribution](#reward-distribution). +There is no `scriptPayout` or `payouts` field in a shared-collateral v4 +ProRegTx. Owner rewards are derived directly from the share table as +specified in [Reward Distribution](#reward-distribution). The non-shared v4 +ProRegTx retains its DIP-0026 payout list unchanged. Unless explicitly replaced by this section, the base ProRegTx validation rules -from DIP-0003, DIP-0026, and DIP-0028 continue to apply to v5 registrations. -This includes network-info validation, Evo Platform-field validation, -operator-key validity, and operator-key uniqueness. - -A v5 ProRegTx is invalid if any of the following conditions hold: - -1. `nVersion != 5`. -2. `nType` is not `Regular` or `Evo`. -3. `nMode != 0`. -4. `netInfo` fails the applicable DIP-0003 network-info validation rules. -5. For Evo masternodes, any Platform field fails the applicable DIP-0028 +from DIP-0003, DIP-0026, and DIP-0028 continue to apply to shared-collateral +v4 registrations. This includes network-info validation, Evo Platform-field +validation, operator-key validity, and operator-key uniqueness. + +A v4 ProRegTx with `isSharedCollateral == true` is invalid if any of the +following conditions hold: + +1. `nVersion != 4`. +2. `isSharedCollateral` is neither `0x00` nor `0x01`. (When + `isSharedCollateral == 0x00`, the payload is governed by DIP-0026 and the + remaining rules in this list do not apply.) +3. `nType` is not `Regular` or `Evo`. +4. `nMode != 0`. +5. `netInfo` fails the applicable DIP-0003 network-info validation rules. +6. For Evo masternodes, any Platform field fails the applicable DIP-0028 validation rules. -6. `pubKeyOperator` is not a valid basic-scheme BLS public key or collides +7. `pubKeyOperator` is not a valid basic-scheme BLS public key or collides with the operator key of any other registered masternode. -7. `collateralOutpoint.hash` is not the null hash. -8. `collateralOutpoint.n` does not point to an output of this transaction. -9. The output at `collateralOutpoint.n` does not pay - `GetMnType(nType).collat_amount` to `SHARED_COLLATERAL_SCRIPT`. -10. The transaction creates more than one output paying +8. `collateralOutpoint.hash` is not the null hash. +9. `collateralOutpoint.n` does not point to an output of this transaction. +10. The output at `collateralOutpoint.n` does not pay + `GetMnType(nType).collat_amount` to `SHARED_COLLATERAL_SCRIPT`. +11. The transaction creates more than one output paying `SHARED_COLLATERAL_SCRIPT`. -11. `shares.size()` is less than `SHARED_MIN_PARTICIPANTS` or greater than +12. `shares.size()` is less than `SHARED_MIN_PARTICIPANTS` or greater than `SHARED_MAX_PARTICIPANTS`. -12. Any share fails its per-field validation rules in [Collateral +13. Any share fails its per-field validation rules in [Collateral Share](#collateral-share). -13. Any `shares[i].amount` fails `MoneyRange`, is less than +14. Any `shares[i].amount` fails `MoneyRange`, is less than `SHARED_MIN_SHARE_DUFFS`, or is greater than `GetMnType(nType).collat_amount`. -14. The exact overflow-safe sum of all `shares[i].amount` values does not +15. The exact overflow-safe sum of all `shares[i].amount` values does not equal `GetMnType(nType).collat_amount`, or the summation overflows. -15. `nOperatorReward > 10000`. -16. Penalty parameter constraints in [Parameters](#parameters) are not +16. `nOperatorReward > 10000`. +17. Penalty parameter constraints in [Parameters](#parameters) are not satisfied. -17. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. -18. `vchSig` is non-empty. -19. `inputsHash != CalcTxInputsHash(tx)`. -20. For any share `i`, `shares[i].joinSig` does not verify against +18. `earlyPeriodBlocks > SHARED_MAX_EARLY_PERIOD_BLOCKS`. +19. `vchSig` is non-empty. +20. `inputsHash != CalcTxInputsHash(tx)`. +21. For any share `i`, `shares[i].joinSig` does not verify against `shares[i].ownerKey` over the registration consent digest defined below. -Rules 18 and 19 are evaluated before any ECDSA signature verification. +Rules 19 and 20 are evaluated before any ECDSA signature verification. Consensus computes `outputsHash` from the transaction outputs when building the registration consent digest; there is no payload `outputsHash` field in -a v5 ProRegTx. +a shared-collateral v4 ProRegTx. ### Registration Consent Digest @@ -457,6 +514,7 @@ SharedRegConsentHash = SHA256d( inputsHash || sequencesHash || outputsHash || LE16(payload.nVersion) || LE16(payload.nType) || LE16(payload.nMode) || + LE8(payload.isSharedCollateral) || LE32(payload.collateralOutpoint.n) || netInfoSerialized || platformFieldsSerialized || // present iff nType == Evo @@ -471,6 +529,10 @@ SharedRegConsentHash = SHA256d( ) ``` +The digest commits explicitly to `payload.isSharedCollateral` so that a +signature produced under one variant cannot be reinterpreted under the +other. + Where: * `LE8`, `LE16`, `LE32`, `LE64` denote little-endian encodings of the @@ -519,11 +581,12 @@ fields a participant would otherwise believe they had committed to. ### Shared Update Transactions Two new special transaction types specialize updates for shared -masternodes. The base `ProUpRegTx` (type `3`) is invalid for v5 masternodes, -because its single-owner authorization model is incompatible with the -shared-registrar tier defined here. The base `ProUpServTx` (type `2`) -remains valid for v5 masternodes with operator authorization (see -[Authorization Tiers](#authorization-tiers)). +masternodes. The base `ProUpRegTx` (type `3`) is invalid for shared-collateral +masternodes (`state.isSharedCollateral == true`), because its single-owner +authorization model is incompatible with the shared-registrar tier defined +here. The base `ProUpServTx` (type `2`) remains valid for shared-collateral +masternodes with operator authorization (see [Authorization +Tiers](#authorization-tiers)). #### ProUpShareTx (type 11) @@ -542,15 +605,16 @@ CProUpShareTx { ``` `nVersion` here is the independent payload version of the new -`TRANSACTION_PROVIDER_UPDATE_SHARE` special transaction; it is NOT the -provider payload version `ProTxVersion::SharedCollateral = 5` of the v5 -`ProRegTx`. The two version namespaces are separate. +`TRANSACTION_PROVIDER_UPDATE_SHARE` special transaction; it is NOT the v4 +ProRegTx provider payload version. The two version namespaces are separate, +and the `ProUpShareTx` payload version evolves independently of the v4 +ProRegTx payload version. Validation: 1. `nVersion` MUST be `1`. -2. The masternode identified by `proTxHash` MUST exist and MUST be a v5 - masternode. +2. The masternode identified by `proTxHash` MUST exist and MUST satisfy + `state.isSharedCollateral == true`. 3. `shareIndex` MUST be less than `shares.size()` in the current state. 4. `newRewardScript` MUST be empty or a standard `P2PKH` or `P2SH` script. An empty `newRewardScript` is interpreted as @@ -607,13 +671,13 @@ CProUpSharedRegTx { As with `ProUpShareTx`, this `nVersion` is the independent payload version of the new `TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR` special -transaction and is unrelated to `ProTxVersion::SharedCollateral = 5`. +transaction and is unrelated to the v4 ProRegTx provider payload version. Validation: 1. `nVersion` MUST be `1`. -2. The masternode identified by `proTxHash` MUST exist and MUST be a v5 - masternode. +2. The masternode identified by `proTxHash` MUST exist and MUST satisfy + `state.isSharedCollateral == true`. 3. `pubKeyOperator` MUST be a valid basic-scheme BLS public key and MUST NOT collide with the operator key of any other registered masternode. 4. `keyIDVoting` MUST be non-null and MUST NOT equal any @@ -661,7 +725,7 @@ reset: `scriptOperatorPayout` becomes empty, `netInfo` becomes the empty network-info value for the masternode's state version, Platform identifiers become null/empty, and the masternode is marked PoSe-banned until the new operator submits a valid `ProUpServTx`. This mirrors the existing -operator-key-reset behavior for non-v5 masternodes and prevents stale +operator-key-reset behavior for non-shared masternodes and prevents stale operator payout or endpoint state from carrying across an operator change. ### Authorization Tiers @@ -682,15 +746,17 @@ operator payout or endpoint state from carrying across an operator change. | Platform service endpoints (Evo) | Operator BLS key | `ProUpServTx` | | PoSe revocation | Operator BLS key | `ProUpRevTx` | -`ProUpRegTx` (type `3`) is invalid against a v5 masternode. `ProUpServTx` and -`ProUpRevTx` continue to use operator authorization as in DIP-0003 and -DIP-0028. The operator can stop service or revoke the masternode but cannot -redirect owner rewards or spend the shared collateral. +`ProUpRegTx` (type `3`) is invalid against a shared-collateral masternode +(`state.isSharedCollateral == true`). `ProUpServTx` and `ProUpRevTx` continue +to use operator authorization as in DIP-0003 and DIP-0028. The operator can +stop service or revoke the masternode but cannot redirect owner rewards or +spend the shared collateral. ### Reward Distribution -Owner-reward distribution for a v5 masternode reuses the DIP-0026 pipeline -but weights by share amount rather than by basis points. +Owner-reward distribution for a shared-collateral masternode +(`state.isSharedCollateral == true`) reuses the DIP-0026 pipeline but weights +by share amount rather than by basis points. 1. Compute the masternode reward and apply the Platform credit-pool reallocation as in current rules. @@ -733,8 +799,9 @@ without calling `DistributeByWeight` (see [Dissolution (ProDisTx)](#dissolution-prodistx)). Coinbase validation requires every expected per-share reward output by exact -amount and script. As in DIP-0026, the relative order of outputs within the -coinbase is not consensus-significant. +amount and script for every shared-collateral masternode scheduled in the +block. As in DIP-0026, the relative order of outputs within the coinbase is +not consensus-significant. The `DistributeByWeight` helper is also used in [Dissolution (ProDisTx)](#dissolution-prodistx) for penalty redistribution. @@ -758,8 +825,8 @@ CProDisTx { ``` `nVersion` here is the independent payload version of the new -`TRANSACTION_PROVIDER_DISSOLVE` special transaction and is unrelated to -`ProTxVersion::SharedCollateral = 5`. +`TRANSACTION_PROVIDER_DISSOLVE` special transaction and is unrelated to the +v4 ProRegTx provider payload version. #### Transaction shape @@ -804,7 +871,7 @@ Consent Digest](#registration-consent-digest); a dissolution signed against one `chainGenesisHash` MUST NOT verify on a network with a different genesis hash. Each signer MUST independently verify the network / chain context before producing its `vchSigs` entry. `payload.nVersion` here is -the dissolution payload version (currently `1`), not the v5 ProRegTx +the dissolution payload version (currently `1`), not the v4 ProRegTx provider payload version. #### Period and penalty @@ -928,18 +995,17 @@ miners include them. #### Same-block ordering -A `ProDisTx` MUST NOT appear in the same block as the v5 `ProRegTx` -that creates the shared collateral output it would spend. Dissolution -operates against the deterministic masternode state at the parent -block, so the registering masternode is not yet part of that state -until the registering block has been connected. A `ProDisTx` whose -input refers to a shared collateral output created earlier in the -same block is therefore invalid even if every other covenant rule -would otherwise hold; dissolution is permitted only in a strictly -later block. Block validation MUST track shared collateral outputs -created earlier in the same block solely so that any spend of those -outputs later in the same block — ordinary or `ProDisTx` — can be -rejected (see [Collateral Spend +A `ProDisTx` MUST NOT appear in the same block as the v4 ProRegTx with +`isSharedCollateral == true` that creates the shared collateral output it +would spend. Dissolution operates against the deterministic masternode state +at the parent block, so the registering masternode is not yet part of that +state until the registering block has been connected. A `ProDisTx` whose +input refers to a shared collateral output created earlier in the same block +is therefore invalid even if every other covenant rule would otherwise hold; +dissolution is permitted only in a strictly later block. Block validation +MUST track shared collateral outputs created earlier in the same block solely +so that any spend of those outputs later in the same block — ordinary or +`ProDisTx` — can be rejected (see [Collateral Spend Enforcement](#collateral-spend-enforcement)). #### State effect @@ -952,33 +1018,37 @@ until the corresponding `ProDisTx` has been validated. ### Collateral Spend Enforcement Provider transaction `CheckSpecialTx` validation alone is insufficient to -enforce the shared collateral covenant: an ordinary transaction that -spends the shared collateral outpoint of a registered v5 masternode but -bears no special-transaction payload would, under DIP-0003 rules, simply -remove the masternode by collateral spend. Implementations MUST also -enforce the rules below outside `CheckSpecialTx`. +enforce the shared collateral covenant: an ordinary transaction that spends +the shared collateral outpoint of a registered shared-collateral masternode +(`state.isSharedCollateral == true`) but bears no special-transaction payload +would, under DIP-0003 rules, simply remove the masternode by collateral +spend. Implementations MUST also enforce the rules below outside +`CheckSpecialTx`. The protected set has three parts with different rules: * **Registered set.** Every collateral outpoint recorded against a - registered v5 masternode in the deterministic masternode list at the - parent of the block (or mempool tip) being validated, including - PoSe-banned entries that remain registered until removed by a valid - `ProDisTx`. A spend of an outpoint in the registered set is rejected - unless it is a valid `ProDisTx` for the matching masternode. + registered shared-collateral masternode + (`state.isSharedCollateral == true`) in the deterministic masternode list at + the parent of the block + (or mempool tip) being validated, including PoSe-banned entries that + remain registered until removed by a valid `ProDisTx`. A spend of an + outpoint in the registered set is rejected unless it is a valid + `ProDisTx` for the matching masternode. * **Same-block pending set.** Every shared collateral outpoint created - by a valid v5 ProRegTx earlier in the block currently being - validated. A spend of an outpoint in the same-block pending set is - rejected unconditionally: ordinary spends are rejected because the - outpoint represents an active masternode commitment, and `ProDisTx` - spends are rejected because the registration is not yet part of the - parent deterministic state. Dissolution of such a masternode is - permitted only in a strictly later block. + by a valid v4 ProRegTx with `isSharedCollateral == true` earlier in + the block currently being validated. A spend of an outpoint in the + same-block pending set is rejected unconditionally: ordinary spends + are rejected because the outpoint represents an active masternode + commitment, and `ProDisTx` spends are rejected because the + registration is not yet part of the parent deterministic state. + Dissolution of such a masternode is permitted only in a strictly + later block. * **Mempool pending set.** Every shared collateral outpoint created by - an unconfirmed v5 ProRegTx accepted into the mempool. A mempool - transaction that spends an outpoint in this set is rejected - unconditionally until the registration confirms and the outpoint moves - into the registered set. + an unconfirmed v4 ProRegTx with `isSharedCollateral == true` accepted + into the mempool. A mempool transaction that spends an outpoint in + this set is rejected unconditionally until the registration confirms + and the outpoint moves into the registered set. A UTXO whose `scriptPubKey` equals `SHARED_COLLATERAL_SCRIPT` but whose outpoint is in neither set is NOT protected by the covenant and is not @@ -997,36 +1067,38 @@ match the template. `ProDisTx`. The same-block pending set is computed per block by block-connection logic; the mempool pending set prevents miners from assembling an invalid parent-plus-child package around an - unconfirmed v5 registration. + unconfirmed shared-collateral registration. 2. **Block connection, prior blocks.** Before applying the deterministic masternode list update for a connected block, scan every non-coinbase transaction in that block for inputs that spend outpoints in the - registered set (collateral outpoints of registered v5 masternodes, - including PoSe-banned entries, in the parent-state deterministic - masternode list). Reject the block unless - every such input is the input of a valid `ProDisTx` for the matching - masternode. + registered set (collateral outpoints of registered shared-collateral + masternodes, including PoSe-banned entries, in the parent-state + deterministic masternode list). Reject the block unless every such + input is the input of a valid `ProDisTx` for the matching masternode. 3. **Block connection, same-block.** Maintain a per-block index of - shared collateral outputs created by valid v5 ProRegTx earlier in - the same block, keyed by `(txid, vout, proTxHash)`. For every later - non-coinbase transaction in the same block, reject any input that - spends such an output. Both ordinary spends and `ProDisTx` spends - are rejected: a `ProDisTx` whose input refers to a shared collateral - output created in the same block is invalid even if every other - covenant rule would otherwise hold. + shared collateral outputs created by valid v4 ProRegTx with + `isSharedCollateral == true` earlier in the same block, keyed by + `(txid, vout, proTxHash)`. For every later non-coinbase transaction + in the same block, reject any input that spends such an output. Both + ordinary spends and `ProDisTx` spends are rejected: a `ProDisTx` + whose input refers to a shared collateral output created in the same + block is invalid even if every other covenant rule would otherwise + hold. 4. **Deterministic masternode list removal.** Replace the "remove on collateral spend" rule from DIP-0003 with the following - for v5 masternodes: a v5 masternode MUST NOT be removed until a + for shared-collateral masternodes: a shared-collateral masternode + (`state.isSharedCollateral == true`) MUST NOT be removed until a corresponding valid `ProDisTx` for that `proTxHash` has been processed in a strictly later block than the block that contained - the registering v5 ProRegTx. Any spend of the masternode's shared + the registering v4 ProRegTx. Any spend of the masternode's shared collateral outpoint by a transaction that is not such a `ProDisTx` is invalid. 5. **Block disconnection and reorg.** Disconnecting a block that - contained a `ProDisTx` MUST restore the v5 masternode entry to the - exact pre-dissolution deterministic masternode state. State diffs - MUST capture the full share vector and shared-registration - parameters as a single replacement (see [Deterministic Masternode + contained a `ProDisTx` MUST restore the shared-collateral masternode + entry to the exact pre-dissolution deterministic masternode state. + State diffs MUST capture the full share vector and + shared-registration parameters as a single replacement (see + [Deterministic Masternode State](#deterministic-masternode-state)) so that reorg replay is deterministic. @@ -1044,12 +1116,14 @@ to the covenant and are script-evaluated normally. ### Deterministic Masternode State The deterministic masternode state defined in DIP-0003 is extended for -v5 masternodes. The pre-v5 state serialization is unchanged. +shared-collateral masternodes. The pre-shared and non-shared serialization +is unchanged. -For `nVersion == ProTxVersion::SharedCollateral`, the masternode state -includes the following additional fields. A future provider payload version -MUST NOT be interpreted as shared collateral unless that future DIP explicitly -extends the shared-collateral version family and defines its state encoding. +The deterministic masternode state for every masternode includes the +`isSharedCollateral` flag carried from its registering v4 ProRegTx (or +implicitly `false` for masternodes registered under earlier provider payload +versions). When `state.isSharedCollateral == true`, the masternode state +includes the following additional fields: | Field | Type | Notes | | --- | --- | --- | @@ -1058,14 +1132,16 @@ extends the shared-collateral version family and defines its state encoding. | `earlyPenalty` | `CAmount` | Frozen at registration. | | `standardPenalty` | `CAmount` | Frozen at registration. | -The fields `scriptPayout` and `payouts` are unused for v5 masternodes and -MUST be serialized as empty. `keyIDOwner` is absent from the v5 payload and -MUST NOT participate in v5 validation or uniqueness indexes. If an +The fields `scriptPayout` and `payouts` are unused when +`state.isSharedCollateral == true` and MUST be serialized as empty. +`keyIDOwner` is absent from the shared-collateral v4 ProRegTx and MUST NOT +participate in shared-collateral validation or uniqueness indexes. If an implementation's deterministic state object contains a legacy `keyIDOwner` -field, that field MUST be serialized as null/zero for v5 masternodes and MUST -be ignored whenever `isSharedCollateral` is true. +field, that field MUST be serialized as null/zero whenever +`state.isSharedCollateral == true` and MUST be ignored whenever +`state.isSharedCollateral` is true. -State diffs MUST be version-gated: +State diffs MUST be gated on `state.isSharedCollateral`: 1. The state-diff bitfield gains a new field bit for the share vector (exact bit value deferred; see [Open Issues](#open-issues)). @@ -1073,13 +1149,15 @@ State diffs MUST be version-gated: per-share `rewardScript` change from a `ProUpShareTx`) MUST produce a state diff in which the entire share vector is fully replaced. This full-replacement encoding is the single canonical - representation of a v5 share-state diff: compact diffs, per-share - field diffs, and any other alternative encoding of share-vector - changes are NOT permitted. Implementations MUST emit and accept - share-vector changes only as a full replacement of the share - vector. -2. State diffs for v5 fields are present only when the masternode is - v5; for pre-v5 masternodes the corresponding bits MUST NOT appear. + representation of a shared-collateral share-state diff: compact + diffs, per-share field diffs, and any other alternative encoding of + share-vector changes are NOT permitted. Implementations MUST emit + and accept share-vector changes only as a full replacement of the + share vector. +2. State diffs for shared-collateral fields are present only when + `state.isSharedCollateral == true`; for non-shared masternodes (both + pre-v4 and v4 with `isSharedCollateral == false`) the corresponding + bits MUST NOT appear. 3. Snapshot serialization MUST round-trip across restart, reorg replay, and historic block validation without loss. @@ -1087,49 +1165,52 @@ Unique-property indexes MUST be extended: * Every `shares[i].ownerKey` participates in the owner-key uniqueness index used to reject reuse across masternodes at registration time. - Pre-v5 masternodes contribute their single `keyIDOwner`; v5 - masternodes contribute every participant owner key. + Non-shared masternodes contribute their single `keyIDOwner`; + shared-collateral masternodes contribute every participant owner key. * No network-wide uniqueness index is added for `keyIDVoting`. As in - DIP-0003, the voting key may be delegated and reused; v5 validation only - forbids `keyIDVoting` from equaling any participant owner key for the same - masternode. + DIP-0003, the voting key may be delegated and reused; shared-collateral + validation only forbids `keyIDVoting` from equaling any participant owner + key for the same masternode. * The operator-key uniqueness index applies to `pubKeyOperator` as in DIP-0003. -A v5 masternode is created only by a valid v5 ProRegTx and removed only -by a valid v5 `ProDisTx` for the same `proTxHash`. Pre-v5 masternodes -continue to be created and removed as in DIP-0003. +A shared-collateral masternode is created only by a valid v4 ProRegTx with +`isSharedCollateral == true` and removed only by a valid `ProDisTx` for the +same `proTxHash`. Non-shared masternodes continue to be created and removed +as in DIP-0003 and DIP-0026. ### Simplified Masternode List and Filters `CSimplifiedMNListEntry::CalcHash`, as used by DIP-0004 simplified masternode list verification, MUST NOT include shares, refund scripts, -reward scripts, penalty parameters, or any other v5-only field. Light -clients receive no SML commitment to shared-collateral metadata. -Diagnostic RPCs and extended JSON output MAY expose v5 fields. +reward scripts, penalty parameters, or any other shared-collateral-only +field. Light clients receive no SML commitment to shared-collateral +metadata. Diagnostic RPCs and extended JSON output MAY expose +shared-collateral fields. The trust boundary this creates is intentional and mirrors the direction DIP-0026 took for multi-payout metadata: shared-collateral fields are not committed to by SML hashes, so light clients cannot independently verify share amounts, refund scripts, reward scripts, -penalty parameters, or any other v5-only field from an SML proof. A -future extension to DIP-0004 would be required before SPV clients -could verify shared-collateral terms without trusting a serving full -node. +penalty parameters, or any other shared-collateral-only field from an +SML proof. A future extension to DIP-0004 would be required before SPV +clients could verify shared-collateral terms without trusting a serving +full node. Full nodes are unaffected by this boundary: a full node validates -every v5 field from the deterministic masternode state that it -reconstructs by replaying blocks, exactly as it does for pre-v5 -masternode state. The SML/filter limitation applies only to clients -that depend on SML hashes or filter matches as their source of +every shared-collateral field from the deterministic masternode state +that it reconstructs by replaying blocks, exactly as it does for +non-shared masternode state. The SML/filter limitation applies only to +clients that depend on SML hashes or filter matches as their source of truth. Special-transaction and bloom filtering, as defined in DIP-0003 for -`ProRegTx`/`ProUpRegTx` payout scripts, is extended for v5 masternodes: +`ProRegTx`/`ProUpRegTx` payout scripts, is extended for shared-collateral +transactions: | Transaction | Filter elements | | --- | --- | -| `ProRegTx` v5 | Every `shares[i].refundScript`; every non-empty `shares[i].rewardScript`; every `shares[i].ownerKey`; `keyIDVoting`; `pubKeyOperator`; the shared collateral outpoint. | +| `ProRegTx` v4 with `isSharedCollateral == true` | Every `shares[i].refundScript`; every non-empty `shares[i].rewardScript`; every `shares[i].ownerKey`; `keyIDVoting`; `pubKeyOperator`; the shared collateral outpoint. | | `ProUpShareTx` | `proTxHash`; the current `shares[shareIndex].ownerKey`; `newRewardScript` when non-empty. | | `ProUpSharedRegTx` | `proTxHash`; every current `shares[i].ownerKey`; `keyIDVoting`; `pubKeyOperator`. | | `ProDisTx` | `proTxHash`; every signing participant owner key required by `mode`; every refund output `scriptPubKey`, matching existing transaction-output filter semantics. | @@ -1137,23 +1218,26 @@ Special-transaction and bloom filtering, as defined in DIP-0003 for These filter extensions exist solely for client-side discovery and relay; a filter match is NOT a consensus commitment to the matched data, and any light client that uses a filter match as authoritative -evidence of v5 state still depends on the honesty of the serving full -node. +evidence of shared-collateral state still depends on the honesty of the +serving full node. -These filter extensions apply only to v5 special transactions after -activation. Pre-v5 special transactions retain DIP-0003 filtering. +These filter extensions apply only to shared-collateral special transactions +after activation. Non-shared special transactions (including DIP-0026 v4 +with `isSharedCollateral == false`) retain their existing DIP-0003 and +DIP-0026 filtering. ### Governance -A v5 masternode retains one `keyIDVoting`, signed by the voting key recorded -in deterministic masternode state. This DIP does not introduce fractional -participant voting or per-share governance keys. +A shared-collateral masternode (`state.isSharedCollateral == true`) retains +one `keyIDVoting`, signed by the voting key recorded in deterministic +masternode state. This DIP does not introduce fractional participant voting +or per-share governance keys. Vote weight remains defined by existing masternode-type rules. A shared `Regular` masternode has the regular masternode vote weight, while a shared `Evo` masternode retains the DIP-0028 evonode vote weight of four relative to -a regular masternode. The single v5 `keyIDVoting` authorizes that whole -masternode-type vote weight. +a regular masternode. The single shared-collateral `keyIDVoting` authorizes +that whole masternode-type vote weight. `keyIDVoting` MAY be updated only by `ProUpSharedRegTx`, which requires unanimous participant signatures. There is no protocol-level mechanism for @@ -1179,7 +1263,9 @@ subject to DIP editor assignment. Before activation: -* Any provider transaction payload with `nVersion == 5` is invalid. +* Any v4 ProRegTx payload with `isSharedCollateral == true` is invalid. + DIP-0026 v4 payloads with `isSharedCollateral == false` are unaffected by + this DIP and remain governed by DIP-0026. * Special transaction types `10`, `11`, and `12` are invalid. * Upgraded relay and mining policy MUST treat any output with `SHARED_COLLATERAL_SCRIPT` as non-standard and SHOULD NOT mine it. @@ -1188,18 +1274,21 @@ Before activation: After activation: -* v5 ProRegTx, `ProUpShareTx`, `ProUpSharedRegTx`, and `ProDisTx` are - valid as specified above. -* DIP-0003 single-owner masternodes (v1, v2, v3) and DIP-0026 - multi-payout masternodes (v4) continue to operate unchanged. -* No upgrade path is defined from v1/v2/v3/v4 to v5. A shared masternode - is established only by a new v5 ProRegTx; a v5 masternode is wound - down only by a `ProDisTx`. -* No downgrade path is defined from v5 to v1/v2/v3/v4. - -A v5 masternode and a non-v5 masternode never appear as the same -`proTxHash`; deterministic masternode state is version-gated as -specified in [Deterministic Masternode +* v4 ProRegTx with `isSharedCollateral == true`, `ProUpShareTx`, + `ProUpSharedRegTx`, and `ProDisTx` are valid as specified above. +* DIP-0003 single-owner masternodes (v1, v2, v3) and DIP-0026 v4 + masternodes with `isSharedCollateral == false` continue to operate + unchanged. +* No conversion path is defined from a non-shared masternode (v1/v2/v3 or + v4 with `isSharedCollateral == false`) to a shared-collateral masternode. + A shared-collateral masternode is established only by a new v4 ProRegTx + with `isSharedCollateral == true`; it is wound down only by a `ProDisTx`. +* No conversion path is defined from a shared-collateral masternode back to + a non-shared masternode. + +A shared-collateral masternode and a non-shared masternode never appear as +the same `proTxHash`; deterministic masternode state is gated on +`state.isSharedCollateral` as specified in [Deterministic Masternode State](#deterministic-masternode-state). ## Rationale @@ -1210,29 +1299,49 @@ DIP-0026 fixes the operational pain of off-chain reward distribution but does not constrain the registrar owner, the collateral UTXO, or the exit path. The most common use cases that motivate DIP-0026 (services that hold the collateral on behalf of multiple beneficiaries) remain -custodial under v4. This DIP addresses that gap by binding share -amounts, refund destinations, and the collateral itself to per-participant -consent. - -### Distinct provider payload version - -Reusing v4 semantics for shared collateral would either redefine -registrar-controlled fields under a multi-signer rule or carry both a -basis-point payout list and a share table on every v4 payload. Either -choice complicates the v4 deserialization rules and the deterministic -masternode state machine. Allocating a separate v5 keeps each version -self-describing and lets nodes that have implemented v4 reject v5 by -version check until activation. +custodial under v4 with `isSharedCollateral == false`. This DIP +addresses that gap by binding share amounts, refund destinations, and +the collateral itself to per-participant consent. + +### Explicit discriminator instead of a distinct provider payload version + +A provider transaction payload version is a wire-format identifier, not +a semantic mode flag. Allocating a new ProRegTx provider payload version +solely for shared collateral would conflate two orthogonal axes: the +on-the-wire layout of the v4 payload, and whether the masternode that v4 +payload describes is shared. The version number would then drift apart from +any future layout changes that are unrelated to shared collateral, and every +downstream consumer would have to learn to treat one specific version value +as a stand-in for "shared". + +Selecting shared-collateral semantics by an explicit discriminator +(`isSharedCollateral`) within the existing v4 payload keeps the wire +version describing only the wire format and keeps the semantic mode +selection a first-class field that callers, validators, indexers, and +state diffs all read directly. The discriminator is committed to by the +registration consent digest and carried into deterministic masternode +state, so a participant signature, a state entry, and an SML query all +agree on the mode without inferring it from the version number, output +shape, or script length. Future layout-level changes to the v4 payload +can advance the wire version without altering what "shared" means; new +shared-mode variants can advance the discriminator without consuming +provider payload version numbers. + +The discriminator also keeps the non-shared v4 layout unchanged: a v4 +ProRegTx with `isSharedCollateral == false` deserializes and validates +exactly as DIP-0026 specifies, and the variant fields specific to +shared mode appear only when the discriminator selects them. ### Internal collateral only External shared collateral would require either a multi-signature collateral UTXO controlled off-chain (defeating the goal of trustless exit) or a covenant attached to a previously created output (out of -scope for the existing UTXO format). Constraining v5 to internal -collateral makes the covenant model tractable and ensures that every -shared collateral output is created by a v5 ProRegTx whose consent -digest committed every participant. +scope for the existing UTXO format). Constraining shared-collateral mode +to internal collateral makes the covenant model tractable and ensures +that every shared collateral output is created by a v4 ProRegTx with +`isSharedCollateral == true` whose consent digest committed every +participant. ### Immutable share parameters @@ -1306,8 +1415,9 @@ and block-validation hooks are made explicit. ### Same-block dissolution forbidden -A `ProDisTx` is invalid in the same block as the v5 ProRegTx that -creates the shared collateral output it would spend. Dissolution +A `ProDisTx` is invalid in the same block as the v4 ProRegTx with +`isSharedCollateral == true` that creates the shared collateral output it +would spend. Dissolution operates against the deterministic masternode state at the parent block, so allowing same-block dissolution would require either a mid-block speculative state machine or a duplicated validation path @@ -1349,44 +1459,61 @@ Implementations SHOULD include at minimum the following tests. ### Registration -1. A v5 ProRegTx with two shares whose amounts sum to 1000 DASH, both - shares signed correctly, is valid. -2. A v5 ProRegTx with eight shares summing to 1000 DASH, all signed, is - valid. -3. A v5 ProRegTx with `nOperatorReward > 10000` is invalid. -4. A v5 ProRegTx with one share is invalid. -5. A v5 ProRegTx with nine shares is invalid. -6. A v5 ProRegTx whose share amounts sum to 999.99 DASH is invalid. -7. A v5 ProRegTx whose share summation overflows is invalid. -8. A v5 ProRegTx with any individual share amount outside `MoneyRange` or - greater than `GetMnType(nType).collat_amount` is invalid. -9. A v5 ProRegTx with a duplicate participant owner key is invalid. -10. A v5 ProRegTx with a duplicate refund script is invalid. -11. A v5 ProRegTx with duplicate effective reward scripts is invalid. -12. A v5 ProRegTx with a refund script paying to a participant owner key - is invalid. -13. A v5 ProRegTx whose collateral output uses a P2PKH script (not - `SHARED_COLLATERAL_SCRIPT`) is invalid. -14. A v5 ProRegTx whose `vchSig` is non-empty is invalid. -15. A v5 ProRegTx whose `joinSig` for share `i` was produced under a - different `outputsHash` is invalid. -16. A v5 ProRegTx whose `joinSig` for share `i` was produced under different - input sequences is invalid. -17. A v5 ProRegTx whose `joinSig` for share `i` was produced under a - different penalty value is invalid. +1. A v4 ProRegTx with `isSharedCollateral == true` and two shares whose + amounts sum to 1000 DASH, both shares signed correctly, is valid. +2. A v4 ProRegTx with `isSharedCollateral == true` and eight shares summing + to 1000 DASH, all signed, is valid. +3. A v4 ProRegTx with `isSharedCollateral == true` and `nOperatorReward > + 10000` is invalid. +4. A v4 ProRegTx with `isSharedCollateral == true` and one share is invalid. +5. A v4 ProRegTx with `isSharedCollateral == true` and nine shares is + invalid. +6. A v4 ProRegTx with `isSharedCollateral == true` whose share amounts sum + to 999.99 DASH is invalid. +7. A v4 ProRegTx with `isSharedCollateral == true` whose share summation + overflows is invalid. +8. A v4 ProRegTx with `isSharedCollateral == true` and any individual share + amount outside `MoneyRange` or greater than `GetMnType(nType).collat_amount` + is invalid. +9. A v4 ProRegTx with `isSharedCollateral == true` and a duplicate + participant owner key is invalid. +10. A v4 ProRegTx with `isSharedCollateral == true` and a duplicate refund + script is invalid. +11. A v4 ProRegTx with `isSharedCollateral == true` and duplicate effective + reward scripts is invalid. +12. A v4 ProRegTx with `isSharedCollateral == true` and a refund script + paying to a participant owner key is invalid. +13. A v4 ProRegTx with `isSharedCollateral == true` whose collateral output + uses a P2PKH script (not `SHARED_COLLATERAL_SCRIPT`) is invalid. +14. A v4 ProRegTx with `isSharedCollateral == true` whose `vchSig` is + non-empty is invalid. +15. A v4 ProRegTx with `isSharedCollateral == true` whose `joinSig` for + share `i` was produced under a different `outputsHash` is invalid. +16. A v4 ProRegTx with `isSharedCollateral == true` whose `joinSig` for + share `i` was produced under different input sequences is invalid. +17. A v4 ProRegTx with `isSharedCollateral == true` whose `joinSig` for + share `i` was produced under a different penalty value is invalid. +18. A v4 ProRegTx with `isSharedCollateral == true` whose `joinSig` for + share `i` was produced under `isSharedCollateral == false` is invalid; + the consent digest commits to the discriminator. +19. A v4 ProRegTx with `isSharedCollateral` set to a value other than `0x00` + or `0x01` is invalid. +20. A v4 ProRegTx with `isSharedCollateral == false` continues to be + validated by DIP-0026 unchanged and is unaffected by the rules in this + section. ### Reward Splitting -1. A v5 masternode with shares `[500, 500]` DASH receives two coinbase - outputs of equal value to each `rewardScript`. -2. A v5 masternode with shares `[300, 300, 400]` DASH receives three - coinbase outputs proportional to the shares, with rounding rules per - `DistributeByWeight`. -3. A v5 masternode with `nOperatorReward = 1000` subtracts the operator - amount first, then splits the remainder by share. +1. A shared-collateral masternode with shares `[500, 500]` DASH receives + two coinbase outputs of equal value to each `rewardScript`. +2. A shared-collateral masternode with shares `[300, 300, 400]` DASH + receives three coinbase outputs proportional to the shares, with + rounding rules per `DistributeByWeight`. +3. A shared-collateral masternode with `nOperatorReward = 1000` subtracts + the operator amount first, then splits the remainder by share. 4. A coinbase missing any expected per-share output is invalid. -5. A coinbase with an extra unexpected output for a v5 masternode is - invalid. +5. A coinbase with an extra unexpected output for a shared-collateral + masternode is invalid. ### Updates @@ -1421,7 +1548,8 @@ Implementations SHOULD include at minimum the following tests. `scriptOperatorPayout`, `netInfo`, and Platform identifiers and leaves the masternode PoSe-banned until the new operator submits a valid `ProUpServTx`. -18. A `ProUpRegTx` (type `3`) targeting a v5 masternode is invalid. +18. A `ProUpRegTx` (type `3`) targeting a shared-collateral masternode + (`state.isSharedCollateral == true`) is invalid. ### Dissolution @@ -1461,23 +1589,27 @@ Implementations SHOULD include at minimum the following tests. transaction `outputsHash` is invalid; the signature check is not reached. 17. A normal transaction (`nVersion < 3` or `nType == 0`) that spends - the collateral outpoint of a registered v5 masternode, including a - PoSe-banned masternode that remains registered, is rejected by mempool - and by block validation. + the collateral outpoint of a registered shared-collateral masternode, + including a PoSe-banned masternode that remains registered, is rejected + by mempool and by block validation. 18. A non-dissolution special transaction whose input spends the - collateral outpoint of a registered v5 masternode is rejected. + collateral outpoint of a registered shared-collateral masternode is + rejected. 19. A normal (non-`ProDisTx`) transaction later in the same block as a - v5 ProRegTx that spends the just-created shared collateral output - is rejected at block connection. + v4 ProRegTx with `isSharedCollateral == true` that spends the + just-created shared collateral output is rejected at block connection. 20. A unilateral `ProDisTx` for the same `proTxHash` in the same block - as the v5 ProRegTx that creates its shared collateral output is - invalid, regardless of ordering within the block and regardless of - whether every other covenant rule holds. + as the v4 ProRegTx with `isSharedCollateral == true` that creates + its shared collateral output is invalid, regardless of ordering + within the block and regardless of whether every other covenant + rule holds. 21. A unanimous `ProDisTx` for the same `proTxHash` in the same block - as the v5 ProRegTx that creates its shared collateral output is - invalid on the same basis as the unilateral case. + as the v4 ProRegTx with `isSharedCollateral == true` that creates + its shared collateral output is invalid on the same basis as the + unilateral case. 22. A `ProDisTx` (unilateral or unanimous) that spends the shared - collateral outpoint of a v5 masternode whose v5 ProRegTx was + collateral outpoint of a shared-collateral masternode whose + registering v4 ProRegTx with `isSharedCollateral == true` was confirmed in a strictly earlier block, and that otherwise satisfies every covenant rule, is valid; the masternode is removed when this block is connected. @@ -1490,12 +1622,13 @@ Implementations SHOULD include at minimum the following tests. ### Reorg -1. Disconnecting a block containing a v5 ProRegTx fully removes the - masternode entry, including all share state. +1. Disconnecting a block containing a v4 ProRegTx with + `isSharedCollateral == true` fully removes the masternode entry, including + all share state. 2. Disconnecting a block containing a `ProUpShareTx` restores the prior `rewardScript`. -3. Disconnecting a block containing a `ProDisTx` restores the v5 - masternode entry with its full share state. +3. Disconnecting a block containing a `ProDisTx` restores the + shared-collateral masternode entry with its full share state. 4. Disconnecting and reconnecting a chain segment that contains a `ProUpSharedRegTx` followed later by a `ProDisTx` in the original chain order produces the same final state. @@ -1507,14 +1640,14 @@ These notes are non-normative guidance for implementers. * The deterministic masternode state-diff bitfield must reserve a new bit for the share vector. Implementations should follow the bit-allocation conventions already used in `CDeterministicMNStateDiff` so that - pre-v5 snapshots continue to round-trip exactly. + non-shared snapshots continue to round-trip exactly. * `CheckSpecialTx` should treat shared collateral outputs as a separate spend-rejection rule rather than as part of the per-payload checks; the spend rejection applies to *every* transaction in mempool and block contexts, not only to special transactions. * The same-block index of new shared collateral outputs introduced by - v5 ProRegTx earlier in the block can be implemented as a small - `std::unordered_set` (or + v4 ProRegTx with `isSharedCollateral == true` earlier in the block can + be implemented as a small `std::unordered_set` (or `std::unordered_map` if the diagnostic value is wanted) that is populated as block transactions are validated and consulted by every later transaction's input scan. @@ -1522,25 +1655,28 @@ These notes are non-normative guidance for implementers. rejected, including by `ProDisTx`; same-block dissolution is not a legal path. * Coinbase construction should reuse the existing payout-pipeline hook - added by DIP-0026 (PR 184) and append one output per v5 share with the - computed amount and target script. + added by DIP-0026 (PR 184) and append one output per share of every + scheduled shared-collateral masternode with the computed amount and + target script. * The wallet RPC surface should expose: a coordinator-style flow that - builds an unsigned v5 ProRegTx given each participant's funding inputs - and share parameters; per-participant `joinSig` production over the - consent digest; and a `dissolvemasternode` RPC that builds a - `ProDisTx`, computes the signed dissolution digest, and either - collects the actor's signature (unilateral) or the full set of - signatures (unanimous). -* RPC output for `protx info` for a v5 masternode should expose the - share table, penalty parameters, and the canonical - `SHARED_COLLATERAL_SCRIPT` template for diagnostic purposes. + builds an unsigned v4 ProRegTx with `isSharedCollateral == true` given + each participant's funding inputs and share parameters; per-participant + `joinSig` production over the consent digest; and a `dissolvemasternode` + RPC that builds a `ProDisTx`, computes the signed dissolution digest, + and either collects the actor's signature (unilateral) or the full set + of signatures (unanimous). +* RPC output for `protx info` for a shared-collateral masternode should + expose `isSharedCollateral`, the share table, penalty parameters, and + the canonical `SHARED_COLLATERAL_SCRIPT` template for diagnostic + purposes. ## Security Considerations ### Trust model -A v5 masternode requires only that consensus is honest. No participant -trusts any other participant with custody of funds. Specifically: +A shared-collateral masternode (`state.isSharedCollateral == true`) +requires only that consensus is honest. No participant trusts any other +participant with custody of funds. Specifically: * Collateral cannot be spent except through a valid `ProDisTx` for the matching masternode, enforced by mempool and block consensus. @@ -1608,10 +1744,11 @@ rewards could approach dust. ### Light client guarantees Shared-collateral metadata is not committed to by SML hashes. SPV -clients cannot verify v5 share state without a full node or a -DIP-0004-extending future proof. SPV-level features that depend on -v5 metadata (filter matching of refund and reward scripts, for example) -require the full node serving the client to be honest about v5 state. +clients cannot verify shared-collateral share state without a full node +or a DIP-0004-extending future proof. SPV-level features that depend on +shared-collateral metadata (filter matching of refund and reward +scripts, for example) require the full node serving the client to be +honest about shared-collateral state. ### Replay across chains @@ -1619,18 +1756,35 @@ Both the registration consent digest and the dissolution authorization digest commit explicitly to `chainGenesisHash`, the genesis block hash of the network on which the transaction is being authorized. The consensus digest domain therefore separates networks with different genesis hashes: -a v5 registration or dissolution signed against one `chainGenesisHash` -cannot be replayed onto a network with a different genesis hash because -the digest verified by consensus on the target network would differ. +a shared-collateral registration or dissolution signed against one +`chainGenesisHash` cannot be replayed onto a network with a different +genesis hash because the digest verified by consensus on the target +network would differ. This does not provide replay separation between forks or deployments that share the same genesis block. Signers MUST verify the intended network / chain context before producing any `joinSig` or dissolution signature, and -implementations deploying v5 on same-genesis forks MUST add any additional -fork-specific replay protection they require. Relying on `proTxHash` alone -is insufficient for cross-chain replay protection because matching -transaction and state context across networks cannot be ruled out by -digest construction alone. +implementations deploying shared-collateral mode on same-genesis forks +MUST add any additional fork-specific replay protection they require. +Relying on `proTxHash` alone is insufficient for cross-chain replay +protection because matching transaction and state context across networks +cannot be ruled out by digest construction alone. + +### Mode-confusion across the discriminator + +Because the v4 ProRegTx payload version is shared between non-shared +(DIP-0026) and shared-collateral modes, an incorrectly implemented validator +that ignores `isSharedCollateral` could mis-parse one variant as the other. +The registration consent digest commits to `payload.isSharedCollateral`, +so a `joinSig` produced under one variant does not verify under the +other; the deterministic masternode state carries +`isSharedCollateral` as a first-class field so that all downstream +authorization, reward, and covenant rules gate on the same flag a +participant signed against. Implementations MUST treat +`isSharedCollateral` as a payload-shape selector that strictly +determines which fields are deserialized and which validation path +runs; falling back to a "best effort" decode that tries both variants +is invalid. ## Open Issues @@ -1643,7 +1797,8 @@ before activation: 2. **Activation deployment name.** Subject to release engineering confirmation that no candidate fork bit has already been consumed. 3. **State-diff bit value for the share vector.** The exact bit position for - the v5 share-vector full-replacement diff MUST be assigned before activation. + the shared-collateral share-vector full-replacement diff MUST be assigned + before activation. The following protocol-level extensions are out of scope for this DIP and may be addressed by future DIPs: From 51ec7ac93540d33f3a8e0136f64c71c6a080509f Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sun, 21 Jun 2026 18:31:29 -0500 Subject: [PATCH 18/18] docs: note v4 discriminator reservation dependency --- dip-decentralized-masternode-shares.md | 33 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/dip-decentralized-masternode-shares.md b/dip-decentralized-masternode-shares.md index 01d68ceb..0c327189 100644 --- a/dip-decentralized-masternode-shares.md +++ b/dip-decentralized-masternode-shares.md @@ -220,7 +220,11 @@ discriminator field, `isSharedCollateral`. This DIP introduces three new special transaction types, each with its own independent payload version, to update and dissolve shared masternodes. -The discriminator is a single `uint8_t` field in the v4 ProRegTx payload: +The discriminator is a single `uint8_t` field in the v4 ProRegTx payload, +serialized immediately after `nMode`. This wire position is a cross-DIP +activation dependency: DIP-0026 v4 MUST reserve the same discriminator byte for +all v4 ProRegTx payloads before this DIP can activate (see +[Open Issues](#open-issues)). | Name | Type | Encoding | | --- | --- | --- | @@ -229,11 +233,12 @@ The discriminator is a single `uint8_t` field in the v4 ProRegTx payload: The discriminator selects mutually exclusive variant fields in the same v4 payload: -* `isSharedCollateral == false`: the v4 payload follows the unchanged - DIP-0026 multi-payout layout (single `keyIDOwner`, `scriptPayout` / - `payouts`, no share table, no shared collateral output, no penalty - parameters, no `earlyPeriodBlocks`). All DIP-0026 v4 semantics, validation, - state, and authorization apply verbatim and are unaffected by this DIP. +* `isSharedCollateral == false`: after the shared discriminator byte reserved + by DIP-0026 v4, the remaining v4 payload follows the unchanged DIP-0026 + multi-payout layout (single `keyIDOwner`, `scriptPayout` / `payouts`, no + share table, no shared collateral output, no penalty parameters, no + `earlyPeriodBlocks`). All DIP-0026 v4 semantics, validation, state, and + authorization apply verbatim and are unaffected by this DIP. * `isSharedCollateral == true`: the v4 payload follows the shared-collateral layout defined in [Shared Registration (ProRegTx v4, shared mode)](#shared-registration-proregtx-v4-shared-mode) (no `keyIDOwner`, no @@ -1327,10 +1332,11 @@ can advance the wire version without altering what "shared" means; new shared-mode variants can advance the discriminator without consuming provider payload version numbers. -The discriminator also keeps the non-shared v4 layout unchanged: a v4 -ProRegTx with `isSharedCollateral == false` deserializes and validates -exactly as DIP-0026 specifies, and the variant fields specific to -shared mode appear only when the discriminator selects them. +The discriminator also keeps the non-shared v4 variant unchanged after the +shared discriminator byte that DIP-0026 v4 must reserve for all v4 ProRegTx +payloads: a v4 ProRegTx with `isSharedCollateral == false` deserializes and +validates exactly as DIP-0026 specifies after that byte, and the variant fields +specific to shared mode appear only when the discriminator selects them. ### Internal collateral only @@ -1796,7 +1802,12 @@ before activation: wallet implementers. 2. **Activation deployment name.** Subject to release engineering confirmation that no candidate fork bit has already been consumed. -3. **State-diff bit value for the share vector.** The exact bit position for +3. **DIP-0026 v4 discriminator reservation.** DIP-0026 v4 MUST reserve the + `isSharedCollateral` discriminator byte immediately after `nMode` for every + v4 ProRegTx payload, including non-shared multi-payout registrations, before + this DIP can activate. Without that reservation, shared and non-shared v4 + parsers would frame the same bytes differently. +4. **State-diff bit value for the share vector.** The exact bit position for the shared-collateral share-vector full-replacement diff MUST be assigned before activation.