mirror of
https://github.com/logos-co/nomos-pocs.git
synced 2025-01-12 02:14:35 +00:00
Merge pull request #9 from logos-co/pol/risc0
Proof of Leadership in risc0
This commit is contained in:
commit
182db86af3
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
2
proof_of_leadership/risc0/.gitignore
vendored
Normal file
2
proof_of_leadership/risc0/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Cargo.lock
|
||||
target/
|
11
proof_of_leadership/risc0/Cargo.toml
Normal file
11
proof_of_leadership/risc0/Cargo.toml
Normal file
@ -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
|
10
proof_of_leadership/risc0/proof_statements/Cargo.toml
Normal file
10
proof_of_leadership/risc0/proof_statements/Cargo.toml
Normal file
@ -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"
|
1
proof_of_leadership/risc0/proof_statements/src/lib.rs
Normal file
1
proof_of_leadership/risc0/proof_statements/src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod proof_of_leadership;
|
@ -0,0 +1,89 @@
|
||||
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
|
||||
{
|
||||
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 {
|
||||
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<cl::merkle::PathNode>,
|
||||
}
|
15
proof_of_leadership/risc0/prover/Cargo.toml
Normal file
15
proof_of_leadership/risc0/prover/Cargo.toml
Normal file
@ -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"]}
|
9
proof_of_leadership/risc0/prover/src/error.rs
Normal file
9
proof_of_leadership/risc0/prover/src/error.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("risc0 failed to serde")]
|
||||
Risc0Serde(#[from] risc0_zkvm::serde::Error),
|
||||
}
|
182
proof_of_leadership/risc0/prover/src/leader.rs
Normal file
182
proof_of_leadership/risc0/prover/src/leader.rs
Normal file
@ -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<LeaderPublic> {
|
||||
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::<MAX_NOTE_COMMS>(¬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());
|
||||
// }
|
||||
}
|
||||
}
|
2
proof_of_leadership/risc0/prover/src/lib.rs
Normal file
2
proof_of_leadership/risc0/prover/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod error;
|
||||
pub mod leader;
|
10
proof_of_leadership/risc0/risc0_proofs/Cargo.toml
Normal file
10
proof_of_leadership/risc0/risc0_proofs/Cargo.toml
Normal file
@ -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"]
|
3
proof_of_leadership/risc0/risc0_proofs/build.rs
Normal file
3
proof_of_leadership/risc0/risc0_proofs/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
risc0_build::embed_methods();
|
||||
}
|
@ -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" }
|
@ -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);
|
||||
}
|
1
proof_of_leadership/risc0/risc0_proofs/src/lib.rs
Normal file
1
proof_of_leadership/risc0/risc0_proofs/src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
Loading…
x
Reference in New Issue
Block a user