2025-05-26 11:05:08 +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";
|
|
|
|
|
|
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
|
2025-05-26 11:05:08 +02:00
|
|
|
|
*/
|
|
|
|
|
|
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-05-26 11:05:08 +02:00
|
|
|
|
|
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
|
2025-05-26 11:05:08 +02:00
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
2025-05-26 11:05:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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();
|
2025-05-26 11:05:08 +02:00
|
|
|
|
kdf.inp[0] <== dstKdf.out;
|
|
|
|
|
|
kdf.inp[1] <== core_sk;
|
|
|
|
|
|
signal pk_core;
|
|
|
|
|
|
pk_core <== kdf.out;
|
|
|
|
|
|
|
|
|
|
|
|
// Merkle‐verify 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;
|
|
|
|
|
|
|
2025-05-30 12:20:28 +02:00
|
|
|
|
// enforce potential PoL (without verification that the note is unspent)
|
2025-05-26 11:05:08 +02:00
|
|
|
|
// (All constraints inside pol ensure LeadershipVerify)
|
2025-05-30 12:20:28 +02:00
|
|
|
|
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++) {
|
2025-05-30 12:20:28 +02:00
|
|
|
|
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++) {
|
2025-05-30 12:20:28 +02:00
|
|
|
|
would_win.aged_nodes[i] <== aged_nodes[i];
|
|
|
|
|
|
would_win.aged_selectors[i] <== aged_selectors[i];
|
2025-05-27 11:41:50 +03:00
|
|
|
|
}
|
2025-05-30 12:20:28 +02: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
|
|
|
|
|
2025-05-26 11:05:08 +02:00
|
|
|
|
// Enforce the selected role is correct
|
2025-05-30 12:20:28 +02:00
|
|
|
|
selector * (would_win.out - coreReg.out) + coreReg.out === 1;
|
2025-05-27 15:55:13 +02:00
|
|
|
|
|
2025-05-26 11:05:08 +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;
|
2025-05-26 11:05:08 +02:00
|
|
|
|
cmp.out === 1;
|
|
|
|
|
|
|
|
|
|
|
|
// Derive selection_randomness
|
|
|
|
|
|
component randomness = Poseidon2_hash(4);
|
2025-05-27 15:55:13 +02:00
|
|
|
|
component dstSel = SELECTION_RANDOMNESS_V1();
|
2025-05-26 11:05:08 +02:00
|
|
|
|
randomness.inp[0] <== dstSel.out;
|
|
|
|
|
|
// choose core_sk or pol.secret_key:
|
2025-05-30 12:20:28 +02:00
|
|
|
|
randomness.inp[1] <== selector * (would_win.secret_key - core_sk ) + core_sk;
|
2025-05-26 11:05:08 +02:00
|
|
|
|
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();
|
2025-05-26 11:05:08 +02:00
|
|
|
|
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 ] }
|
2025-05-30 12:20:28 +02:00
|
|
|
|
= ProofOfQuota(20, 25, 20);
|