Add Cryptarchia Proof of Leadership Specification

This commit is contained in:
Cofson
2025-12-13 11:13:30 +01:00
parent b2f35644a4
commit d7d83be70d
2 changed files with 523 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,523 @@
---
title: CRYPTARCHIA-PROOF-OF-LEADERSHIP
name: Cryptarchia Proof of Leadership Specification
status: raw
category: Standards Track
tags: nomos, cryptarchia, proof-of-leadership, zero-knowledge, consensus
editor: Thomas Lavaur <thomas@status.im>
contributors:
- Mehmet <mehmet@status.im>
- Giacomo Pasini <giacomo@status.im>
- Daniel Sanchez Quiros <daniel@status.im>
- Álvaro Castro-Castilla <alvaro@status.im>
- David Rusu <david@status.im>
---
## Abstract
The Proof of Leadership (PoL) enables a leader to produce a zero-knowledge proof
attesting to the fact that they have an eligible note
that has won the leadership lottery.
This proof MUST be as lightweight as possible to generate and verify,
to impose minimal restrictions on access to the role of leader
and maximize the decentralization of that role.
This document specifies the PoL mechanism for Cryptarchia,
extending the work presented in the Ouroboros Crypsinous paper
with recent cryptographic developments.
## Semantics
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in RFC 2119.
## Introduction
The Proof of Leadership enables a leader to produce a zero-knowledge proof
attesting to the fact that they have an eligible note
that has won the leadership lottery.
This proof MUST be as lightweight as possible to generate and verify,
due to the following reasons:
- Impose minimal restrictions on access to the role of leader
and thus maximize the decentralization of that role.
- Similarly, the proof and its context MUST be efficiently verifiable for validators.
This document extends the work presented in the Ouroboros Crypsinous paper
with recent cryptographic developments.
## Overview
### Overview of the Protocol
The PoL mechanism ensures that a note has legitimately won the leadership election
while protecting the leader's privacy.
The protocol is comprised of two parts: setup and PoL generation.
**Setup:**
1. Draw uniformly a random seed.
2. Construct a Merkle tree composed of slot secrets derived from the seed.
3. Use the root of the tree and the starting slot to get the leader's secret key.
The starting slot is when the note can start to be used for PoL.
4. The leader receives their stake in a note that uses this generated secret key.
The leader either transfers this stake to themselves
or obtains it from a different user.
5. The note becomes eligible for PoS when it has aged sufficiently,
and the actual slot number is greater than or equal to
the starting slot of the note.
**PoL generation:**
1. First, check if the note is winning by simulating the lottery.
2. Prove the membership of the note identifier in an old snapshot of the Mantle Ledger,
proving its age and its existence.
3. Prove the membership of the note identifier in the most recent Mantle ledger,
proving it's unspent.
4. Prove that the note won the PoS lottery.
5. Prove the knowledge of the slot secret for the winning slot.
6. The proof is bound to a cryptographic public key used for signing
the leader's proposed blocks.
### Comparison with Original Crypsinous PoL
Our description differs from the original paper proposition,
proving that a note is unspent directly
instead of delegating the verification to validators.
This design choice brings the following tradeoffs:
**Advantages:**
1. The ledger isn't required to be private using shielded notes.
- Validators don't need to maintain a nullifier list.
- Leaders keep their privacy unlinking their stake, block and PoL.
2. There is no leader note evolution mechanism anymore (see the paper for details).
- There are no orphan proofs anymore,
removing the need to include valid PoL proofs from abandoned forks.
- Crypsinous forced us to maintain a parallel note commitment set
integrating evolving notes over time.
This requirement is removed now.
- The derivation of the slot secret and its Merkle proof
can be done locally without connection to the Nomos chain.
**Disadvantages:**
1. We cannot compute the PoL far in advance
because the leader MUST know the latest ledger state of Mantle.
## Protocol
### Protection Against Adaptive Adversaries
The Ouroboros Crypsinous paper integrates protection against adaptive adversaries:
> The design has several subtleties since a critical consideration in the PoS setting
> is tolerating adaptive corruptions:
> this ensures that even if the adversary can corrupt parties
> in the course of the protocol execution in an adaptive manner,
> it does not gain any non-negligible advantage by e.g., re-issuing past PoS blocks.
> (p. 2)
To avoid a leaked note being reused to maliciously regenerate past PoLs,
we adopt the solution proposed in the paper using slightly different parameters.
We recall here the solution proposed in the paper:
> We solve the former issue, by adding a cheap key-erasure scheme
> into the NIZK for leadership proofs.
> Specifically, **parties have a Merkle tree of secret keys**,
> the root of which is hashed to create the corresponding public key.
> The **Merkle tree roots acts like a Zerocash coin secret key**,
> and can be used to spend coins.
> For leadership however, parties also must prove knowledge of a path
> in the Merkle tree to a leaf at the index of the slot they are claiming to lead.
> **After a slot passes, honest parties erase their preimages**
> of this part of that path in the tree.
> As the size of this tree is linear with the number of slots,
> we allow parties to keep it small, by restricting its size.
> (p. 5)
The paper proposed a tree of depth 24.
- This implies that the note is usable for PoS for only 194 days approximately
(because 1 slot is 1 second).
- After this period, the note MUST be refreshed to include new randomness.
We will keep it simple and design the refresh mechanism
as a classical transaction modifying the nullifier secret key.
- This solution has good performance:
> For a reasonable value of $R = 2^{24}$, this is of little practical concern.
> Public keys are valid for $2^{24}$ slots
> and employing standard space/time trade-offs,
> key updates take under 10,000 hashes, with less than 500kB storage requirement.
> The most expensive part of the process, key generation,
> still takes less than a minute on a modern CPU.
> (p. 29)
The disadvantages of this solution are that:
1. The public key of the note will change periodically
(each time all slot secrets are consumed) for the ones participating in PoL.
2. The note will not be reusable directly after refresh
as only old enough notes are usable for PoS.
We propose a tree with a depth of 25,
extending the note's eligibility to around 388 days,
with a maximum of **two epochs remaining ineligible** not counted in these days.
Note that this requirement applies specifically to proving leadership in PoS
and is not needed for every note.
While any note can be used for PoL,
the knowledge of the secret slots behind the public key
is only necessary to demonstrate that you are a leader.
**Setup:** When refreshing their notes, potential leaders will:
1. Uniformly randomly draw a seed $r_1 \stackrel{\$}{\leftarrow} \mathbb{F}_p$.
2. Construct a Merkle Tree of root $R$ containing $2^{25}$ slot secrets
(that are random numbers).
One way to efficiently construct the tree is to:
- Derive the slot secrets using a zkhash chain:
$\forall i \in [2, 2^{25}], r_i := \text{hash}(r_{i-1})$.
- More concretely, each leaf is the zkhash of the previous leaf (slot secret).
- This reduces storage requirements compared to directly randomly drawn
independently $2^{25}$ slot secrets.
- The first generation of the Merkle tree should be fast enough
as it only requires hashing data.
A correct implementation that erases data over time
could maintain an upper bound in memory usage during the generation
of the tree to only $\log_2(2^{25}) = 25$ zk hashes which is 800 bytes.
- Leaders are only required to maintain the MMR up to the current slot.
This means at minimum,
leaders hold only 25 hashes in memory at any point in time.
- After the first generation, the wallets optimize their storage
by holding only the necessary information to maintain a correct Merkle path,
deriving the next one over time using the fact that
slot secrets were derived from the previous ones.
- It guarantees protection against adaptive adversaries.
- Thanks to the pseudo-random properties of the hash function,
slot secrets are indistinguishable from true randomness.
- The one-way property of the hash function guarantees
that an adaptive adversary cannot retrieve past slot secrets
using a fresher one.
3. The user chooses a starting slot $sl_{start}$ from which their note
will be eligible for PoS.
The note MUST be on-chain by the start of epoch $ep - 1$
to be eligible for PoL in epoch $ep$ because of the age requirement.
Based on this, we suggest $sl_{start}$ to not be earlier
than the start of the epoch following the one after the transaction is emitted.
This prevents the inclusion of unusable slot secrets in the tree
(because the note would not be aged enough),
optimizing the PoL lifetime of the note.
4. Finally, they derive their secret key
$sk := \text{hash}(\text{NOMOS\_POL\_SK\_V1}||sl_{start}||R)$,
binding the starting slot and the Merkle tree of slot secret
to the note secret key.
This is verified in Circuit Constraints.
These four steps are summarized in the following pseudo-code:
```python
def pol_sk_gen(sl_start, seed):
frontier_nodes = MMR()
path = MerkleProof()
# Generate 2^25 slot secrets using a hash chain initialized with `seed`.
r = zkhash(seed)
for i in range(2**25):
frontier_nodes.append(r) # Append the slot secret to the MMR
path.update(frontier_nodes) # Update Merkle path of this slot secret
r = zkhash(r) # Derive the next slot secret
# Derive the root of the MMR
root = frontier_nodes.get_root()
# Finally, derive the final PoL secret key.
# Return the secret key and the Merkle proof of seed.
return (zkhash(b"NOMOS_POL_SK_V1" + sl_start + root), path)
def update_secret_and_path(r, path):
r = zkhash(r) # Derive next slot secret
path.update(r) # Update the path for the Merkle proof of the new slot secret
return (r, path)
```
> Note that the generation of the slot secret tree is not constrained
> by proofs or at consensus level and can be adapted by the node
> as long as they are able to derive the merkle proof of their slot secret.
**PoL:** When proving the leadership election,
note owners will prove knowledge of the slot secret corresponding to the slot $sl$.
1. To do that, they will give a Merkle path from the leaf at index $sl - sl_{start}$.
2. The root of the tree hashed with $sl_{start}$ MUST be the secret key $sk$,
which will be used for public key derivation.
**Protection against adaptive adversaries:**
Since each slot has its own slot secret,
requiring wallets to delete slot secrets used for previous slots
avoids the risk of corruption that leads to the creation of PoL for previous blocks.
- The slot secret is derived from the previous one but the opposite is impossible.
- An adaptive adversary corrupting the node would not have access to
previous slot secrets if correctly deleted.
Therefore, an adversary would not be able to generate the PoL for previous slots.
### Ledger Root
In order to prove that the winning note exists in the ledger
and existed at the start of the previous epoch,
every node MUST compute two ledger commitments.
These commitments $ledger_{AGED}$ and $ledger_{LATEST}$
are Merkle roots constructed over the Note IDs.
The trees have a depth of 32 and are populated with note IDs.
The value 0 represents an empty leaf.
When the set is updated, during insertion,
the first empty leaf is replaced with the new note ID,
and during deletion, the leaf containing the deleted note ID is replaced with 0.
The following pseudo-code shows how the tree is managed:
```python
def insert_new_note(note_set: list[NoteId], new_note: NoteId):
i = 0
while i < len(note_set) and note_set[i] != 0:
i += 1
if i < len(note_set):
note_set[i] = new_note
else:
note_set.append(new_note)
return note_set
def delete_note(note_set: list[NoteId], note: NoteId):
i = 0
while i < len(note_set) and note_set[i] != note:
i += 1
if i == len(note_set):
# note not in the set
return note_set
note_set[i] = 0
return note_set
def empty_tree_root(depth: int):
root = 0
for i in range(depth):
h = hasher() # zk hash
h.update(root)
h.update(root)
root = h.digest()
return root
def get_ledger_root(note_set: list[NoteId]):
assert(len(note_set) < 2**32)
ledger_root = get_merkle_root(note_set)
# return the Merkle root of the set padded with 0 to next power of 2
ledger_root_height = len(note_set).bit_length()
for height in range(ledger_root_height, 32):
h = Hasher() # zk hash
h.update(ledger_root)
h.update(empty_tree_root(height))
ledger_root = h.digest()
return ledger_root
```
> The ledger root may not be unique because the note IDs set can cycle.
> Indeed, even if it's not possible to insert the same note ID twice,
> it's possible to cycle on a previous set state by removing notes.
> However, note IDs uniqueness guarantees protection against attacks on note aging.
### Zero-knowledge Proof Statement
#### Circuit Public Inputs
The prover (the leader) and the verifiers (nodes of the chain)
MUST agree on these values:
1. **The slot number:** $sl$.
2. **The epoch nonce:** $\eta$.
- For details see Cryptarchia v1 Protocol Specification - Epoch Nonce.
3. **The lottery function constants:**
$t_0 = -\frac{\text{VRF\_order} \cdot \ln(1-f)}{\text{inferred\_total\_stake}}$
and
$t_1 = -\frac{\text{VRF\_order} \cdot \ln^2(1-f)}{2 \cdot \text{inferred\_total\_stake}^2}$.
- For details see Lottery Approximation.
- These numbers MUST be computed with high precision outside the proof.
4. **The root of the note Merkle tree when the stake distribution was frozen:**
$ledger_{AGED}$.
- For details see Cryptarchia v1 Protocol Specification - Epoch State Pseudocode.
5. **The latest root of the note Merkle tree:** $ledger_{LATEST}$.
- Used to ensure the leadership note has not been spent.
6. **The leader's one-time public key** $P_{LEAD}$
represented by 2 public inputs, each of 16 bytes in little endian.
This key is needed to sign the proposed block.
- For details see Linking the Proof of Leadership to a Block.
7. **The entropy contribution** $\rho_{LEAD}$ verified to be correctly derived.
- This is the epoch nonce entropy contribution.
See Cryptarchia v1 Protocol Specification - Epoch Nonce.
#### Circuit Private Inputs
The prover has to provide these values, but they remain secret:
1. **The slot secret and the related information** used for the slot $sl$
as described in Protection Against Adaptive Adversaries:
- The slot secret $r_{sl}$.
- The Merkle path $slot\_secret\_path$ of $r_{sl}$ leading to the root $R$.
- The starting secret slot $sl_{start}$.
2. **The eligible note and its related information** used to derive the $noteID$
(the secret key is derived for the previous step):
- The note value: $v$.
- The note transaction zk hash: $note\_tx\_hash$.
- The note outputs number: $note\_output\_number$.
3. **The proof of membership** of the note identifier in the zone ledgers
$ledger_{AGED}$ and $ledger_{LATEST}$.
This is done by providing the complementary Merkle nodes
and indicating whether they are left (0) or right (1) through boolean selectors:
- The aged ledger complementary nodes: $noteid\_aged\_path$.
- The aged ledger complementary node selectors: $note\_id\_aged\_selectors$.
- The latest ledger complementary nodes: $noteid\_latest\_path$.
- The latest ledger complementary node selectors: $note\_id\_latest\_selectors$.
#### Circuit Constraints
The proof confirms the following relations:
1. The derivation of the Merkle tree root $R$
using the slot secret $r_{sl}$ as the $sl - sl_{start}$'s leaf
of the Merkle tree using the Merkle path.
This is a proof of knowledge of the secret slot $r_{sl}$
guaranteeing protection against adaptive adversaries.
2. The derivation of $sk = \text{hash}(\text{NOMOS\_POL\_SK\_V1}||sl_{start}||R)$,
as documented in Protection Against Adaptive Adversaries.
3. The computation of the note identifier.
4. The note identifier is in $ledger_{AGED}$ and $ledger_{LATEST}$.
5. The computation of the lottery ticket:
$ticket := \text{hash}(\text{LEAD\_V1}||\eta||sl||noteID||sk)$ using Poseidon2.
6. The computation of the threshold: $t := v(t_0 + t_1 \cdot v)$.
The ticket MUST be lower than this threshold to win the lottery.
7. The check that indeed $ticket < t$.
8. Compute and output the entropy contribution
$\rho_{LEAD} := \text{hash}(\text{NOMOS\_NONCE\_CONTRIB\_V1}||sl||noteID||sk)$.
### Linking the Proof of Leadership to a Block
The PoL is bound to a public key from an asymmetric signature scheme.
This public key $P_{LEAD}$ is given as two public inputs during the PoL proof generation,
binding the proof to the key.
- The public key is represented by two public inputs of 16 bytes
to guarantee the support of every possible EdDSA25519 public key.
- This public key is later used to verify the signature $\sigma$ of a block
when it is dispersed.
This ensures that the PoL is tied to a specific block,
and only the entity creating the proof can perform this binding.
- The key is single-use, as reusing the same one could allow multiple PoLs
to be linked to the same identity.
An observer could then infer the stake of that identity
by observing the frequency at which it emits a PoL.
## Appendix
### Lottery Approximation
- The $\phi_f(\alpha) = 1 - (1-f)^\alpha$ function of Ouroboros Crypsinous
cannot be computed in a hand-written circuit
as it can only operate on elements of $\mathbb{F}_p$
for a certain prime number $p$.
- Managing floating point numbers and mathematical functions
involving floating points like exponentiations or logarithms in circuits
is very inefficient.
- We compared the Taylor expansion of order 1 and 2
and used the Taylor expansion of order 2 method
to approximate the Ouroboros Genesis (and Crypsinous) function
by the following linear function:
- $\stackrel{0}{\sim}$ means nearly equal in the neighborhood of 0
- $f$ is the probability that at least one leader wins the lottery on each slot
- $x$ is the stake of the proven note
$$1 - (1-f)^x = 1 - e^{x \ln(1-f)}$$
$$1 - e^{x \ln(1-f)} \stackrel{0}{\sim} x(-\ln(1-f) - 0.5 \ln^2(1-f)x)$$
Then the threshold is $stake(t_0 + t_1 \cdot stake)$ with
$t_0 := -\frac{\text{VRF\_order} \cdot \ln(1-f)}{\text{inferred\_total\_stake}}$
and
$t_1 := -\frac{\text{VRF\_order} \cdot \ln^2(1-f)}{2 \cdot \text{inferred\_total\_stake}^2}$.
Since everything is known by every node except the value of the staked note,
we pre-compute $t_0$ and $t_1$ outside of the circuit.
- The Hash functions used to derive the lottery ticket is Poseidon2
so the VRF_order is $p$ the order of the scalar field of the BN254 elliptic curve.
- To compute $t_0$ and $t_1$, we precomputed the constant parts using sagemath
and real number of 512 bits precision.
In the implementation, $t_0$ and $t_1$ should then be derived
using 256-bit precision integers following:
| Variable | Formula |
|----------|---------|
| $p$ | `0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001` |
| $t_0\_constant$ | `0x1a3fb997fd58374772808c13d1c2ddacb5ab3ea77413f86fd6e0d3d978e5438` |
| $t_1\_constant$ | `0x71e790b41991052e30c93934b5612412e7958837bac8b1c524c24d84cc7d0` |
| $t_0$ | $\frac{t_0\_constant}{\text{inferred\_total\_stake}}$ |
| $t_1$ | $p - \lfloor\frac{t_1\_constant}{\text{inferred\_total\_stake}^2}\rfloor$ |
### Error Analysis
- For $f = 0.05$.
The error percentage is computed with $100 \cdot \frac{estimation - real\_value}{real\_value}$.
- We will consider that inferred_total_stake is 23.5B as in Cardano.
- Original function: $1 - (1-f)^{\frac{stake}{\text{inferred\_total\_stake}}}$
- Taylor expansion of order 1:
$-\frac{stake}{\text{inferred\_total\_stake}} \ln(1-f) := stake \cdot t_0$
- Taylor expansion of order 2:
$\frac{stake}{\text{inferred\_total\_stake}}(-\ln(1-f) - 0.5 \ln^2(1-f)(\frac{stake}{\text{inferred\_total\_stake}})) := stake(t_0 + stake \cdot t_1)$
| stake (%) | order 1 error | order 2 error |
|-----------|---------------|---------------|
| 5% | 0.13% | -0.0001% |
| 10% | 0.26% | -0.0004% |
| 15% | 0.39% | -0.0010% |
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
## References
### Normative
- [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
\- Key words for use in RFCs to Indicate Requirement Levels
- [Cryptarchia v1 Protocol Specification](https://nomos-tech.notion.site/Cryptarchia-v1-Protocol-Specification-21c261aa09df810cb85eff1c76e5798c)
\- Parent protocol specification
### Informative
- [Proof of Leadership Specification](https://nomos-tech.notion.site/Proof-of-Leadership-Specification-21c261aa09df819ba5b6d95d0fe3066d)
\- Original Proof of Leadership documentation
- [Ouroboros Crypsinous: Privacy-Preserving Proof-of-Stake](https://eprint.iacr.org/2018/1132.pdf)
\- Foundation for the PoL design