2025-11-17 11:06:30 +01:00

138 lines
5.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// PoQ.circom
pragma circom 2.1.9;
include "../hash_bn/poseidon2_hash.circom";
include "../misc/constants.circom"; // defines NOMOS_KDF, SELECTION_RANDOMNESS, PROOF_NULLIFIER
include "../misc/comparator.circom";
include "../circomlib/circuits/bitify.circom";
include "../Mantle/pol_lib.circom"; // defines proof_of_leadership
include "../ledger/notes.circom";
/**
* ProofOfQuota(nLevelsPK, nLevelsPol)
*
* - nLevelsPK : depth of the core-node public-key registry Merkle tree
* - nLevelsPol : depth of the slot-secret tree used in PoL (25)
* - bitsQuota : bit-width for the index comparator
*/
template ProofOfQuota(nLevelsPK, nLevelsPol, bitsQuota) {
// Public Inputs
signal input session; // session s
signal input core_quota;
signal input leader_quota;
signal input core_root;
signal input pol_ledger_aged; // PoL: aged notes root
signal input K_part_one; // Blend: one-time signature public key
signal input K_part_two; // Blend: one-time signature public key
// dummy constraints to avoid unused public input to be erased after compilation optimisation
signal dummy_one;
dummy_one <== K_part_one * K_part_one;
signal dummy_two;
dummy_two <== K_part_two * K_part_two;
signal output key_nullifier; //key_nullifier
// Private Inputs
signal input selector; // 0 = core, 1 = leader
signal input index; // nullifier index
// Core-nodes inputs
signal input core_sk; // core node secret key
signal input core_path[nLevelsPK]; // Merkle path for core PK
signal input core_path_selectors[nLevelsPK]; // path selectors (bits)
// PoL branch inputs (all the PoL private data)
signal input pol_sl;
signal input pol_epoch_nonce;
signal input pol_t0;
signal input pol_t1;
signal input pol_slot_secret;
signal input pol_slot_secret_path[nLevelsPol];
signal input pol_noteid_path[32];
signal input pol_noteid_path_selectors[32];
signal input pol_note_tx_hash;
signal input pol_note_output_number;
signal input pol_sk_starting_slot;
signal input pol_note_value;
// Constraint the selector to be a bit
selector * (1 - selector) === 0;
// Quota check: index < core_quota if core, index < leader_quota if leader
component cmp = SafeLessThan(bitsQuota);
cmp.in[0] <== index;
cmp.in[1] <== selector * (leader_quota - core_quota) + core_quota;
cmp.out === 1;
// derive zk_id
component zk_id = derive_public_key();
zk_id.secret_key <== core_sk;
// Merkleverify zk_id in core_root
component is_registered = proof_of_membership(nLevelsPK);
for (var i = 0; i < nLevelsPK; i++) {
//check that the selectors are indeed bits
core_path_selectors[i] * (1 - core_path_selectors[i]) === 0;
//call the merkle proof checker
is_registered.nodes[i] <== core_path[i];
is_registered.selector[i] <== core_path_selectors[i];
}
is_registered.root <== core_root;
is_registered.leaf <== zk_id.out;
// enforce potential PoL (without verification that the note is unspent)
// (All constraints inside pol ensure LeadershipVerify)
component would_win = would_win_leadership(nLevelsPol);
would_win.slot <== pol_sl;
would_win.epoch_nonce <== pol_epoch_nonce;
would_win.t0 <== pol_t0;
would_win.t1 <== pol_t1;
would_win.slot_secret <== pol_slot_secret;
for (var i = 0; i < nLevelsPol; i++) {
would_win.slot_secret_path[i] <== pol_slot_secret_path[i];
}
for (var i = 0; i < 32; i++) {
would_win.aged_nodes[i] <== pol_noteid_path[i];
would_win.aged_selectors[i] <== pol_noteid_path_selectors[i];
}
would_win.aged_root <== pol_ledger_aged;
would_win.transaction_hash <== pol_note_tx_hash;
would_win.output_number <== pol_note_output_number;
would_win.starting_slot <== pol_sk_starting_slot;
would_win.value <== pol_note_value;
// Enforce the selected role is correct
selector * (would_win.out - is_registered.out) + is_registered.out === 1;
// Derive selection_randomness
component selection_randomness = Poseidon2_hash(4);
component dstSel = SELECTION_RANDOMNESS_V1();
selection_randomness.inp[0] <== dstSel.out;
// choose core_sk or pol.secret_key:
selection_randomness.inp[1] <== selector * (would_win.secret_key - core_sk ) + core_sk;
selection_randomness.inp[2] <== index;
selection_randomness.inp[3] <== session;
// Derive key_nullifier
component nf = Poseidon2_hash(2);
component dstNF = KEY_NULLIFIER_V1();
nf.inp[0] <== dstNF.out;
nf.inp[1] <== selection_randomness.out;
key_nullifier <== nf.out;
}
// Instantiate with chosen depths: 20 for core PK tree, 25 for PoL secret slot tree
component main { public [ session, core_quota, leader_quota, core_root, K_part_one, K_part_two, pol_epoch_nonce, pol_t0, pol_t1, pol_ledger_aged ] }
= ProofOfQuota(20, 25, 20);