From 2c315fcb4926350d829be1bd9937c6a256479a32 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 17 Jan 2026 01:50:35 -0600 Subject: [PATCH] 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)