mirror of
https://github.com/logos-blockchain/logos-blockchain-circuits.git
synced 2026-08-09 01:23:10 +00:00
* make pow pk and block hash private * fix * remove slot secrets that was remvoed a long time ago
166 lines
6.0 KiB
Plaintext
166 lines
6.0 KiB
Plaintext
// 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;
|
||
|
||
|
||
// Merkle‐verify 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);
|