mirror of
https://github.com/vacp2p/rfc-index.git
synced 2026-01-09 22:08:07 -05:00
Created nomos/raw/nomos-proof-of-quota.md file
This commit is contained in:
437
nomos/raw/nomos-proof-of-quota.md
Normal file
437
nomos/raw/nomos-proof-of-quota.md
Normal file
@@ -0,0 +1,437 @@
|
||||
---
|
||||
title: NOMOS-PROOF-OF-QUOTA
|
||||
name: Nomos Proof of Quota Specification
|
||||
status: raw
|
||||
category: Standards Track
|
||||
tags: cryptography, zero-knowledge, blend, quota, rate-limiting
|
||||
editor: Mehmet Gonen <mehmet@status.im>
|
||||
contributors:
|
||||
- Marcin Pawlowski <marcin@status.im>
|
||||
- Thomas Lavaur <thomaslavaur@status.im>
|
||||
- Youngjoon Lee <youngjoon@status.im>
|
||||
- David Rusu <davidrusu@status.im>
|
||||
- Álvaro Castro-Castilla <alvaro@status.im>
|
||||
- Filip Dimitrijevic <filip@status.im>
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
This document defines an implementation-friendly specification of the Proof of Quota (PoQ), which ensures that there is a limited number of message encapsulations that a node can perform, thereby constraining the number of messages a node can introduce to the Blend network.
|
||||
|
||||
## Overview
|
||||
|
||||
The PoQ ensures that there is a limited number of message encapsulations that a node can perform. This constrains the number of messages a node can introduce to the Blend network. The mechanism regulating these messages is similar to rate-limiting nullifiers.
|
||||
|
||||
The proof verifies that a node's public key is within a limit for either a core node or a leader node.
|
||||
|
||||
## Document Structure
|
||||
|
||||
This specification is organized into two distinct parts to serve different audiences and use cases:
|
||||
|
||||
**Protocol Specification** contains the normative requirements necessary for implementing an interoperable Blend Protocol node. This section defines the cryptographic primitives, message formats, network protocols, and behavioral requirements that all implementations MUST follow to ensure compatibility and maintain the protocol's privacy guarantees. Protocol designers, auditors, and those seeking to understand the core mechanisms should focus on this part.
|
||||
|
||||
**Implementation Considerations** provides non-normative guidance for implementers. This section offers practical recommendations, optimization strategies, and detailed examples that help developers build efficient and robust implementations. While these details are not required for interoperability, they represent best practices learned from reference implementations and can significantly improve performance and reliability.
|
||||
|
||||
## Protocol Specification
|
||||
|
||||
This section defines the normative cryptographic protocol requirements for the Proof of Quota.
|
||||
|
||||
### Construction
|
||||
|
||||
The Proof of Quota (PoQ) consists of two parts:
|
||||
|
||||
1. **Proof of Core Quota (PoQ_C)**: Ensures that the core node is declared and hasn't already produced more keys than the core quota Q_C.
|
||||
|
||||
2. **Proof of Leadership Quota (PoQ_L)**: Ensures that the leader node would win the proof of stake for current Cryptarchia epoch and hasn't already produced more keys than the leadership quota Q_L. This doesn't guarantee that the node is indeed winning because the PoQ doesn't check if the note is unspent, enabling generation of the proof ahead of time preventing extreme delays.
|
||||
|
||||
**Validity**: The final proof PoQ is valid if either PoQ_C or PoQ_L holds.
|
||||
|
||||
### Zero-Knowledge Proof Statement
|
||||
|
||||
#### Public Values
|
||||
|
||||
A proof attesting that for the following public values derived from blockchain parameters:
|
||||
|
||||
```python
|
||||
class ProofOfQuotaPublic:
|
||||
session: int # Session number (uint64)
|
||||
core_quota: int # Allowed messages per session for core nodes (20 bits)
|
||||
leader_quota: int # Allowed messages per session for potential leaders (20 bits)
|
||||
core_root: zkhash # Merkle root of zk_id of the core nodes
|
||||
K_part_one: int # First part of the signature public key (16 bytes)
|
||||
K_part_two: int # Second part of the signature public key (16 bytes)
|
||||
pol_epoch_nonce: int # PoL Epoch nonce
|
||||
pol_t0: int # PoL constant t0
|
||||
pol_t1: int # PoL constant t1
|
||||
pol_ledger_aged: zkhash # Merkle root of the PoL eligible notes
|
||||
|
||||
# Outputs:
|
||||
key_nullifier: zkhash # Derived from session, private index and private sk
|
||||
```
|
||||
|
||||
**Field Descriptions:**
|
||||
|
||||
- `session`: Unique session identifier for temporal partitioning
|
||||
- `core_quota`: Maximum number of message encapsulations allowed per session for core nodes (20-bit value)
|
||||
- `leader_quota`: Maximum number of message encapsulations allowed per session for potential leaders (20-bit value)
|
||||
- `core_root`: Root of Merkle tree containing zk_id values of all declared core nodes
|
||||
- `K_part_one`, `K_part_two`: Split representation of one-time signature public key (32 bytes total)
|
||||
- `pol_epoch_nonce`: Proof of Leadership epoch nonce for lottery
|
||||
- `pol_t0`, `pol_t1`: Proof of Leadership threshold constants
|
||||
- `pol_ledger_aged`: Root of Merkle tree containing eligible Proof of Leadership notes
|
||||
- `key_nullifier`: Output nullifier preventing key reuse within a session
|
||||
|
||||
#### Witness
|
||||
|
||||
The prover knows a witness:
|
||||
|
||||
```python
|
||||
class ProofOfQuotaWitness:
|
||||
index: int # Index of the generated key (20 bits)
|
||||
selector: bool # Indicates if it's a leader (=1) or core node (=0)
|
||||
|
||||
# This part is filled randomly by potential leaders
|
||||
core_sk: zkhash # sk corresponding to the zk_id of the core node
|
||||
core_path: list[zkhash] # Merkle path proving zk_id membership (len = 20)
|
||||
core_path_selectors: list[bool] # Indicates how to read the core_path
|
||||
|
||||
# This part is filled randomly by core nodes
|
||||
pol_sl: int # PoL slot
|
||||
pol_sk_starting_slot: int # PoL starting slot of the slot secrets
|
||||
pol_note_value: int # PoL note value
|
||||
pol_note_tx_hash: zkhash # PoL note transaction
|
||||
pol_note_output_number: int # PoL note transaction output number
|
||||
pol_noteid_path: list[zkhash] # PoL Merkle path proving noteID membership (len = 32)
|
||||
pol_noteid_path_selectors: list[bool] # Indicates how to read the note_path
|
||||
pol_slot_secret: int # PoL slot secret corresponding to sl
|
||||
pol_slot_secret_path: list[zkhash] # PoL slot secret Merkle path (len = 25)
|
||||
```
|
||||
|
||||
**Witness Field Descriptions:**
|
||||
|
||||
- `index`: The index of the generated key. Limiting this index limits the maximum number of keys generated (20 bits enables up to 2^20 = 1,048,576 messages per node per session)
|
||||
- `selector`: Boolean flag indicating node type (1 for leader, 0 for core node)
|
||||
- `core_sk`: Secret key corresponding to the core node's zk_id
|
||||
- `core_path`: Merkle authentication path for core node membership
|
||||
- `core_path_selectors`: Navigation bits for Merkle path (left/right)
|
||||
- `pol_*`: Proof of Leadership witness fields (filled randomly by core nodes)
|
||||
|
||||
**Note**: All inputs and outputs of zero-knowledge proofs are scalar field elements.
|
||||
|
||||
### Constraints
|
||||
|
||||
The following constraints MUST hold for a valid proof:
|
||||
|
||||
#### Step 1: Index Selection and Quota Limitation
|
||||
|
||||
The prover selects an index for the chosen key. This index MUST be lower than the allowed quota and not already used. This index is used to derive the key nullifier in Step 4.
|
||||
|
||||
**Purpose**: Limiting the possible values of this index limits the possible nullifiers created, which produces the desired effect of limiting the generation of keys to a certain quota.
|
||||
|
||||
**Specification**: `index` is 20 bits, enabling up to 2^20 messages per node per session.
|
||||
|
||||
#### Step 2: Core Node Verification
|
||||
|
||||
If the prover indicated that the node is a core node for the proof (`selector = 0`), the proof checks that:
|
||||
|
||||
1. **Core Node Registration**: The core node is registered in the set N = SDP(session). This is proven by demonstrating knowledge of a `core_sk` that corresponds to a declared `zk_id`, which is a valid SDP registry for the current session.
|
||||
|
||||
- The `zk_id` values are stored in a Merkle tree with a fixed depth of 20
|
||||
- The root is provided as a public input
|
||||
- To build the Merkle tree, `zk_id` values are ordered from smallest to biggest (when seen as natural numbers between 0 and p)
|
||||
- Remaining empty leaves are represented by 0 after the sorting (appended at the end of the vector)
|
||||
- This structure supports up to 1M validators
|
||||
|
||||
2. **Index Validity**: The index MUST satisfy: `index < core_quota`
|
||||
|
||||
#### Step 3: Leader Node Verification
|
||||
|
||||
If the prover indicated that the node is a potential leader node for the proof (`selector = 1`), the proof checks that:
|
||||
|
||||
1. **Leadership Lottery**: The leader node possesses a note that would win a slot in the consensus lottery. Unlike leadership conditions, the proof of quota doesn't verify that the note is unspent. This enables potential provers to generate the PoQ well in advance. All other lottery constraints are the same as in Circuit Constraints.
|
||||
|
||||
2. **Index Validity**: The index MUST satisfy: `index < leader_quota`
|
||||
|
||||
#### Step 4: Key Nullifier Derivation
|
||||
|
||||
The prover derives a `key_nullifier` maintained by blend nodes during the session for message deduplication purposes:
|
||||
|
||||
```python
|
||||
selection_randomness = zkhash(b"SELECTION_RANDOMNESS_V1", sk, index, session)
|
||||
key_nullifier = zkhash(b"KEY_NULLIFIER_V1", selection_randomness)
|
||||
```
|
||||
|
||||
Where `sk` is:
|
||||
|
||||
- The `core_sk` as defined in the Mantle specification if the node is a core node
|
||||
- The secret key of the PoL note if it's a leader node derived from inputs
|
||||
|
||||
**Rationale**: Two hashes are used because the selection randomness is used in the Proof of Selection to prove the ownership of a valid PoQ.
|
||||
|
||||
#### Step 5: One-Time Signature Key Attachment
|
||||
|
||||
The prover attaches a one-time signature key used in the blend protocol. This public key is split into two 16-byte parts: `K_part_one` and `K_part_two`.
|
||||
|
||||
**Encoding**: When written in little-endian byte order, the complete public key equals the concatenation `K_part_one || K_part_two`.
|
||||
|
||||
### Circuit Implementation
|
||||
|
||||
```python
|
||||
# Verify selector is a boolean
|
||||
# selector = 1 if it's a potential leader and 0 if it's a core node
|
||||
selector * (1 - selector) == 0 # Check that selector is indeed a bit
|
||||
|
||||
# Verify index is lower than quota
|
||||
# Equivalent to: index < leader_quota if selector == 1
|
||||
# or index < core_quota if selector == 0
|
||||
index < selector * (leader_quota - core_quota) + core_quota
|
||||
|
||||
# Check if it's a registered core node
|
||||
zk_id = zkhash(b"NOMOS_KDF", core_sk)
|
||||
is_registered = merkle_verify(core_root, core_path, core_path_selectors, zk_id)
|
||||
|
||||
# Check if it's a potential leader
|
||||
is_leader = would_win_leadership(
|
||||
pol_epoch_nonce,
|
||||
pol_t0,
|
||||
pol_t1,
|
||||
pol_ledger_aged,
|
||||
pol_sl,
|
||||
pol_sk_starting_slot,
|
||||
pol_sk_secrets_root,
|
||||
pol_note_value,
|
||||
pol_note_tx_hash,
|
||||
pol_note_output_number,
|
||||
pol_noteid_path,
|
||||
pol_noteid_path_selectors,
|
||||
pol_slot_secret,
|
||||
pol_slot_secret_path
|
||||
)
|
||||
|
||||
# Verify that it's a core node or a leader
|
||||
assert(selector * (is_leader - is_registered) + is_registered == 1)
|
||||
|
||||
# Get leader note secret key
|
||||
pol_sk_secrets_root = get_merkle_root(pol_sk_starting_slot, sl, pol_slot_secret_path)
|
||||
pol_note_sk = zkhash(b"NOMOS_POL_SK_V1", pol_sk_starting_slot, pol_sk_secrets_root)
|
||||
|
||||
# Derive nullifier
|
||||
selection_randomness = zkhash(
|
||||
b"SELECTION_RANDOMNESS_V1",
|
||||
selector * (pol_note_sk - core_sk) + core_sk,
|
||||
index,
|
||||
session
|
||||
)
|
||||
key_nullifier = zkhash(b"KEY_NULLIFIER_V1", selection_randomness)
|
||||
```
|
||||
|
||||
### Proof Compression
|
||||
|
||||
The proof confirming that the PoQ is correct MUST be compressed to a size of 128 bytes.
|
||||
|
||||
**Uncompressed Format**: The UncompressedProof comprises 2 G1 and 1 G2 BN256 elements:
|
||||
|
||||
```python
|
||||
class UncompressedProof:
|
||||
pi_a: G1 # BN256 element
|
||||
pi_b: G2 # BN256 element
|
||||
pi_c: G1 # BN256 element
|
||||
```
|
||||
|
||||
**Compression Requirements**:
|
||||
|
||||
- Compressed size: 128 bytes
|
||||
- Curve: BN256 (also known as BN254 or alt_bn128)
|
||||
- Compression MUST preserve proof validity
|
||||
|
||||
### Proof Serialization
|
||||
|
||||
The ProofOfQuota structure contains `key_nullifier` and the compressed proof transformed into bytes.
|
||||
|
||||
```python
|
||||
class ProofOfQuota:
|
||||
key_nullifier: zkhash # 32 bytes
|
||||
proof: bytes # 128 bytes
|
||||
```
|
||||
|
||||
**Serialization Format**:
|
||||
|
||||
1. Transform `key_nullifier` into 32 bytes
|
||||
2. Compress proof to 128 bytes
|
||||
3. Concatenate: `key_nullifier || proof`
|
||||
4. Total size: 160 bytes
|
||||
|
||||
**Deserialization**:
|
||||
|
||||
Interpret the 160-byte sequence as:
|
||||
|
||||
- Bytes 0-31: `key_nullifier`
|
||||
- Bytes 32-159: `proof`
|
||||
|
||||
### Security Considerations
|
||||
|
||||
#### Quota Enforcement
|
||||
|
||||
- Implementations MUST track `key_nullifier` values during each session
|
||||
- Duplicate `key_nullifier` values MUST be rejected
|
||||
- Session transitions MUST clear the nullifier set
|
||||
|
||||
#### Proof Verification
|
||||
|
||||
- All Merkle path verifications MUST be performed
|
||||
- The `selector` bit MUST be verified as boolean (0 or 1)
|
||||
- Index bounds MUST be strictly enforced
|
||||
- Implementations MUST reject proofs where neither core nor leader conditions hold
|
||||
|
||||
#### Cryptographic Assumptions
|
||||
|
||||
- Relies on soundness of the underlying zk-SNARK system
|
||||
- Assumes collision resistance of `zkhash` function
|
||||
- Assumes computational Diffie-Hellman assumption on BN256 curve
|
||||
|
||||
#### Note Unspent Condition
|
||||
|
||||
- **Critical**: The PoQ does NOT verify that Proof of Leadership notes are unspent
|
||||
- This allows pre-generation of proofs to avoid delays
|
||||
- Implementations SHOULD implement additional checks for actual leadership
|
||||
|
||||
## Implementation Considerations
|
||||
|
||||
This section provides guidance for implementing the Proof of Quota protocol.
|
||||
|
||||
### Proof Generation
|
||||
|
||||
**Performance Characteristics**:
|
||||
|
||||
Implementations SHOULD consider:
|
||||
|
||||
- Proof generation is computationally intensive
|
||||
- Pre-generation is recommended for leader nodes
|
||||
- Witness preparation involves Merkle path computation
|
||||
|
||||
### Proof Verification Implementation
|
||||
|
||||
**Verification Steps**:
|
||||
|
||||
1. Deserialize proof into `key_nullifier` and `proof` components
|
||||
2. Verify proof size (160 bytes total)
|
||||
3. Check `key_nullifier` against session nullifier set
|
||||
4. Verify zk-SNARK proof with public inputs
|
||||
5. Add `key_nullifier` to session set if valid
|
||||
|
||||
### Merkle Tree Construction
|
||||
|
||||
#### Core Nodes Merkle Tree
|
||||
|
||||
**Specification**:
|
||||
|
||||
- Depth: 20 levels
|
||||
- Leaf values: `zk_id` of declared core nodes
|
||||
- Ordering: Ascending numerical order (as natural numbers 0 to p)
|
||||
- Empty leaves: Represented by 0, appended after sorted values
|
||||
- Capacity: 2^20 = 1,048,576 validators
|
||||
|
||||
**Construction Algorithm**:
|
||||
|
||||
```python
|
||||
def build_core_tree(zk_ids: list[int]) -> MerkleTree:
|
||||
# Sort zk_ids in ascending order
|
||||
sorted_ids = sorted(zk_ids)
|
||||
|
||||
# Pad to 2^20 with zeros
|
||||
padded = sorted_ids + [0] * (2**20 - len(sorted_ids))
|
||||
|
||||
# Build Merkle tree
|
||||
return MerkleTree(padded, depth=20)
|
||||
```
|
||||
|
||||
#### PoL Ledger Merkle Tree
|
||||
|
||||
**Specification**:
|
||||
|
||||
- Depth: 32 levels
|
||||
- Leaf values: Note IDs of eligible PoL notes
|
||||
- Purpose: Prove note membership in aged ledger
|
||||
|
||||
### Session Management
|
||||
|
||||
**Session Lifecycle**:
|
||||
|
||||
1. **Session Start**:
|
||||
- Initialize empty nullifier set
|
||||
- Load current session parameters (quotas, roots)
|
||||
- Prepare session number for proofs
|
||||
|
||||
2. **During Session**:
|
||||
- Verify incoming proofs
|
||||
- Track nullifiers in set
|
||||
- Reject duplicate nullifiers
|
||||
|
||||
3. **Session End**:
|
||||
- Clear nullifier set
|
||||
- Archive session data
|
||||
- Transition to next session
|
||||
|
||||
### Best Practices
|
||||
|
||||
#### Nullifier Set Management
|
||||
|
||||
- Use efficient data structure (hash set or Bloom filter with fallback)
|
||||
- Implement atomic operations for nullifier insertion
|
||||
- Consider memory constraints for long sessions
|
||||
|
||||
#### Pre-Generation Strategy
|
||||
|
||||
For leader nodes:
|
||||
|
||||
- Generate proofs before slot assignment
|
||||
- Cache proofs for multiple indices
|
||||
- Monitor note status separately from PoQ
|
||||
|
||||
#### Error Handling
|
||||
|
||||
Implementations SHOULD handle:
|
||||
|
||||
- Invalid proof format
|
||||
- Duplicate nullifiers
|
||||
- Index out of bounds
|
||||
- Merkle path verification failures
|
||||
- Invalid selector values
|
||||
|
||||
### Performance Benchmarks
|
||||
|
||||
**Reference Hardware**:
|
||||
|
||||
- CPU: 13th Gen Intel(R) Core(TM) i9-13980HX (24 cores / 32 threads)
|
||||
- RAM: 32GB - Speed: 5600 MT/s
|
||||
- Motherboard: Micro-Star International Co., Ltd. MS-17S1
|
||||
- OS: Ubuntu 22.04.5 LTS
|
||||
- Kernel: 6.8.0-59-generic
|
||||
|
||||
**Expected Performance** (implementation-dependent):
|
||||
|
||||
- Proof generation: TBD
|
||||
- Proof verification: TBD
|
||||
- Merkle path computation: TBD
|
||||
|
||||
## References
|
||||
|
||||
### Normative
|
||||
|
||||
- Proof of Quota (original paper/specification)
|
||||
- Service Declaration Protocol (SDP)
|
||||
- Mantle Specification
|
||||
- Circuit Constraints (Cryptarchia)
|
||||
- Proof of Selection
|
||||
- rate-limiting nullifiers
|
||||
|
||||
### Informative
|
||||
|
||||
- BN256 Curve Specification
|
||||
- zk-SNARKs (Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge)
|
||||
- [Cryptarchia Consensus](https://arxiv.org/abs/2402.06408)
|
||||
- Merkle Trees and Authentication Paths
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
Reference in New Issue
Block a user