From 2c315fcb4926350d829be1bd9937c6a256479a32 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 17 Jan 2026 01:50:35 -0600 Subject: [PATCH 01/13] feat: add DIP for Compact Quorum Proof Chains Introduces a new DIP specifying trustless verification of LLMQ public keys using ChainLocks and merkleRootQuorums. This enables light clients and the Platform SDK to cryptographically verify Platform quorum public keys without trusting external parties. Key features: - Compact proofs (~1 KB typical) using chainlock-based verification - No header chains needed - verifies against chainlocked block's cbtx - New P2P messages (GETQUORUMPROOFCHAIN, QUORUMPROOFCHAIN) - gRPC API for Platform SDK integration Co-Authored-By: Claude Opus 4.5 --- dip-pasta-compact-quorum-proofs.md | 503 +++++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 dip-pasta-compact-quorum-proofs.md diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md new file mode 100644 index 00000000..13165848 --- /dev/null +++ b/dip-pasta-compact-quorum-proofs.md @@ -0,0 +1,503 @@ +
+  DIP: pasta-compact-quorum-proofs
+  Title: Compact Quorum Proof Chains for Trustless Platform Verification
+  Author(s): PastaPastaPasta
+  Special-Thanks:
+  Comments-Summary: No comments yet.
+  Status: Draft
+  Type: Standard
+  Created: 2026-01-17
+  License: MIT License
+
+ +## Table of Contents + +1. [Abstract](#abstract) +1. [Motivation](#motivation) +1. [Prior Work](#prior-work) +1. [Trusted Initial State](#trusted-initial-state) +1. [Quorum Proof Chain Data Structures](#quorum-proof-chain-data-structures) + 1. [ChainlockEntry](#chainlockentry) + 1. [QuorumCommitmentProof](#quorumcommitmentproof) + 1. [QuorumProofChainResponse](#quorumproofchainresponse) +1. [Verification Algorithm](#verification-algorithm) + 1. [Verifier State](#verifier-state) + 1. [ChainLock Verification](#chainlock-verification) + 1. [Quorum Commitment Verification](#quorum-commitment-verification) + 1. [Proof Processing Algorithm](#proof-processing-algorithm) +1. [Proof Construction](#proof-construction) + 1. [ChainLock Selection Strategy](#chainlock-selection-strategy) + 1. [Quorum Commitment Merkle Proof Construction](#quorum-commitment-merkle-proof-construction) +1. [P2P Messages](#p2p-messages) + 1. [GETQUORUMPROOFCHAIN](#getquorumproofchain) + 1. [QUORUMPROOFCHAIN](#quorumproofchain) +1. [gRPC API](#grpc-api) +1. [Proof Size Analysis](#proof-size-analysis) +1. [Security Considerations](#security-considerations) +1. [Backward Compatibility](#backward-compatibility) +1. [Reference Implementation](#reference-implementation) +1. [Copyright](#copyright) + +## Abstract + +This DIP defines Compact Quorum Proof Chains, a mechanism for trustlessly verifying LLMQ public keys using ChainLocks and the `merkleRootQuorums` field in coinbase transactions. This enables light clients and the Platform SDK to cryptographically verify Platform quorum public keys without trusting any external party. + +The key insight is that a ChainLock at height H proves block H, and that block's coinbase transaction contains a `merkleRootQuorums` covering ALL currently active quorums at that height. This allows verification of any active quorum's commitment using a single chainlocked block's data, resulting in compact proofs of approximately 1 KB in typical scenarios. + +## Motivation + +Platform proof verification currently requires two steps: + +1. **GroveDB Proof**: Verify data against a Merkle root (cryptographic) +2. **Tenderdash Signature**: Verify BLS signature from a Platform quorum (requires quorum public key) + +The quorum public key is currently obtained via trusted sources: + +| Method | Trust Model | +| ------ | ----------- | +| Dash Core RPC | Trust the node operator | +| TrustedHttpContextProvider | Trust centralized quorum servers | + +Both methods require trusting an external party, which undermines the trustless nature of the verification. + +This DIP enables verification of quorum public keys cryptographically using only: + +1. A hardcoded checkpoint (block hash + known quorum keys) embedded in the SDK +2. Proofs provided by any untrusted server (verified client-side) + +By leveraging the existing ChainLock infrastructure and `merkleRootQuorums` commitment in each block's coinbase, clients can build a cryptographic chain of trust from a known checkpoint to any currently active quorum. + +## Prior Work + +* [DIP-0002: Special Transactions](https://github.com/dashpay/dips/blob/master/dip-0002.md) +* [DIP-0004: Simplified Verification of Deterministic Masternode Lists](https://github.com/dashpay/dips/blob/master/dip-0004.md) +* [DIP-0006: Long-Living Masternode Quorums](https://github.com/dashpay/dips/blob/master/dip-0006.md) +* [DIP-0007: LLMQ Signing Requests / Sessions](https://github.com/dashpay/dips/blob/master/dip-0007.md) +* [DIP-0008: ChainLocks](https://github.com/dashpay/dips/blob/master/dip-0008.md) + +## Trusted Initial State + +Verification requires a trusted starting point embedded in client software. This checkpoint must contain: + +1. A block hash and height identifying a known-good block +2. The public keys of active ChainLock quorums at that height (identified by quorum hash and type) + +The specific serialization format of this checkpoint is an implementation detail left to client software. + +### Quorum Lifespans and Checkpoint Freshness + +The effectiveness of this verification scheme depends on overlapping quorum lifespans between the checkpoint and current chain tip. + +**Mainnet:** + +| Quorum Type | Purpose | DKG Interval | Active Count | Lifespan | +| ----------- | ------- | ------------ | ------------ | -------- | +| LLMQ_400_60 | ChainLocks | 288 blocks (~12 hours) | 4 | ~48 hours | +| LLMQ_100_67 | Platform | 24 blocks (~1 hour) | 24 | ~24 hours | + +**Testnet:** + +| Quorum Type | Purpose | DKG Interval | Active Count | Lifespan | +| ----------- | ------- | ------------ | ------------ | -------- | +| LLMQ_50_60 | ChainLocks | 288 blocks (~12 hours) | 4 | ~48 hours | +| LLMQ_25_67 | Platform | 24 blocks (~1 hour) | 24 | ~24 hours | + +With a checkpoint less than 36 hours old, at least one ChainLock quorum from the checkpoint will still be active, enabling direct verification with a single chainlock. Older checkpoints require bridging through intermediate chainlock quorums. + +## Quorum Proof Chain Data Structures + +### ChainlockEntry + +A ChainLock signature that proves a specific block is canonical. + +| Field | Type | Size | Description | +| ----- | ---- | ---- | ----------- | +| height | int32_t | 4 | Height of the chainlocked block | +| blockHash | uint256 | 32 | Hash of the chainlocked block | +| signature | BLSSig | 96 | Recovered threshold signature | +| signingQuorumHash | uint256 | 32 | Hash of the quorum that signed this chainlock | +| signingQuorumType | uint8_t | 1 | LLMQ type of the signing quorum | + +Total size: ~165 bytes + +**Note**: The signing quorum can be deterministically calculated using `SelectQuorumForSigning` given the height, but including it explicitly simplifies verification and enables the verifier to check if the signing quorum is known before attempting signature verification. + +### QuorumCommitmentProof + +A proof that a specific quorum commitment is included in a chainlocked block's `merkleRootQuorums`. + +| Field | Type | Size | Description | +| ----- | ---- | ---- | ----------- | +| commitment | CFinalCommitment | variable | The quorum final commitment (see [DIP-0006](https://github.com/dashpay/dips/blob/master/dip-0006.md)) | +| chainlockIndex | uint32_t | 4 | Index into the response's chainlocks array | +| quorumMerklePathLength | compactSize uint | 1-9 | Number of hashes in the merkle path | +| quorumMerklePath | uint256[] | 32 * length | Merkle path from commitment hash to `merkleRootQuorums` | +| coinbaseTx | CTransaction | variable | The coinbase transaction containing `merkleRootQuorums` | +| coinbaseMerkleProof | CPartialMerkleTree | variable | Merkle proof that coinbase is in the block | + +The `CFinalCommitment` structure is defined in [DIP-0006](https://github.com/dashpay/dips/blob/master/dip-0006.md) and contains the `quorumPublicKey` that this proof ultimately verifies. + +Estimated size: ~700-900 bytes (varies by quorum size and tree depth) + +### QuorumProofChainResponse + +The complete response containing all data needed to verify a target quorum. + +| Field | Type | Size | Description | +| ----- | ---- | ---- | ----------- | +| headerCount | compactSize uint | 1-9 | Number of block headers | +| headers | CBlockHeader[] | 80 * count | Block headers for each chainlock | +| chainlockCount | compactSize uint | 1-9 | Number of chainlock entries | +| chainlocks | ChainlockEntry[] | variable | ChainLock signatures in verification order | +| quorumProofCount | compactSize uint | 1-9 | Number of quorum proofs | +| quorumProofs | QuorumCommitmentProof[] | variable | Quorum commitment proofs | + +Headers and chainlocks are paired by index: `headers[i]` corresponds to `chainlocks[i]`. + +## Verification Algorithm + +### Verifier State + +The verifier maintains the following state: + +```text +H_verified: uint32 // Highest height cryptographically confirmed +Q_known: Map<(uint256, uint8), BLSPubKey> // Known quorum public keys + // Key: (quorumHash, quorumType) + // Value: quorumPublicKey +``` + +Initial state is populated from the checkpoint: + +* `H_verified = checkpoint.height` +* `Q_known = checkpoint.chainlockQuorums ∪ checkpoint.platformQuorums` + +### ChainLock Verification + +To verify a ChainLock against a known quorum: + +1. Verify the signing quorum is in `Q_known` +2. Retrieve the quorum's public key from `Q_known` +3. Calculate the message hash: + `msgHash = SHA256(llmqType, quorumHash, SHA256(height), blockHash)` +4. Verify the BLS signature against the quorum public key and message hash +5. Verify that `hash(header) == blockHash` + +If verification succeeds, update `H_verified = max(H_verified, chainlock.height)`. + +### Quorum Commitment Verification + +To verify a quorum commitment proof: + +1. Verify `chainlockIndex` references a chainlock with height <= `H_verified` +2. Retrieve the corresponding coinbase transaction and block header +3. Verify the coinbase merkle proof: + * The `coinbaseMerkleProof` must prove that `coinbaseTx` is in the block + * The block's merkle root must match `header.hashMerkleRoot` +4. Extract `merkleRootQuorums` from the coinbase transaction's extra payload +5. Calculate `commitmentHash = SHA256(serialize(commitment))` +6. Verify the quorum merkle path: + * Compute the merkle root from `commitmentHash` and `quorumMerklePath` + * The computed root must match `merkleRootQuorums` +7. Verify the commitment's `quorumSig`: + * This threshold signature proves the quorum members agreed on this public key + * Verification uses the aggregated public keys from the commitment's `signers` bitvector + +If verification succeeds, add the quorum to `Q_known`: +`Q_known[(commitment.quorumHash, commitment.llmqType)] = commitment.quorumPublicKey` + +### Proof Processing Algorithm + +```text +FUNCTION verify_quorum_proof(checkpoint, proof, targetQuorum) -> Result: + + // Initialize state from checkpoint + H_verified = checkpoint.height + Q_known = checkpoint.chainlockQuorums ∪ checkpoint.platformQuorums + + // Process proof iteratively until no more progress + LOOP: + made_progress = false + + // 1. Process chainlocks that can now be verified + FOR i, CL IN enumerate(proof.chainlocks): + IF CL.height <= H_verified: + CONTINUE // Already verified + + IF NOT Q_known.contains((CL.signingQuorumHash, CL.signingQuorumType)): + CONTINUE // Cannot verify yet + + // Verify chainlock signature + quorumPubKey = Q_known[(CL.signingQuorumHash, CL.signingQuorumType)] + msgHash = SHA256(CL.signingQuorumType, CL.signingQuorumHash, + SHA256(CL.height), CL.blockHash) + VERIFY_BLS(CL.signature, quorumPubKey, msgHash)? + + // Verify header matches chainlock + header = proof.headers[i] + ASSERT(hash(header) == CL.blockHash) + + // Extend verified horizon + H_verified = CL.height + made_progress = true + + // 2. Learn quorum commitments from verified blocks + FOR QP IN proof.quorumProofs: + clHeight = proof.chainlocks[QP.chainlockIndex].height + IF clHeight > H_verified: + CONTINUE // Chainlock not yet verified + + key = (QP.commitment.quorumHash, QP.commitment.llmqType) + IF Q_known.contains(key): + CONTINUE // Already known + + // Verify coinbase is in the chainlocked block + VERIFY_COINBASE_MERKLE_PROOF(QP.coinbaseTx, QP.coinbaseMerkleProof, + proof.headers[QP.chainlockIndex])? + + // Verify commitment is in merkleRootQuorums + merkleRootQuorums = extract_merkle_root_quorums(QP.coinbaseTx) + VERIFY_QUORUM_MERKLE_PATH(QP.commitment, QP.quorumMerklePath, + merkleRootQuorums)? + + // Verify commitment signature (proves DKG validity) + VERIFY_COMMITMENT_SIGNATURE(QP.commitment)? + + // Add to known quorums + Q_known[key] = QP.commitment.quorumPublicKey + made_progress = true + + IF NOT made_progress: + BREAK + + // 3. Return target quorum if found + targetKey = (targetQuorum.quorumHash, targetQuorum.quorumType) + RETURN Q_known.get(targetKey).ok_or(Error::InsufficientProof) +``` + +## Proof Construction + +This section describes how a node constructs proofs for requesting clients. + +### ChainLock Selection Strategy + +The goal is to find the shortest chain of chainlocks from the checkpoint to the target quorum. + +#### Case 1: Checkpoint quorum overlap exists + +When the checkpoint is less than approximately 48 hours old: + +1. Identify which checkpoint chainlock quorums are still active +2. Find the most recent chainlock signed by any of these quorums +3. This single chainlock can prove all currently active quorums + +#### Case 2: No overlap + +When the checkpoint is more than approximately 48 hours old: + +1. Identify the checkpoint chainlock quorum with the longest remaining lifespan at checkpoint time +2. Find the most recent chainlock signed by that quorum +3. Learn new chainlock quorums from that block's `merkleRootQuorums` +4. Repeat with newly learned quorums until reaching a chainlock whose block contains the target quorum + +### Quorum Commitment Merkle Proof Construction + +The `merkleRootQuorums` in each coinbase is calculated as follows (per [DIP-0004](https://github.com/dashpay/dips/blob/master/dip-0004.md)): + +1. Collect all final commitments from all active LLMQ sets at the block height +2. Calculate `hash = SHA256(serialize(commitment))` for each commitment +3. Sort hashes in ascending order +4. Calculate merkle root from the sorted list + +To construct a proof for a specific commitment: + +1. Retrieve all active commitments at the chainlock height +2. Compute all commitment hashes and sort them +3. Find the index of the target commitment's hash +4. Construct the merkle path from that index to the root + +## P2P Messages + +### GETQUORUMPROOFCHAIN + +Request a quorum proof chain from a peer. + +| Field | Type | Size | Description | +| ----- | ---- | ---- | ----------- | +| checkpointBlockHash | uint256 | 32 | Block hash of the client's checkpoint | +| checkpointHeight | uint32_t | 4 | Height of the checkpoint block | +| checkpointQuorumCount | compactSize uint | 1-9 | Number of known chainlock quorums | +| checkpointQuorums | QuorumEntry[] | variable | Known chainlock quorum entries from checkpoint | +| targetQuorumHash | uint256 | 32 | Hash of the target quorum to prove | +| targetQuorumType | uint8_t | 1 | LLMQ type of the target quorum | + +### QUORUMPROOFCHAIN + +Response containing the proof chain. + +The response uses the `QuorumProofChainResponse` structure defined in [Quorum Proof Chain Data Structures](#quorum-proof-chain-data-structures). + +| Field | Type | Size | Description | +| ----- | ---- | ---- | ----------- | +| response | QuorumProofChainResponse | variable | The complete proof chain | + +## gRPC API + +For Platform SDK integration, the following gRPC endpoint is defined: + +```protobuf +service Core { + rpc GetQuorumProofChain(GetQuorumProofChainRequest) + returns (GetQuorumProofChainResponse); +} + +message QuorumEntry { + bytes quorum_hash = 1; // 32 bytes + uint32 quorum_type = 2; + bytes quorum_public_key = 3; // 48 bytes +} + +message GetQuorumProofChainRequest { + bytes checkpoint_block_hash = 1; // 32 bytes + uint32 checkpoint_height = 2; + repeated QuorumEntry checkpoint_chainlock_quorums = 3; + bytes target_quorum_hash = 4; // 32 bytes + uint32 target_quorum_type = 5; +} + +message ChainlockEntry { + int32 height = 1; + bytes block_hash = 2; // 32 bytes + bytes signature = 3; // 96 bytes + bytes signing_quorum_hash = 4; // 32 bytes + uint32 signing_quorum_type = 5; +} + +message QuorumCommitmentProof { + bytes commitment = 1; // Serialized CFinalCommitment + uint32 chainlock_index = 2; + repeated bytes quorum_merkle_path = 3; // Each 32 bytes + bytes coinbase_tx = 4; // Serialized transaction + bytes coinbase_merkle_proof = 5; // Serialized CPartialMerkleTree +} + +message GetQuorumProofChainResponse { + repeated bytes headers = 1; // Each 80 bytes + repeated ChainlockEntry chainlocks = 2; + repeated QuorumCommitmentProof quorum_proofs = 3; +} +``` + +## Proof Size Analysis + +### Component Sizes + +| Component | Size | +| --------- | ---- | +| Block header | 80 bytes | +| ChainlockEntry | ~165 bytes | +| QuorumCommitmentProof | ~700-900 bytes | + +### Per-Chainlock Overhead + +Each chainlock in the proof requires: + +* 1 ChainlockEntry: ~165 bytes +* 1 Block header: 80 bytes +* **Subtotal: ~245 bytes** + +### Scenarios + +#### Fresh Checkpoint + +When the checkpoint is less than 36 hours old, a checkpoint chainlock quorum is still active. A single chainlock reaches the tip. + +| Component | Count | Size Each | Total | +| --------- | ----- | --------- | ----- | +| Chainlock + header | 1 | 245 B | 245 B | +| Target quorum proof | 1 | 800 B | 800 B | +| **Total** | | | **~1 KB** | + +#### Stale Checkpoint + +When the checkpoint is 2-4 days old, checkpoint quorums have expired. Need 1-2 bridging chainlock quorums. + +| Component | Count | Size Each | Total | +| --------- | ----- | --------- | ----- | +| Chainlocks + headers | 2-3 | 245 B | 490-735 B | +| Bridge CL quorum proofs | 1-2 | 800 B | 800-1,600 B | +| Target quorum proof | 1 | 800 B | 800 B | +| **Total** | | | **~2-3 KB** | + +#### Very Old Checkpoint + +When the checkpoint is 30+ days old, approximately 15 bridging chainlock quorums are needed (one per ~2 day interval). + +| Component | Count | Size Each | Total | +| --------- | ----- | --------- | ----- | +| Chainlocks + headers | ~15 | 245 B | ~3,700 B | +| Bridge CL quorum proofs | ~15 | 800 B | ~12,000 B | +| Target quorum proof | 1 | 800 B | 800 B | +| **Total** | | | **~16 KB** | + +### Summary + +| Checkpoint Age | Proof Size | +| -------------- | ---------- | +| < 36 hours | ~1 KB | +| 2-4 days | ~2-3 KB | +| 30+ days | ~16 KB | + +Checkpoints should be updated with each SDK release (monthly recommended) to maintain minimal proof sizes. + +## Security Considerations + +### Trust Assumptions + +| Assumption | Basis | +| ---------- | ----- | +| Checkpoint is correct | Code review, reproducible builds, distribution via official channels | +| BLS signatures unforgeable | Cryptographic hardness of BLS scheme | +| Merkle proofs sound | Collision resistance of SHA-256 | +| ChainLocks are secure | DIP-0008 security analysis | + +### Attack Resistance + +| Attack | Why It Fails | +| ------ | ------------ | +| Forge chainlock | Requires 240 of 400 masternode operator keys (60% threshold) | +| Forge quorum commitment | `quorumSig` is threshold signature requiring quorum threshold | +| Wrong block hash | Header hash must match chainlock's `blockHash` | +| Tampered coinbase | Coinbase merkle proof verification fails | +| Tampered commitment | Commitment merkle proof verification fails | +| Omit proof data | Verification fails, client retries or tries different server | + +### Failure Modes + +| Failure | Cause | Resolution | +| ------- | ----- | ---------- | +| No verifiable chainlock | Server omitted required data | Retry or try different server | +| Verification timeout | Large proof on slow device | Increase timeout or use fresher checkpoint | +| Checkpoint too old | SDK not updated in 30+ days | Ship new checkpoint in SDK update | + +## Backward Compatibility + +This DIP introduces new P2P messages (`GETQUORUMPROOFCHAIN`, `QUORUMPROOFCHAIN`) and gRPC endpoints that do not affect existing functionality. Nodes that do not implement this DIP will not respond to these messages. + +Clients implementing trustless verification should fall back to trusted verification methods if: + +1. No peers support the new messages +2. Proof verification fails repeatedly +3. The checkpoint is too old to construct a valid proof chain + +## Reference Implementation + +Reference implementation will be provided in: + +* Dash Core: Chainlock indexing and proof generation RPCs +* Platform: `rs-trustless-quorum-verifier` crate for Rust verification +* DAPI: gRPC endpoint wrapping Core RPCs + +## Copyright + +Copyright (c) 2026 Dash Core Group, Inc. [Licensed under the MIT License](https://opensource.org/licenses/MIT) From e378afa6502b0d464c6950f37b5066cc65df0fe8 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 17 Jan 2026 11:02:22 -0600 Subject: [PATCH 02/13] docs: add Dash Core RPC methods, clarify P2P vs RPC communication Address review feedback: - Add Dash Core RPC section (getquorumproofchain, verifyquorumproofchain) - Clarify that Platform/DAPI uses RPC to local Core node - Note that P2P messages are for SPV light clients - Add protocol version requirement note for P2P messages Co-Authored-By: Claude Opus 4.5 --- dip-pasta-compact-quorum-proofs.md | 70 +++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index 13165848..1f8870fa 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -28,8 +28,11 @@ 1. [Proof Construction](#proof-construction) 1. [ChainLock Selection Strategy](#chainlock-selection-strategy) 1. [Quorum Commitment Merkle Proof Construction](#quorum-commitment-merkle-proof-construction) +1. [Dash Core RPC Methods](#dash-core-rpc-methods) + 1. [getquorumproofchain](#getquorumproofchain) + 1. [verifyquorumproofchain](#verifyquorumproofchain) 1. [P2P Messages](#p2p-messages) - 1. [GETQUORUMPROOFCHAIN](#getquorumproofchain) + 1. [GETQUORUMPROOFCHAIN](#getquorumproofchain-1) 1. [QUORUMPROOFCHAIN](#quorumproofchain) 1. [gRPC API](#grpc-api) 1. [Proof Size Analysis](#proof-size-analysis) @@ -316,8 +319,71 @@ To construct a proof for a specific commitment: 3. Find the index of the target commitment's hash 4. Construct the merkle path from that index to the root +## Dash Core RPC Methods + +Platform nodes communicate with their local Dash Core node via RPC, not P2P. The following RPC methods are added to Dash Core for proof generation and verification. + +### getquorumproofchain + +Generates a quorum proof chain from a checkpoint to a target quorum. + +**Arguments:** + +| # | Name | Type | Description | +| - | ---- | ---- | ----------- | +| 1 | checkpointBlockHash | string | Block hash of the checkpoint (hex) | +| 2 | checkpointHeight | number | Height of the checkpoint block | +| 3 | checkpointQuorums | array | Array of known chainlock quorum objects | +| 4 | targetQuorumHash | string | Hash of the target quorum to prove (hex) | +| 5 | targetQuorumType | number | LLMQ type of the target quorum | + +Each checkpoint quorum object contains: + +* `quorumHash` (string): Quorum identifier (hex) +* `quorumType` (number): LLMQ type +* `quorumPublicKey` (string): Quorum public key (hex) + +**Result:** + +Returns the `QuorumProofChainResponse` structure with headers, chainlocks, and quorum proofs encoded as hex strings. + +### verifyquorumproofchain + +Verifies a quorum proof chain and returns the target quorum's public key if valid. + +**Arguments:** + +| # | Name | Type | Description | +| - | ---- | ---- | ----------- | +| 1 | checkpointBlockHash | string | Block hash of the checkpoint (hex) | +| 2 | checkpointHeight | number | Height of the checkpoint block | +| 3 | checkpointQuorums | array | Array of known chainlock quorum objects | +| 4 | proof | object | The proof chain to verify | +| 5 | targetQuorumHash | string | Hash of the target quorum (hex) | +| 6 | targetQuorumType | number | LLMQ type of the target quorum | + +**Result:** + +```json +{ + "valid": true, + "quorumPublicKey": "hexstring" +} +``` + +Or on failure: + +```json +{ + "valid": false, + "error": "error message" +} +``` + ## P2P Messages +These messages enable SPV light clients to request quorum proofs directly from peers without requiring a local Dash Core node. Nodes supporting these messages must advertise protocol version >= XXXXXX (to be assigned). + ### GETQUORUMPROOFCHAIN Request a quorum proof chain from a peer. @@ -343,7 +409,7 @@ The response uses the `QuorumProofChainResponse` structure defined in [Quorum Pr ## gRPC API -For Platform SDK integration, the following gRPC endpoint is defined: +DAPI exposes quorum proof functionality to remote clients via gRPC. Internally, DAPI calls the Dash Core RPC methods described above on its local Core node. ```protobuf service Core { From fa0fc4ef093afb2874f03f41767dbaa87eb114d7 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 17 Jan 2026 11:29:31 -0600 Subject: [PATCH 03/13] docs: simplify RPC/P2P interfaces per review feedback Remove redundant fields from API - Core derives checkpoint height and active chainlock quorums from the checkpoint block hash. Co-Authored-By: Claude Opus 4.5 --- dip-pasta-compact-quorum-proofs.md | 39 +++++++----------------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index 1f8870fa..d11294d8 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -321,7 +321,7 @@ To construct a proof for a specific commitment: ## Dash Core RPC Methods -Platform nodes communicate with their local Dash Core node via RPC, not P2P. The following RPC methods are added to Dash Core for proof generation and verification. +Platform nodes communicate with their local Dash Core node via RPC, not P2P. The following RPC methods are added to Dash Core for proof generation and verification. Core derives the checkpoint height and active chainlock quorums from the checkpoint block hash. ### getquorumproofchain @@ -332,16 +332,8 @@ Generates a quorum proof chain from a checkpoint to a target quorum. | # | Name | Type | Description | | - | ---- | ---- | ----------- | | 1 | checkpointBlockHash | string | Block hash of the checkpoint (hex) | -| 2 | checkpointHeight | number | Height of the checkpoint block | -| 3 | checkpointQuorums | array | Array of known chainlock quorum objects | -| 4 | targetQuorumHash | string | Hash of the target quorum to prove (hex) | -| 5 | targetQuorumType | number | LLMQ type of the target quorum | - -Each checkpoint quorum object contains: - -* `quorumHash` (string): Quorum identifier (hex) -* `quorumType` (number): LLMQ type -* `quorumPublicKey` (string): Quorum public key (hex) +| 2 | targetQuorumHash | string | Hash of the target quorum to prove (hex) | +| 3 | targetQuorumType | number | LLMQ type of the target quorum | **Result:** @@ -356,11 +348,9 @@ Verifies a quorum proof chain and returns the target quorum's public key if vali | # | Name | Type | Description | | - | ---- | ---- | ----------- | | 1 | checkpointBlockHash | string | Block hash of the checkpoint (hex) | -| 2 | checkpointHeight | number | Height of the checkpoint block | -| 3 | checkpointQuorums | array | Array of known chainlock quorum objects | -| 4 | proof | object | The proof chain to verify | -| 5 | targetQuorumHash | string | Hash of the target quorum (hex) | -| 6 | targetQuorumType | number | LLMQ type of the target quorum | +| 2 | proof | object | The proof chain to verify | +| 3 | targetQuorumHash | string | Hash of the target quorum (hex) | +| 4 | targetQuorumType | number | LLMQ type of the target quorum | **Result:** @@ -386,14 +376,11 @@ These messages enable SPV light clients to request quorum proofs directly from p ### GETQUORUMPROOFCHAIN -Request a quorum proof chain from a peer. +Request a quorum proof chain from a peer. The serving node derives the checkpoint height and active chainlock quorums from the checkpoint block hash. | Field | Type | Size | Description | | ----- | ---- | ---- | ----------- | | checkpointBlockHash | uint256 | 32 | Block hash of the client's checkpoint | -| checkpointHeight | uint32_t | 4 | Height of the checkpoint block | -| checkpointQuorumCount | compactSize uint | 1-9 | Number of known chainlock quorums | -| checkpointQuorums | QuorumEntry[] | variable | Known chainlock quorum entries from checkpoint | | targetQuorumHash | uint256 | 32 | Hash of the target quorum to prove | | targetQuorumType | uint8_t | 1 | LLMQ type of the target quorum | @@ -417,18 +404,10 @@ service Core { returns (GetQuorumProofChainResponse); } -message QuorumEntry { - bytes quorum_hash = 1; // 32 bytes - uint32 quorum_type = 2; - bytes quorum_public_key = 3; // 48 bytes -} - message GetQuorumProofChainRequest { bytes checkpoint_block_hash = 1; // 32 bytes - uint32 checkpoint_height = 2; - repeated QuorumEntry checkpoint_chainlock_quorums = 3; - bytes target_quorum_hash = 4; // 32 bytes - uint32 target_quorum_type = 5; + bytes target_quorum_hash = 2; // 32 bytes + uint32 target_quorum_type = 3; } message ChainlockEntry { From b82325b54ba723378e5c80e4ae1d88ffae6f0fe6 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 8 Sep 2026 17:20:46 -0500 Subject: [PATCH 04/13] docs(dip): specify mining-only snapshot proof format --- dip-pasta-compact-quorum-proofs.md | 718 ++++++++++------------------- 1 file changed, 247 insertions(+), 471 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index d11294d8..f16c16e9 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -15,534 +15,310 @@ 1. [Abstract](#abstract) 1. [Motivation](#motivation) 1. [Prior Work](#prior-work) -1. [Trusted Initial State](#trusted-initial-state) -1. [Quorum Proof Chain Data Structures](#quorum-proof-chain-data-structures) - 1. [ChainlockEntry](#chainlockentry) - 1. [QuorumCommitmentProof](#quorumcommitmentproof) - 1. [QuorumProofChainResponse](#quorumproofchainresponse) -1. [Verification Algorithm](#verification-algorithm) - 1. [Verifier State](#verifier-state) - 1. [ChainLock Verification](#chainlock-verification) - 1. [Quorum Commitment Verification](#quorum-commitment-verification) - 1. [Proof Processing Algorithm](#proof-processing-algorithm) -1. [Proof Construction](#proof-construction) - 1. [ChainLock Selection Strategy](#chainlock-selection-strategy) - 1. [Quorum Commitment Merkle Proof Construction](#quorum-commitment-merkle-proof-construction) -1. [Dash Core RPC Methods](#dash-core-rpc-methods) - 1. [getquorumproofchain](#getquorumproofchain) - 1. [verifyquorumproofchain](#verifyquorumproofchain) -1. [P2P Messages](#p2p-messages) - 1. [GETQUORUMPROOFCHAIN](#getquorumproofchain-1) - 1. [QUORUMPROOFCHAIN](#quorumproofchain) -1. [gRPC API](#grpc-api) -1. [Proof Size Analysis](#proof-size-analysis) +1. [Trust Model](#trust-model) +1. [Wire Format](#wire-format) +1. [Verification](#verification) +1. [Construction and Serving](#construction-and-serving) +1. [SDK Integration](#sdk-integration) +1. [Size and Resource Limits](#size-and-resource-limits) 1. [Security Considerations](#security-considerations) -1. [Backward Compatibility](#backward-compatibility) -1. [Reference Implementation](#reference-implementation) +1. [Compatibility](#compatibility) 1. [Copyright](#copyright) ## Abstract -This DIP defines Compact Quorum Proof Chains, a mechanism for trustlessly verifying LLMQ public keys using ChainLocks and the `merkleRootQuorums` field in coinbase transactions. This enables light clients and the Platform SDK to cryptographically verify Platform quorum public keys without trusting any external party. +This proposal authenticates current Dash Core quorum keys and EvoNode records from +an application-supplied trusted snapshot. A relay provides ordinary ChainLock +certificates, quorum mining transactions, and Merkle paths. The SDK verifies them +locally before verifying Platform responses. Relays supply evidence and do not +supply trusted keys. No consensus change, zero-knowledge system, trusted setup, or +specialized proving hardware is required. -The key insight is that a ChainLock at height H proves block H, and that block's coinbase transaction contains a `merkleRootQuorums` covering ALL currently active quorums at that height. This allows verification of any active quorum's commitment using a single chainlocked block's data, resulting in compact proofs of approximately 1 KB in typical scenarios. +The format uses only mining-transaction handoffs (previously called route B). +When the mining block lacks a usable ChainLock, a later certificate authenticates +that block through consecutive X11 headers. There is no alternate quorum-root +handoff format. ## Motivation -Platform proof verification currently requires two steps: - -1. **GroveDB Proof**: Verify data against a Merkle root (cryptographic) -2. **Tenderdash Signature**: Verify BLS signature from a Platform quorum (requires quorum public key) - -The quorum public key is currently obtained via trusted sources: - -| Method | Trust Model | -| ------ | ----------- | -| Dash Core RPC | Trust the node operator | -| TrustedHttpContextProvider | Trust centralized quorum servers | - -Both methods require trusting an external party, which undermines the trustless nature of the verification. - -This DIP enables verification of quorum public keys cryptographically using only: - -1. A hardcoded checkpoint (block hash + known quorum keys) embedded in the SDK -2. Proofs provided by any untrusted server (verified client-side) - -By leveraging the existing ChainLock infrastructure and `merkleRootQuorums` commitment in each block's coinbase, clients can build a cryptographic chain of trust from a known checkpoint to any currently active quorum. +An SDK can distribute a small, fixed Core snapshot and network addresses with its +release, then acquire the evidence needed to authenticate newer Platform quorum +keys. The intended history window is three to twelve months. The proof and the +SDK verifier both contribute to download cost, so they must be measured together. ## Prior Work -* [DIP-0002: Special Transactions](https://github.com/dashpay/dips/blob/master/dip-0002.md) -* [DIP-0004: Simplified Verification of Deterministic Masternode Lists](https://github.com/dashpay/dips/blob/master/dip-0004.md) -* [DIP-0006: Long-Living Masternode Quorums](https://github.com/dashpay/dips/blob/master/dip-0006.md) -* [DIP-0007: LLMQ Signing Requests / Sessions](https://github.com/dashpay/dips/blob/master/dip-0007.md) -* [DIP-0008: ChainLocks](https://github.com/dashpay/dips/blob/master/dip-0008.md) - -## Trusted Initial State - -Verification requires a trusted starting point embedded in client software. This checkpoint must contain: - -1. A block hash and height identifying a known-good block -2. The public keys of active ChainLock quorums at that height (identified by quorum hash and type) - -The specific serialization format of this checkpoint is an implementation detail left to client software. - -### Quorum Lifespans and Checkpoint Freshness - -The effectiveness of this verification scheme depends on overlapping quorum lifespans between the checkpoint and current chain tip. - -**Mainnet:** - -| Quorum Type | Purpose | DKG Interval | Active Count | Lifespan | -| ----------- | ------- | ------------ | ------------ | -------- | -| LLMQ_400_60 | ChainLocks | 288 blocks (~12 hours) | 4 | ~48 hours | -| LLMQ_100_67 | Platform | 24 blocks (~1 hour) | 24 | ~24 hours | - -**Testnet:** - -| Quorum Type | Purpose | DKG Interval | Active Count | Lifespan | -| ----------- | ------- | ------------ | ------------ | -------- | -| LLMQ_50_60 | ChainLocks | 288 blocks (~12 hours) | 4 | ~48 hours | -| LLMQ_25_67 | Platform | 24 blocks (~1 hour) | 24 | ~24 hours | - -With a checkpoint less than 36 hours old, at least one ChainLock quorum from the checkpoint will still be active, enabling direct verification with a single chainlock. Older checkpoints require bridging through intermediate chainlock quorums. - -## Quorum Proof Chain Data Structures - -### ChainlockEntry - -A ChainLock signature that proves a specific block is canonical. - -| Field | Type | Size | Description | -| ----- | ---- | ---- | ----------- | -| height | int32_t | 4 | Height of the chainlocked block | -| blockHash | uint256 | 32 | Hash of the chainlocked block | -| signature | BLSSig | 96 | Recovered threshold signature | -| signingQuorumHash | uint256 | 32 | Hash of the quorum that signed this chainlock | -| signingQuorumType | uint8_t | 1 | LLMQ type of the signing quorum | - -Total size: ~165 bytes - -**Note**: The signing quorum can be deterministically calculated using `SelectQuorumForSigning` given the height, but including it explicitly simplifies verification and enables the verifier to check if the signing quorum is known before attempting signature verification. - -### QuorumCommitmentProof - -A proof that a specific quorum commitment is included in a chainlocked block's `merkleRootQuorums`. - -| Field | Type | Size | Description | -| ----- | ---- | ---- | ----------- | -| commitment | CFinalCommitment | variable | The quorum final commitment (see [DIP-0006](https://github.com/dashpay/dips/blob/master/dip-0006.md)) | -| chainlockIndex | uint32_t | 4 | Index into the response's chainlocks array | -| quorumMerklePathLength | compactSize uint | 1-9 | Number of hashes in the merkle path | -| quorumMerklePath | uint256[] | 32 * length | Merkle path from commitment hash to `merkleRootQuorums` | -| coinbaseTx | CTransaction | variable | The coinbase transaction containing `merkleRootQuorums` | -| coinbaseMerkleProof | CPartialMerkleTree | variable | Merkle proof that coinbase is in the block | +[DIP-0004](dip-0004.md) commits simplified masternode lists in coinbase transactions. +[DIP-0006](dip-0006.md) defines LLMQ commitments and +[DIP-0008](dip-0008.md) defines ChainLocks. -The `CFinalCommitment` structure is defined in [DIP-0006](https://github.com/dashpay/dips/blob/master/dip-0006.md) and contains the `quorumPublicKey` that this proof ultimately verifies. +The earlier version of this proposal carried intermediate coinbases and quorum +root openings. A mining transaction already commits the complete next quorum +commitment. Authenticating that transaction avoids the intermediate coinbase and +second tree opening. Only the final coinbase remains, because it supplies the +roots needed for current quorum and masternode record openings. -Estimated size: ~700-900 bytes (varies by quorum size and tree depth) +## Trust Model -### QuorumProofChainResponse +The application independently fixes a snapshot containing: -The complete response containing all data needed to verify a target quorum. +* Network (0 mainnet, 1 testnet). +* Core height and block hash. +* Simplified masternode-list Merkle root. +* Active quorum-list Merkle root. -| Field | Type | Size | Description | -| ----- | ---- | ---- | ----------- | -| headerCount | compactSize uint | 1-9 | Number of block headers | -| headers | CBlockHeader[] | 80 * count | Block headers for each chainlock | -| chainlockCount | compactSize uint | 1-9 | Number of chainlock entries | -| chainlocks | ChainlockEntry[] | variable | ChainLock signatures in verification order | -| quorumProofCount | compactSize uint | 1-9 | Number of quorum proofs | -| quorumProofs | QuorumCommitmentProof[] | variable | Quorum commitment proofs | +A response MUST exactly match this snapshot. A relay-provided snapshot MUST NOT +be promoted to trusted configuration merely because a proof is internally valid. +A successfully verified target can serve as the next session checkpoint. -Headers and chainlocks are paired by index: `headers[i]` corresponds to `chainlocks[i]`. +The design assumes historically authenticated ChainLock quorums do not sign false +certificates, including after leaving the active set. It proves a sequence of +statements by authenticated quorum keys. It does **not** independently reconstruct +DKG, full Core consensus, or the exact active signing-quorum selection for each +certificate. Membership of a key is not proof of its current signing authority. +These are deliberate constraints of this compact certificate trust model. -## Verification Algorithm +## Wire Format -### Verifier State +All proof framing integers are unsigned little-endian fixed-width integers. +Hashes are 32 bytes in Core serialization order, reversed from RPC display hex. +Nested transactions and commitments use their existing canonical Core consensus +serialization, including CompactSize where consensus requires it. There is no +protobuf or general-purpose object encoding on the proof wire. -The verifier maintains the following state: +A `blob` is `length:u32 || bytes[length]`. A `path` is: ```text -H_verified: uint32 // Highest height cryptographically confirmed -Q_known: Map<(uint256, uint8), BLSPubKey> // Known quorum public keys - // Key: (quorumHash, quorumType) - // Value: quorumPublicKey +index:u32 | leaf_count:u32 | sibling_count:u8 | siblings[32]... ``` -Initial state is populated from the checkpoint: - -* `H_verified = checkpoint.height` -* `Q_known = checkpoint.chainlockQuorums ∪ checkpoint.platformQuorums` - -### ChainLock Verification - -To verify a ChainLock against a known quorum: - -1. Verify the signing quorum is in `Q_known` -2. Retrieve the quorum's public key from `Q_known` -3. Calculate the message hash: - `msgHash = SHA256(llmqType, quorumHash, SHA256(height), blockHash)` -4. Verify the BLS signature against the quorum public key and message hash -5. Verify that `hash(header) == blockHash` - -If verification succeeds, update `H_verified = max(H_verified, chainlock.height)`. - -### Quorum Commitment Verification - -To verify a quorum commitment proof: - -1. Verify `chainlockIndex` references a chainlock with height <= `H_verified` -2. Retrieve the corresponding coinbase transaction and block header -3. Verify the coinbase merkle proof: - * The `coinbaseMerkleProof` must prove that `coinbaseTx` is in the block - * The block's merkle root must match `header.hashMerkleRoot` -4. Extract `merkleRootQuorums` from the coinbase transaction's extra payload -5. Calculate `commitmentHash = SHA256(serialize(commitment))` -6. Verify the quorum merkle path: - * Compute the merkle root from `commitmentHash` and `quorumMerklePath` - * The computed root must match `merkleRootQuorums` -7. Verify the commitment's `quorumSig`: - * This threshold signature proves the quorum members agreed on this public key - * Verification uses the aggregated public keys from the commitment's `signers` bitvector - -If verification succeeds, add the quorum to `Q_known`: -`Q_known[(commitment.quorumHash, commitment.llmqType)] = commitment.quorumPublicKey` - -### Proof Processing Algorithm +A `certificate` is exactly 180 bytes: ```text -FUNCTION verify_quorum_proof(checkpoint, proof, targetQuorum) -> Result: - - // Initialize state from checkpoint - H_verified = checkpoint.height - Q_known = checkpoint.chainlockQuorums ∪ checkpoint.platformQuorums - - // Process proof iteratively until no more progress - LOOP: - made_progress = false - - // 1. Process chainlocks that can now be verified - FOR i, CL IN enumerate(proof.chainlocks): - IF CL.height <= H_verified: - CONTINUE // Already verified - - IF NOT Q_known.contains((CL.signingQuorumHash, CL.signingQuorumType)): - CONTINUE // Cannot verify yet - - // Verify chainlock signature - quorumPubKey = Q_known[(CL.signingQuorumHash, CL.signingQuorumType)] - msgHash = SHA256(CL.signingQuorumType, CL.signingQuorumHash, - SHA256(CL.height), CL.blockHash) - VERIFY_BLS(CL.signature, quorumPubKey, msgHash)? - - // Verify header matches chainlock - header = proof.headers[i] - ASSERT(hash(header) == CL.blockHash) - - // Extend verified horizon - H_verified = CL.height - made_progress = true - - // 2. Learn quorum commitments from verified blocks - FOR QP IN proof.quorumProofs: - clHeight = proof.chainlocks[QP.chainlockIndex].height - IF clHeight > H_verified: - CONTINUE // Chainlock not yet verified - - key = (QP.commitment.quorumHash, QP.commitment.llmqType) - IF Q_known.contains(key): - CONTINUE // Already known - - // Verify coinbase is in the chainlocked block - VERIFY_COINBASE_MERKLE_PROOF(QP.coinbaseTx, QP.coinbaseMerkleProof, - proof.headers[QP.chainlockIndex])? - - // Verify commitment is in merkleRootQuorums - merkleRootQuorums = extract_merkle_root_quorums(QP.coinbaseTx) - VERIFY_QUORUM_MERKLE_PATH(QP.commitment, QP.quorumMerklePath, - merkleRootQuorums)? - - // Verify commitment signature (proves DKG validity) - VERIFY_COMMITMENT_SIGNATURE(QP.commitment)? - - // Add to known quorums - Q_known[key] = QP.commitment.quorumPublicKey - made_progress = true - - IF NOT made_progress: - BREAK - - // 3. Return target quorum if found - targetKey = (targetQuorum.quorumHash, targetQuorum.quorumType) - RETURN Q_known.get(targetKey).ok_or(Error::InsufficientProof) +height:u32 | core_block_header[80] | Basic_BLS_signature[96] ``` -## Proof Construction - -This section describes how a node constructs proofs for requesting clients. - -### ChainLock Selection Strategy - -The goal is to find the shortest chain of chainlocks from the checkpoint to the target quorum. - -#### Case 1: Checkpoint quorum overlap exists - -When the checkpoint is less than approximately 48 hours old: - -1. Identify which checkpoint chainlock quorums are still active -2. Find the most recent chainlock signed by any of these quorums -3. This single chainlock can prove all currently active quorums - -#### Case 2: No overlap - -When the checkpoint is more than approximately 48 hours old: - -1. Identify the checkpoint chainlock quorum with the longest remaining lifespan at checkpoint time -2. Find the most recent chainlock signed by that quorum -3. Learn new chainlock quorums from that block's `merkleRootQuorums` -4. Repeat with newly learned quorums until reaching a chainlock whose block contains the target quorum - -### Quorum Commitment Merkle Proof Construction - -The `merkleRootQuorums` in each coinbase is calculated as follows (per [DIP-0004](https://github.com/dashpay/dips/blob/master/dip-0004.md)): - -1. Collect all final commitments from all active LLMQ sets at the block height -2. Calculate `hash = SHA256(serialize(commitment))` for each commitment -3. Sort hashes in ascending order -4. Calculate merkle root from the sorted list - -To construct a proof for a specific commitment: - -1. Retrieve all active commitments at the chainlock height -2. Compute all commitment hashes and sort them -3. Find the index of the target commitment's hash -4. Construct the merkle path from that index to the root - -## Dash Core RPC Methods - -Platform nodes communicate with their local Dash Core node via RPC, not P2P. The following RPC methods are added to Dash Core for proof generation and verification. Core derives the checkpoint height and active chainlock quorums from the checkpoint block hash. +The main proof is: -### getquorumproofchain - -Generates a quorum proof chain from a checkpoint to a target quorum. - -**Arguments:** - -| # | Name | Type | Description | -| - | ---- | ---- | ----------- | -| 1 | checkpointBlockHash | string | Block hash of the checkpoint (hex) | -| 2 | targetQuorumHash | string | Hash of the target quorum to prove (hex) | -| 3 | targetQuorumType | number | LLMQ type of the target quorum | - -**Result:** - -Returns the `QuorumProofChainResponse` structure with headers, chainlocks, and quorum proofs encoded as hex strings. - -### verifyquorumproofchain - -Verifies a quorum proof chain and returns the target quorum's public key if valid. - -**Arguments:** - -| # | Name | Type | Description | -| - | ---- | ---- | ----------- | -| 1 | checkpointBlockHash | string | Block hash of the checkpoint (hex) | -| 2 | proof | object | The proof chain to verify | -| 3 | targetQuorumHash | string | Hash of the target quorum (hex) | -| 4 | targetQuorumType | number | LLMQ type of the target quorum | - -**Result:** - -```json -{ - "valid": true, - "quorumPublicKey": "hexstring" +```text +magic[8] = ASCII "DASHNC02" +snapshot { + network:u8 | height:u32 | block_hash[32] + masternode_root[32] | quorum_root[32] } -``` - -Or on failure: - -```json -{ - "valid": false, - "error": "error message" +seed_commitment:blob | seed_membership:path +handoff_count:u16 +handoffs[handoff_count] { + certificate + mining_transaction:blob | transaction_membership:path + ancestor_count:u16 | ancestor_headers[80]... } +final_certificate +final_coinbase:blob | coinbase_membership:path ``` -## P2P Messages - -These messages enable SPV light clients to request quorum proofs directly from peers without requiring a local Dash Core node. Nodes supporting these messages must advertise protocol version >= XXXXXX (to be assigned). - -### GETQUORUMPROOFCHAIN - -Request a quorum proof chain from a peer. The serving node derives the checkpoint height and active chainlock quorums from the checkpoint block hash. - -| Field | Type | Size | Description | -| ----- | ---- | ---- | ----------- | -| checkpointBlockHash | uint256 | 32 | Block hash of the client's checkpoint | -| targetQuorumHash | uint256 | 32 | Hash of the target quorum to prove | -| targetQuorumType | uint8_t | 1 | LLMQ type of the target quorum | - -### QUORUMPROOFCHAIN - -Response containing the proof chain. +The seed is the complete final quorum commitment, including its vector hash and +**both** embedded signatures. Its double-SHA256 hash is opened in the snapshot's +quorum root. Embedded commitment signatures are included in the authenticated +serialization; this verifier does not re-execute their DKG validation. -The response uses the `QuorumProofChainResponse` structure defined in [Quorum Proof Chain Data Structures](#quorum-proof-chain-data-structures). +Ancestor headers are ordered oldest first: mining block, then its descendants, +ending at the certificate's parent. Zero ancestors means the certificate signs +the mining block itself. The mining height equals certificate height minus +ancestor count. There is no independent relay-selected mining height. -| Field | Type | Size | Description | -| ----- | ---- | ---- | ----------- | -| response | QuorumProofChainResponse | variable | The complete proof chain | - -## gRPC API - -DAPI exposes quorum proof functionality to remote clients via gRPC. Internally, DAPI calls the Dash Core RPC methods described above on its local Core node. - -```protobuf -service Core { - rpc GetQuorumProofChain(GetQuorumProofChainRequest) - returns (GetQuorumProofChainResponse); -} - -message GetQuorumProofChainRequest { - bytes checkpoint_block_hash = 1; // 32 bytes - bytes target_quorum_hash = 2; // 32 bytes - uint32 target_quorum_type = 3; -} +The HTTP bootstrap envelope adds authenticated consensus records: -message ChainlockEntry { - int32 height = 1; - bytes block_hash = 2; // 32 bytes - bytes signature = 3; // 96 bytes - bytes signing_quorum_hash = 4; // 32 bytes - uint32 signing_quorum_type = 5; -} - -message QuorumCommitmentProof { - bytes commitment = 1; // Serialized CFinalCommitment - uint32 chainlock_index = 2; - repeated bytes quorum_merkle_path = 3; // Each 32 bytes - bytes coinbase_tx = 4; // Serialized transaction - bytes coinbase_merkle_proof = 5; // Serialized CPartialMerkleTree -} - -message GetQuorumProofChainResponse { - repeated bytes headers = 1; // Each 80 bytes - repeated ChainlockEntry chainlocks = 2; - repeated QuorumCommitmentProof quorum_proofs = 3; +```text +proof:blob +record_count:u8 +records[record_count] { + kind:u8 # 0 quorum commitment; 1 simplified masternode entry + consensus_leaf:blob + membership:path } ``` -## Proof Size Analysis - -### Component Sizes - -| Component | Size | -| --------- | ---- | -| Block header | 80 bytes | -| ChainlockEntry | ~165 bytes | -| QuorumCommitmentProof | ~700-900 bytes | - -### Per-Chainlock Overhead - -Each chainlock in the proof requires: - -* 1 ChainlockEntry: ~165 bytes -* 1 Block header: 80 bytes -* **Subtotal: ~245 bytes** - -### Scenarios - -#### Fresh Checkpoint - -When the checkpoint is less than 36 hours old, a checkpoint chainlock quorum is still active. A single chainlock reaches the tip. +A quorum leaf is the full commitment. A masternode leaf is exactly the +`CSimplifiedMNListEntry::CalcHash` preimage, excluding the network-only version +prefix. The decoder must consume the complete supported canonical serialization; +ambiguous or unsupported masternode encodings are rejected. + +## Verification + +1. Enforce all framing limits before allocation. Reject truncation, trailing + bytes, unknown tags, and retired format identifiers. +2. Require exact equality with the application's trusted snapshot. This version + supports the Basic BLS and coinbase-v3 era after buried v20 activation on + mainnet and testnet. +3. Parse the seed commitment canonically, require a nonzero subgroup-valid public + key, and verify its membership in the snapshot quorum root. +4. For each handoff, require a strictly increasing certificate height and the + network's ChainLock quorum type (2 mainnet, 1 testnet). Verify its Basic BLS + signature using the current key and Dash's existing ChainLock request/signing + hash construction, including the certificate height, quorum identity, and X11 + block hash. +5. Starting at that signed header, check every `hashPrevBlock` against the X11 + hash of the preceding supplied header. Verify the complete mining transaction + against the oldest header's transaction root. Transaction index must be + nonzero. Require a canonical v3 quorum-commitment transaction with no inputs, + outputs, or locktime, payload version 1, and the derived mining height. +6. Parse its full non-null commitment and install the authenticated next key. + Reject a handoff to the same quorum identity. The mining block may precede + the initial snapshot; certificate heights still advance beyond it. +7. Verify the final certificate with the last key. Open transaction index zero, + parse its complete v3 coinbase, and require coinbase payload height to equal + the signed height. Extract both final roots. +8. Enforce the caller's minimum target height. Verify every requested record's + double-SHA256 leaf hash against its corresponding final root. Require the + requested Platform quorum type and hash to match the authenticated commitment. +9. Publish the new state, key, and eligible EvoNode endpoints only after the entire + envelope succeeds. Then verify the Platform response signature and GroveDB + proof before returning application data or advancing Platform freshness state. + +Merkle verification consumes exactly the tree depth implied by leaf count. An +odd final node must use its own hash as the duplicate sibling. Equal siblings at +non-duplicate positions are rejected. Transaction and record leaves of exactly +64 bytes are rejected to prevent interpreting an internal tree node as a leaf. + +## Construction and Serving + +An unpruned Core node opts in with `-quorumproofindex`. Startup scans historical +blocks to archive coinbase-carried ChainLocks and quorum mining transaction +paths. Missing history causes indexing to fail explicitly. A new database prefix +separates this index from the retired format. Disconnect handling tracks the +carrier block, preserving evidence from an earlier carrier when a later repeated +certificate disconnects. + +Construction works backwards from the requested target signer to a quorum present +in the initial snapshot. For each needed quorum, the node locates its mining +transaction and a usable certificate at or after mining. Searching nearby +certificates minimizes serialized bytes per height advanced; the search can +expand when a nearby ChainLock is unavailable. This heuristic is not part of +verification or a claim of global minimum size. Construction fails explicitly if +history, a bridge, or the resource budget is unavailable. Multiple bounded +requests can advance a checkpoint over longer gaps. + +The Core RPC is: -| Component | Count | Size Each | Total | -| --------- | ----- | --------- | ----- | -| Chainlock + header | 1 | 245 B | 245 B | -| Target quorum proof | 1 | 800 B | 800 B | -| **Total** | | | **~1 KB** | - -#### Stale Checkpoint - -When the checkpoint is 2-4 days old, checkpoint quorums have expired. Need 1-2 bridging chainlock quorums. +```text +getquorumproofchain checkpoint_hash height=0 quorum_hash="" llmq_type=0 node_count=4 +``` -| Component | Count | Size Each | Total | -| --------- | ----- | --------- | ----- | -| Chainlocks + headers | 2-3 | 245 B | 490-735 B | -| Bridge CL quorum proofs | 1-2 | 800 B | 800-1,600 B | -| Target quorum proof | 1 | 800 B | 800 B | -| **Total** | | | **~2-3 KB** | +`height=0` chooses the latest archived certificate within the search budget. A +positive height is a minimum: the node searches for a certificate at or above +both it and snapshot height plus one. `quorum_hash` and `llmq_type` request one +quorum opening; `node_count` requests zero through fifteen eligible EvoNodes. +The result contains `proof_hex`, `bootstrap_hex`, and `target`. The bootstrap +field is empty if no records were requested. Generation supports mainnet/testnet. -#### Very Old Checkpoint +```text +verifyquorumproofchain checkpoint_object proof_hex minimum_height=0 +``` -When the checkpoint is 30+ days old, approximately 15 bridging chainlock quorums are needed (one per ~2 day interval). +The verification RPC takes all independently trusted snapshot fields and returns +`valid` plus either the authenticated `target` or an `error`. It does not consult +RPC metadata to obtain trust roots. -| Component | Count | Size Each | Total | -| --------- | ----- | --------- | ----- | -| Chainlocks + headers | ~15 | 245 B | ~3,700 B | -| Bridge CL quorum proofs | ~15 | 800 B | ~12,000 B | -| Target quorum proof | 1 | 800 B | 800 B | -| **Total** | | | **~16 KB** | +DAPI and quorum servers expose the same relay interface: -### Summary +```http +POST /proofs +Content-Type: application/json -| Checkpoint Age | Proof Size | -| -------------- | ---------- | -| < 36 hours | ~1 KB | -| 2-4 days | ~2-3 KB | -| 30+ days | ~16 KB | +{"checkpoint":"","height":1549547, + "quorumHash":"","llmqType":6,"nodeCount":4} +``` -Checkpoints should be updated with each SDK release (monthly recommended) to maintain minimal proof sizes. +Success is the binary bootstrap envelope with content type +`application/octet-stream`; HTTP gzip compression is permitted. Servers bound +request sizes, concurrent Core workers, cached bytes, and cache lifetime. +A timeout does not release a worker permit while its blocking RPC is still +running. Failure returns an HTTP error, never trusted fallback keys. + +## SDK Integration + +Mainnet/testnet network builders use the verified provider by default. Release +snapshots and untrusted seed addresses are embedded. Proof sources can be seeded +EvoNodes, quorum servers, or an explicitly supplied list of either. Authenticated +EvoNode records add connection candidates; addresses themselves never confer +signing authority. + +The synchronous Platform verifier reports a typed missing-quorum condition. The +SDK asynchronously obtains a bootstrap proof, verifies it, then repeats the +complete Platform verification. This applies to reads and transaction results, +and works without synchronous network calls in browser verification code. + +Applications can explicitly select trusted mode or supply their own context +provider. Trusted mode obtains quorum keys from its configured provider and skips +the additional Core bootstrap proof download and verification; Platform response +proof verification remains separately configurable. Failed verified mode MUST NOT +silently become trusted mode. + +## Size and Resource Limits + +| Item | Maximum | +| --- | ---: | +| Decoded proof or bootstrap HTTP response | 1,048,576 bytes | +| Certificates, including final certificate | 4,096 | +| Ancestor headers across the whole proof | 4,096 | +| Merkle leaf count | 100,000 | +| Merkle siblings | 17 | +| Transaction blob | 100,000 bytes | +| Seed commitment blob | 1,024 bytes | +| Bootstrap records | 16 | +| Record leaf | 4,096 bytes | + +These limits bound individual requests, not the duration of history. Certificate +availability and quorum cadence determine achievable history per request. + +Real testnet history encoded by the mining-only reference implementation measured +85,827 / 159,536 / 314,357 gzip bytes for 90 / 180 / 366 days respectively. +The shared short cross-implementation fixture is 3,469 raw proof bytes, or 4,506 +raw bytes with one quorum and one EvoNode opening. These are testnet observations, +not mainnet measurements or worst-case guarantees. Final record openings add to +the history-only figures. HTTP compression is a transport optimization and does +not change verification. + +The release requirement is less than 500,000 additional SDK download bytes. +A standalone verifier artifact cannot establish the integrated SDK delta; compare +matching release targets and compression settings before shipping a release. ## Security Considerations -### Trust Assumptions - -| Assumption | Basis | -| ---------- | ----- | -| Checkpoint is correct | Code review, reproducible builds, distribution via official channels | -| BLS signatures unforgeable | Cryptographic hardness of BLS scheme | -| Merkle proofs sound | Collision resistance of SHA-256 | -| ChainLocks are secure | DIP-0008 security analysis | - -### Attack Resistance - -| Attack | Why It Fails | -| ------ | ------------ | -| Forge chainlock | Requires 240 of 400 masternode operator keys (60% threshold) | -| Forge quorum commitment | `quorumSig` is threshold signature requiring quorum threshold | -| Wrong block hash | Header hash must match chainlock's `blockHash` | -| Tampered coinbase | Coinbase merkle proof verification fails | -| Tampered commitment | Commitment merkle proof verification fails | -| Omit proof data | Verification fails, client retries or tries different server | - -### Failure Modes - -| Failure | Cause | Resolution | -| ------- | ----- | ---------- | -| No verifiable chainlock | Server omitted required data | Retry or try different server | -| Verification timeout | Large proof on slow device | Increase timeout or use fresher checkpoint | -| Checkpoint too old | SDK not updated in 30+ days | Ship new checkpoint in SDK update | - -## Backward Compatibility +An attacker controlling all relays can withhold evidence, replay sufficiently +recent valid evidence, or exhaust a client's bounded request budget. Successful +verification establishes authenticity under the trust model, not that the target +is the globally newest block. Platform signed-time/height freshness policy and +caller minimum heights are required. Unauthenticated metadata must not advance a +freshness ratchet. -This DIP introduces new P2P messages (`GETQUORUMPROOFCHAIN`, `QUORUMPROOFCHAIN`) and gRPC endpoints that do not affect existing functionality. Nodes that do not implement this DIP will not respond to these messages. +The design does not protect against compromise of enough historical quorum keys +to forge this certificate chain. Stronger guarantees require a stronger trust +model or additional consensus evidence and have different size costs. -Clients implementing trustless verification should fall back to trusted verification methods if: +Snapshots are release trust material. Their hashes and roots require independent +release verification and network binding. Updating a snapshot from an unverified +HTTP response defeats the design. Persistent caches, if implemented, require the +same provenance and integrity protections as their original trust configuration. -1. No peers support the new messages -2. Proof verification fails repeatedly -3. The checkpoint is too old to construct a valid proof chain +No headers are treated as authenticated merely because a matching quorum key is +known. Every accepted statement is covered by the certificate chain under the +historical quorum honesty assumption above. Full state validity and exact signer +eligibility are deliberately outside this proof's statement. -## Reference Implementation +## Compatibility -Reference implementation will be provided in: +`DASHNC02` replaces the earlier proof encoding and RPC argument layout. Old proof +bytes are rejected; there is no route-A compatibility branch. Existing Dash +blocks, commitments, signatures, and consensus rules are unchanged. This proposal +does not assign new P2P inventory types or require P2P protocol changes. -* Dash Core: Chainlock indexing and proof generation RPCs -* Platform: `rs-trustless-quorum-verifier` crate for Rust verification -* DAPI: gRPC endpoint wrapping Core RPCs +Deploy index-enabled Core and relay endpoints before distributing SDK releases +that require them by default. Explicit trusted mode remains available to callers. +Devnet/regtest require explicit trust configuration; this version does not invent +release checkpoints for those networks. ## Copyright -Copyright (c) 2026 Dash Core Group, Inc. [Licensed under the MIT License](https://opensource.org/licenses/MIT) +Copyright (c) 2026 PastaPastaPasta. Licensed under the MIT License. From 6dfb54d8cebc600cc126ec71d392b47b87c6c243 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 8 Sep 2026 19:55:37 -0500 Subject: [PATCH 05/13] docs: describe quorum proofs without draft compatibility history --- dip-pasta-compact-quorum-proofs.md | 58 +++++++++++++----------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index f16c16e9..84a496da 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -14,7 +14,7 @@ 1. [Abstract](#abstract) 1. [Motivation](#motivation) -1. [Prior Work](#prior-work) +1. [Protocol Foundations](#protocol-foundations) 1. [Trust Model](#trust-model) 1. [Wire Format](#wire-format) 1. [Verification](#verification) @@ -22,7 +22,6 @@ 1. [SDK Integration](#sdk-integration) 1. [Size and Resource Limits](#size-and-resource-limits) 1. [Security Considerations](#security-considerations) -1. [Compatibility](#compatibility) 1. [Copyright](#copyright) ## Abstract @@ -31,13 +30,12 @@ This proposal authenticates current Dash Core quorum keys and EvoNode records fr an application-supplied trusted snapshot. A relay provides ordinary ChainLock certificates, quorum mining transactions, and Merkle paths. The SDK verifies them locally before verifying Platform responses. Relays supply evidence and do not -supply trusted keys. No consensus change, zero-knowledge system, trusted setup, or -specialized proving hardware is required. +supply trusted keys. The proof uses existing Dash blocks, commitments, signatures, +and consensus rules. -The format uses only mining-transaction handoffs (previously called route B). +Each handoff authenticates the next quorum through its mining transaction. When the mining block lacks a usable ChainLock, a later certificate authenticates -that block through consecutive X11 headers. There is no alternate quorum-root -handoff format. +that block through consecutive X11 headers. ## Motivation @@ -46,17 +44,16 @@ release, then acquire the evidence needed to authenticate newer Platform quorum keys. The intended history window is three to twelve months. The proof and the SDK verifier both contribute to download cost, so they must be measured together. -## Prior Work +## Protocol Foundations [DIP-0004](dip-0004.md) commits simplified masternode lists in coinbase transactions. [DIP-0006](dip-0006.md) defines LLMQ commitments and [DIP-0008](dip-0008.md) defines ChainLocks. -The earlier version of this proposal carried intermediate coinbases and quorum -root openings. A mining transaction already commits the complete next quorum -commitment. Authenticating that transaction avoids the intermediate coinbase and -second tree opening. Only the final coinbase remains, because it supplies the -roots needed for current quorum and masternode record openings. +A mining transaction contains the complete next quorum commitment. A handoff +authenticates that transaction with a Merkle path to a ChainLock-authenticated +block. The final coinbase supplies the quorum and masternode roots used to +authenticate the requested records. ## Trust Model @@ -71,6 +68,9 @@ A response MUST exactly match this snapshot. A relay-provided snapshot MUST NOT be promoted to trusted configuration merely because a proof is internally valid. A successfully verified target can serve as the next session checkpoint. +This proposal defines mainnet and testnet snapshots and quorum parameters. +Devnet and regtest require application-supplied trust configuration. + The design assumes historically authenticated ChainLock quorums do not sign false certificates, including after leaving the active set. It proves a sequence of statements by authenticated quorum keys. It does **not** independently reconstruct @@ -147,10 +147,10 @@ ambiguous or unsupported masternode encodings are rejected. ## Verification 1. Enforce all framing limits before allocation. Reject truncation, trailing - bytes, unknown tags, and retired format identifiers. -2. Require exact equality with the application's trusted snapshot. This version - supports the Basic BLS and coinbase-v3 era after buried v20 activation on - mainnet and testnet. + bytes, unknown tags, and a magic value other than `DASHNC02`. +2. Require exact equality with the application's trusted snapshot. The snapshot + MUST be after v20 activation on mainnet or testnet; the proof uses Basic BLS + signatures and v3 coinbase payloads. 3. Parse the seed commitment canonically, require a nonzero subgroup-valid public key, and verify its membership in the snapshot quorum root. 4. For each handoff, require a strictly increasing certificate height and the @@ -185,10 +185,9 @@ non-duplicate positions are rejected. Transaction and record leaves of exactly An unpruned Core node opts in with `-quorumproofindex`. Startup scans historical blocks to archive coinbase-carried ChainLocks and quorum mining transaction -paths. Missing history causes indexing to fail explicitly. A new database prefix -separates this index from the retired format. Disconnect handling tracks the -carrier block, preserving evidence from an earlier carrier when a later repeated -certificate disconnects. +paths. Missing history causes indexing to fail explicitly. Disconnect handling +tracks the carrier block, preserving evidence from an earlier carrier when a +later repeated certificate disconnects. Construction works backwards from the requested target signer to a quorum present in the initial snapshot. For each needed quorum, the node locates its mining @@ -244,6 +243,9 @@ EvoNodes, quorum servers, or an explicitly supplied list of either. Authenticate EvoNode records add connection candidates; addresses themselves never confer signing authority. +Verified SDK operation requires reachable proof-serving endpoints backed by +index-enabled Core nodes. + The synchronous Platform verifier reports a typed missing-quorum condition. The SDK asynchronously obtains a bootstrap proof, verifies it, then repeats the complete Platform verification. This applies to reads and transaction results, @@ -272,7 +274,7 @@ silently become trusted mode. These limits bound individual requests, not the duration of history. Certificate availability and quorum cadence determine achievable history per request. -Real testnet history encoded by the mining-only reference implementation measured +Real testnet history encoded by the reference implementation measured 85,827 / 159,536 / 314,357 gzip bytes for 90 / 180 / 366 days respectively. The shared short cross-implementation fixture is 3,469 raw proof bytes, or 4,506 raw bytes with one quorum and one EvoNode opening. These are testnet observations, @@ -307,18 +309,6 @@ known. Every accepted statement is covered by the certificate chain under the historical quorum honesty assumption above. Full state validity and exact signer eligibility are deliberately outside this proof's statement. -## Compatibility - -`DASHNC02` replaces the earlier proof encoding and RPC argument layout. Old proof -bytes are rejected; there is no route-A compatibility branch. Existing Dash -blocks, commitments, signatures, and consensus rules are unchanged. This proposal -does not assign new P2P inventory types or require P2P protocol changes. - -Deploy index-enabled Core and relay endpoints before distributing SDK releases -that require them by default. Explicit trusted mode remains available to callers. -Devnet/regtest require explicit trust configuration; this version does not invent -release checkpoints for those networks. - ## Copyright Copyright (c) 2026 PastaPastaPasta. Licensed under the MIT License. From db23bb97774e2a49ee2b7a03c56b18081dc6e30e Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 8 Sep 2026 21:04:35 -0500 Subject: [PATCH 06/13] docs: require final quorum certificate checks --- dip-pasta-compact-quorum-proofs.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index 84a496da..9161d39a 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -68,8 +68,8 @@ A response MUST exactly match this snapshot. A relay-provided snapshot MUST NOT be promoted to trusted configuration merely because a proof is internally valid. A successfully verified target can serve as the next session checkpoint. -This proposal defines mainnet and testnet snapshots and quorum parameters. -Devnet and regtest require application-supplied trust configuration. +This proposal supports mainnet and testnet only. Devnet and regtest are outside +its scope. The design assumes historically authenticated ChainLock quorums do not sign false certificates, including after leaving the active set. It proves a sequence of @@ -166,9 +166,14 @@ ambiguous or unsupported masternode encodings are rejected. 6. Parse its full non-null commitment and install the authenticated next key. Reject a handoff to the same quorum identity. The mining block may precede the initial snapshot; certificate heights still advance beyond it. -7. Verify the final certificate with the last key. Open transaction index zero, - parse its complete v3 coinbase, and require coinbase payload height to equal - the signed height. Extract both final roots. +7. Require the final certificate height to strictly exceed the last handoff's + certificate height, or the trusted snapshot height when there are no handoffs, + including when the caller's minimum target height is zero. Require the last + key's commitment to have the network's ChainLock quorum type (2 mainnet, + 1 testnet), and verify the final certificate's Basic BLS signature using that + key and the request/signing hash construction in step 4. Open transaction + index zero, parse its complete v3 coinbase, and require coinbase payload height + to equal the signed height. Extract both final roots. 8. Enforce the caller's minimum target height. Verify every requested record's double-SHA256 leaf hash against its corresponding final root. Require the requested Platform quorum type and hash to match the authenticated commitment. From 1295a0950ec0c88d555305afb47334c3ea524f81 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 8 Sep 2026 21:10:00 -0500 Subject: [PATCH 07/13] docs(dip): describe on-demand historical proof construction --- dip-pasta-compact-quorum-proofs.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index 9161d39a..c2e7a891 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -188,11 +188,21 @@ non-duplicate positions are rejected. Transaction and record leaves of exactly ## Construction and Serving -An unpruned Core node opts in with `-quorumproofindex`. Startup scans historical -blocks to archive coinbase-carried ChainLocks and quorum mining transaction -paths. Missing history causes indexing to fail explicitly. Disconnect handling -tracks the carrier block, preserving evidence from an earlier carrier when a -later repeated certificate disconnects. +Core reads historical evidence from block files on demand. ChainLock code locates +coinbase-carried certificates using exponential search followed by binary search: +consensus requires the certified height to never decrease, and a non-null +certificate cannot be followed by a null one. The existing mined-commitment +database identifies quorum mining blocks; their transactions and Merkle paths +are read and constructed as needed. No additional persistent index or startup +scan is required. A bounded certificate cache lasts for one RPC request. + +Construction uses a fixed view of the active chain. Disk reads and proof +verification run outside the main chain lock; existing quorum database queries +take short locks. Before returning, Core checks that the target certificate's +carrier remains on the active chain. A conflicting reorganization requires a +retry. Missing or pruned block data causes an explicit error rather than being +interpreted as an absent certificate. Proof-serving nodes should retain the +historical blocks needed by their supported checkpoints. Construction works backwards from the requested target signer to a quorum present in the initial snapshot. For each needed quorum, the node locates its mining @@ -209,7 +219,7 @@ The Core RPC is: getquorumproofchain checkpoint_hash height=0 quorum_hash="" llmq_type=0 node_count=4 ``` -`height=0` chooses the latest archived certificate within the search budget. A +`height=0` chooses the certificate carried by the current chain tip. A positive height is a minimum: the node searches for a certificate at or above both it and snapshot height plus one. `quorum_hash` and `llmq_type` request one quorum opening; `node_count` requests zero through fifteen eligible EvoNodes. From b24d999760789bfe971ddaa896d309f0ea866da4 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 9 Sep 2026 16:45:36 -0500 Subject: [PATCH 08/13] docs: clarify compact proof construction semantics --- dip-pasta-compact-quorum-proofs.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index c2e7a891..b4eaa2ad 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -164,8 +164,10 @@ ambiguous or unsupported masternode encodings are rejected. nonzero. Require a canonical v3 quorum-commitment transaction with no inputs, outputs, or locktime, payload version 1, and the derived mining height. 6. Parse its full non-null commitment and install the authenticated next key. - Reject a handoff to the same quorum identity. The mining block may precede - the initial snapshot; certificate heights still advance beyond it. + Reject a handoff to the same quorum identity. A verifier may accept a mining + block that precedes the initial snapshot, provided the certificate height still + advances beyond it. Core proof construction may impose a narrower search window + when selecting bridges; that is a serving limitation, not a wire-format rule. 7. Require the final certificate height to strictly exceed the last handoff's certificate height, or the trusted snapshot height when there are no handoffs, including when the caller's minimum target height is zero. Require the last @@ -219,8 +221,10 @@ The Core RPC is: getquorumproofchain checkpoint_hash height=0 quorum_hash="" llmq_type=0 node_count=4 ``` -`height=0` chooses the certificate carried by the current chain tip. A -positive height is a minimum: the node searches for a certificate at or above +`height=0` chooses the latest usable ChainLock at the current chain tip. The +signature may already be known to the live ChainLock manager before a later +coinbase carries it. A positive height is a minimum: the node searches for a +certificate at or above both it and snapshot height plus one. `quorum_hash` and `llmq_type` request one quorum opening; `node_count` requests zero through fifteen eligible EvoNodes. The result contains `proof_hex`, `bootstrap_hex`, and `target`. The bootstrap From b05d4b9f2a17c776ee18be00101c23abdc98786f Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 10 Sep 2026 11:46:13 -0500 Subject: [PATCH 09/13] docs(dip): clarify compact proof specification --- dip-pasta-compact-quorum-proofs.md | 97 ++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index b4eaa2ad..4fc98982 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -19,8 +19,10 @@ 1. [Wire Format](#wire-format) 1. [Verification](#verification) 1. [Construction and Serving](#construction-and-serving) +1. [Worked Example](#worked-example) 1. [SDK Integration](#sdk-integration) 1. [Size and Resource Limits](#size-and-resource-limits) +1. [Validation](#validation) 1. [Security Considerations](#security-considerations) 1. [Copyright](#copyright) @@ -168,6 +170,9 @@ ambiguous or unsupported masternode encodings are rejected. block that precedes the initial snapshot, provided the certificate height still advances beyond it. Core proof construction may impose a narrower search window when selecting bridges; that is a serving limitation, not a wire-format rule. + The commitment MUST use a supported version and quorum type, contain complete + canonical signer and valid-member vectors, and satisfy that type's declared + size, threshold, quorum-index, public-key, and quorum-hash rules. 7. Require the final certificate height to strictly exceed the last handoff's certificate height, or the trusted snapshot height when there are no handoffs, including when the caller's minimum target height is zero. Require the last @@ -190,27 +195,21 @@ non-duplicate positions are rejected. Transaction and record leaves of exactly ## Construction and Serving -Core reads historical evidence from block files on demand. ChainLock code locates -coinbase-carried certificates using exponential search followed by binary search: -consensus requires the certified height to never decrease, and a non-null -certificate cannot be followed by a null one. The existing mined-commitment -database identifies quorum mining blocks; their transactions and Merkle paths -are read and constructed as needed. No additional persistent index or startup -scan is required. A bounded certificate cache lasts for one RPC request. - -Construction uses a fixed view of the active chain. Disk reads and proof -verification run outside the main chain lock; existing quorum database queries -take short locks. Before returning, Core checks that the target certificate's -carrier remains on the active chain. A conflicting reorganization requires a -retry. Missing or pruned block data causes an explicit error rather than being -interpreted as an absent certificate. Proof-serving nodes should retain the -historical blocks needed by their supported checkpoints. +Core reads historical evidence from retained block files on demand. The producer +locates coinbase-carried certificates and quorum mining transactions, then supplies +the certificates, headers, transactions, and Merkle paths required by the wire +format. No proof-specific index or startup scan is part of this specification. + +Construction uses a fixed view of the active chain. Before returning, the +producer checks that the target signed block remains on the active chain; a +conflicting reorganization requires a retry. Missing or pruned block data causes +an explicit error rather than being interpreted as an absent certificate. A +producer MUST retain the historical blocks needed by its supported checkpoints. Construction works backwards from the requested target signer to a quorum present -in the initial snapshot. For each needed quorum, the node locates its mining -transaction and a usable certificate at or after mining. Searching nearby -certificates minimizes serialized bytes per height advanced; the search can -expand when a nearby ChainLock is unavailable. This heuristic is not part of +in the initial snapshot. For each needed quorum, the producer locates its mining +transaction and a usable certificate at or after mining. It MAY choose among +valid certificates to reduce proof size; certificate selection is not part of verification or a claim of global minimum size. Construction fails explicitly if history, a bridge, or the resource budget is unavailable. Multiple bounded requests can advance a checkpoint over longer gaps. @@ -249,10 +248,29 @@ Content-Type: application/json ``` Success is the binary bootstrap envelope with content type -`application/octet-stream`; HTTP gzip compression is permitted. Servers bound -request sizes, concurrent Core workers, cached bytes, and cache lifetime. -A timeout does not release a worker permit while its blocking RPC is still -running. Failure returns an HTTP error, never trusted fallback keys. +`application/octet-stream`; HTTP gzip compression is permitted. A server MUST +enforce the request and response limits in this DIP and return an error rather +than trusted fallback keys when it cannot produce or validate the evidence. +Caching, concurrency, and timeout policy are implementation choices. + +## Worked Example + +Suppose the trusted snapshot is at height `S` and contains quorum `Q0`. A +request asks for `Q2` at a target height above `S`: + +```text +snapshot(Q0) → [certificate C1 + mining transaction Q1] + → [certificate C2 + mining transaction Q2] + → final certificate + coinbase roots + → requested Q2 and EvoNode record openings +``` + +Each certificate is verified with the currently authenticated quorum key. Each +mining transaction is verified against the signed block, using supplied ancestor +headers when necessary, and the final coinbase authenticates the record roots. +The SDK accepts the requested key and endpoints only after the complete sequence +verifies. The repository's proof fixture and adversarial tests provide a compact +valid vector and rejection examples. ## SDK Integration @@ -262,8 +280,8 @@ EvoNodes, quorum servers, or an explicitly supplied list of either. Authenticate EvoNode records add connection candidates; addresses themselves never confer signing authority. -Verified SDK operation requires reachable proof-serving endpoints backed by -index-enabled Core nodes. +Verified SDK operation requires reachable proof-serving endpoints that can obtain +the retained historical evidence required by the request. The synchronous Platform verifier reports a typed missing-quorum condition. The SDK asynchronously obtains a bootstrap proof, verifies it, then repeats the @@ -293,18 +311,33 @@ silently become trusted mode. These limits bound individual requests, not the duration of history. Certificate availability and quorum cadence determine achievable history per request. -Real testnet history encoded by the reference implementation measured -85,827 / 159,536 / 314,357 gzip bytes for 90 / 180 / 366 days respectively. -The shared short cross-implementation fixture is 3,469 raw proof bytes, or 4,506 -raw bytes with one quorum and one EvoNode opening. These are testnet observations, -not mainnet measurements or worst-case guarantees. Final record openings add to -the history-only figures. HTTP compression is a transport optimization and does -not change verification. +Reference measurements on testnet history were 85,827 / 159,536 / 314,357 gzip +bytes for 90 / 180 / 366 days respectively. Full-stack archive tests measured +175,781 raw bytes for a year-long mainnet request and 343,014 raw bytes for a +year-long testnet request, including one quorum and four EvoNode openings. These +are observations, not worst-case guarantees. HTTP compression is a transport +optimization and does not change verification. The release requirement is less than 500,000 additional SDK download bytes. A standalone verifier artifact cannot establish the integrated SDK delta; compare matching release targets and compression settings before shipping a release. +## Validation + +The reference implementation includes a valid cross-language fixture and +adversarial vectors covering truncation, altered signatures, headers, roots, +records, stale targets, and oversized inputs. See the [Core proof tests][core-tests] +and [Rust verifier fixture][rust-fixture]. + +Archive measurements cover 90, 180, and 366 days on testnet, and full-stack +Core-to-relay-to-SDK runs cover year-long mainnet and testnet histories. The +measured year-long bootstraps, including one quorum and four EvoNode openings, +were 175,781 and 343,014 raw bytes respectively. The integrated SDK download +increase remains a release gate and MUST be measured for each target package. + +[core-tests]: https://github.com/dashpay/dash/blob/platform-sdk-compact-proof/src/test/quorum_proofs_tests.cpp +[rust-fixture]: https://github.com/PastaPastaPasta/platform/blob/feat/mining-proof-sdk/packages/rs-core-proof/tests/data/bootstrap.bin + ## Security Considerations An attacker controlling all relays can withhold evidence, replay sufficiently From 05fcc6e7cfe75e26014d707fef623456904f0e8b Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 10 Sep 2026 11:50:30 -0500 Subject: [PATCH 10/13] docs(dip): separate protocol rules from implementation details --- dip-pasta-compact-quorum-proofs.md | 186 +++++++++++++++-------------- 1 file changed, 95 insertions(+), 91 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index 4fc98982..eeda6c97 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -146,13 +146,33 @@ A quorum leaf is the full commitment. A masternode leaf is exactly the prefix. The decoder must consume the complete supported canonical serialization; ambiguous or unsupported masternode encodings are rejected. +Seed, handoff, and requested quorum commitments MUST use Basic BLS version 3 +(types 1, 2, 3, 4, 6) or rotation version 4 (type 5, index 0–31). +The signer and valid-member vectors MUST each have the type's exact size and at +least its threshold number of set bits, with zero unused padding bits: + +| Quorum type | Size | Threshold | +| --- | ---: | ---: | +| 1 | 50 | 30 | +| 2 | 400 | 240 | +| 3 | 400 | 340 | +| 4 | 100 | 67 | +| 5 | 60 | 45 | +| 6 | 25 | 17 | + +Unknown types or versions, null quorum hashes, and noncanonical encodings are +rejected. Public keys and certificate signatures MUST be canonical, non-infinity +points in the correct BLS subgroups. Embedded commitment signatures remain part +of the authenticated leaf; their DKG validity is not independently established. + ## Verification 1. Enforce all framing limits before allocation. Reject truncation, trailing bytes, unknown tags, and a magic value other than `DASHNC02`. 2. Require exact equality with the application's trusted snapshot. The snapshot - MUST be after v20 activation on mainnet or testnet; the proof uses Basic BLS - signatures and v3 coinbase payloads. + height MUST exceed 1,987,776 on mainnet or 905,100 on testnet (v20 activation). + Snapshot and certificate heights MUST be at most 2,147,483,647; snapshot block + hash and quorum root MUST be nonzero. 3. Parse the seed commitment canonically, require a nonzero subgroup-valid public key, and verify its membership in the snapshot quorum root. 4. For each handoff, require a strictly increasing certificate height and the @@ -163,16 +183,13 @@ ambiguous or unsupported masternode encodings are rejected. 5. Starting at that signed header, check every `hashPrevBlock` against the X11 hash of the preceding supplied header. Verify the complete mining transaction against the oldest header's transaction root. Transaction index must be - nonzero. Require a canonical v3 quorum-commitment transaction with no inputs, - outputs, or locktime, payload version 1, and the derived mining height. + nonzero, and ancestor count MUST be less than certificate height. Require a + canonical v3 quorum-commitment transaction with no inputs, outputs, or + locktime, payload version 1, and the derived mining height. 6. Parse its full non-null commitment and install the authenticated next key. - Reject a handoff to the same quorum identity. A verifier may accept a mining - block that precedes the initial snapshot, provided the certificate height still - advances beyond it. Core proof construction may impose a narrower search window - when selecting bridges; that is a serving limitation, not a wire-format rule. - The commitment MUST use a supported version and quorum type, contain complete - canonical signer and valid-member vectors, and satisfy that type's declared - size, threshold, quorum-index, public-key, and quorum-hash rules. + Reject a handoff to the same quorum identity. A mining block preceding the + initial snapshot is permitted; the certificate height must still advance + beyond the previous certificate or, for the first handoff, the snapshot. 7. Require the final certificate height to strictly exceed the last handoff's certificate height, or the trusted snapshot height when there are no handoffs, including when the caller's minimum target height is zero. Require the last @@ -180,10 +197,16 @@ ambiguous or unsupported masternode encodings are rejected. 1 testnet), and verify the final certificate's Basic BLS signature using that key and the request/signing hash construction in step 4. Open transaction index zero, parse its complete v3 coinbase, and require coinbase payload height - to equal the signed height. Extract both final roots. -8. Enforce the caller's minimum target height. Verify every requested record's - double-SHA256 leaf hash against its corresponding final root. Require the - requested Platform quorum type and hash to match the authenticated commitment. + to equal the signed height. Require one coinbase input, a scriptSig of 1–100 + bytes, 1–4,096 outputs, a v3 payload, `bestCLHeightDiff` less than the signed + height, and a nonzero quorum root. Extract both final roots. +8. Enforce the caller's minimum target height. For a bootstrap, require 1–16 + records with nonempty leaves. Verify every supplied record's double-SHA256 + leaf hash against its corresponding final root. If a Platform quorum was + requested, require exactly one opening matching its type (4 mainnet, + 6 testnet) and hash. Before using an EvoNode endpoint, require an unambiguously + decoded, valid, confirmed high-performance masternode record with a supported + HTTPS endpoint. 9. Publish the new state, key, and eligible EvoNode endpoints only after the entire envelope succeeds. Then verify the Platform response signature and GroveDB proof before returning application data or advancing Platform freshness state. @@ -195,24 +218,16 @@ non-duplicate positions are rejected. Transaction and record leaves of exactly ## Construction and Serving -Core reads historical evidence from retained block files on demand. The producer -locates coinbase-carried certificates and quorum mining transactions, then supplies -the certificates, headers, transactions, and Merkle paths required by the wire -format. No proof-specific index or startup scan is part of this specification. - -Construction uses a fixed view of the active chain. Before returning, the -producer checks that the target signed block remains on the active chain; a -conflicting reorganization requires a retry. Missing or pruned block data causes -an explicit error rather than being interpreted as an absent certificate. A -producer MUST retain the historical blocks needed by its supported checkpoints. - -Construction works backwards from the requested target signer to a quorum present -in the initial snapshot. For each needed quorum, the producer locates its mining -transaction and a usable certificate at or after mining. It MAY choose among -valid certificates to reduce proof size; certificate selection is not part of -verification or a claim of global minimum size. Construction fails explicitly if -history, a bridge, or the resource budget is unavailable. Multiple bounded -requests can advance a checkpoint over longer gaps. +A producer supplies a proof from the requested checkpoint to a certified target +on its active chain, including the requested record openings. It MUST return an +error if the necessary historical evidence or a valid route within the resource +limits is unavailable. Each successful response MUST satisfy the verification +rules above. A ChainLock signature can be used before a later coinbase carries +it; coinbase inclusion of the signature itself is not required. + +Multiple bounded proofs can advance an authenticated checkpoint over longer +gaps. This specification does not require a particular search algorithm, storage +index, or globally smallest proof. The Core RPC is: @@ -220,11 +235,9 @@ The Core RPC is: getquorumproofchain checkpoint_hash height=0 quorum_hash="" llmq_type=0 node_count=4 ``` -`height=0` chooses the latest usable ChainLock at the current chain tip. The -signature may already be known to the live ChainLock manager before a later -coinbase carries it. A positive height is a minimum: the node searches for a -certificate at or above -both it and snapshot height plus one. `quorum_hash` and `llmq_type` request one +`height=0` selects the producer's latest available ChainLock on its active chain. +A positive height is a minimum: the target must be at or above that height and +strictly above the checkpoint. `quorum_hash` and `llmq_type` request one quorum opening; `node_count` requests zero through fifteen eligible EvoNodes. The result contains `proof_hex`, `bootstrap_hex`, and `target`. The bootstrap field is empty if no records were requested. Generation supports mainnet/testnet. @@ -249,50 +262,46 @@ Content-Type: application/json Success is the binary bootstrap envelope with content type `application/octet-stream`; HTTP gzip compression is permitted. A server MUST -enforce the request and response limits in this DIP and return an error rather -than trusted fallback keys when it cannot produce or validate the evidence. -Caching, concurrency, and timeout policy are implementation choices. +enforce the parameter bounds and decoded response limits in this DIP. Failure +returns an HTTP error, never trusted fallback keys. ## Worked Example -Suppose the trusted snapshot is at height `S` and contains quorum `Q0`. A -request asks for `Q2` at a target height above `S`: +Suppose a snapshot at height `S` authenticates ChainLock quorum `Q0`. The client +requests Platform quorum `P` at height `S+30`. One possible proof is: ```text -snapshot(Q0) → [certificate C1 + mining transaction Q1] - → [certificate C2 + mining transaction Q2] - → final certificate + coinbase roots - → requested Q2 and EvoNode record openings +snapshot quorum root → Q0 +Q0 signs block S+12 → headers S+10, S+11 → Q1 mining transaction at S+10 +Q1 signs block S+30 → coinbase → quorum root → Platform quorum P + → masternode root → EvoNode records ``` -Each certificate is verified with the currently authenticated quorum key. Each -mining transaction is verified against the signed block, using supplied ancestor -headers when necessary, and the final coinbase authenticates the record roots. -The SDK accepts the requested key and endpoints only after the complete sequence -verifies. The repository's proof fixture and adversarial tests provide a compact -valid vector and rejection examples. +The first handoff carries two ancestor headers because its certificate is two +blocks after the mining transaction. Its derived mining height is `S+12−2`. +Merkle paths open Q0, Q1's transaction, the final coinbase, and the requested +records. Q0 and Q1 are ChainLock quorums; P is a separate Platform quorum whose +key is authenticated by the final root. See [Validation](#validation) for vectors. ## SDK Integration -Mainnet/testnet network builders use the verified provider by default. Release -snapshots and untrusted seed addresses are embedded. Proof sources can be seeded +Mainnet/testnet SDKs use verified mode by default, with independently pinned +release snapshots and untrusted seed addresses. Proof sources can be seeded EvoNodes, quorum servers, or an explicitly supplied list of either. Authenticated -EvoNode records add connection candidates; addresses themselves never confer -signing authority. +EvoNode records provide connection candidates; addresses never confer signing +authority. Verified SDK operation requires reachable proof-serving endpoints that can obtain -the retained historical evidence required by the request. +the historical evidence required by the request. -The synchronous Platform verifier reports a typed missing-quorum condition. The -SDK asynchronously obtains a bootstrap proof, verifies it, then repeats the -complete Platform verification. This applies to reads and transaction results, -and works without synchronous network calls in browser verification code. +Before using a Platform quorum key, the SDK MUST authenticate it from the trusted +snapshot or previously verified state. It MUST then verify the Platform response +signature and state proof, for both reads and transaction results. -Applications can explicitly select trusted mode or supply their own context -provider. Trusted mode obtains quorum keys from its configured provider and skips -the additional Core bootstrap proof download and verification; Platform response -proof verification remains separately configurable. Failed verified mode MUST NOT -silently become trusted mode. +Applications can explicitly select trusted mode, accepting quorum keys from a +configured source without the Core proof. This choice does not itself disable +Platform response proof verification. Failed verified mode MUST NOT silently +become trusted mode. ## Size and Resource Limits @@ -311,32 +320,27 @@ silently become trusted mode. These limits bound individual requests, not the duration of history. Certificate availability and quorum cadence determine achievable history per request. -Reference measurements on testnet history were 85,827 / 159,536 / 314,357 gzip -bytes for 90 / 180 / 366 days respectively. Full-stack archive tests measured -175,781 raw bytes for a year-long mainnet request and 343,014 raw bytes for a -year-long testnet request, including one quorum and four EvoNode openings. These -are observations, not worst-case guarantees. HTTP compression is a transport -optimization and does not change verification. - -The release requirement is less than 500,000 additional SDK download bytes. -A standalone verifier artifact cannot establish the integrated SDK delta; compare -matching release targets and compression settings before shipping a release. - ## Validation -The reference implementation includes a valid cross-language fixture and -adversarial vectors covering truncation, altered signatures, headers, roots, -records, stale targets, and oversized inputs. See the [Core proof tests][core-tests] -and [Rust verifier fixture][rust-fixture]. - -Archive measurements cover 90, 180, and 366 days on testnet, and full-stack -Core-to-relay-to-SDK runs cover year-long mainnet and testnet histories. The -measured year-long bootstraps, including one quorum and four EvoNode openings, -were 175,781 and 343,014 raw bytes respectively. The integrated SDK download -increase remains a release gate and MUST be measured for each target package. - -[core-tests]: https://github.com/dashpay/dash/blob/platform-sdk-compact-proof/src/test/quorum_proofs_tests.cpp -[rust-fixture]: https://github.com/PastaPastaPasta/platform/blob/feat/mining-proof-sdk/packages/rs-core-proof/tests/data/bootstrap.bin +The [Core test vector][core-vector] supplies a trusted checkpoint, proof bytes, +and expected target at testnet height 1,549,547. The proof is 3,469 bytes; +the [matching bootstrap][rust-fixture] is 4,506 bytes with one quorum and one +EvoNode opening. [Core tests][core-tests] and [Rust tests][rust-tests] exercise +valid verification and rejection of altered or malformed evidence. + +[Archive measurements][archive-results] cover 90, 180, and 366 days on both +networks. [Native SDK integration tests][stack-results] verify live Platform +queries and year-long histories through Core and a quorum server. The year-long +bootstraps were 175,781 bytes on mainnet and 343,014 bytes on testnet, each with +one quorum and four EvoNode openings, before compression. These observations are +not worst-case bounds or guarantees of history coverage. + +[core-vector]: https://github.com/PastaPastaPasta/dash/blob/9f67367df634/test/functional/data/quorum_proof.json +[core-tests]: https://github.com/PastaPastaPasta/dash/blob/378d0fb22c28/src/test/quorum_proofs_tests.cpp +[rust-fixture]: https://github.com/PastaPastaPasta/platform/blob/e243ea60c856/packages/rs-core-proof/tests/data/bootstrap.bin +[rust-tests]: https://github.com/PastaPastaPasta/platform/blob/e243ea60c856/packages/rs-core-proof/tests/verification.rs +[archive-results]: https://github.com/PastaPastaPasta/dash/blob/7e7be9bbf4b0/doc/benchmarks/quorum-proof-2026-09-09/README.md +[stack-results]: https://github.com/PastaPastaPasta/dash/blob/7e7be9bbf4b0/doc/benchmarks/quorum-proof-full-stack-2026-09-09/README.md ## Security Considerations From ee3cf5f45befa3a9a9c825f3a71c1bc13c04947a Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 10 Sep 2026 12:00:11 -0500 Subject: [PATCH 11/13] docs(dip): illustrate snapshot proof verification and handoffs --- dip-pasta-compact-quorum-proofs.md | 47 ++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index eeda6c97..56898f5b 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -80,6 +80,24 @@ DKG, full Core consensus, or the exact active signing-quorum selection for each certificate. Membership of a key is not proof of its current signing authority. These are deliberate constraints of this compact certificate trust model. +The snapshot is supplied independently; both network responses below remain +untrusted until the client verifies them: + +```mermaid +flowchart TD + snapshot["Pinned Core snapshot"] --> core["Verify Core proof and record openings"] + relay["Proof relay: seeded EvoNode or quorum server"] -. "bootstrap evidence" .-> core + core --> key["Authenticated Platform quorum key"] + core --> nodes["Authenticated EvoNode connection candidates"] + key --> platform["Verify Platform signature and state proof"] + dapi["Platform DAPI"] -. "response and proof" .-> platform + platform --> data["Return verified application data"] +``` + +The solid arrows show verification dependencies; dashed arrows show untrusted +network inputs. These checks establish authenticity under the trust model above; +the client also enforces its freshness policy. + ## Wire Format All proof framing integers are unsigned little-endian fixed-width integers. @@ -270,11 +288,15 @@ returns an HTTP error, never trusted fallback keys. Suppose a snapshot at height `S` authenticates ChainLock quorum `Q0`. The client requests Platform quorum `P` at height `S+30`. One possible proof is: -```text -snapshot quorum root → Q0 -Q0 signs block S+12 → headers S+10, S+11 → Q1 mining transaction at S+10 -Q1 signs block S+30 → coinbase → quorum root → Platform quorum P - → masternode root → EvoNode records +```mermaid +flowchart TD + snapshot["Snapshot quorum root at S"] -->|"membership proof"| q0["ChainLock quorum Q0"] + q0 -->|"verifies signature"| c1["Certificate at S+12"] + c1 -->|"2 headers and transaction proof"| q1["Q1 commitment and key
in mining transaction at S+10"] + q1 -->|"verifies signature"| final["Final certificate at S+30"] + final -->|"transaction proof"| coinbase["Final coinbase: quorum and masternode roots"] + coinbase -->|"membership proof"| p["Platform quorum P"] + coinbase -->|"membership proofs"| nodes["EvoNode records"] ``` The first handoff carries two ancestor headers because its certificate is two @@ -283,6 +305,21 @@ Merkle paths open Q0, Q1's transaction, the final coinbase, and the requested records. Q0 and Q1 are ChainLock quorums; P is a separate Platform quorum whose key is authenticated by the final root. See [Validation](#validation) for vectors. +The two-header bridge expands as follows. Each `hashPrevBlock` link points +from a checked header to the predecessor whose X11 hash it commits to: + +```mermaid +flowchart RL + certified["S+12
signed by Q0"] -->|"hashPrevBlock"| parent["S+11
header"] + parent -->|"hashPrevBlock"| mining["S+10
mining header"] + mining -->|"Merkle proof"| tx["Q1 mining
transaction"] +``` + +No separate ChainLock for `S+10` or `S+11` is needed. If the mining block itself +has a usable certificate, the handoff carries zero ancestor headers. This bridge +covers only the gap from a mining block to its certificate; the proof does not +include every header between the snapshot and the final target. + ## SDK Integration Mainnet/testnet SDKs use verified mode by default, with independently pinned From a04e7320542a498cff1037f377d2aac9b788716b Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 10 Sep 2026 12:21:07 -0500 Subject: [PATCH 12/13] docs(dip): simplify compact proof specification and explanations --- dip-pasta-compact-quorum-proofs.md | 474 ++++++++++++++++------------- 1 file changed, 257 insertions(+), 217 deletions(-) diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index 56898f5b..0dec3785 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -14,12 +14,12 @@ 1. [Abstract](#abstract) 1. [Motivation](#motivation) -1. [Protocol Foundations](#protocol-foundations) +1. [How It Works](#how-it-works) 1. [Trust Model](#trust-model) +1. [Worked Example](#worked-example) 1. [Wire Format](#wire-format) 1. [Verification](#verification) 1. [Construction and Serving](#construction-and-serving) -1. [Worked Example](#worked-example) 1. [SDK Integration](#sdk-integration) 1. [Size and Resource Limits](#size-and-resource-limits) 1. [Validation](#validation) @@ -28,85 +28,149 @@ ## Abstract -This proposal authenticates current Dash Core quorum keys and EvoNode records from -an application-supplied trusted snapshot. A relay provides ordinary ChainLock -certificates, quorum mining transactions, and Merkle paths. The SDK verifies them -locally before verifying Platform responses. Relays supply evidence and do not -supply trusted keys. The proof uses existing Dash blocks, commitments, signatures, -and consensus rules. +This proposal lets an SDK verify Platform quorum keys and EvoNode records using +ordinary Dash signatures and Merkle proofs. The SDK starts with a trusted Core +snapshot included in its release. A server supplies a compact proof connecting +that snapshot to a newer Core block, along with the requested records and proofs +that they belong to that block's quorum or masternode list. -Each handoff authenticates the next quorum through its mining transaction. -When the mining block lacks a usable ChainLock, a later certificate authenticates -that block through consecutive X11 headers. +The SDK checks this evidence locally, then uses the verified quorum key to check +Platform responses. It does not need to trust the server supplying the proof. +The design uses existing Dash consensus rules and requires no trusted setup. ## Motivation -An SDK can distribute a small, fixed Core snapshot and network addresses with its -release, then acquire the evidence needed to authenticate newer Platform quorum -keys. The intended history window is three to twelve months. The proof and the -SDK verifier both contribute to download cost, so they must be measured together. +An SDK needs up-to-date Platform quorum keys to verify responses. Asking a server +for those keys requires trusting that server unless the SDK can check where the +keys came from. + +A small, fixed Core snapshot gives the SDK a trusted starting point. Compact +proofs let it verify newer keys without downloading and validating every Core +block. The intended history window is three to twelve months. Both the proof +size and the verifier's contribution to the SDK download matter. + +## How It Works -## Protocol Foundations +The proof builds on three existing Dash features: -[DIP-0004](dip-0004.md) commits simplified masternode lists in coinbase transactions. -[DIP-0006](dip-0006.md) defines LLMQ commitments and -[DIP-0008](dip-0008.md) defines ChainLocks. +* [DIP-0004](dip-0004.md): coinbase transactions contain Merkle roots for the + simplified masternode list and active quorum list. +* [DIP-0006](dip-0006.md): a quorum commitment records a quorum's identity, public + key, and membership information. A quorum mining transaction includes this + commitment in a Core block. +* [DIP-0008](dip-0008.md): a ChainLock is a quorum signature identifying a Core + block at a particular height. -A mining transaction contains the complete next quorum commitment. A handoff -authenticates that transaction with a Merkle path to a ChainLock-authenticated -block. The final coinbase supplies the quorum and masternode roots used to -authenticate the requested records. +A **Merkle root** is a single hash representing a list of records. A **Merkle +path** proves that one record belongs to that list without sending the whole list. In this proposal, a +**certificate** contains a block header, its height, and its ChainLock signature. + +The proof follows this sequence: + +1. Prove that an initial ChainLock quorum belongs to the snapshot's quorum list. +2. Use that quorum's key to verify a certificate covering the mining transaction + of another ChainLock quorum. That proves the next key. Repeat as needed; + each change of quorum is called a **handoff**. +3. Use the last ChainLock quorum's key to verify a certificate for the target + block. Prove that the block contains a coinbase transaction with the final + quorum-list and masternode-list roots. +4. Use those roots to check the requested Platform quorum and EvoNode records. + +If a quorum's mining block has no usable ChainLock, a later certificate can +cover it through consecutive block headers. Only these short gaps need headers; +the proof does not include every block since the snapshot. ## Trust Model -The application independently fixes a snapshot containing: +The application must obtain its starting snapshot independently of the proof +server, for example by including it in the SDK release. The snapshot contains: * Network (0 mainnet, 1 testnet). * Core height and block hash. * Simplified masternode-list Merkle root. * Active quorum-list Merkle root. -A response MUST exactly match this snapshot. A relay-provided snapshot MUST NOT -be promoted to trusted configuration merely because a proof is internally valid. -A successfully verified target can serve as the next session checkpoint. +The snapshot in a proof MUST match all of these fields. A valid proof from a +server-chosen snapshot is not sufficient. After verification, the target block +and its roots can become the checkpoint for the next proof. -This proposal supports mainnet and testnet only. Devnet and regtest are outside -its scope. +This proposal supports mainnet and testnet only. -The design assumes historically authenticated ChainLock quorums do not sign false -certificates, including after leaving the active set. It proves a sequence of -statements by authenticated quorum keys. It does **not** independently reconstruct -DKG, full Core consensus, or the exact active signing-quorum selection for each -certificate. Membership of a key is not proof of its current signing authority. -These are deliberate constraints of this compact certificate trust model. +The design assumes that ChainLock quorums whose keys the proof establishes do +not sign false certificates, even after those quorums leave the active set. The +proof checks their signatures, but does not repeat distributed key generation +(DKG), validate all Core consensus rules, or reconstruct which quorum was +eligible to sign each block. Proving that a key belongs to a quorum list does +not, by itself, prove that the quorum is still eligible to sign. -The snapshot is supplied independently; both network responses below remain -untrusted until the client verifies them: +The client verifies both the Core evidence and the Platform response: ```mermaid flowchart TD - snapshot["Pinned Core snapshot"] --> core["Verify Core proof and record openings"] - relay["Proof relay: seeded EvoNode or quorum server"] -. "bootstrap evidence" .-> core - core --> key["Authenticated Platform quorum key"] - core --> nodes["Authenticated EvoNode connection candidates"] + snapshot["Trusted Core snapshot"] --> core["Verify Core proof and record membership"] + relay["Proof server: seeded EvoNode or quorum server"] -. "proof and records" .-> core + core --> key["Verified Platform quorum key"] + core --> nodes["Verified EvoNode records for connections"] key --> platform["Verify Platform signature and state proof"] dapi["Platform DAPI"] -. "response and proof" .-> platform platform --> data["Return verified application data"] ``` -The solid arrows show verification dependencies; dashed arrows show untrusted -network inputs. These checks establish authenticity under the trust model above; -the client also enforces its freshness policy. +Solid arrows show what each check depends on. Dashed arrows show network inputs +that the client must verify. These checks prove authenticity under the assumption +above. The client must also check that the data is recent enough for its needs. + +## Worked Example + +Suppose the trusted snapshot is at height `S`. Its quorum list contains ChainLock +quorum `Q0`, and the client requests Platform quorum `P` from a target block at +`S+30`. A proof with one handoff to ChainLock quorum `Q1` looks like this: + +```mermaid +flowchart TD + snapshot["Snapshot quorum root at S"] -->|"membership proof"| q0["ChainLock quorum Q0"] + q0 -->|"verifies signature"| c1["Certificate at S+12"] + c1 -->|"2 headers and transaction proof"| q1["Q1 commitment and key
in mining transaction at S+10"] + q1 -->|"verifies signature"| final["Final certificate at S+30"] + final -->|"transaction proof"| coinbase["Final coinbase: quorum and masternode roots"] + coinbase -->|"membership proof"| p["Platform quorum P"] + coinbase -->|"membership proofs"| nodes["EvoNode records"] +``` + +Q0 signs the block at `S+12`. That block links back through two headers to the +block at `S+10`, which contains Q1's mining transaction. The client can therefore +verify Q1's key and use it to check the final certificate at `S+30`. + +Q0 and Q1 are ChainLock quorums. P is a separate Platform quorum, verified against +the final coinbase's quorum root. Merkle paths prove membership for Q0, Q1's +transaction, the final coinbase, and the requested records. Linked test vectors +are in [Validation](#validation). + +The two headers connect the mining transaction to the signed block as follows. +Each header's `hashPrevBlock` field contains the X11 hash of the previous header: + +```mermaid +flowchart RL + certified["S+12
signed by Q0"] -->|"hashPrevBlock"| parent["S+11
header"] + parent -->|"hashPrevBlock"| mining["S+10
mining header"] + mining -->|"Merkle proof"| tx["Q1 mining
transaction"] +``` + +No separate ChainLock for `S+10` or `S+11` is needed. If the mining block itself +has a usable certificate, the handoff carries zero ancestor headers. These extra +headers cover only the gap between a mining block and its certificate. ## Wire Format -All proof framing integers are unsigned little-endian fixed-width integers. -Hashes are 32 bytes in Core serialization order, reversed from RPC display hex. -Nested transactions and commitments use their existing canonical Core consensus -serialization, including CompactSize where consensus requires it. There is no -protobuf or general-purpose object encoding on the proof wire. +The layouts below give fields in wire order. Integers are unsigned, fixed-width, +and little-endian; `u8`, `u16`, and `u32` mean 1, 2, and 4 bytes. Hashes are 32 +bytes in Core wire order, reversed from the hex strings shown by RPCs. +Transactions and commitments embedded in the proof use their existing canonical +Core encoding, including CompactSize where that encoding requires it. -A `blob` is `length:u32 || bytes[length]`. A `path` is: +A `blob` is a length followed by that many bytes: `length:u32 || bytes[length]`. +A Merkle `path` gives the leaf's zero-based index, the number of leaves in the +tree, and the sibling hashes needed to calculate the root: ```text index:u32 | leaf_count:u32 | sibling_count:u8 | siblings[32]... @@ -137,17 +201,20 @@ final_certificate final_coinbase:blob | coinbase_membership:path ``` -The seed is the complete final quorum commitment, including its vector hash and -**both** embedded signatures. Its double-SHA256 hash is opened in the snapshot's -quorum root. Embedded commitment signatures are included in the authenticated -serialization; this verifier does not re-execute their DKG validation. +`seed_commitment` is the initial ChainLock quorum's complete final commitment, +including its verification-vector hash and both embedded signatures. +`seed_membership` proves that the commitment's double-SHA256 hash belongs to the +snapshot's quorum root. -Ancestor headers are ordered oldest first: mining block, then its descendants, -ending at the certificate's parent. Zero ancestors means the certificate signs -the mining block itself. The mining height equals certificate height minus -ancestor count. There is no independent relay-selected mining height. +`ancestor_headers` are ordered oldest first, from the mining block through the +block just before the certificate's block. Zero ancestors means the certificate +signs the mining block itself. The mining height is calculated as certificate +height minus ancestor count; the server cannot choose it separately. -The HTTP bootstrap envelope adds authenticated consensus records: +The server response contains the proof above, followed by the requested quorum +and masternode records. Each record includes a Merkle path to the appropriate +root in the final coinbase. This combined response is called the **bootstrap +response**: ```text proof:blob @@ -159,15 +226,17 @@ records[record_count] { } ``` -A quorum leaf is the full commitment. A masternode leaf is exactly the -`CSimplifiedMNListEntry::CalcHash` preimage, excluding the network-only version -prefix. The decoder must consume the complete supported canonical serialization; -ambiguous or unsupported masternode encodings are rejected. +`consensus_leaf` contains the record's canonical Core bytes: the full commitment +for a quorum, or the bytes hashed by `CSimplifiedMNListEntry::CalcHash` for a +masternode. The masternode bytes exclude the version prefix used only in network +messages. The decoder must consume the entire record and reject ambiguous or +unsupported masternode encodings. Seed, handoff, and requested quorum commitments MUST use Basic BLS version 3 (types 1, 2, 3, 4, 6) or rotation version 4 (type 5, index 0–31). -The signer and valid-member vectors MUST each have the type's exact size and at -least its threshold number of set bits, with zero unused padding bits: +Each commitment has bit vectors identifying its signers and valid members. Both +vectors MUST have the size listed below and at least the threshold number of +set bits. Unused padding bits MUST be zero: | Quorum type | Size | Threshold | | --- | ---: | ---: | @@ -180,72 +249,83 @@ least its threshold number of set bits, with zero unused padding bits: Unknown types or versions, null quorum hashes, and noncanonical encodings are rejected. Public keys and certificate signatures MUST be canonical, non-infinity -points in the correct BLS subgroups. Embedded commitment signatures remain part -of the authenticated leaf; their DKG validity is not independently established. +points in the correct BLS subgroups. The commitment's embedded signatures are +included in the bytes checked by the Merkle proof; the verifier does not repeat +their DKG signature checks. ## Verification -1. Enforce all framing limits before allocation. Reject truncation, trailing - bytes, unknown tags, and a magic value other than `DASHNC02`. -2. Require exact equality with the application's trusted snapshot. The snapshot - height MUST exceed 1,987,776 on mainnet or 905,100 on testnet (v20 activation). - Snapshot and certificate heights MUST be at most 2,147,483,647; snapshot block - hash and quorum root MUST be nonzero. -3. Parse the seed commitment canonically, require a nonzero subgroup-valid public - key, and verify its membership in the snapshot quorum root. -4. For each handoff, require a strictly increasing certificate height and the - network's ChainLock quorum type (2 mainnet, 1 testnet). Verify its Basic BLS - signature using the current key and Dash's existing ChainLock request/signing - hash construction, including the certificate height, quorum identity, and X11 - block hash. -5. Starting at that signed header, check every `hashPrevBlock` against the X11 - hash of the preceding supplied header. Verify the complete mining transaction - against the oldest header's transaction root. Transaction index must be - nonzero, and ancestor count MUST be less than certificate height. Require a - canonical v3 quorum-commitment transaction with no inputs, outputs, or - locktime, payload version 1, and the derived mining height. -6. Parse its full non-null commitment and install the authenticated next key. - Reject a handoff to the same quorum identity. A mining block preceding the - initial snapshot is permitted; the certificate height must still advance - beyond the previous certificate or, for the first handoff, the snapshot. -7. Require the final certificate height to strictly exceed the last handoff's - certificate height, or the trusted snapshot height when there are no handoffs, - including when the caller's minimum target height is zero. Require the last - key's commitment to have the network's ChainLock quorum type (2 mainnet, - 1 testnet), and verify the final certificate's Basic BLS signature using that - key and the request/signing hash construction in step 4. Open transaction - index zero, parse its complete v3 coinbase, and require coinbase payload height - to equal the signed height. Require one coinbase input, a scriptSig of 1–100 - bytes, 1–4,096 outputs, a v3 payload, `bestCLHeightDiff` less than the signed - height, and a nonzero quorum root. Extract both final roots. -8. Enforce the caller's minimum target height. For a bootstrap, require 1–16 - records with nonempty leaves. Verify every supplied record's double-SHA256 - leaf hash against its corresponding final root. If a Platform quorum was - requested, require exactly one opening matching its type (4 mainnet, - 6 testnet) and hash. Before using an EvoNode endpoint, require an unambiguously - decoded, valid, confirmed high-performance masternode record with a supported - HTTPS endpoint. -9. Publish the new state, key, and eligible EvoNode endpoints only after the entire - envelope succeeds. Then verify the Platform response signature and GroveDB - proof before returning application data or advancing Platform freshness state. - -Merkle verification consumes exactly the tree depth implied by leaf count. An -odd final node must use its own hash as the duplicate sibling. Equal siblings at -non-duplicate positions are rejected. Transaction and record leaves of exactly -64 bytes are rejected to prevent interpreting an internal tree node as a leaf. +A verifier MUST perform the following checks. Any failed check rejects the proof +or bootstrap response. + +1. **Check the encoding.** Enforce the [resource limits](#size-and-resource-limits) + before allocating memory. Reject truncated data, trailing bytes, unknown tags, + and a magic value other than `DASHNC02`. +2. **Check the starting snapshot.** All fields must match the application's + trusted snapshot. Its height must exceed 1,987,776 on mainnet or 905,100 on + testnet (v20 activation). Snapshot and certificate heights must be at most + 2,147,483,647. The snapshot block hash and quorum root must be nonzero. +3. **Verify the initial key.** Decode the seed commitment using the commitment + rules above, including the public-key checks. Verify its Merkle path against + the snapshot's quorum root. +4. **Verify each handoff's certificate.** Its height must exceed the previous + certificate's height, or the snapshot height for the first handoff. The current + signing commitment must have the network's ChainLock quorum type: 2 on mainnet, + 1 on testnet. Verify the Basic BLS signature using the current key and Dash's + existing ChainLock request and signing hashes, which bind the certificate + height, quorum identity, and X11 block hash. +5. **Verify the mining transaction.** Starting at the signed header, check that + each `hashPrevBlock` equals the X11 hash of the preceding supplied header. + Verify the transaction's Merkle path against the oldest supplied header's + transaction root, or the signed header's root if there are no ancestors. + The transaction index must be nonzero, and ancestor count must be less than + certificate height. Require a complete, canonical v3 quorum-commitment + transaction with no inputs or outputs, zero locktime, and a v1 payload whose + height equals certificate height minus ancestor count. +6. **Continue with the next key.** Check the transaction's full, non-null quorum + commitment using the commitment rules above. Its quorum identity must differ + from the current one. Use its key for the next certificate. The mining block + may predate the snapshot; the certificate must still advance as required in + step 4. +7. **Verify the target block and coinbase.** The final certificate's height must + exceed the last handoff's height, or the snapshot height if there are no + handoffs, even when the caller's minimum height is zero. Check the last key's + ChainLock quorum type and the final signature as in step 4. Verify the + coinbase's Merkle path at transaction index zero against the signed header. + Require a complete v3 coinbase transaction with one coinbase input, a scriptSig + of 1–100 bytes, and 1–4,096 outputs. Its payload must be v3, with height equal + to the signed height, `bestCLHeightDiff` less than that height, and a nonzero + quorum root. Read the quorum and masternode roots from this payload. +8. **Check the requested height and records.** The final height must meet the + caller's minimum. A bootstrap response must contain 1–16 records with nonempty + leaves. For each record, calculate its double-SHA256 hash and verify its Merkle + path against the corresponding final root. If a Platform quorum was requested, + require exactly one quorum record matching its type (4 on mainnet, 6 on + testnet) and hash. Before using an EvoNode address, require an unambiguously + decoded record for a valid, confirmed high-performance masternode with a + supported HTTPS endpoint. +9. **Use the results only after all checks pass.** Only then accept the new + checkpoint, quorum key, and eligible EvoNode addresses. Verify the Platform + response signature and GroveDB proof before returning application data or + advancing the stored Platform height or signed time used for freshness checks. + +Every Merkle path must have exactly the tree depth implied by its leaf count. +When a level has an odd number of nodes, the last node must use its own hash as +its sibling. Equal sibling hashes are rejected at all other positions. +Transaction and record leaves of exactly 64 bytes are rejected so that an +internal tree node cannot be mistaken for a leaf. ## Construction and Serving -A producer supplies a proof from the requested checkpoint to a certified target -on its active chain, including the requested record openings. It MUST return an -error if the necessary historical evidence or a valid route within the resource -limits is unavailable. Each successful response MUST satisfy the verification -rules above. A ChainLock signature can be used before a later coinbase carries -it; coinbase inclusion of the signature itself is not required. +A proof server supplies a proof from the requested checkpoint to a +ChainLock-signed block on its active chain, together with the requested records +and their Merkle paths. It MUST return an error if the historical evidence is +unavailable or it cannot construct a valid proof within the resource limits. +Every successful response MUST satisfy the verification rules above. -Multiple bounded proofs can advance an authenticated checkpoint over longer -gaps. This specification does not require a particular search algorithm, storage -index, or globally smallest proof. +A ChainLock signature can be used as soon as it is available; there is no need to +wait for a later coinbase to include it. For a long gap, the client can verify +several proofs in sequence, using each verified target as the next checkpoint. The Core RPC is: @@ -253,22 +333,22 @@ The Core RPC is: getquorumproofchain checkpoint_hash height=0 quorum_hash="" llmq_type=0 node_count=4 ``` -`height=0` selects the producer's latest available ChainLock on its active chain. +`height=0` selects the server's latest available ChainLock on its active chain. A positive height is a minimum: the target must be at or above that height and strictly above the checkpoint. `quorum_hash` and `llmq_type` request one -quorum opening; `node_count` requests zero through fifteen eligible EvoNodes. -The result contains `proof_hex`, `bootstrap_hex`, and `target`. The bootstrap -field is empty if no records were requested. Generation supports mainnet/testnet. +quorum record; `node_count` requests zero through fifteen eligible EvoNode +records. The result contains `proof_hex`, `bootstrap_hex`, and `target`. +`bootstrap_hex` is empty if no records were requested. ```text verifyquorumproofchain checkpoint_object proof_hex minimum_height=0 ``` -The verification RPC takes all independently trusted snapshot fields and returns -`valid` plus either the authenticated `target` or an `error`. It does not consult -RPC metadata to obtain trust roots. +`checkpoint_object` supplies all fields of the caller's independently trusted +snapshot. The verification RPC returns `valid` plus either the verified `target` +or an `error`. It must not obtain the trusted snapshot from server metadata. -DAPI and quorum servers expose the same relay interface: +DAPI and quorum servers expose the same HTTP interface: ```http POST /proofs @@ -278,73 +358,35 @@ Content-Type: application/json "quorumHash":"","llmqType":6,"nodeCount":4} ``` -Success is the binary bootstrap envelope with content type -`application/octet-stream`; HTTP gzip compression is permitted. A server MUST -enforce the parameter bounds and decoded response limits in this DIP. Failure -returns an HTTP error, never trusted fallback keys. - -## Worked Example - -Suppose a snapshot at height `S` authenticates ChainLock quorum `Q0`. The client -requests Platform quorum `P` at height `S+30`. One possible proof is: - -```mermaid -flowchart TD - snapshot["Snapshot quorum root at S"] -->|"membership proof"| q0["ChainLock quorum Q0"] - q0 -->|"verifies signature"| c1["Certificate at S+12"] - c1 -->|"2 headers and transaction proof"| q1["Q1 commitment and key
in mining transaction at S+10"] - q1 -->|"verifies signature"| final["Final certificate at S+30"] - final -->|"transaction proof"| coinbase["Final coinbase: quorum and masternode roots"] - coinbase -->|"membership proof"| p["Platform quorum P"] - coinbase -->|"membership proofs"| nodes["EvoNode records"] -``` - -The first handoff carries two ancestor headers because its certificate is two -blocks after the mining transaction. Its derived mining height is `S+12−2`. -Merkle paths open Q0, Q1's transaction, the final coinbase, and the requested -records. Q0 and Q1 are ChainLock quorums; P is a separate Platform quorum whose -key is authenticated by the final root. See [Validation](#validation) for vectors. - -The two-header bridge expands as follows. Each `hashPrevBlock` link points -from a checked header to the predecessor whose X11 hash it commits to: - -```mermaid -flowchart RL - certified["S+12
signed by Q0"] -->|"hashPrevBlock"| parent["S+11
header"] - parent -->|"hashPrevBlock"| mining["S+10
mining header"] - mining -->|"Merkle proof"| tx["Q1 mining
transaction"] -``` - -No separate ChainLock for `S+10` or `S+11` is needed. If the mining block itself -has a usable certificate, the handoff carries zero ancestor headers. This bridge -covers only the gap from a mining block to its certificate; the proof does not -include every header between the snapshot and the final target. +A successful response contains the binary bootstrap response with content type +`application/octet-stream`. HTTP gzip compression is permitted. A server MUST +enforce this DIP's parameter bounds and response size limits after decompression. +On failure, it returns an HTTP error. ## SDK Integration -Mainnet/testnet SDKs use verified mode by default, with independently pinned -release snapshots and untrusted seed addresses. Proof sources can be seeded -EvoNodes, quorum servers, or an explicitly supplied list of either. Authenticated -EvoNode records provide connection candidates; addresses never confer signing -authority. - -Verified SDK operation requires reachable proof-serving endpoints that can obtain -the historical evidence required by the request. +Mainnet and testnet SDKs use verified mode by default. Each release includes an +independently verified snapshot and seed addresses for finding proof servers. +The SDK can request proofs from seeded EvoNodes, quorum servers, or an explicitly +configured list of either. These servers must be reachable and able to obtain +the historical evidence needed for the proof. -Before using a Platform quorum key, the SDK MUST authenticate it from the trusted -snapshot or previously verified state. It MUST then verify the Platform response -signature and state proof, for both reads and transaction results. +Before using a Platform quorum key, the SDK MUST verify it from the trusted +snapshot or a previously verified checkpoint. It MUST then verify the Platform +response signature and state proof, for both reads and transaction results. +Verified EvoNode records supply addresses for further connections; knowing an +address does not establish a quorum's authority to sign. -Applications can explicitly select trusted mode, accepting quorum keys from a -configured source without the Core proof. This choice does not itself disable -Platform response proof verification. Failed verified mode MUST NOT silently -become trusted mode. +Applications can explicitly select trusted mode. In that mode, the SDK accepts +quorum keys from a configured source without requiring the Core proof. This +choice does not itself disable Platform response proof verification. A failure +in verified mode MUST NOT silently switch the SDK to trusted mode. ## Size and Resource Limits | Item | Maximum | | --- | ---: | -| Decoded proof or bootstrap HTTP response | 1,048,576 bytes | +| Proof or bootstrap HTTP response, after decompression | 1,048,576 bytes | | Certificates, including final certificate | 4,096 | | Ancestor headers across the whole proof | 4,096 | | Merkle leaf count | 100,000 | @@ -354,23 +396,24 @@ become trusted mode. | Bootstrap records | 16 | | Record leaf | 4,096 bytes | -These limits bound individual requests, not the duration of history. Certificate -availability and quorum cadence determine achievable history per request. +These limits apply to each response. The amount of history that fits depends on +how often quorums change and which ChainLocks are available. ## Validation The [Core test vector][core-vector] supplies a trusted checkpoint, proof bytes, and expected target at testnet height 1,549,547. The proof is 3,469 bytes; -the [matching bootstrap][rust-fixture] is 4,506 bytes with one quorum and one -EvoNode opening. [Core tests][core-tests] and [Rust tests][rust-tests] exercise -valid verification and rejection of altered or malformed evidence. +the [matching bootstrap response][rust-fixture] is 4,506 bytes with one quorum +record, one EvoNode record, and their Merkle paths. [Core tests][core-tests] and +[Rust tests][rust-tests] check valid proofs and reject altered or malformed ones. [Archive measurements][archive-results] cover 90, 180, and 366 days on both networks. [Native SDK integration tests][stack-results] verify live Platform queries and year-long histories through Core and a quorum server. The year-long -bootstraps were 175,781 bytes on mainnet and 343,014 bytes on testnet, each with -one quorum and four EvoNode openings, before compression. These observations are -not worst-case bounds or guarantees of history coverage. +bootstrap responses were 175,781 bytes on mainnet and 343,014 bytes on testnet +before compression. Each included one quorum record, four EvoNode records, and +their Merkle paths. These measurements are not worst-case size bounds or +guarantees of history coverage. [core-vector]: https://github.com/PastaPastaPasta/dash/blob/9f67367df634/test/functional/data/quorum_proof.json [core-tests]: https://github.com/PastaPastaPasta/dash/blob/378d0fb22c28/src/test/quorum_proofs_tests.cpp @@ -381,26 +424,23 @@ not worst-case bounds or guarantees of history coverage. ## Security Considerations -An attacker controlling all relays can withhold evidence, replay sufficiently -recent valid evidence, or exhaust a client's bounded request budget. Successful -verification establishes authenticity under the trust model, not that the target -is the globally newest block. Platform signed-time/height freshness policy and -caller minimum heights are required. Unauthenticated metadata must not advance a -freshness ratchet. - -The design does not protect against compromise of enough historical quorum keys -to forge this certificate chain. Stronger guarantees require a stronger trust -model or additional consensus evidence and have different size costs. - -Snapshots are release trust material. Their hashes and roots require independent -release verification and network binding. Updating a snapshot from an unverified -HTTP response defeats the design. Persistent caches, if implemented, require the -same provenance and integrity protections as their original trust configuration. - -No headers are treated as authenticated merely because a matching quorum key is -known. Every accepted statement is covered by the certificate chain under the -historical quorum honesty assumption above. Full state validity and exact signer -eligibility are deliberately outside this proof's statement. +A server can withhold proofs or replay valid older proofs. An attacker +controlling all proof servers can also use up the client's request budget. +Verification proves authenticity under the trust model above; it does not prove +that the target is the newest block. Clients must enforce minimum heights and +Platform freshness checks using signed times and heights. Unverified metadata +must never advance those stored values. + +If an attacker compromises enough historical quorum keys, they can forge a +certificate chain. This design relies on historical quorums remaining honest; +it does not independently check full Core state validity or each signer's +eligibility. Merely knowing a quorum key never authenticates a header: its +certificate and the preceding proof chain must pass verification. + +Release snapshots must be independently verified and tied to the correct network. +Accepting a new snapshot from an unverified server response defeats the design. +Any saved verified state must remain tied to the trusted snapshot it came from +and retain the same integrity and network checks. ## Copyright From 3c59cff6a619207f99a9b1970ef107c087f87203 Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 10 Sep 2026 12:38:08 -0500 Subject: [PATCH 13/13] docs(dip): include self-contained compact proof test vector --- dip-pasta-compact-quorum-proofs.md | 36 ++++++++---------- dip-pasta-compact-quorum-proofs/bootstrap.bin | Bin 0 -> 4506 bytes .../test-vector.json | 24 ++++++++++++ 3 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 dip-pasta-compact-quorum-proofs/bootstrap.bin create mode 100644 dip-pasta-compact-quorum-proofs/test-vector.json diff --git a/dip-pasta-compact-quorum-proofs.md b/dip-pasta-compact-quorum-proofs.md index 0dec3785..d9e69477 100644 --- a/dip-pasta-compact-quorum-proofs.md +++ b/dip-pasta-compact-quorum-proofs.md @@ -401,26 +401,22 @@ how often quorums change and which ChainLocks are available. ## Validation -The [Core test vector][core-vector] supplies a trusted checkpoint, proof bytes, -and expected target at testnet height 1,549,547. The proof is 3,469 bytes; -the [matching bootstrap response][rust-fixture] is 4,506 bytes with one quorum -record, one EvoNode record, and their Merkle paths. [Core tests][core-tests] and -[Rust tests][rust-tests] check valid proofs and reject altered or malformed ones. - -[Archive measurements][archive-results] cover 90, 180, and 366 days on both -networks. [Native SDK integration tests][stack-results] verify live Platform -queries and year-long histories through Core and a quorum server. The year-long -bootstrap responses were 175,781 bytes on mainnet and 343,014 bytes on testnet -before compression. Each included one quorum record, four EvoNode records, and -their Merkle paths. These measurements are not worst-case size bounds or -guarantees of history coverage. - -[core-vector]: https://github.com/PastaPastaPasta/dash/blob/9f67367df634/test/functional/data/quorum_proof.json -[core-tests]: https://github.com/PastaPastaPasta/dash/blob/378d0fb22c28/src/test/quorum_proofs_tests.cpp -[rust-fixture]: https://github.com/PastaPastaPasta/platform/blob/e243ea60c856/packages/rs-core-proof/tests/data/bootstrap.bin -[rust-tests]: https://github.com/PastaPastaPasta/platform/blob/e243ea60c856/packages/rs-core-proof/tests/verification.rs -[archive-results]: https://github.com/PastaPastaPasta/dash/blob/7e7be9bbf4b0/doc/benchmarks/quorum-proof-2026-09-09/README.md -[stack-results]: https://github.com/PastaPastaPasta/dash/blob/7e7be9bbf4b0/doc/benchmarks/quorum-proof-full-stack-2026-09-09/README.md +The [test vector](dip-pasta-compact-quorum-proofs/test-vector.json) provides a +trusted checkpoint, minimum height, and expected target and record kinds for the +[bootstrap response](dip-pasta-compact-quorum-proofs/bootstrap.bin) stored with +this DIP. JSON hashes use RPC display order. Verification must succeed at testnet +height 1,549,547 and return one quorum record and one masternode record, in that +order. It must fail if the minimum height is raised above the target, the trusted +quorum root is changed, or the response's last byte is changed. + +The response is 4,506 bytes. Its first four bytes give the length of the embedded +proof. The next 3,469 bytes are that proof and can also be tested on their own +against the same checkpoint and expected target. + +Measured year-long responses, including one quorum record and four EvoNode +records, were 175,781 bytes on mainnet and 343,014 bytes on testnet before +compression. These are examples, not size guarantees. Implementation tests and +benchmark details accompany [Core PR #7107](https://github.com/dashpay/dash/pull/7107). ## Security Considerations diff --git a/dip-pasta-compact-quorum-proofs/bootstrap.bin b/dip-pasta-compact-quorum-proofs/bootstrap.bin new file mode 100644 index 0000000000000000000000000000000000000000..e95535736b6a6728fd5597a60fcaf26e305e1864 GIT binary patch literal 4506 zcmai%XEdB$yT@k;LUhI;W1~E~1N0h#Dm!daof$5G7(5qIZJmQG@6udK)Cs zMMfvgJ3MdB`SP4~uC?~Pzx?m3?cd&O?^qH5Kwefy`4OB?0QfBn45-sf*GA|oO#Tk| ziu0r>Eamh!&<&m4OruzOA5jDV0GNWtp&0`{w=6qsW_w3~X&luGp(wwBbf@+NsTjmT z8>A%d0Mj>Mmnkz8$aD2nPZHxB%e3XYK3X>0m$i*7lB` zp$5wYdTu*%lDcLM&C|#NY#0H|wc-Aqs}YL3J#>`j{z-KlHW_7E2#V8?-3ig^Ewue( z1-ZCL*efr8LKmrV@yRGRsv4~qspx>(jzojX05aK{hlu$XL1@{wk7RW!gE)vwbS4g8 zp(Z*;?g;}&_d6d&da-I{5u!kulkLxQjvD;bJJP7Emt!PxrNP<;#B+^qOkKh>yA9{Vxp*<(#v7g!DU6LBDSMBG9d0B4*%@Pa2#xpoUnu7R^w$-)nr2t6w98FP$bT_`|MI+2XJSBG{r>kWu1{_7mUX8eYc z)SxO@&>(p2m$#go8%K<(`&?LkY*m9mQAp6uw(D&+r{EPnk6+m@OG0; z^+$@}NHw5XDh>&6!N7(nSt~d`Z7COHFPZ&b&Cq^xCQG0&W7YWH66Z4}!CST#g*L$O zvH5*3#fKX7vZo)Uqb+hfrXfi@Uv7PRdRtt5+UsC>%;obM&92?zl%kFDlrJ}qA3FaH zbveU9L`2tbd0fCMmP)KKV%7`Ywanw49sxTm#%?yx5-+x5l}DQCFv2ENT+lmeDH!o$BO^be6HI_?4k0WSt>c?O)*k&avD?T2-)7wL&s_T;`wvYkuOpohC-9 z)08FU-JU5>a}lKFj5xGQ!LGX7)37Y%pUDuNNNS2vHD?_j7$rl==*pRt5j4 zfW|h_mZP}b4d5gwG(~Hn6tV@&tFml`xWMUdO85I7W+&l-4(Ow4786%pJ*jOVA=(n6 zzv%`p?p21^?bk2_F$UA{C4f(RHYFvkK0NyDhg1LFsO-%qD~rdsSb}l=j9sc2*+hTE|R)15FU}k4;NOEkvKfYh-9=5T78`y#4-6t$f zvH@C6wzW^iQM0%&roSPROSOY=VIVgBwSY&Kmn6S87e9Zl`nl;DQ*g6LMY}`{I1As7 ze5aZ9P3D7H008pgAFx!kkTGpsQR$ZZGZP`z-WB6y0>yXWYH;^^VVx$58rv68NVX4&Mg>P)0>kKnWP>&aB(W1USUYEUrNN z7RcvLf-wJ(*ca;0tae1#Oh0|3}Qj=Yi zC6D-Mj_X`z^i-rZIbe75tUy($;7^kRvM{m7X8e$)R z$qQZcwV8-g;M)R>`_26eSmneOMiD_4UB5cy!}kukK-!}_(PCsf-(`M8TFNzs%-12z z=Bw!mQE_D`NyqX}BmnlQV78RPASbk7{qyJZ?ucB0tclWSao;7A;5^f>y1Dn0-6yIu z2-AH)=Vc;uj*Db_tp-I&$$UjJFclE7fV6fDkt``rn@6N__|`y}Gi{oQV*%(c>$`9~ zUEZVqE>nRvWNp{V7uEjb@v+W$si*W)#+9`{L-%1(7h6C<#RNS$)gD%_y9t5p&v&M7 zg!Rh%d4sFqltw^hXFm1yMA}Q^)S%Kj!O59kN?*IzKXC+eLK>^2I-S{1DgqsYs^(!k zT#xcl>->pB_63upY0KntdSH!*P4#83b?-{QQu!aS2oo%ymay8i&tR0TI3)~R4R~f| zAVQDlBAyH?h|Jox*<22b-Wi5a6-<+_0Kkk9MY;AXd8>p*GS$o}N-u7jTsZ}C6@65|_1~C_7>*T|$ zJ(0p=?oytbWgEO#r0u0ep45&$Q$MllVqcRSyXCy<#z)GDbeItXLfcG90SUt;6vZ-? zV=yI}`>sP|1ajVs{GCJWjwJZC#q4Rz^^wFUd@&J1L!CiXY_i*z+_YZbV1vso?|k1j z^B7)Gg4j>FE0^#_sdv>Wm`A<|+r)&m7`oCJOSMqmdCsOze*0fQg=5RWf;x7`;dQNU zVMHP;evun_@Y<~D4)sAPZ)VlD^nxe0ioYk{-#IvXqG!OKTJApGQQvU^Tmt&dYBvDVs zOWj?aT1gwk&3K>$3rde$zO&aK6|t#nXpVNp)j~ATPeC0?tPN$43zRYUZ8!=rc($dce=;ov_`H#>{Z}4I%vk(v^iNgUbpn zEn^eBo2r?1RknxzVe{~v6zRnPp=s|LMk6{2zH#&U%|+Tz_}Rq>IutBkd5@?$Wjd6w zXwON`Bdu?jGdZo!@?Y#8Ycj#)NMcl>63Jn?6tP^5Rg-gSK3T+CGus^@1F z4s@1ju=?S+b!MEIa?tGUF`DO_K&~66iV`nm+Fs*|r1wG1gF$0rS4aUEC8?z(h`B7- zF7C9iTZ>GnIhM_}m<;6UkdO+v#A9=@+ke*>Cdc|QrAluG|Y%l;m< z&pFR8BX*<%#uy0Y3ixMZ$e1QYFa=59-*jewS(-V7GcV)x;O7@tmfq_N;LIZ?0&k`hSE z-9$^FUj8T41&F6vNh4pub9|$FY5CXRTyK(!t3l%`fKScd!#uoJh zOoS)z0&FaKm6Hs1B0o4{eP+ccqVQXUL(WPH@IAHRZC!T9h%)*3k0^%tFl~Amx$56KCU|gr=OqPlquZ zhl7*bM52@Q0>$YiFObEcNcwE)MGlkSg}WEFQLUE@N83t<%edwwCDJT-K!>9BmxsUb z1+dG6iv{d@wXQnyf7#Uq9q4fN#jRJ1!M&2vc@$CCL{S)M^$~ntthMV4N9s=&Q&-v9U7(D1R0 zLD)S=HzkTN^u#z3gsfnCz28B*zn}XRXLzVs0ctaYs-iU31kXm-`7NIFYoJ&gq|$x0 z8jmE{<;JIzl6Td*5q8_mW&y=?V7f4u5n>WurW^j~8lFyylO06nyRL+d*Gbk>f{Tn-8UOcMQZ49gSkr6o0Gz2#+@H2CAyC8?7{;gX2@ zWxh_D0Na+kx|)AnK&S?}d*prkA}yN}m2x$Cyd^!}{Jd!|-u`Z-D6t^xO2~|2Y|yRg zggAm54%6jyB<{*b@KkhM)$Jd)&U^cU{kJ}~!Wt~OJ0H&RQ+58T9CZ@Ue4Qlx40CBB z%ci3dKLzQ8jGD76KT|GnEivadth7uIaasbTU2# zCb`2{a9(`BvoXCgf_jf2d(UgOJaPQv{i^LT*`&0yOr`+~G~LEPO?c&xw_H=;)wy;3 zHX^^7hCD`@HM!odmO)6=EUanEJvAD86t3MRHjLDr zu9Op+y&zW~jgpuAG~8VMQI|G0L2rp)3?m|IDb`Dxm8sw(wLHfOhG-Jt)z?u$~oZ?8fyAI!9 zlx=ma{U!*Tt@<51cM{^4=JXS>6QuZDV}EzV_=rT_dKP&5?wysi)6S6KVV%a#nZ?_N z#=d50RtyH^`IQQ5q9oSG3ueFyY^6lTfz)2{JbfRdg>D?KWyUj>Hk6FhJ5wdbuxfr| z5BpShXrF?5BmyTwyVkc=?J!LEK2h}46Zj$`v|alN`~UwM1|tvm!GCn8K3nsr5_d;% zEl6}BXT+0TDFRI}V)CeWoVqqrTA)e|MBuxR7&cARec&>(%M#^<^8^0Q3w}PDysokT zAwsl-nSUl4`{2pJGprTO8smz^blVRg|VhJboQ#`!c$DB9j8J!@j zjeEg~`6YC2U2WYyG&QPYa?q>w`pe3Wfj<;(6p6Z%#+w{zQSdPQ(A`>#+?VhD$RlE9 zkIF2W_+3Fw%d=Y5$h2*(Qbp9ECoCvz_iDKIu z(VF}7$4+aQlyYy#0v;QINEJ{@d<+x-iZ!{)&C=k=lw9t{)64$=+%hKj literal 0 HcmV?d00001 diff --git a/dip-pasta-compact-quorum-proofs/test-vector.json b/dip-pasta-compact-quorum-proofs/test-vector.json new file mode 100644 index 00000000..76f27b0b --- /dev/null +++ b/dip-pasta-compact-quorum-proofs/test-vector.json @@ -0,0 +1,24 @@ +{ + "bootstrap_file": "bootstrap.bin", + "checkpoint": { + "network": 1, + "height": 1548500, + "block_hash": "000000a99c2dac4616bca1f27301f1f99684a96102c97ef1d645569c529b55b7", + "masternode_root": "c0f49c8c3d91c16d9bcc7ce08345b3279901d0c8da66c2620b7acc9d1ed38021", + "quorum_root": "ba4b901be120adc613b38914a09f3745de1112fa9b49d34d6b435643fd99ab1c" + }, + "minimum_height": 1549547, + "expected": { + "target": { + "network": 1, + "height": 1549547, + "block_hash": "0000006c4949598a2dab62d679834121646a225fc6e8cd44c39d8ee9f8c19771", + "masternode_root": "c0f49c8c3d91c16d9bcc7ce08345b3279901d0c8da66c2620b7acc9d1ed38021", + "quorum_root": "6349769daa7cfbe4c61e9879c8ffe282076dff7759860a5d9b3a689c8c627a4f" + }, + "record_kinds": [ + 0, + 1 + ] + } +}