thomaslavaur 888bbe0dd8
make pow_pk and pow_block_hash private (#53)
* make pow pk and block hash private

* fix

* remove slot secrets that was remvoed a long time ago
2026-08-05 13:30:05 +02:00

166 lines
6.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 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, bitsQuota)
*
* - nLevelsPK : depth of the core-node public-key registry Merkle tree
* - bitsQuota : bit-width for the index comparator
*/
template ProofOfQuota(nLevelsPK, bitsQuota) {
// Public Inputs
signal input core_quota;
signal input leader_quota;
signal input core_root;
signal input pow_quota;
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
signal input pow_block_hash;
signal input pow_blend_difficulty;
// 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, 2 = pow
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_noteid_path[32];
signal input pol_noteid_path_selectors[32];
signal input pol_secret_key;
signal input pol_note_tx_hash;
signal input pol_note_output_number;
signal input pol_note_value;
// PoW branch input
signal input pow_sk;
// Constraint the selector to be a 0, 1 or 2
signal selector_squared;
selector_squared <== selector * selector;
(selector_squared - selector) * (selector - 2) === 0;
// compute lagrange polynomial for selectors
signal L1;
signal L2;
component inv_2 = INV_2();
L1 <== - selector_squared + 2 * selector;
L2 <== (selector_squared - selector) * inv_2.out;
// Quota check: index < core_quota if core, index < leader_quota if leader, index < pow_quota if pow
signal lh_quota_cmp;
lh_quota_cmp <== (leader_quota - core_quota) * L1;
component cmp = SafeLessThan(bitsQuota);
cmp.in[0] <== index;
cmp.in[1] <== core_quota + lh_quota_cmp + (pow_quota - core_quota) * L2;
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();
would_win.slot <== pol_sl;
would_win.epoch_nonce <== pol_epoch_nonce;
would_win.t0 <== pol_t0;
would_win.t1 <== pol_t1;
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.secret_key <== pol_secret_key;
would_win.value <== pol_note_value;
// Derive pow pk
component pow_pk_derivation = derive_public_key();
pow_pk_derivation.secret_key <== pow_sk;
// Get the blend PoW result
component pow_ticket = Poseidon2_hash(3);
pow_ticket.inp[0] <== pol_epoch_nonce;
pow_ticket.inp[1] <== pow_block_hash;
pow_ticket.inp[2] <== pow_pk_derivation.out;
component is_winning_pow = SafeFullLessThan();
is_winning_pow.a <== pow_ticket.out;
is_winning_pow.b <== pow_blend_difficulty;
// Enforce the selected role is correct
signal lh_correctness_selector;
lh_correctness_selector <== (would_win.out - is_registered.out) * L1;
is_registered.out + lh_correctness_selector + (is_winning_pow.out - is_registered.out) * L2 === 1;
// Derive selection_randomness
component selection_randomness = Poseidon2_hash(4);
component dstSel = SELECTION_RANDOMNESS_V1();
selection_randomness.inp[0] <== dstSel.out;
// choose core_sk, pol.secret_key or pow_sk:
signal lh_key_selector;
lh_key_selector <== (would_win.secret_key - core_sk) * L1;
selection_randomness.inp[1] <== core_sk + lh_key_selector + (pow_sk - core_sk) * L2;
selection_randomness.inp[2] <== index;
selection_randomness.inp[3] <== pol_epoch_nonce + (would_win.slot - pol_epoch_nonce) * L1; // because the last term is (pol_epoch_nonce - pol_epoch_nonce) * L2 = 0
// Derive key_nullifier
component nf = Compression();
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
component main { public [ core_quota, leader_quota, pow_quota, core_root, K_part_one, K_part_two, pol_epoch_nonce, pol_t0, pol_t1, pol_ledger_aged, pow_blend_difficulty] }
= ProofOfQuota(20, 20);