2025-06-11 17:12:52 +03:00

132 lines
4.8 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.circom"; // defines proof_of_leadership
/**
* ProofOfQuota(nLevelsPK, nLevelsPol, bitsQuota, zoneTreeDepth)
*
* - 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
* - zoneTreeDepth : depth of the zones aged notes Merkle tree (32)
*/
template ProofOfQuota(nLevelsPK, nLevelsPol, bitsQuota, zoneTreeDepth) {
// 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
signal input K; // Blend: one-time signature public key
// Although K is listed as a public input in the circuit, its conceptually generated by the prover and thus considered an output of the proof process.
signal dummy;
dummy <== K * K;
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)
// PoL branch inputs (all the PoL private data)
signal input slot_secret;
signal input slot_secret_path[nLevelsPol];
signal input aged_nodes[zoneTreeDepth];
signal input aged_selectors[zoneTreeDepth];
signal input transaction_hash;
signal input output_number;
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);
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;
for (var i = 0; i < nLevelsPol; i++) {
would_win.slot_secret_path[i] <== slot_secret_path[i];
}
for (var i = 0; i < zoneTreeDepth; i++) {
would_win.aged_nodes[i] <== aged_nodes[i];
would_win.aged_selectors[i] <== aged_selectors[i];
}
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;
// Enforce the selected role is correct
selector * (would_win.out - coreReg.out) + coreReg.out === 1;
// Quota check: index < Qc if core, index < Ql if leader
component cmp = SafeLessThan(bitsQuota);
cmp.in[0] <== index;
cmp.in[1] <== selector * (Ql - Qc) + Qc;
cmp.out === 1;
// Derive selection_randomness
component randomness = Poseidon2_hash(4);
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);
component dstNF = PROOF_NULLIFIER_V1();
nf.inp[0] <== dstNF.out;
nf.inp[1] <== randomness.out;
nullifier <== nf.out;
}
// Instantiate with chosen depths: 20 for core PK tree, 25 for PoL slot tree
component main { public [ session, Qc, Ql, pk_root, aged_root, slot, epoch_nonce, t0, t1, K ] }
= ProofOfQuota(20, 25, 20, 32);