136 lines
4.7 KiB
Plaintext
Raw Normal View History

// 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.circom"; // defines proof_of_leadership
/**
* 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)
2025-05-27 11:41:50 +03:00
* - bitsQuota : bit-width for the index comparator
*/
template ProofOfQuota(nLevelsPK, nLevelsPol, bitsQuota) {
// Public Inputs
signal input session; // session s
signal input Qc; // core quota Q_C
signal input Ql; // leadership quota Q_L
signal input pk_root; // Merkle root of registered core-node public keys
signal input aged_root; // PoL: aged notes root
2025-08-07 08:38:51 +02:00
signal input K_part_one; // Blend: one-time signature public key
signal input K_part_two; // Blend: one-time signature public key
2025-08-07 08:38:51 +02:00
signal dummy_one;
dummy_one <== K_part_one * K_part_one;
signal dummy_two;
dummy_two <== K_part_two * K_part_two;
2025-05-27 15:55:13 +02:00
2025-05-27 11:41:50 +03:00
signal output 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_selectors[nLevelsPK]; // path selectors (bits)
2025-05-27 11:41:50 +03:00
// PoL branch inputs (all the PoL private data)
signal input slot;
signal input epoch_nonce;
signal input t0;
signal input t1;
signal input slot_secret;
signal input slot_secret_path[nLevelsPol];
signal input aged_nodes[32];
signal input aged_selectors[32];
signal input transaction_hash;
signal input output_number;
2025-05-27 15:55:13 +02:00
signal input starting_slot;
signal input secrets_root;
signal input value;
// Constraints
selector * (1 - selector) === 0;
// derive pk_core = Poseidon(NOMOS_KDF || core_sk)
component kdf = Poseidon2_hash(2);
2025-05-27 15:55:13 +02:00
component dstKdf = NOMOS_KDF_V1();
kdf.inp[0] <== dstKdf.out;
kdf.inp[1] <== core_sk;
signal pk_core;
pk_core <== kdf.out;
// Merkleverify pk_core in pk_root
component coreReg = proof_of_membership(nLevelsPK);
for (var i = 0; i < nLevelsPK; i++) {
core_selectors[i] * (1 - core_selectors[i]) === 0;
coreReg.nodes[i] <== core_path[i];
coreReg.selector[i] <== core_selectors[i];
}
coreReg.root <== pk_root;
coreReg.leaf <== pk_core;
// 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 <== slot;
would_win.epoch_nonce <== epoch_nonce;
would_win.t0 <== t0;
would_win.t1 <== t1;
would_win.slot_secret <== slot_secret;
2025-05-27 11:41:50 +03:00
for (var i = 0; i < nLevelsPol; i++) {
would_win.slot_secret_path[i] <== slot_secret_path[i];
2025-05-27 11:41:50 +03:00
}
for (var i = 0; i < 32; i++) {
would_win.aged_nodes[i] <== aged_nodes[i];
would_win.aged_selectors[i] <== aged_selectors[i];
2025-05-27 11:41:50 +03:00
}
would_win.aged_root <== aged_root;
would_win.transaction_hash <== transaction_hash;
would_win.output_number <== output_number;
would_win.starting_slot <== starting_slot;
would_win.secrets_root <== secrets_root;
would_win.value <== value;
2025-05-27 11:41:50 +03:00
// Enforce the selected role is correct
selector * (would_win.out - coreReg.out) + coreReg.out === 1;
2025-05-27 15:55:13 +02:00
// Quota check: index < Qc if core, index < Ql if leader
component cmp = SafeLessThan(bitsQuota);
2025-05-27 15:55:13 +02:00
cmp.in[0] <== index;
cmp.in[1] <== selector * (Ql - Qc) + Qc;
cmp.out === 1;
// Derive selection_randomness
component randomness = Poseidon2_hash(4);
2025-05-27 15:55:13 +02:00
component dstSel = SELECTION_RANDOMNESS_V1();
randomness.inp[0] <== dstSel.out;
// choose core_sk or pol.secret_key:
randomness.inp[1] <== selector * (would_win.secret_key - core_sk ) + core_sk;
randomness.inp[2] <== index;
randomness.inp[3] <== session;
// Derive proof_nullifier
component nf = Poseidon2_hash(2);
2025-05-27 15:55:13 +02:00
component dstNF = PROOF_NULLIFIER_V1();
nf.inp[0] <== dstNF.out;
nf.inp[1] <== randomness.out;
nullifier <== nf.out;
}
2025-05-27 15:55:13 +02:00
// Instantiate with chosen depths: 20 for core PK tree, 25 for PoL slot tree
2025-08-07 08:38:51 +02:00
component main { public [ session, Qc, Ql, pk_root, aged_root, K_part_one, K_part_two ] }
= ProofOfQuota(20, 25, 20);