138 lines
5.0 KiB
Plaintext
Raw Normal View History

2025-10-02 17:55:20 +02:00
// 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";
2025-10-06 11:49:54 +02:00
include "../mantle/pol_lib.circom"; // defines proof_of_leadership
2025-10-02 17:55:20 +02:00
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);