From 5743b5e9abf3552d830315c131245de25c7761c6 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Wed, 24 Jul 2024 14:27:16 +0400 Subject: [PATCH 1/5] pol:mv circom impl into folder --- .gitmodules | 4 ++-- .../{ => circom}/anemoi/anemoi_16_to_1_Jubjub.circom | 0 .../{ => circom}/anemoi/anemoi_2_to_1_Jubjub.circom | 0 .../{ => circom}/anemoi/anemoi_4_to_1_Jubjub.circom | 0 .../anemoi/anemoi_Jubjub_16_to_1_constants.circom | 0 .../{ => circom}/anemoi/anemoi_Jubjub_2_to_1_constants.circom | 0 .../{ => circom}/anemoi/anemoi_Jubjub_4_to_1_constants.circom | 0 .../{ => circom}/anemoi/script_setup_prover.sh | 0 proof_of_leadership/{ => circom}/generate_inputs.py | 0 proof_of_leadership/{ => circom}/leadership_anemoi.circom | 0 proof_of_leadership/{ => circom}/leadership_anemoi_sha.circom | 0 proof_of_leadership/{ => circom}/leadership_poseidon.circom | 0 .../{ => circom}/leadership_poseidon_sha.circom | 0 proof_of_leadership/{ => circom}/leadership_sha256.circom | 0 .../{ => circom}/poseidon/poseidon_16_to_1_Jubjub.circom | 0 .../{ => circom}/poseidon/poseidon_2_to_1_Jubjub.circom | 0 .../{ => circom}/poseidon/poseidon_4_to_1_Jubjub.circom | 0 .../poseidon/poseidon_Jubjub_16_to_1_constants.circom | 0 .../poseidon/poseidon_Jubjub_2_to_1_constants.circom | 0 .../poseidon/poseidon_Jubjub_4_to_1_constants.circom | 0 proof_of_leadership/circomlib | 1 - 21 files changed, 2 insertions(+), 3 deletions(-) rename proof_of_leadership/{ => circom}/anemoi/anemoi_16_to_1_Jubjub.circom (100%) rename proof_of_leadership/{ => circom}/anemoi/anemoi_2_to_1_Jubjub.circom (100%) rename proof_of_leadership/{ => circom}/anemoi/anemoi_4_to_1_Jubjub.circom (100%) rename proof_of_leadership/{ => circom}/anemoi/anemoi_Jubjub_16_to_1_constants.circom (100%) rename proof_of_leadership/{ => circom}/anemoi/anemoi_Jubjub_2_to_1_constants.circom (100%) rename proof_of_leadership/{ => circom}/anemoi/anemoi_Jubjub_4_to_1_constants.circom (100%) rename proof_of_leadership/{ => circom}/anemoi/script_setup_prover.sh (100%) rename proof_of_leadership/{ => circom}/generate_inputs.py (100%) rename proof_of_leadership/{ => circom}/leadership_anemoi.circom (100%) rename proof_of_leadership/{ => circom}/leadership_anemoi_sha.circom (100%) rename proof_of_leadership/{ => circom}/leadership_poseidon.circom (100%) rename proof_of_leadership/{ => circom}/leadership_poseidon_sha.circom (100%) rename proof_of_leadership/{ => circom}/leadership_sha256.circom (100%) rename proof_of_leadership/{ => circom}/poseidon/poseidon_16_to_1_Jubjub.circom (100%) rename proof_of_leadership/{ => circom}/poseidon/poseidon_2_to_1_Jubjub.circom (100%) rename proof_of_leadership/{ => circom}/poseidon/poseidon_4_to_1_Jubjub.circom (100%) rename proof_of_leadership/{ => circom}/poseidon/poseidon_Jubjub_16_to_1_constants.circom (100%) rename proof_of_leadership/{ => circom}/poseidon/poseidon_Jubjub_2_to_1_constants.circom (100%) rename proof_of_leadership/{ => circom}/poseidon/poseidon_Jubjub_4_to_1_constants.circom (100%) delete mode 160000 proof_of_leadership/circomlib diff --git a/.gitmodules b/.gitmodules index a3bfbb7..6a3650c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "proof_of_leadership/circomlib"] - path = proof_of_leadership/circomlib +[submodule "proof_of_leadership/circom/circomlib"] + path = proof_of_leadership/circom/circomlib url = https://github.com/iden3/circomlib.git diff --git a/proof_of_leadership/anemoi/anemoi_16_to_1_Jubjub.circom b/proof_of_leadership/circom/anemoi/anemoi_16_to_1_Jubjub.circom similarity index 100% rename from proof_of_leadership/anemoi/anemoi_16_to_1_Jubjub.circom rename to proof_of_leadership/circom/anemoi/anemoi_16_to_1_Jubjub.circom diff --git a/proof_of_leadership/anemoi/anemoi_2_to_1_Jubjub.circom b/proof_of_leadership/circom/anemoi/anemoi_2_to_1_Jubjub.circom similarity index 100% rename from proof_of_leadership/anemoi/anemoi_2_to_1_Jubjub.circom rename to proof_of_leadership/circom/anemoi/anemoi_2_to_1_Jubjub.circom diff --git a/proof_of_leadership/anemoi/anemoi_4_to_1_Jubjub.circom b/proof_of_leadership/circom/anemoi/anemoi_4_to_1_Jubjub.circom similarity index 100% rename from proof_of_leadership/anemoi/anemoi_4_to_1_Jubjub.circom rename to proof_of_leadership/circom/anemoi/anemoi_4_to_1_Jubjub.circom diff --git a/proof_of_leadership/anemoi/anemoi_Jubjub_16_to_1_constants.circom b/proof_of_leadership/circom/anemoi/anemoi_Jubjub_16_to_1_constants.circom similarity index 100% rename from proof_of_leadership/anemoi/anemoi_Jubjub_16_to_1_constants.circom rename to proof_of_leadership/circom/anemoi/anemoi_Jubjub_16_to_1_constants.circom diff --git a/proof_of_leadership/anemoi/anemoi_Jubjub_2_to_1_constants.circom b/proof_of_leadership/circom/anemoi/anemoi_Jubjub_2_to_1_constants.circom similarity index 100% rename from proof_of_leadership/anemoi/anemoi_Jubjub_2_to_1_constants.circom rename to proof_of_leadership/circom/anemoi/anemoi_Jubjub_2_to_1_constants.circom diff --git a/proof_of_leadership/anemoi/anemoi_Jubjub_4_to_1_constants.circom b/proof_of_leadership/circom/anemoi/anemoi_Jubjub_4_to_1_constants.circom similarity index 100% rename from proof_of_leadership/anemoi/anemoi_Jubjub_4_to_1_constants.circom rename to proof_of_leadership/circom/anemoi/anemoi_Jubjub_4_to_1_constants.circom diff --git a/proof_of_leadership/anemoi/script_setup_prover.sh b/proof_of_leadership/circom/anemoi/script_setup_prover.sh similarity index 100% rename from proof_of_leadership/anemoi/script_setup_prover.sh rename to proof_of_leadership/circom/anemoi/script_setup_prover.sh diff --git a/proof_of_leadership/generate_inputs.py b/proof_of_leadership/circom/generate_inputs.py similarity index 100% rename from proof_of_leadership/generate_inputs.py rename to proof_of_leadership/circom/generate_inputs.py diff --git a/proof_of_leadership/leadership_anemoi.circom b/proof_of_leadership/circom/leadership_anemoi.circom similarity index 100% rename from proof_of_leadership/leadership_anemoi.circom rename to proof_of_leadership/circom/leadership_anemoi.circom diff --git a/proof_of_leadership/leadership_anemoi_sha.circom b/proof_of_leadership/circom/leadership_anemoi_sha.circom similarity index 100% rename from proof_of_leadership/leadership_anemoi_sha.circom rename to proof_of_leadership/circom/leadership_anemoi_sha.circom diff --git a/proof_of_leadership/leadership_poseidon.circom b/proof_of_leadership/circom/leadership_poseidon.circom similarity index 100% rename from proof_of_leadership/leadership_poseidon.circom rename to proof_of_leadership/circom/leadership_poseidon.circom diff --git a/proof_of_leadership/leadership_poseidon_sha.circom b/proof_of_leadership/circom/leadership_poseidon_sha.circom similarity index 100% rename from proof_of_leadership/leadership_poseidon_sha.circom rename to proof_of_leadership/circom/leadership_poseidon_sha.circom diff --git a/proof_of_leadership/leadership_sha256.circom b/proof_of_leadership/circom/leadership_sha256.circom similarity index 100% rename from proof_of_leadership/leadership_sha256.circom rename to proof_of_leadership/circom/leadership_sha256.circom diff --git a/proof_of_leadership/poseidon/poseidon_16_to_1_Jubjub.circom b/proof_of_leadership/circom/poseidon/poseidon_16_to_1_Jubjub.circom similarity index 100% rename from proof_of_leadership/poseidon/poseidon_16_to_1_Jubjub.circom rename to proof_of_leadership/circom/poseidon/poseidon_16_to_1_Jubjub.circom diff --git a/proof_of_leadership/poseidon/poseidon_2_to_1_Jubjub.circom b/proof_of_leadership/circom/poseidon/poseidon_2_to_1_Jubjub.circom similarity index 100% rename from proof_of_leadership/poseidon/poseidon_2_to_1_Jubjub.circom rename to proof_of_leadership/circom/poseidon/poseidon_2_to_1_Jubjub.circom diff --git a/proof_of_leadership/poseidon/poseidon_4_to_1_Jubjub.circom b/proof_of_leadership/circom/poseidon/poseidon_4_to_1_Jubjub.circom similarity index 100% rename from proof_of_leadership/poseidon/poseidon_4_to_1_Jubjub.circom rename to proof_of_leadership/circom/poseidon/poseidon_4_to_1_Jubjub.circom diff --git a/proof_of_leadership/poseidon/poseidon_Jubjub_16_to_1_constants.circom b/proof_of_leadership/circom/poseidon/poseidon_Jubjub_16_to_1_constants.circom similarity index 100% rename from proof_of_leadership/poseidon/poseidon_Jubjub_16_to_1_constants.circom rename to proof_of_leadership/circom/poseidon/poseidon_Jubjub_16_to_1_constants.circom diff --git a/proof_of_leadership/poseidon/poseidon_Jubjub_2_to_1_constants.circom b/proof_of_leadership/circom/poseidon/poseidon_Jubjub_2_to_1_constants.circom similarity index 100% rename from proof_of_leadership/poseidon/poseidon_Jubjub_2_to_1_constants.circom rename to proof_of_leadership/circom/poseidon/poseidon_Jubjub_2_to_1_constants.circom diff --git a/proof_of_leadership/poseidon/poseidon_Jubjub_4_to_1_constants.circom b/proof_of_leadership/circom/poseidon/poseidon_Jubjub_4_to_1_constants.circom similarity index 100% rename from proof_of_leadership/poseidon/poseidon_Jubjub_4_to_1_constants.circom rename to proof_of_leadership/circom/poseidon/poseidon_Jubjub_4_to_1_constants.circom diff --git a/proof_of_leadership/circomlib b/proof_of_leadership/circomlib deleted file mode 160000 index cff5ab6..0000000 --- a/proof_of_leadership/circomlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cff5ab6288b55ef23602221694a6a38a0239dcc0 From c97499f6aceb6872fb0e206c8f840b1c601a39c8 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Wed, 24 Jul 2024 14:34:31 +0400 Subject: [PATCH 2/5] pol: attempt to fix circom submodule --- proof_of_leadership/circom/circomlib | 1 + 1 file changed, 1 insertion(+) create mode 160000 proof_of_leadership/circom/circomlib diff --git a/proof_of_leadership/circom/circomlib b/proof_of_leadership/circom/circomlib new file mode 160000 index 0000000..cff5ab6 --- /dev/null +++ b/proof_of_leadership/circom/circomlib @@ -0,0 +1 @@ +Subproject commit cff5ab6288b55ef23602221694a6a38a0239dcc0 From 884232b2d36c2c9e3bfed7aba5407c0dedd4d0e1 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 25 Jul 2024 02:19:03 +0400 Subject: [PATCH 3/5] pol: risc0 implementation of the PoL zk statemetn --- goas/cl/cl/src/input.rs | 9 + goas/cl/cl/src/nullifier.rs | 20 +- proof_of_leadership/risc0/.gitignore | 2 + proof_of_leadership/risc0/Cargo.toml | 11 ++ .../risc0/proof_statements/Cargo.toml | 10 + .../proof_statements/src/death_constraint.rs | 8 + .../risc0/proof_statements/src/input.rs | 21 ++ .../risc0/proof_statements/src/lib.rs | 4 + .../src/proof_of_leadership.rs | 95 +++++++++ .../risc0/proof_statements/src/ptx.rs | 36 ++++ proof_of_leadership/risc0/prover/Cargo.toml | 15 ++ proof_of_leadership/risc0/prover/src/error.rs | 9 + .../risc0/prover/src/leader.rs | 182 ++++++++++++++++++ proof_of_leadership/risc0/prover/src/lib.rs | 2 + .../risc0/risc0_proofs/Cargo.toml | 10 + .../risc0/risc0_proofs/build.rs | 3 + .../proof_of_leadership/Cargo.toml | 23 +++ .../proof_of_leadership/src/main.rs | 34 ++++ .../risc0/risc0_proofs/src/lib.rs | 1 + 19 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 proof_of_leadership/risc0/.gitignore create mode 100644 proof_of_leadership/risc0/Cargo.toml create mode 100644 proof_of_leadership/risc0/proof_statements/Cargo.toml create mode 100644 proof_of_leadership/risc0/proof_statements/src/death_constraint.rs create mode 100644 proof_of_leadership/risc0/proof_statements/src/input.rs create mode 100644 proof_of_leadership/risc0/proof_statements/src/lib.rs create mode 100644 proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs create mode 100644 proof_of_leadership/risc0/proof_statements/src/ptx.rs create mode 100644 proof_of_leadership/risc0/prover/Cargo.toml create mode 100644 proof_of_leadership/risc0/prover/src/error.rs create mode 100644 proof_of_leadership/risc0/prover/src/leader.rs create mode 100644 proof_of_leadership/risc0/prover/src/lib.rs create mode 100644 proof_of_leadership/risc0/risc0_proofs/Cargo.toml create mode 100644 proof_of_leadership/risc0/risc0_proofs/build.rs create mode 100644 proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/Cargo.toml create mode 100644 proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/src/main.rs create mode 100644 proof_of_leadership/risc0/risc0_proofs/src/lib.rs diff --git a/goas/cl/cl/src/input.rs b/goas/cl/cl/src/input.rs index 8a9ff2f..17b495c 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -41,6 +41,15 @@ impl InputWitness { } } + pub fn evolve_output(&self, balance_blinding: BalanceWitness) -> crate::OutputWitness { + crate::OutputWitness { + note: self.note, + balance_blinding, + nf_pk: self.nf_sk.commit(), + nonce: self.nonce.evolve(&self.nf_sk), + } + } + pub fn nullifier(&self) -> Nullifier { Nullifier::new(self.nf_sk, self.nonce) } diff --git a/goas/cl/cl/src/nullifier.rs b/goas/cl/cl/src/nullifier.rs index b378093..f0cf311 100644 --- a/goas/cl/cl/src/nullifier.rs +++ b/goas/cl/cl/src/nullifier.rs @@ -17,7 +17,7 @@ use sha2::{Digest, Sha256}; // Maintained privately by note holder #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct NullifierSecret([u8; 16]); +pub struct NullifierSecret(pub [u8; 16]); // Nullifier commitment is public information that // can be provided to anyone wishing to transfer @@ -29,7 +29,7 @@ pub struct NullifierCommitment([u8; 32]); // provide a nonce to differentiate notes controlled by the same // secret. Each note is assigned a unique nullifier nonce. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct NullifierNonce([u8; 16]); +pub struct NullifierNonce([u8; 32]); // The nullifier attached to input notes to prove an input has not // already been spent. @@ -69,18 +69,28 @@ impl NullifierCommitment { impl NullifierNonce { pub fn random(mut rng: impl RngCore) -> Self { - let mut nonce = [0u8; 16]; + let mut nonce = [0u8; 32]; rng.fill_bytes(&mut nonce); Self(nonce) } - pub fn as_bytes(&self) -> &[u8; 16] { + pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } - pub fn from_bytes(bytes: [u8; 16]) -> Self { + pub fn from_bytes(bytes: [u8; 32]) -> Self { Self(bytes) } + + pub fn evolve(&self, nf_sk: &NullifierSecret) -> Self { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_COIN_EVOLVE"); + hasher.update(&self.0); + hasher.update(nf_sk.0); + + let nonce_bytes: [u8; 32] = hasher.finalize().into(); + Self(nonce_bytes) + } } impl Nullifier { diff --git a/proof_of_leadership/risc0/.gitignore b/proof_of_leadership/risc0/.gitignore new file mode 100644 index 0000000..b354aec --- /dev/null +++ b/proof_of_leadership/risc0/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ \ No newline at end of file diff --git a/proof_of_leadership/risc0/Cargo.toml b/proof_of_leadership/risc0/Cargo.toml new file mode 100644 index 0000000..51caf20 --- /dev/null +++ b/proof_of_leadership/risc0/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = [ "prover", "proof_statements", "risc0_proofs"] + +# Always optimize; building and running the risc0_proofs takes much longer without optimization. +[profile.dev] +opt-level = 3 + +[profile.release] +debug = 1 +lto = true diff --git a/proof_of_leadership/risc0/proof_statements/Cargo.toml b/proof_of_leadership/risc0/proof_statements/Cargo.toml new file mode 100644 index 0000000..dff4529 --- /dev/null +++ b/proof_of_leadership/risc0/proof_statements/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "proof_statements" +version = "0.1.0" +edition = "2021" + +[dependencies] +cl = { path = "../../../goas/cl/cl" } +serde = { version = "1.0", features = ["derive"] } +crypto-bigint = { version = "0.5.5", features = ["serde"] } +sha2 = "0.10" \ No newline at end of file diff --git a/proof_of_leadership/risc0/proof_statements/src/death_constraint.rs b/proof_of_leadership/risc0/proof_statements/src/death_constraint.rs new file mode 100644 index 0000000..019ecdb --- /dev/null +++ b/proof_of_leadership/risc0/proof_statements/src/death_constraint.rs @@ -0,0 +1,8 @@ +use cl::{Nullifier, PtxRoot}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DeathConstraintPublic { + pub nf: Nullifier, + pub ptx_root: PtxRoot, +} diff --git a/proof_of_leadership/risc0/proof_statements/src/input.rs b/proof_of_leadership/risc0/proof_statements/src/input.rs new file mode 100644 index 0000000..20dab89 --- /dev/null +++ b/proof_of_leadership/risc0/proof_statements/src/input.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +/// for public inputs `nf` (nullifier), `root_cm` (root of merkle tree over commitment set) and `death_cm` (commitment to death constraint). +/// the prover has knowledge of `output = (note, nf_pk, nonce)`, `nf` and `path` s.t. that the following constraints hold +/// 0. nf_pk = hash(nf_sk) +/// 1. nf = hash(nonce||nf_sk) +/// 2. note_cm = output_commitment(output) +/// 3. verify_merkle_path(note_cm, root, path) +/// 4. death_cm = death_commitment(note.death_constraint) + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct InputPublic { + pub cm_root: [u8; 32], + pub input: cl::Input, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct InputPrivate { + pub input: cl::InputWitness, + pub cm_path: Vec, +} diff --git a/proof_of_leadership/risc0/proof_statements/src/lib.rs b/proof_of_leadership/risc0/proof_statements/src/lib.rs new file mode 100644 index 0000000..27f48ea --- /dev/null +++ b/proof_of_leadership/risc0/proof_statements/src/lib.rs @@ -0,0 +1,4 @@ +pub mod death_constraint; +pub mod input; +pub mod ptx; +pub mod proof_of_leadership; diff --git a/proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs b/proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs new file mode 100644 index 0000000..acc9f56 --- /dev/null +++ b/proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs @@ -0,0 +1,95 @@ +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use crypto_bigint::{U256, Encoding, CheckedMul, CheckedSub}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct LeaderPublic { + pub cm_root: [u8; 32], + pub epoch_nonce: [u8; 32], + pub slot: u64, + pub scaled_phi_approx: (U256, U256), + pub nullifier: cl::Nullifier, + pub updated_commitment: cl::NoteCommitment, +} + +impl LeaderPublic { + pub fn new( + cm_root: [u8; 32], + epoch_nonce: [u8; 32], + slot: u64, + active_slot_coefficient: f64, + total_stake: u64, + nullifier: cl::Nullifier, + updated_commitment: cl::NoteCommitment + ) -> Self + { + + + // TODO: check in sage that these precision adjustments are valid. + // also ensure that intermediate values going above the field + // order don't mess the final result of the evaluation. + let total_stake_big = U256::from_u64(total_stake); + let total_stake_sq_big = total_stake_big.checked_mul(&total_stake_big).unwrap(); + let double_total_stake_sq_big = total_stake_sq_big.checked_mul(&U256::from_u64(2)).unwrap(); + + let precision_u64 = u64::MAX; + let precision_big = U256::from_u64(u64::MAX); + let precision_f64 = precision_u64 as f64; + let order: U256 = U256::MAX; + + let order_div_precision = order.checked_div(&precision_big).unwrap(); + let order_div_precision_sq = order_div_precision.checked_div(&precision_big).unwrap() +; + let neg_f_ln: U256 = U256::from_u64(((-f64::ln(1f64 - active_slot_coefficient)) * precision_f64) as u64); + let neg_f_ln_sq = neg_f_ln.checked_mul(&neg_f_ln).unwrap(); + + let neg_f_ln_order: U256 = order_div_precision.checked_mul(&neg_f_ln).unwrap(); + let t0 = neg_f_ln_order.checked_div(&total_stake_big).unwrap(); + let t1 = order_div_precision_sq.checked_mul(&neg_f_ln_sq).unwrap().checked_div(&double_total_stake_sq_big).unwrap(); + + Self { + cm_root, + epoch_nonce, + slot, + nullifier, + updated_commitment, + scaled_phi_approx: (t0, t1), + } + } + + + + pub fn check_winning(&self, input: &cl::InputWitness) -> bool { + // Lottery checks + let threshold = phi_approx(U256::from_u64(input.note.value), self.scaled_phi_approx); + let ticket = ticket(&input, self.epoch_nonce, self.slot); + ticket < threshold + } +} + + +fn phi_approx(stake: U256, approx: (U256, U256)) -> U256 { + // stake * (t0 - t1 * stake) + stake.checked_mul(&approx.0.checked_sub(&approx.1.checked_mul(&stake).unwrap()).unwrap()).unwrap() +} + +fn ticket(input: &cl::InputWitness, epoch_nonce: [u8;32], slot: u64) -> U256 { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_LEAD"); + hasher.update(epoch_nonce); + hasher.update(slot.to_be_bytes()); + hasher.update(input.note_commitment().as_bytes()); + hasher.update(input.nf_sk.0); + + let ticket_bytes: [u8; 32] = hasher.finalize().into(); + + U256::from_be_bytes(ticket_bytes) +} + + + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct LeaderPrivate { + pub input: cl::InputWitness, + pub input_cm_path: Vec, +} diff --git a/proof_of_leadership/risc0/proof_statements/src/ptx.rs b/proof_of_leadership/risc0/proof_statements/src/ptx.rs new file mode 100644 index 0000000..9701646 --- /dev/null +++ b/proof_of_leadership/risc0/proof_statements/src/ptx.rs @@ -0,0 +1,36 @@ +use cl::{merkle, InputWitness, OutputWitness, PtxRoot}; +use serde::{Deserialize, Serialize}; + +/// An input to a partial transaction +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTxInputPrivate { + pub input: InputWitness, + pub cm_path: Vec, + pub ptx_path: Vec, +} + +impl PartialTxInputPrivate { + pub fn ptx_root(&self) -> PtxRoot { + let leaf = merkle::leaf(&self.input.commit().to_bytes()); + PtxRoot(merkle::path_root(leaf, &self.ptx_path)) + } + + pub fn cm_root(&self) -> [u8; 32] { + let leaf = merkle::leaf(self.input.note_commitment().as_bytes()); + merkle::path_root(leaf, &self.cm_path) + } +} + +/// An output to a partial transaction +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTxOutputPrivate { + pub output: OutputWitness, + pub ptx_path: Vec, +} + +impl PartialTxOutputPrivate { + pub fn ptx_root(&self) -> PtxRoot { + let leaf = merkle::leaf(&self.output.commit().to_bytes()); + PtxRoot(merkle::path_root(leaf, &self.ptx_path)) + } +} diff --git a/proof_of_leadership/risc0/prover/Cargo.toml b/proof_of_leadership/risc0/prover/Cargo.toml new file mode 100644 index 0000000..2a1e334 --- /dev/null +++ b/proof_of_leadership/risc0/prover/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nomos_pol_prover" +version = "0.1.0" +edition = "2021" + +[dependencies] +cl = { path = "../../../goas/cl/cl" } +proof_statements = { path = "../proof_statements" } +nomos_pol_risc0_proofs = { path = "../risc0_proofs" } +risc0-zkvm = { version = "1.0", features = ["prove", "metal"] } +risc0-groth16 = { version = "1.0" } +rand = "0.8.5" +rand_core = "0.6.0" +thiserror = "1.0.62" +curve25519-dalek = {version = "4.1", features = ["serde", "digest", "rand_core"]} diff --git a/proof_of_leadership/risc0/prover/src/error.rs b/proof_of_leadership/risc0/prover/src/error.rs new file mode 100644 index 0000000..3204a72 --- /dev/null +++ b/proof_of_leadership/risc0/prover/src/error.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +pub type Result = core::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("risc0 failed to serde")] + Risc0Serde(#[from] risc0_zkvm::serde::Error), +} diff --git a/proof_of_leadership/risc0/prover/src/leader.rs b/proof_of_leadership/risc0/prover/src/leader.rs new file mode 100644 index 0000000..449dd90 --- /dev/null +++ b/proof_of_leadership/risc0/prover/src/leader.rs @@ -0,0 +1,182 @@ +use crate::error::Result; + +use curve25519_dalek::Scalar; + +use proof_statements::proof_of_leadership::{LeaderPrivate, LeaderPublic}; + +const MAX_NOTE_COMMS: usize = 2usize.pow(8); + + +pub struct ProvedLeader { + pub leader: LeaderPublic, + pub risc0_receipt: risc0_zkvm::Receipt, +} + + +impl ProvedLeader { + pub fn prove(input: &cl::InputWitness, epoch_nonce: [u8;32], slot: u64, active_slot_coefficient: f64, total_stake: u64, note_commitments: &[cl::NoteCommitment]) -> Self { + let note_cm = input.note_commitment(); + let cm_leaves = note_commitment_leaves(note_commitments); + let cm_idx = note_commitments + .iter() + .position(|c| c == ¬e_cm) + .unwrap(); + let note_cm_path = cl::merkle::path(cm_leaves, cm_idx); + let cm_root = cl::merkle::root(cm_leaves); + + let leader_private = LeaderPrivate { + input: *input, + input_cm_path: note_cm_path, + }; + + let leader_public = LeaderPublic::new( + cm_root, + epoch_nonce, + slot, + active_slot_coefficient, + total_stake, + input.nullifier(), + input.evolve_output(cl::BalanceWitness::new(Scalar::ZERO)).commit_note(), + ); + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&leader_public) + .unwrap() + .write(&leader_private) + .unwrap() + .build() + .unwrap(); + + // Obtain the default prover. + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::Instant::now(); + + // Proof information by proving the specified ELF binary. + // This struct contains the receipt along with statistics about execution of the guest + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, nomos_pol_risc0_proofs::PROOF_OF_LEADERSHIP_ELF, &opts) + .unwrap(); + + println!( + "STARK prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + // extract the receipt. + let receipt = prove_info.receipt; + + Self { + leader: leader_public, + risc0_receipt: receipt, + } + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self) -> bool { + let Ok(proved_public_inputs) = self.public() else { + return false; + }; + + self.leader == proved_public_inputs + && self + .risc0_receipt + .verify(nomos_pol_risc0_proofs::PROOF_OF_LEADERSHIP_ID) + .is_ok() + } +} + + +fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] { + let note_comm_bytes = Vec::from_iter(note_commitments.iter().map(|c| c.as_bytes().to_vec())); + let cm_leaves = cl::merkle::padded_leaves::(¬e_comm_bytes); + cm_leaves +} + +#[cfg(test)] +mod test { + use rand::thread_rng; + + use super::*; + + #[test] + fn test_leader_prover() { + let mut rng = thread_rng(); + + let input = cl::InputWitness { + note: cl::NoteWitness::basic(32, "NMO"), + balance_blinding: cl::BalanceWitness::random(&mut rng), + nf_sk: cl::NullifierSecret::random(&mut rng), + nonce: cl::NullifierNonce::random(&mut rng), + }; + + let notes = vec![input.note_commitment()]; + let epoch_nonce = [0u8; 32]; + let slot = 0; + let active_slot_coefficient = 0.05; + let total_stake = 1000; + + let mut expected_public_inputs = LeaderPublic::new( + cl::merkle::root(note_commitment_leaves(¬es)), + epoch_nonce, + slot, + active_slot_coefficient, + total_stake, + input.nullifier(), + input.evolve_output(cl::BalanceWitness::new(Scalar::ZERO)).commit_note(), + ); + + while !expected_public_inputs.check_winning(&input) { + expected_public_inputs.slot += 1; + } + + println!("slot={}", expected_public_inputs.slot); + + let proved_leader = ProvedLeader::prove(&input, expected_public_inputs.epoch_nonce, expected_public_inputs.slot, active_slot_coefficient, total_stake, ¬es); + + + assert_eq!(proved_leader.leader, expected_public_inputs); + assert!(proved_leader.verify()); + + // let wrong_public_inputs = [ + // InputPublic { + // cm_root: cl::merkle::root([cl::merkle::leaf(b"bad_root")]), + // ..expected_public_inputs + // }, + // InputPublic { + // input: cl::Input { + // nullifier: cl::Nullifier::new( + // cl::NullifierSecret::random(&mut rng), + // cl::NullifierNonce::random(&mut rng), + // ), + // ..expected_public_inputs.input + // }, + // ..expected_public_inputs + // }, + // InputPublic { + // input: cl::Input { + // death_cm: cl::note::death_commitment(b"wrong death vk"), + // ..expected_public_inputs.input + // }, + // ..expected_public_inputs + // }, + // InputPublic { + // input: cl::Input { + // balance: cl::BalanceWitness::random(&mut rng) + // .commit(&cl::NoteWitness::basic(32, "NMO")), + // ..expected_public_inputs.input + // }, + // ..expected_public_inputs + // }, + // ]; + + // for wrong_input in wrong_public_inputs { + // proved_input.input = wrong_input; + // assert!(!proved_input.verify()); + // } + } +} diff --git a/proof_of_leadership/risc0/prover/src/lib.rs b/proof_of_leadership/risc0/prover/src/lib.rs new file mode 100644 index 0000000..ffc73a0 --- /dev/null +++ b/proof_of_leadership/risc0/prover/src/lib.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod leader; diff --git a/proof_of_leadership/risc0/risc0_proofs/Cargo.toml b/proof_of_leadership/risc0/risc0_proofs/Cargo.toml new file mode 100644 index 0000000..a636444 --- /dev/null +++ b/proof_of_leadership/risc0/risc0_proofs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "nomos_pol_risc0_proofs" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "1.0" } + +[package.metadata.risc0] +methods = ["proof_of_leadership"] diff --git a/proof_of_leadership/risc0/risc0_proofs/build.rs b/proof_of_leadership/risc0/risc0_proofs/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/proof_of_leadership/risc0/risc0_proofs/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/Cargo.toml b/proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/Cargo.toml new file mode 100644 index 0000000..88c87b4 --- /dev/null +++ b/proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "proof_of_leadership" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +cl = { path = "../../../../goas/cl/cl" } +proof_statements = { path = "../../proof_statements" } +curve25519-dalek = {version = "4.1", features = ["serde", "digest", "rand_core"]} +sha2 = "0.10" +crypto-bigint = "0.5.5" + + + +[patch.crates-io] +# add RISC Zero accelerator support for all downstream usages of the following crates. +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" } +crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" } +curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" } diff --git a/proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/src/main.rs b/proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/src/main.rs new file mode 100644 index 0000000..ce81223 --- /dev/null +++ b/proof_of_leadership/risc0/risc0_proofs/proof_of_leadership/src/main.rs @@ -0,0 +1,34 @@ +/// Proof of Leadership +use cl::merkle; +use curve25519_dalek::Scalar; +use proof_statements::proof_of_leadership::{LeaderPrivate, LeaderPublic}; +use risc0_zkvm::guest::env; + + +fn main() { + let public_inputs: LeaderPublic = env::read(); + + let LeaderPrivate { + input, + input_cm_path, + } = env::read(); + + // Lottery checks + assert!(public_inputs.check_winning(&input)); + + + // Ensure note is valid + let note_cm = input.note_commitment(); + let note_cm_leaf = merkle::leaf(note_cm.as_bytes()); + let note_cm_root = merkle::path_root(note_cm_leaf, &input_cm_path); + assert_eq!(note_cm_root, public_inputs.cm_root); + + + // Public input constraints + assert_eq!(input.nullifier(), public_inputs.nullifier); + + let evolved_output = input.evolve_output(cl::BalanceWitness::new(Scalar::ZERO)); + assert_eq!(evolved_output.commit_note(), public_inputs.updated_commitment); + + env::commit(&public_inputs); +} diff --git a/proof_of_leadership/risc0/risc0_proofs/src/lib.rs b/proof_of_leadership/risc0/risc0_proofs/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/proof_of_leadership/risc0/risc0_proofs/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); From a268129ee954b9d03f00299485e68d424bf92009 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 25 Jul 2024 02:24:56 +0400 Subject: [PATCH 4/5] pol: remove copied proof statements --- .../proof_statements/src/death_constraint.rs | 8 ----- .../risc0/proof_statements/src/input.rs | 21 ----------- .../risc0/proof_statements/src/lib.rs | 3 -- .../risc0/proof_statements/src/ptx.rs | 36 ------------------- 4 files changed, 68 deletions(-) delete mode 100644 proof_of_leadership/risc0/proof_statements/src/death_constraint.rs delete mode 100644 proof_of_leadership/risc0/proof_statements/src/input.rs delete mode 100644 proof_of_leadership/risc0/proof_statements/src/ptx.rs diff --git a/proof_of_leadership/risc0/proof_statements/src/death_constraint.rs b/proof_of_leadership/risc0/proof_statements/src/death_constraint.rs deleted file mode 100644 index 019ecdb..0000000 --- a/proof_of_leadership/risc0/proof_statements/src/death_constraint.rs +++ /dev/null @@ -1,8 +0,0 @@ -use cl::{Nullifier, PtxRoot}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct DeathConstraintPublic { - pub nf: Nullifier, - pub ptx_root: PtxRoot, -} diff --git a/proof_of_leadership/risc0/proof_statements/src/input.rs b/proof_of_leadership/risc0/proof_statements/src/input.rs deleted file mode 100644 index 20dab89..0000000 --- a/proof_of_leadership/risc0/proof_statements/src/input.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// for public inputs `nf` (nullifier), `root_cm` (root of merkle tree over commitment set) and `death_cm` (commitment to death constraint). -/// the prover has knowledge of `output = (note, nf_pk, nonce)`, `nf` and `path` s.t. that the following constraints hold -/// 0. nf_pk = hash(nf_sk) -/// 1. nf = hash(nonce||nf_sk) -/// 2. note_cm = output_commitment(output) -/// 3. verify_merkle_path(note_cm, root, path) -/// 4. death_cm = death_commitment(note.death_constraint) - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct InputPublic { - pub cm_root: [u8; 32], - pub input: cl::Input, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct InputPrivate { - pub input: cl::InputWitness, - pub cm_path: Vec, -} diff --git a/proof_of_leadership/risc0/proof_statements/src/lib.rs b/proof_of_leadership/risc0/proof_statements/src/lib.rs index 27f48ea..6ea6aa1 100644 --- a/proof_of_leadership/risc0/proof_statements/src/lib.rs +++ b/proof_of_leadership/risc0/proof_statements/src/lib.rs @@ -1,4 +1 @@ -pub mod death_constraint; -pub mod input; -pub mod ptx; pub mod proof_of_leadership; diff --git a/proof_of_leadership/risc0/proof_statements/src/ptx.rs b/proof_of_leadership/risc0/proof_statements/src/ptx.rs deleted file mode 100644 index 9701646..0000000 --- a/proof_of_leadership/risc0/proof_statements/src/ptx.rs +++ /dev/null @@ -1,36 +0,0 @@ -use cl::{merkle, InputWitness, OutputWitness, PtxRoot}; -use serde::{Deserialize, Serialize}; - -/// An input to a partial transaction -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PartialTxInputPrivate { - pub input: InputWitness, - pub cm_path: Vec, - pub ptx_path: Vec, -} - -impl PartialTxInputPrivate { - pub fn ptx_root(&self) -> PtxRoot { - let leaf = merkle::leaf(&self.input.commit().to_bytes()); - PtxRoot(merkle::path_root(leaf, &self.ptx_path)) - } - - pub fn cm_root(&self) -> [u8; 32] { - let leaf = merkle::leaf(self.input.note_commitment().as_bytes()); - merkle::path_root(leaf, &self.cm_path) - } -} - -/// An output to a partial transaction -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PartialTxOutputPrivate { - pub output: OutputWitness, - pub ptx_path: Vec, -} - -impl PartialTxOutputPrivate { - pub fn ptx_root(&self) -> PtxRoot { - let leaf = merkle::leaf(&self.output.commit().to_bytes()); - PtxRoot(merkle::path_root(leaf, &self.ptx_path)) - } -} From 7e19f8bce9fee5175886bd6e6aa22a18195e074e Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 25 Jul 2024 16:27:59 +0400 Subject: [PATCH 5/5] pol: lottery evaluation looks correct --- .../risc0/proof_statements/src/proof_of_leadership.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs b/proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs index acc9f56..04add9c 100644 --- a/proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs +++ b/proof_of_leadership/risc0/proof_statements/src/proof_of_leadership.rs @@ -23,11 +23,6 @@ impl LeaderPublic { updated_commitment: cl::NoteCommitment ) -> Self { - - - // TODO: check in sage that these precision adjustments are valid. - // also ensure that intermediate values going above the field - // order don't mess the final result of the evaluation. let total_stake_big = U256::from_u64(total_stake); let total_stake_sq_big = total_stake_big.checked_mul(&total_stake_big).unwrap(); let double_total_stake_sq_big = total_stake_sq_big.checked_mul(&U256::from_u64(2)).unwrap(); @@ -60,7 +55,6 @@ impl LeaderPublic { pub fn check_winning(&self, input: &cl::InputWitness) -> bool { - // Lottery checks let threshold = phi_approx(U256::from_u64(input.note.value), self.scaled_phi_approx); let ticket = ticket(&input, self.epoch_nonce, self.slot); ticket < threshold