PROOF-OF-LEADERSHIP
| Field | Value |
|---|---|
| Name | Proof of Leadership |
| Slug | 83 |
| Status | raw |
| Category | Standards Track |
| Editor | Thomas Lavaur [email protected] |
| Contributors | Mehmet [email protected], Giacomo Pasini [email protected], Daniel Sanchez Quiros [email protected], Álvaro Castro-Castilla [email protected], David Rusu [email protected], Filip Dimitrijevic [email protected] |
Timeline
- 2026-05-28 —
d45eed2— Chore: mirror blochain specs into github/mdbook (#347) - 2026-05-18 —
58b5698— chore(blockchain): migrate contributor emails to @logos.co (#338) - 2026-01-19 —
f24e567— Chore/updates mdbook (#262) - 2026-01-16 —
89f2ea8— Chore/mdbook updates (#258)
Revision History
| Version | Changes | Date |
|---|---|---|
| 1.0.0 | Initial revision. | 2026-12-09 |
| 1.1.0 | Remove the protection against adaptive adversary from PoL removing a non-enforced feature, simplifying work for engineers, improving UX and performances of PoL and PoQ. Update the performance according to the new circuit. Remove the notion of NOMOS in DSTs | 2026-01-29 |
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.
References
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:
- Setup: The note becomes eligible for PoS when it has aged sufficiently.
- PoL generation:
- First, check if the note is winning by simulating the lottery
- Prove the membership of the note identifier in an old snapshot of the Mantle Ledger, proving its age and its existence.
- Prove the membership of the note identifier in the most recent Mantle ledger, proving it’s unspent.
- Prove that the note won the PoS lottery.
- 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. Moreover, we don't include the protection against adaptive adversaries that cannot be enforced by the chain or incentivized. 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](https://eprint.iacr.org/2018/1132.pdf) 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
Disadvantages
1. We cannot compute the PoL far in advance because the leader must know the latest ledger state of Mantle.
Protocol
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 and are Merkle roots constructed over the Note IDs. The trees have a depth of (32 layers without counting the root) and are populated with note IDs, that is, the tree has a maximal capacity of note IDs. The value 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 . The following pseudo-code shows how the tree is managed:
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[NodeId], 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:
- The slot number: .
- The epoch nonce: .
- For details see Epoch Nonce.
- The lottery function constants: and .
- For details see Lottery Approximation.
- These numbers must be computed with high precision outside the proof.
- The root of the note Merkle tree when the stake distribution was frozen .
- For details see Epoch State Pseudocode.
- The latest root of the note Merkle tree: .
- Used to ensure the leadership note has not been spent.
- The leader's one-time public key 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.
- The entropy contribution verified to be correctly derived.
- This is the epoch nonce entropy contribution. See Epoch Nonce.
Circuit Private Inputs
The prover has to provide these values, but they remain secret:
- The eligible note and its related information used to derive the Note Id:
- The note secret key: .
- The note value: .
- The note transaction zk hash: .
- The note outputs number: .
- The proof of membership of the note identifier in the zone ledgers and . 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: .
- The aged ledger complementary node selectors: .
- The latest ledger complementary nodes: .
- The latest ledger complementary node selectors: .
Circuit Constraints
The proof confirms the following relations:
-
The derivation of the public key.
-
The computation of the note identifier.
-
The note identifier is in and .
-
The computation of the lottery ticket: using Poseidon2.
-
The computation of the threshold: . The ticket must be lower than this threshold to win the lottery.
-
The check that indeed .
-
Compute and output the entropy contribution
Linking the Proof of Leadership to a Block
The PoL is bound to a public key from an asymmetric signature scheme. This public key 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 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 function of Ouroboros Crypsinous cannot be computed in a hand-written circuit as it can only operates on elements of for a certain prime number .
- 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
- means nearly equal in the neighborhood of 0
- is the probability that at least one leader wins the lottery on each slot
- is the stake of the proven note
Then the threshold is with and
. Since everything is known by every node except the value of the staked note, we pre-compute and outside of the circuit.
- The Hash functions used to derive the lottery ticket is Poseidon2 so the is the order of the scalar field of the BN254 elliptic curve.
- To compute and , we precomputed the constant parts using sagemath and real number of 512 bits precision. In the implementation, and should then be derived using 256-bit precision integers following:
| Variable | Formula |
|---|---|
| 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 | |
| 0x1a3fb997fd5838f2a1585ee090a95c88129ab25cc4d2e2d28f1a95f81d85465 | |
| 0x71e790b4199113a9a00298d823c5716ddac764a110a45fe3b770bbb3e8a57 | |
**Python code to derive constants**
from sage.all import RealField
FIELD_ORDER = 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001
R = RealField(512)
F = R(1) / R(30)
t_0_constant = int(-R(FIELD_ORDER) * (R(1) - F).log())
t_1_constant = int(R(FIELD_ORDER) * (R(1) - F).log() ** 2 / R(2))
def lottery_constants(inferred_total_stake: int) -> tuple[int, int]:
t_0 = t_0_constant // inferred_total_stake
t_1 = FIELD_ORDER - (t_1_constant // inferred_total_stake**2)
return t_0, t_1
print(f"p = {FIELD_ORDER:#x}")
print(f"t_0_constant = {t_0_constant:#x}")
print(f"t_1_constant = {t_1_constant:#x}")
Error Analysis
- For . The error percentage is computed with
- We will consider that is 23.5B as in Cardano
- Original function:
- Taylor expansion of order 1:
- Taylor expansion of order 2:
| stake (%) | order 1 error | order 2 error |
|---|---|---|
| 5% | 0.13% | -0.0001% |
| 10% | 0.26% | -0.0004% |
| 15% | 0.39% | -0.0010% |
| 20% | 0.51% | -0.0018% |
| 25% | 0.64% | -0.0027% |
| 30% | 0.77% | -0.0040% |
| 35% | 0.90% | -0.0054% |
| 40% | 1.03% | -0.0071% |
| 45% | 1.16% | -0.0089% |
| 50% | 1.29% | -0.0110% |
| 55% | 1.42% | -0.0134% |
| 60% | 1.55% | -0.0159% |
| 65% | 1.68% | -0.0187% |
| 70% | 1.81% | -0.0217% |
| 75% | 1.94% | -0.0249% |
| 80% | 2.07% | -0.0284% |
| 85% | 2.20% | -0.0320% |
| 90% | 2.33% | -0.0359% |
| 95% | 2.46% | -0.0406% |
| 100% | 2.59% | -0.0444% |
Benchmarks
The material used for the benchmarks is the following:
- 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
