From 3213453c4372e59bf4d459bbcb586f0d55d64b18 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini <21265557+zeegomo@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:07:55 +0100 Subject: [PATCH] [CL-1] Add proofs to CL txs (#850) * Add missing tx proofs to complete bundle validation * address review comments --- ledger/nomos-ledger/src/lib.rs | 91 ++++---- nodes/nomos-node/src/config.rs | 31 ++- nomos-core/chain-defs/src/lib.rs | 1 + nomos-core/chain-defs/src/proofs/balance.rs | 75 +++++++ nomos-core/chain-defs/src/proofs/covenant.rs | 91 ++++++++ nomos-core/chain-defs/src/proofs/mod.rs | 3 + nomos-core/chain-defs/src/proofs/ptx.rs | 70 ++++++ nomos-core/chain-defs/src/tx/bundle.rs | 202 ++++++++++++------ nomos-core/chain-defs/src/tx/mod.rs | 4 +- nomos-core/cl/src/bundle.rs | 7 +- nomos-core/cl/src/input.rs | 42 ++-- nomos-core/cl/src/lib.rs | 2 +- nomos-core/cl/src/note.rs | 51 +++-- nomos-core/cl/src/nullifier.rs | 6 +- nomos-core/cl/src/partial_tx.rs | 6 +- nomos-core/cl/tests/simple_transfer.rs | 2 +- nomos-core/proof_statements/src/ptx.rs | 1 - nomos-core/risc0_proofs/Cargo.toml | 2 +- .../{bundle => bundle_balance}/Cargo.toml | 2 +- .../{bundle => bundle_balance}/src/main.rs | 0 nomos-core/risc0_proofs/ptx/src/main.rs | 11 +- .../cryptarchia-consensus/src/leadership.rs | 62 ++++-- .../cryptarchia-consensus/src/lib.rs | 8 +- .../data-availability/tests/src/common.rs | 5 +- .../tests/src/indexer_integration.rs | 33 +-- .../tests/src/verifier_integration.rs | 33 +-- .../proof_statements/src/lib.rs | 1 - proof_of_leadership/risc0/prover/src/lib.rs | 22 +- .../proof_of_leadership/src/main.rs | 7 +- tests/src/nodes/executor.rs | 2 +- tests/src/nodes/validator.rs | 2 +- tests/src/topology/configs/consensus.rs | 27 ++- 32 files changed, 629 insertions(+), 273 deletions(-) create mode 100644 nomos-core/chain-defs/src/proofs/balance.rs create mode 100644 nomos-core/chain-defs/src/proofs/covenant.rs create mode 100644 nomos-core/chain-defs/src/proofs/mod.rs create mode 100644 nomos-core/chain-defs/src/proofs/ptx.rs rename nomos-core/risc0_proofs/{bundle => bundle_balance}/Cargo.toml (95%) rename nomos-core/risc0_proofs/{bundle => bundle_balance}/src/main.rs (100%) diff --git a/ledger/nomos-ledger/src/lib.rs b/ledger/nomos-ledger/src/lib.rs index 5cd29b27..db61d992 100644 --- a/ledger/nomos-ledger/src/lib.rs +++ b/ledger/nomos-ledger/src/lib.rs @@ -420,17 +420,16 @@ pub mod tests { use super::*; use crate::{crypto::Blake2b, leader_proof::LeaderProof, Config, LedgerError}; use blake2::Digest; - use cl::{note::NoteWitness, InputWitness as Note, NullifierSecret}; + use cl::{note::NoteWitness as Note, NullifierSecret}; use cryptarchia_engine::Slot; use rand::thread_rng; type HeaderId = [u8; 32]; - fn note(sk: u8) -> Note { - Note::new( - NoteWitness::basic(0, [0; 32], &mut thread_rng()), - NullifierSecret::from_bytes([sk as u8; 16]), - ) + const NF_SK: NullifierSecret = NullifierSecret([0; 16]); + + fn note() -> Note { + Note::basic(0, [0; 32], &mut thread_rng()) } struct DummyProof { @@ -457,14 +456,15 @@ pub mod tests { } } + fn commit(note: Note) -> NoteCommitment { + note.commit(NF_SK.commit()) + } + fn evolve(note: Note) -> Note { - Note::new( - NoteWitness { - nonce: note.evolved_nonce(b"test"), - ..note.note - }, - note.nf_sk, - ) + Note { + nonce: note.evolved_nonce(NF_SK, b"test"), + ..note + } } fn update_ledger( @@ -480,7 +480,7 @@ pub mod tests { Blake2b::new() .chain_update(parent) .chain_update(slot.into().to_be_bytes()) - .chain_update(note.note_commitment().as_bytes()) + .chain_update(commit(note).as_bytes()) .finalize() .into() } @@ -496,15 +496,15 @@ pub mod tests { note_tree .commitments() .iter() - .find(|n| n == &¬e.note_commitment()) + .find(|n| n == &&commit(*note)) .is_some() } fn proof(note: Note, cm_root: [u8; 32]) -> DummyProof { DummyProof { cm_root, - nullifier: note.nullifier(), - commitment: evolve(note).note_commitment(), + nullifier: Nullifier::new(NF_SK, commit(note)), + commitment: commit(evolve(note)), } } @@ -528,7 +528,7 @@ pub mod tests { .into_iter() .map(|note| { let cm_root = get_cm_root(note, &[&lead_comms, snapshot_comms]); - lead_comms = lead_comms.insert(evolve(note).note_commitment()); + lead_comms = lead_comms.insert(evolve(note).commit(NF_SK.commit())); proof(note, cm_root).to_orphan_proof() }) .zip(ids) @@ -614,17 +614,15 @@ pub mod tests { // we still don't have transactions, so the only way to add a commitment to spendable commitments and // test epoch snapshotting is by doing this manually let mut block_state = ledger.states[&id].clone(); - block_state.spend_commitments = block_state - .spend_commitments - .insert(note_add.note_commitment()); + block_state.spend_commitments = block_state.spend_commitments.insert(commit(note_add)); ledger.states.insert(id, block_state); id } #[test] fn test_ledger_state_prevents_note_reuse() { - let note = note(0); - let (mut ledger, genesis) = ledger(&[note.note_commitment()]); + let note = note(); + let (mut ledger, genesis) = ledger(&[commit(note)]); let h = update_ledger(&mut ledger, genesis, 1, note).unwrap(); @@ -637,7 +635,7 @@ pub mod tests { #[test] fn test_ledger_state_uncommited_note() { - let note = note(0); + let note = note(); let (mut ledger, genesis) = ledger(&[]); assert!(matches!( update_ledger(&mut ledger, genesis, 1, note), @@ -647,15 +645,11 @@ pub mod tests { #[test] fn test_ledger_state_is_properly_updated_on_reorg() { - let note_1 = note(0); - let note_2 = note(1); - let note_3 = note(2); + let note_1 = note(); + let note_2 = note(); + let note_3 = note(); - let (mut ledger, genesis) = ledger(&[ - note_1.note_commitment(), - note_2.note_commitment(), - note_3.note_commitment(), - ]); + let (mut ledger, genesis) = ledger(&[commit(note_1), commit(note_2), commit(note_3)]); // note_1 & note_2 both concurrently win slot 0 @@ -665,20 +659,15 @@ pub mod tests { // then note_3 wins slot 1 and chooses to extend from block_2 let h_3 = update_ledger(&mut ledger, h, 2, note_3).unwrap(); // note 1 is not spent in the chain that ends with block_3 - assert!(!ledger.states[&h_3].is_nullified(¬e_1.nullifier())); + assert!(!ledger.states[&h_3].is_nullified(&Nullifier::new(NF_SK, commit(note_1)))); } #[test] fn test_epoch_transition() { - let notes = (0..4).map(note).collect::>(); - let note_4 = note(4); - let note_5 = note(5); - let (mut ledger, genesis) = ledger( - ¬es - .iter() - .map(|c| c.note_commitment()) - .collect::>(), - ); + let notes = (0..4).map(|_| note()).collect::>(); + let note_4 = note(); + let note_5 = note(); + let (mut ledger, genesis) = ledger(¬es.iter().copied().map(commit).collect::>()); // An epoch will be 10 slots long, with stake distribution snapshot taken at the start of the epoch // and nonce snapshot before slot 7 @@ -722,8 +711,8 @@ pub mod tests { #[test] fn test_evolved_note_is_eligible_for_leadership() { - let note = note(0); - let (mut ledger, genesis) = ledger(&[note.note_commitment()]); + let note = note(); + let (mut ledger, genesis) = ledger(&[commit(note)]); let h = update_ledger(&mut ledger, genesis, 1, note).unwrap(); @@ -745,10 +734,10 @@ pub mod tests { #[test] fn test_new_notes_becoming_eligible_after_stake_distribution_stabilizes() { - let note_1 = note(1); - let note = note(0); + let note_1 = note(); + let note = note(); - let (mut ledger, genesis) = ledger(&[note.note_commitment()]); + let (mut ledger, genesis) = ledger(&[commit(note)]); // EPOCH 0 // mint a new note to be used for leader elections in upcoming epochs @@ -783,8 +772,8 @@ pub mod tests { #[test] fn test_orphan_proof_import() { - let note = note(0); - let (mut ledger, genesis) = ledger(&[note.note_commitment()]); + let note = note(); + let (mut ledger, genesis) = ledger(&[commit(note)]); let note_new = evolve(note); let note_new_new = evolve(note_new); @@ -873,8 +862,8 @@ pub mod tests { #[test] fn test_update_epoch_state_with_outdated_slot_error() { - let note = note(0); - let commitment = note.note_commitment(); + let note = note(); + let commitment = commit(note); let (ledger, genesis) = ledger(&[commitment]); let ledger_state = ledger.state(&genesis).unwrap().clone(); diff --git a/nodes/nomos-node/src/config.rs b/nodes/nomos-node/src/config.rs index 5c2a65b1..6ce275a5 100644 --- a/nodes/nomos-node/src/config.rs +++ b/nodes/nomos-node/src/config.rs @@ -4,7 +4,7 @@ use std::{ path::PathBuf, }; // crates -use cl::{InputWitness, NoteWitness, NullifierSecret}; +use cl::{Nonce, NoteWitness, NullifierSecret}; use clap::{Parser, ValueEnum}; use color_eyre::eyre::{eyre, Result}; use hex::FromHex; @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use tracing::Level; // internal use crate::{NomosApiService, NomosDaMembership, Wire}; -use nomos_core::staking::NMO_UNIT; +use nomos_core::{proofs::covenant::CovenantProof, staking::NMO_UNIT}; use nomos_da_network_service::backends::libp2p::validator::DaNetworkValidatorBackend; use nomos_da_network_service::NetworkService as DaNetworkService; use nomos_libp2p::{ed25519::SecretKey, Multiaddr}; @@ -123,6 +123,13 @@ pub struct CryptarchiaArgs { requires("note_secret_key") )] note_value: Option, + + #[clap( + long = "consensus-note-nonce", + env = "CONSENSUS_NOTE_NONCE", + requires("note_value") + )] + note_nonce: Option, } #[derive(Parser, Debug, Clone)] @@ -307,6 +314,7 @@ pub fn update_cryptarchia_consensus( slot_duration, note_secret_key, note_value, + note_nonce, } = consensus_args; if let Some(start_time) = chain_start_time { @@ -317,15 +325,20 @@ pub fn update_cryptarchia_consensus( cryptarchia.time.slot_duration = std::time::Duration::from_secs(duration); } + if let (Some(value), Some(nonce)) = (note_value, note_nonce) { + let nonce = Nonce::from_bytes(<[u8; 32]>::from_hex(nonce)?); + cryptarchia.leader_config.notes.push(NoteWitness::new( + value as u64, + NMO_UNIT, + CovenantProof::nop_constraint(), + [0; 32], + nonce, + )); + } + if let Some(sk) = note_secret_key { let sk = <[u8; 16]>::from_hex(sk)?; - - let value = note_value.expect("Should be available if coin sk provided"); - - cryptarchia.notes.push(InputWitness::new( - NoteWitness::basic(value as u64, NMO_UNIT, &mut rand::thread_rng()), - NullifierSecret::from_bytes(sk), - )); + cryptarchia.leader_config.nf_sk = NullifierSecret::from_bytes(sk); } Ok(()) diff --git a/nomos-core/chain-defs/src/lib.rs b/nomos-core/chain-defs/src/lib.rs index 7e813cd6..72d57fbf 100644 --- a/nomos-core/chain-defs/src/lib.rs +++ b/nomos-core/chain-defs/src/lib.rs @@ -2,6 +2,7 @@ pub mod block; pub mod crypto; pub mod da; pub mod header; +pub mod proofs; pub mod staking; pub mod tx; pub mod utils; diff --git a/nomos-core/chain-defs/src/proofs/balance.rs b/nomos-core/chain-defs/src/proofs/balance.rs new file mode 100644 index 00000000..d7fea71e --- /dev/null +++ b/nomos-core/chain-defs/src/proofs/balance.rs @@ -0,0 +1,75 @@ +// std +// crates +use risc0_zkvm::Prover; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +// internal +use nomos_proof_statements::bundle::{BundlePrivate, BundlePublic}; + +#[derive(Debug, Clone)] +pub struct BalanceProof { + proof: risc0_zkvm::Receipt, +} + +impl BalanceProof { + pub(crate) fn public_inputs(&self) -> Result { + self.proof.journal.decode() + } + + pub(crate) fn prove( + bundle_witness: &cl::BundleWitness, + prover: &dyn Prover, + ) -> Result { + // need to show that bundle is balanced. + // i.e. the sum of ptx balances is 0 + let bundle_private = BundlePrivate { + balances: bundle_witness + .partial_witnesses() + .iter() + .map(|ptx| ptx.balance()) + .collect(), + }; + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&bundle_private)? + .build()?; + + let start_t = std::time::Instant::now(); + + let opts = risc0_zkvm::ProverOpts::groth16(); + let prove_info = + prover.prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_BALANCE_ELF, &opts)?; + + tracing::trace!( + "STARK 'bundle' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + Ok(Self { + proof: prove_info.receipt, + }) + } +} + +impl Serialize for BalanceProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.proof.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for BalanceProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let proof = risc0_zkvm::Receipt::deserialize(deserializer)?; + proof + .verify(nomos_cl_risc0_proofs::BUNDLE_BALANCE_ID) + .map_err(serde::de::Error::custom)?; + + Ok(Self { proof }) + } +} diff --git a/nomos-core/chain-defs/src/proofs/covenant.rs b/nomos-core/chain-defs/src/proofs/covenant.rs new file mode 100644 index 00000000..d9752dea --- /dev/null +++ b/nomos-core/chain-defs/src/proofs/covenant.rs @@ -0,0 +1,91 @@ +// std +// crates +use risc0_zkvm::Prover; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +// internal +use cl::Covenant; +use nomos_proof_statements::covenant::CovenantPublic; + +#[derive(Debug, Clone)] +pub struct CovenantProof { + pub risc0_id: [u32; 8], + pub proof: risc0_zkvm::Receipt, +} + +pub fn risc0_covenant(risc0_id: [u32; 8]) -> Covenant { + // Commit to a RISC0 ID for use as a note constraint + + let mut bytes = [0u8; 32]; + + for (i, word) in risc0_id.iter().enumerate() { + let word_bytes = word.to_le_bytes(); + bytes[i * 4] = word_bytes[0]; + bytes[i * 4 + 1] = word_bytes[1]; + bytes[i * 4 + 2] = word_bytes[2]; + bytes[i * 4 + 3] = word_bytes[3]; + } + + Covenant::from_vk(&bytes) +} + +impl CovenantProof { + pub fn covenant(&self) -> Covenant { + risc0_covenant(self.risc0_id) + } + + pub(crate) fn public_inputs(&self) -> Result { + self.proof.journal.decode() + } + + pub fn nop_constraint() -> Covenant { + risc0_covenant(nomos_cl_risc0_proofs::COVENANT_NOP_ID) + } + + pub fn prove_nop( + nf: cl::Nullifier, + ptx_root: cl::PtxRoot, + prover: &dyn Prover, + ) -> Result { + let constraint_public = CovenantPublic { nf, ptx_root }; + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&constraint_public)? + .build()?; + let start_t = std::time::Instant::now(); + + let opts = risc0_zkvm::ProverOpts::groth16(); + let prove_info = + prover.prove_with_opts(env, nomos_cl_risc0_proofs::COVENANT_NOP_ELF, &opts)?; + + tracing::trace!( + "STARK 'constraint-nop' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + Ok(Self { + risc0_id: nomos_cl_risc0_proofs::COVENANT_NOP_ID, + proof: prove_info.receipt, + }) + } +} + +impl Serialize for CovenantProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + (&self.risc0_id, &self.proof).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for CovenantProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let (risc0_id, proof) = <([u32; 8], risc0_zkvm::Receipt)>::deserialize(deserializer)?; + proof.verify(risc0_id).map_err(serde::de::Error::custom)?; + + Ok(Self { risc0_id, proof }) + } +} diff --git a/nomos-core/chain-defs/src/proofs/mod.rs b/nomos-core/chain-defs/src/proofs/mod.rs new file mode 100644 index 00000000..f7cf84d8 --- /dev/null +++ b/nomos-core/chain-defs/src/proofs/mod.rs @@ -0,0 +1,3 @@ +pub mod balance; +pub mod covenant; +pub mod ptx; diff --git a/nomos-core/chain-defs/src/proofs/ptx.rs b/nomos-core/chain-defs/src/proofs/ptx.rs new file mode 100644 index 00000000..17481dba --- /dev/null +++ b/nomos-core/chain-defs/src/proofs/ptx.rs @@ -0,0 +1,70 @@ +// std +// crates +use risc0_zkvm::Prover; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +// internal +use nomos_proof_statements::ptx::{PtxPrivate, PtxPublic}; + +#[derive(Debug, Clone)] +pub struct PtxProof { + proof: risc0_zkvm::Receipt, +} + +impl PtxProof { + pub(crate) fn public_inputs(&self) -> Result { + self.proof.journal.decode() + } + + pub(crate) fn prove( + ptx_witness: &cl::PartialTxWitness, + cm_root: [u8; 32], + prover: &dyn Prover, + ) -> Result { + let ptx_private = PtxPrivate { + ptx: ptx_witness.clone(), + cm_root, + }; + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&ptx_private)? + .build()?; + + let start_t = std::time::Instant::now(); + + let opts = risc0_zkvm::ProverOpts::groth16(); + let prove_info = prover.prove_with_opts(env, nomos_cl_risc0_proofs::PTX_ELF, &opts)?; + + tracing::trace!( + "STARK 'ptx' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + Ok(Self { + proof: prove_info.receipt, + }) + } +} + +impl Serialize for PtxProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.proof.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PtxProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let proof = risc0_zkvm::Receipt::deserialize(deserializer)?; + proof + .verify(nomos_cl_risc0_proofs::PTX_ID) + .map_err(serde::de::Error::custom)?; + + Ok(Self { proof }) + } +} diff --git a/nomos-core/chain-defs/src/tx/bundle.rs b/nomos-core/chain-defs/src/tx/bundle.rs index 2806c6cb..55041ab2 100644 --- a/nomos-core/chain-defs/src/tx/bundle.rs +++ b/nomos-core/chain-defs/src/tx/bundle.rs @@ -2,17 +2,23 @@ //crates use bytes::{Bytes, BytesMut}; use risc0_zkvm::Prover; -use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; //internal use super::Error; -use crate::wire; -use nomos_proof_statements::bundle::{BundlePrivate, BundlePublic}; - +use crate::{ + proofs::{balance::BalanceProof, covenant::CovenantProof, ptx::PtxProof}, + wire, +}; #[derive(Debug, Clone)] pub struct Bundle { + cm_roots: Vec<[u8; 32]>, bundle: cl::Bundle, - //TODO: this can be pruned once validated - proof: risc0_zkvm::Receipt, + //TODO: these can be pruned once validated + // prove knowledge of input notes and well-formedness of outputs + ptx_proofs: Vec, + // prove covenant constraints (one for each input) + covenant_proofs: Vec>, + // prove bundle balance + balance_proof: BalanceProof, } impl Bundle { @@ -20,37 +26,41 @@ impl Bundle { &self.bundle } - pub fn prove(bundle_witness: &cl::BundleWitness, prover: &dyn Prover) -> Result { - // need to show that bundle is balanced. - // i.e. the sum of ptx balances is 0 - let bundle_private = BundlePrivate { - balances: bundle_witness - .partial_witnesses() + pub fn cm_roots(&self) -> &[[u8; 32]] { + &self.cm_roots + } + + /// Requires a x86 machine with docker installed or RISC0_DEV_MODE=1 + pub fn prove( + bundle_witness: &cl::BundleWitness, + cm_root: [u8; 32], + covenant_proofs: Vec>, + prover: &dyn Prover, + ) -> Result { + if bundle_witness.partial_witnesses().len() != covenant_proofs.len() + || covenant_proofs .iter() - .map(|ptx| ptx.balance()) - .collect(), - }; + .zip(bundle_witness.partial_witnesses()) + .any(|(covenant_proofs, ptx)| covenant_proofs.len() != ptx.inputs.len()) + { + return Err(Error::InvalidWitness); + } - let env = risc0_zkvm::ExecutorEnv::builder() - .write(&bundle_private)? - .build()?; + let ptx_proofs = bundle_witness + .partial_witnesses() + .iter() + .map(|ptx| PtxProof::prove(ptx, cm_root, prover)) + .collect::, _>>()?; - let start_t = std::time::Instant::now(); - - let opts = risc0_zkvm::ProverOpts::groth16(); - let prove_info = prover.prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_ELF, &opts)?; - - tracing::trace!( - "STARK 'bundle' prover time: {:.2?}, total_cycles: {}", - start_t.elapsed(), - prove_info.stats.total_cycles - ); - - let receipt = prove_info.receipt; + let balance_proof = BalanceProof::prove(bundle_witness, prover)?; + let bundle = bundle_witness.commit(); Ok(Self { - bundle: bundle_witness.commit(), - proof: receipt, + cm_roots: vec![cm_root], + bundle, + ptx_proofs, + covenant_proofs, + balance_proof, }) } @@ -63,35 +73,96 @@ impl Bundle { } } -impl Serialize for Bundle { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (&self.bundle, &self.proof).serialize(serializer) +mod serde { + use crate::proofs::{balance::BalanceProof, covenant::CovenantProof, ptx::PtxProof}; + + use super::Bundle; + use nomos_proof_statements::{bundle::BundlePublic, ptx::PtxPublic}; + use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; + use std::collections::HashSet; + + #[derive(Serialize, Deserialize)] + struct BundleInner { + ptx_proofs: Vec, + covenant_proofs: Vec>, + balance_proof: BalanceProof, } -} -impl<'de> Deserialize<'de> for Bundle { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - // TODO: limit number of bites serialized - let (bundle, proof) = <(cl::Bundle, risc0_zkvm::Receipt)>::deserialize(deserializer)?; - - proof - .verify(nomos_cl_risc0_proofs::BUNDLE_ID) - .map_err(D::Error::custom)?; - - let bundle_public: BundlePublic = proof.journal.decode().map_err(D::Error::custom)?; - if Vec::from_iter(bundle.partial_txs().iter().map(|ptx| ptx.balance)) - != bundle_public.balances + impl Serialize for Bundle { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, { - return Err(D::Error::custom("Bundle balance mismatch")); + // TODO: make zero copy + BundleInner { + ptx_proofs: self.ptx_proofs.clone(), + covenant_proofs: self.covenant_proofs.clone(), + balance_proof: self.balance_proof.clone(), + } + .serialize(serializer) } + } - Ok(Self { bundle, proof }) + impl<'de> Deserialize<'de> for Bundle { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let BundleInner { + ptx_proofs, + covenant_proofs, + balance_proof, + } = BundleInner::deserialize(deserializer)?; + + // public inputs are reconstructed from proof statements + let mut cm_roots: HashSet<[u8; 32]> = HashSet::new(); + let mut ptxs = Vec::new(); + + if ptx_proofs.len() != covenant_proofs.len() { + return Err(D::Error::custom("Invalid number of covenant proofs")); + } + + for (ptx_proof, covenant_proofs) in ptx_proofs.iter().zip(&covenant_proofs) { + let PtxPublic { ptx, cm_root } = + ptx_proof.public_inputs().map_err(D::Error::custom)?; + + for (covenant_proof, input) in covenant_proofs.iter().zip(ptx.inputs.iter()) { + let covenant_public = + covenant_proof.public_inputs().map_err(D::Error::custom)?; + if covenant_public.ptx_root != ptx.root() + || covenant_public.nf != input.nullifier + { + return Err(D::Error::custom("Invalid covenant proof public inputs")); + } + if covenant_proof.covenant() != input.covenant { + return Err(D::Error::custom("Invalid covenant proof")); + } + } + + ptxs.push(ptx); + cm_roots.insert(cm_root); + } + + let balances = ptxs.iter().map(|ptx| ptx.balance).collect(); + let bundle_public = BundlePublic { balances }; + if bundle_public != balance_proof.public_inputs().map_err(D::Error::custom)? { + return Err(D::Error::custom(format!( + "Invalid balance proof public inputs {:?} != {:?}", + bundle_public, + balance_proof.public_inputs().map_err(D::Error::custom)? + ))); + } + + let bundle = cl::Bundle::new(ptxs); + + Ok(Self { + bundle, + balance_proof, + ptx_proofs, + covenant_proofs, + cm_roots: cm_roots.into_iter().collect(), + }) + } } } @@ -115,7 +186,10 @@ mod test { let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit(); // Assume the sender has received an unspent output from somewhere - let utxo = receive_utxo(cl::NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk); + let mut utxo = receive_utxo(cl::NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk); + utxo.note.covenant = CovenantProof::nop_constraint(); + // a little hack: if we only have one note we can put it as the merkle root + let cm_root = cl::merkle::leaf(&utxo.commit_note().0); // and wants to send 8 NMO to some recipient and return 2 NMO to itself. let recipient_output = @@ -124,17 +198,23 @@ mod test { cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk); let ptx_witness = cl::PartialTxWitness { - inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], + inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk, vec![])], outputs: vec![recipient_output, change_output], balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - let bundle = cl::BundleWitness::new(vec![ptx_witness]); - // ATTENTION: building a valid proof requires a x86 machine with docker installed // if you don't have one, you can run this test with RISC0_DEV_MODE=1 or skip the test let prover = risc0_zkvm::default_prover(); - let bundle = super::Bundle::prove(&bundle, prover.as_ref()).unwrap(); + let no_op = CovenantProof::prove_nop( + ptx_witness.inputs[0].nullifier(), + ptx_witness.commit().root(), + prover.as_ref(), + ) + .unwrap(); + let bundle = cl::BundleWitness::new(vec![ptx_witness]); + let bundle = + super::Bundle::prove(&bundle, cm_root, vec![vec![no_op]], prover.as_ref()).unwrap(); assert_eq!( wire::serialize(&bundle).unwrap(), diff --git a/nomos-core/chain-defs/src/tx/mod.rs b/nomos-core/chain-defs/src/tx/mod.rs index 18855208..1019cdd2 100644 --- a/nomos-core/chain-defs/src/tx/mod.rs +++ b/nomos-core/chain-defs/src/tx/mod.rs @@ -51,6 +51,8 @@ impl Transaction for Tx { #[derive(Debug, Error)] pub enum Error { - #[error("risc0 failed to prove execution of the zkvm")] + #[error("Risc0 failed to prove execution of the zkvm")] Risc0ProofFailed(#[from] anyhow::Error), + #[error("Invalid witness")] + InvalidWitness, } diff --git a/nomos-core/cl/src/bundle.rs b/nomos-core/cl/src/bundle.rs index e5ee1a50..eb256a5a 100644 --- a/nomos-core/cl/src/bundle.rs +++ b/nomos-core/cl/src/bundle.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; use crate::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}; - /// The transaction bundle is a collection of partial transactions. /// The goal in bundling transactions is to produce a set of partial transactions /// that balance each other. @@ -68,10 +67,10 @@ mod test { let nf_c = NullifierSecret::random(&mut rng); let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit()); - let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a); + let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a, vec![]); let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit()); - let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b); + let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b, vec![]); let crv_4840_out = OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit()); @@ -108,7 +107,7 @@ mod test { ] ); - let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c); + let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c, vec![]); let nmo_10_out = OutputWitness::new( NoteWitness::basic(10, nmo, &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner diff --git a/nomos-core/cl/src/input.rs b/nomos-core/cl/src/input.rs index e47bfb0e..4a66b342 100644 --- a/nomos-core/cl/src/input.rs +++ b/nomos-core/cl/src/input.rs @@ -3,50 +3,52 @@ /// Partial transactions, as the name suggests, are transactions /// which on their own may not balance (i.e. \sum inputs != \sum outputs) use crate::{ - note::{Constraint, NoteWitness}, + merkle, + note::{Covenant, NoteWitness}, nullifier::{Nullifier, NullifierSecret}, Nonce, }; use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Input { pub nullifier: Nullifier, - pub constraint: Constraint, + pub covenant: Covenant, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct InputWitness { pub note: NoteWitness, pub nf_sk: NullifierSecret, + pub cm_path: Vec, } impl InputWitness { - pub fn new(note: NoteWitness, nf_sk: NullifierSecret) -> Self { - Self { note, nf_sk } + pub fn new(note: NoteWitness, nf_sk: NullifierSecret, cm_path: Vec) -> Self { + Self { + note, + nf_sk, + cm_path, + } } - pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self { + pub fn from_output( + output: crate::OutputWitness, + nf_sk: NullifierSecret, + cm_path: Vec, + ) -> Self { assert_eq!(nf_sk.commit(), output.nf_pk); - Self::new(output.note, nf_sk) + Self::new(output.note, nf_sk, cm_path) } - pub fn public(output: crate::OutputWitness) -> Self { + pub fn public(output: crate::OutputWitness, cm_path: Vec) -> Self { let nf_sk = NullifierSecret::zero(); assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO - Self::new(output.note, nf_sk) + Self::new(output.note, nf_sk, cm_path) } pub fn evolved_nonce(&self, domain: &[u8]) -> Nonce { - let mut hasher = Sha256::new(); - hasher.update(b"NOMOS_COIN_EVOLVE"); - hasher.update(domain); - hasher.update(self.nf_sk.0); - hasher.update(self.note.commit(self.nf_sk.commit()).0); - - let nonce_bytes: [u8; 32] = hasher.finalize().into(); - Nonce::from_bytes(nonce_bytes) + self.note.evolved_nonce(self.nf_sk, domain) } pub fn evolve_output(&self, domain: &[u8]) -> crate::OutputWitness { @@ -66,7 +68,7 @@ impl InputWitness { pub fn commit(&self) -> Input { Input { nullifier: self.nullifier(), - constraint: self.note.constraint, + covenant: self.note.covenant, } } @@ -79,7 +81,7 @@ impl Input { pub fn to_bytes(&self) -> [u8; 64] { let mut bytes = [0u8; 64]; bytes[..32].copy_from_slice(self.nullifier.as_bytes()); - bytes[32..64].copy_from_slice(&self.constraint.0); + bytes[32..64].copy_from_slice(&self.covenant.0); bytes } } diff --git a/nomos-core/cl/src/lib.rs b/nomos-core/cl/src/lib.rs index ac23f329..c0f004d7 100644 --- a/nomos-core/cl/src/lib.rs +++ b/nomos-core/cl/src/lib.rs @@ -11,7 +11,7 @@ pub mod partial_tx; pub use balance::{Balance, BalanceWitness}; pub use bundle::{Bundle, BundleWitness}; pub use input::{Input, InputWitness}; -pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness}; +pub use note::{Covenant, Nonce, NoteCommitment, NoteWitness}; pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret}; pub use output::{Output, OutputWitness}; pub use partial_tx::{ diff --git a/nomos-core/cl/src/note.rs b/nomos-core/cl/src/note.rs index d28a9204..632f5f95 100644 --- a/nomos-core/cl/src/note.rs +++ b/nomos-core/cl/src/note.rs @@ -2,19 +2,19 @@ use rand::RngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use crate::{balance::Unit, nullifier::NullifierCommitment}; +use crate::{balance::Unit, nullifier::NullifierCommitment, NullifierSecret}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct Constraint(pub [u8; 32]); +pub struct Covenant(pub [u8; 32]); -impl Constraint { - pub fn from_vk(constraint_vk: &[u8]) -> Self { +impl Covenant { + pub fn from_vk(covenant_vk: &[u8]) -> Self { let mut hasher = Sha256::new(); - hasher.update(b"NOMOS_CL_CONSTRAINT_COMMIT"); - hasher.update(constraint_vk); - let constraint_cm: [u8; 32] = hasher.finalize().into(); + hasher.update(b"NOMOS_CL_COVENANT_COMMIT"); + hasher.update(covenant_vk); + let covenant_cm: [u8; 32] = hasher.finalize().into(); - Self(constraint_cm) + Self(covenant_cm) } } @@ -39,36 +39,41 @@ impl NoteCommitment { pub struct NoteWitness { pub value: u64, pub unit: Unit, - pub constraint: Constraint, + pub covenant: Covenant, pub state: [u8; 32], pub nonce: Nonce, } impl NoteWitness { - pub fn new( - value: u64, - unit: Unit, - constraint: Constraint, - state: [u8; 32], - nonce: Nonce, - ) -> Self { + pub fn new(value: u64, unit: Unit, covenant: Covenant, state: [u8; 32], nonce: Nonce) -> Self { Self { value, unit, - constraint, + covenant, state, nonce, } } pub fn basic(value: u64, unit: Unit, rng: impl RngCore) -> Self { - let constraint = Constraint([0u8; 32]); + let covenant = Covenant([0u8; 32]); let nonce = Nonce::random(rng); - Self::new(value, unit, constraint, [0u8; 32], nonce) + Self::new(value, unit, covenant, [0u8; 32], nonce) } - pub fn stateless(value: u64, unit: Unit, constraint: Constraint, rng: impl RngCore) -> Self { - Self::new(value, unit, constraint, [0u8; 32], Nonce::random(rng)) + pub fn stateless(value: u64, unit: Unit, covenant: Covenant, rng: impl RngCore) -> Self { + Self::new(value, unit, covenant, [0u8; 32], Nonce::random(rng)) + } + + pub fn evolved_nonce(&self, nf_sk: NullifierSecret, domain: &[u8]) -> Nonce { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_COIN_EVOLVE"); + hasher.update(domain); + hasher.update(nf_sk.0); + hasher.update(self.commit(nf_sk.commit()).0); + + let nonce_bytes: [u8; 32] = hasher.finalize().into(); + Nonce::from_bytes(nonce_bytes) } pub fn commit(&self, nf_pk: NullifierCommitment) -> NoteCommitment { @@ -84,7 +89,7 @@ impl NoteWitness { hasher.update(self.state); // COMMIT TO CONSTRAINT - hasher.update(self.constraint.0); + hasher.update(self.covenant.0); // COMMIT TO NONCE hasher.update(self.nonce.as_bytes()); @@ -142,7 +147,7 @@ mod test { ..reference_note }, NoteWitness { - constraint: Constraint::from_vk(&[1u8; 32]), + covenant: Covenant::from_vk(&[1u8; 32]), ..reference_note }, NoteWitness { diff --git a/nomos-core/cl/src/nullifier.rs b/nomos-core/cl/src/nullifier.rs index ab39f8ff..348bf69f 100644 --- a/nomos-core/cl/src/nullifier.rs +++ b/nomos-core/cl/src/nullifier.rs @@ -84,7 +84,7 @@ impl Nullifier { #[cfg(test)] mod test { - use crate::{note::derive_unit, Constraint, Nonce, NoteWitness}; + use crate::{note::derive_unit, Covenant, Nonce, NoteWitness}; use super::*; @@ -112,7 +112,7 @@ mod test { let note_1 = NoteWitness { value: 1, unit: derive_unit("NMO"), - constraint: Constraint::from_vk(&[]), + covenant: Covenant::from_vk(&[]), state: [0u8; 32], nonce: Nonce::random(&mut rng), }; @@ -140,7 +140,7 @@ mod test { let note_1 = NoteWitness { value: 1, unit: derive_unit("NMO"), - constraint: Constraint::from_vk(&[]), + covenant: Covenant::from_vk(&[]), state: [0u8; 32], nonce, }; diff --git a/nomos-core/cl/src/partial_tx.rs b/nomos-core/cl/src/partial_tx.rs index 844957c6..5bbb1e58 100644 --- a/nomos-core/cl/src/partial_tx.rs +++ b/nomos-core/cl/src/partial_tx.rs @@ -77,7 +77,7 @@ impl PartialTxWitness { let input_merkle_leaves = merkle::padded_leaves::(&input_bytes); let path = merkle::path(input_merkle_leaves, idx); - let input = self.inputs[idx]; + let input = self.inputs[idx].clone(); PartialTxInputWitness { input, path } } @@ -168,10 +168,10 @@ mod test { let nf_c = NullifierSecret::random(&mut rng); let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit()); - let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a); + let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a, vec![]); let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit()); - let eth_23 = InputWitness::from_output(eth_23_utxo, nf_b); + let eth_23 = InputWitness::from_output(eth_23_utxo, nf_b, vec![]); let crv_4840 = OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit()); diff --git a/nomos-core/cl/tests/simple_transfer.rs b/nomos-core/cl/tests/simple_transfer.rs index 5f1a2248..49f3b63d 100644 --- a/nomos-core/cl/tests/simple_transfer.rs +++ b/nomos-core/cl/tests/simple_transfer.rs @@ -24,7 +24,7 @@ fn test_simple_transfer() { cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk); let ptx_witness = cl::PartialTxWitness { - inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], + inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk, vec![])], outputs: vec![recipient_output, change_output], balance_blinding: BalanceWitness::random_blinding(&mut rng), }; diff --git a/nomos-core/proof_statements/src/ptx.rs b/nomos-core/proof_statements/src/ptx.rs index 2eaf57e2..c1f19d8f 100644 --- a/nomos-core/proof_statements/src/ptx.rs +++ b/nomos-core/proof_statements/src/ptx.rs @@ -10,6 +10,5 @@ pub struct PtxPublic { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPrivate { pub ptx: PartialTxWitness, - pub input_cm_paths: Vec>, pub cm_root: [u8; 32], } diff --git a/nomos-core/risc0_proofs/Cargo.toml b/nomos-core/risc0_proofs/Cargo.toml index 9da019be..2a7a3f3a 100644 --- a/nomos-core/risc0_proofs/Cargo.toml +++ b/nomos-core/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["bundle", "covenant_nop", "ptx"] +methods = ["bundle_balance", "covenant_nop", "ptx"] diff --git a/nomos-core/risc0_proofs/bundle/Cargo.toml b/nomos-core/risc0_proofs/bundle_balance/Cargo.toml similarity index 95% rename from nomos-core/risc0_proofs/bundle/Cargo.toml rename to nomos-core/risc0_proofs/bundle_balance/Cargo.toml index 5d892103..f062fb0e 100644 --- a/nomos-core/risc0_proofs/bundle/Cargo.toml +++ b/nomos-core/risc0_proofs/bundle_balance/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bundle" +name = "bundle_balance" version = "0.1.0" edition = "2021" diff --git a/nomos-core/risc0_proofs/bundle/src/main.rs b/nomos-core/risc0_proofs/bundle_balance/src/main.rs similarity index 100% rename from nomos-core/risc0_proofs/bundle/src/main.rs rename to nomos-core/risc0_proofs/bundle_balance/src/main.rs diff --git a/nomos-core/risc0_proofs/ptx/src/main.rs b/nomos-core/risc0_proofs/ptx/src/main.rs index 85cab190..739cf227 100644 --- a/nomos-core/risc0_proofs/ptx/src/main.rs +++ b/nomos-core/risc0_proofs/ptx/src/main.rs @@ -3,17 +3,12 @@ use nomos_proof_statements::ptx::{PtxPrivate, PtxPublic}; use risc0_zkvm::guest::env; fn main() { - let PtxPrivate { - ptx, - input_cm_paths, - cm_root, - } = env::read(); + let PtxPrivate { ptx, cm_root } = env::read(); - assert_eq!(ptx.inputs.len(), input_cm_paths.len()); - for (input, cm_path) in ptx.inputs.iter().zip(input_cm_paths) { + for input in ptx.inputs.iter() { let note_cm = input.note_commitment(); let cm_leaf = merkle::leaf(note_cm.as_bytes()); - assert_eq!(cm_root, merkle::path_root(cm_leaf, &cm_path)); + assert_eq!(cm_root, merkle::path_root(cm_leaf, &input.cm_path)); } for output in ptx.outputs.iter() { diff --git a/nomos-services/cryptarchia-consensus/src/leadership.rs b/nomos-services/cryptarchia-consensus/src/leadership.rs index e4e789bc..83bdf1e2 100644 --- a/nomos-services/cryptarchia-consensus/src/leadership.rs +++ b/nomos-services/cryptarchia-consensus/src/leadership.rs @@ -1,20 +1,38 @@ -use cl::{input::InputWitness, note::NoteWitness, nullifier::Nullifier}; +use cl::{ + note::NoteWitness, + nullifier::{Nullifier, NullifierSecret}, + InputWitness, +}; use cryptarchia_engine::Slot; use leader_proof_statements::{LeaderPrivate, LeaderPublic}; use nomos_core::header::HeaderId; use nomos_ledger::{leader_proof::Risc0LeaderProof, Config, EpochState, NoteTree}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; pub struct Leader { // for each block, the indexes in the note tree of the notes we control - notes: HashMap>, + notes: HashMap>, + nf_sk: NullifierSecret, config: nomos_ledger::Config, } +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct LeaderConfig { + pub notes: Vec, + // this is common to every note + pub nf_sk: NullifierSecret, +} + impl Leader { - pub fn new(genesis: HeaderId, notes: Vec, config: Config) -> Self { + pub fn new( + genesis: HeaderId, + LeaderConfig { notes, nf_sk }: LeaderConfig, + config: Config, + ) -> Self { Leader { notes: HashMap::from([(genesis, notes)]), + nf_sk, config, } } @@ -27,8 +45,9 @@ impl Leader { let notes = notes .iter() .map(|note| { - if note.nullifier() == to_evolve { - evolve(note) + let note_cm = note.commit(self.nf_sk.commit()); + if Nullifier::new(self.nf_sk, note_cm) == to_evolve { + evolve(note, self.nf_sk) } else { *note } @@ -47,10 +66,11 @@ impl Leader { ) -> Option { let notes = self.notes.get(&parent)?; for note in notes { + let note_commit = note.commit(self.nf_sk.commit()); let Some(index) = note_tree .commitments() .iter() - .position(|cm| cm == ¬e.note_commitment()) + .position(|cm| cm == ¬e_commit) else { continue; }; @@ -58,31 +78,30 @@ impl Leader { let input_cm_path = note_tree .witness(index) .expect("Note was found in the tree"); + let note = InputWitness { + note: *note, + nf_sk: self.nf_sk, + cm_path: input_cm_path, + }; let public_inputs = LeaderPublic::new( note_tree.root(), *epoch_state.nonce(), slot.into(), self.config.consensus_config.active_slot_coeff, epoch_state.total_stake(), - note.nullifier(), + Nullifier::new(self.nf_sk, note_commit), note.evolve_output(b"NOMOS_POL").commit_note(), ); - if public_inputs.check_winning(note) { + if public_inputs.check_winning(¬e) { tracing::debug!( "leader for slot {:?}, {:?}/{:?}", slot, note.note.value, epoch_state.total_stake() ); - let input = *note; + let input = note.clone(); let res = tokio::task::spawn_blocking(move || { - Risc0LeaderProof::build( - public_inputs, - LeaderPrivate { - input, - input_cm_path, - }, - ) + Risc0LeaderProof::build(public_inputs, LeaderPrivate { input }) }) .await; match res { @@ -101,12 +120,9 @@ impl Leader { } } -fn evolve(note: &InputWitness) -> InputWitness { - InputWitness { - note: NoteWitness { - nonce: note.evolved_nonce(b"NOMOS_POL"), - ..note.note - }, - nf_sk: note.nf_sk, +fn evolve(note: &NoteWitness, nf_sk: NullifierSecret) -> NoteWitness { + NoteWitness { + nonce: note.evolved_nonce(nf_sk, b"NOMOS_POL"), + ..*note } } diff --git a/nomos-services/cryptarchia-consensus/src/lib.rs b/nomos-services/cryptarchia-consensus/src/lib.rs index ff44b99c..7737ff0b 100644 --- a/nomos-services/cryptarchia-consensus/src/lib.rs +++ b/nomos-services/cryptarchia-consensus/src/lib.rs @@ -4,10 +4,10 @@ pub mod mix; pub mod network; mod time; -use cl::InputWitness; use core::fmt::Debug; use cryptarchia_engine::Slot; use futures::StreamExt; +pub use leadership::LeaderConfig; use network::NetworkAdapter; use nomos_core::da::blob::{ info::DispersedBlobInfo, metadata::Metadata as BlobMetadata, BlobSelect, @@ -130,7 +130,7 @@ pub struct CryptarchiaSettings, + pub leader_config: LeaderConfig, pub network_adapter_settings: NetworkAdapterSettings, pub mix_adapter_settings: MixAdapterSettings, } @@ -409,7 +409,7 @@ where transaction_selector_settings, blob_selector_settings, time, - notes, + leader_config, network_adapter_settings, mix_adapter_settings, } = self.service_state.settings_reader.get_updated_settings(); @@ -432,7 +432,7 @@ where let blob_selector = BS::new(blob_selector_settings); let mut incoming_blocks = network_adapter.blocks_stream().await?; - let mut leader = leadership::Leader::new(genesis_id, notes, config); + let mut leader = leadership::Leader::new(genesis_id, leader_config, config); let timer = time::Timer::new(time); let mut slot_timer = IntervalStream::new(timer.slot_interval()); diff --git a/nomos-services/data-availability/tests/src/common.rs b/nomos-services/data-availability/tests/src/common.rs index cc2b5a4a..edc406aa 100644 --- a/nomos-services/data-availability/tests/src/common.rs +++ b/nomos-services/data-availability/tests/src/common.rs @@ -1,3 +1,4 @@ +use cryptarchia_consensus::LeaderConfig; // std use nomos_da_network_service::backends::libp2p::common::DaNetworkBackendSettings; use nomos_mix::message_blend::{ @@ -187,7 +188,7 @@ pub struct TestDaNetworkSettings { } pub fn new_node( - note: &InputWitness, + leader_config: &LeaderConfig, ledger_config: &nomos_ledger::Config, genesis_state: &LedgerState, time_config: &TimeConfig, @@ -266,7 +267,7 @@ pub fn new_node( config: ledger_config.clone(), genesis_state: genesis_state.clone(), time: time_config.clone(), - notes: vec![note.clone()], + leader_config: leader_config.clone(), network_adapter_settings: cryptarchia_consensus::network::adapters::libp2p::LibP2pAdapterSettings { topic: String::from(nomos_node::CONSENSUS_TOPIC), diff --git a/nomos-services/data-availability/tests/src/indexer_integration.rs b/nomos-services/data-availability/tests/src/indexer_integration.rs index 2462bd6c..249f73b7 100644 --- a/nomos-services/data-availability/tests/src/indexer_integration.rs +++ b/nomos-services/data-availability/tests/src/indexer_integration.rs @@ -9,8 +9,8 @@ use std::{ }; // crates use bytes::Bytes; -use cl::{InputWitness, NoteWitness, NullifierSecret}; -use cryptarchia_consensus::{ConsensusMsg, TimeConfig}; +use cl::{NoteWitness, NullifierSecret}; +use cryptarchia_consensus::{ConsensusMsg, LeaderConfig, TimeConfig}; use kzgrs_backend::{ common::blob::DaBlob, dispersal::{BlobInfo, Metadata}, @@ -53,21 +53,22 @@ fn test_indexer() { thread_rng().fill(id); } - let notes = ids + let sks = ids .iter() .map(|&id| { let mut sk = [0; 16]; sk.copy_from_slice(&id[0..16]); - InputWitness::new( - NoteWitness::basic(1, NMO_UNIT, &mut thread_rng()), - NullifierSecret(sk), - ) + NullifierSecret(sk) }) .collect::>(); - let genesis_state = LedgerState::from_commitments( - notes.iter().map(|n| n.note_commitment()), - (ids.len() as u32).into(), - ); + + let notes = (0..ids.len()) + .map(|_| NoteWitness::basic(1, NMO_UNIT, &mut thread_rng())) + .collect::>(); + + let commitments = notes.iter().zip(&sks).map(|(n, sk)| n.commit(sk.commit())); + + let genesis_state = LedgerState::from_commitments(commitments, (ids.len() as u32).into()); let ledger_config = nomos_ledger::Config { epoch_stake_distribution_stabilization: 3, epoch_period_nonce_buffer: 3, @@ -113,7 +114,10 @@ fn test_indexer() { let nodes_per_subnet = 2; let node1 = new_node( - ¬es[0], + &LeaderConfig { + notes: vec![notes[0].clone()], + nf_sk: sks[0], + }, &ledger_config, &genesis_state, &time_config, @@ -138,7 +142,10 @@ fn test_indexer() { ); let node2 = new_node( - ¬es[1], + &LeaderConfig { + notes: vec![notes[1].clone()], + nf_sk: sks[1], + }, &ledger_config, &genesis_state, &time_config, diff --git a/nomos-services/data-availability/tests/src/verifier_integration.rs b/nomos-services/data-availability/tests/src/verifier_integration.rs index b57e894f..fe53abff 100644 --- a/nomos-services/data-availability/tests/src/verifier_integration.rs +++ b/nomos-services/data-availability/tests/src/verifier_integration.rs @@ -8,8 +8,8 @@ use std::{ time::Duration, }; // crates -use cl::{InputWitness, NoteWitness, NullifierSecret}; -use cryptarchia_consensus::TimeConfig; +use cl::{NoteWitness, NullifierSecret}; +use cryptarchia_consensus::{LeaderConfig, TimeConfig}; use kzgrs_backend::common::blob::DaBlob; use nomos_core::{da::DaEncoder as _, staking::NMO_UNIT}; use nomos_da_verifier::backend::kzgrs::KzgrsDaVerifierSettings; @@ -33,22 +33,21 @@ fn test_verifier() { for id in &mut ids { thread_rng().fill(id); } - - let notes = ids + let sks = ids .iter() .map(|&id| { let mut sk = [0; 16]; sk.copy_from_slice(&id[0..16]); - InputWitness::new( - NoteWitness::basic(1, NMO_UNIT, &mut thread_rng()), - NullifierSecret(sk), - ) + NullifierSecret(sk) }) .collect::>(); - let genesis_state = LedgerState::from_commitments( - notes.iter().map(|n| n.note_commitment()), - (ids.len() as u32).into(), - ); + + let notes = (0..ids.len()) + .map(|_| NoteWitness::basic(1, NMO_UNIT, &mut thread_rng())) + .collect::>(); + + let commitments = notes.iter().zip(&sks).map(|(n, sk)| n.commit(sk.commit())); + let genesis_state = LedgerState::from_commitments(commitments, (ids.len() as u32).into()); let ledger_config = nomos_ledger::Config { epoch_stake_distribution_stabilization: 3, epoch_period_nonce_buffer: 3, @@ -96,7 +95,10 @@ fn test_verifier() { let nodes_per_subnet = 1; let node1 = new_node( - ¬es[0], + &LeaderConfig { + notes: vec![notes[0].clone()], + nf_sk: sks[0], + }, &ledger_config, &genesis_state, &time_config, @@ -121,7 +123,10 @@ fn test_verifier() { ); let node2 = new_node( - ¬es[1], + &LeaderConfig { + notes: vec![notes[1].clone()], + nf_sk: sks[1], + }, &ledger_config, &genesis_state, &time_config, diff --git a/proof_of_leadership/proof_statements/src/lib.rs b/proof_of_leadership/proof_statements/src/lib.rs index 8bd464ec..e48e6bb1 100644 --- a/proof_of_leadership/proof_statements/src/lib.rs +++ b/proof_of_leadership/proof_statements/src/lib.rs @@ -90,5 +90,4 @@ fn ticket(input: &cl::InputWitness, epoch_nonce: [u8; 32], slot: u64) -> U256 { #[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/prover/src/lib.rs b/proof_of_leadership/risc0/prover/src/lib.rs index 9f7b2b82..eb58e7ed 100644 --- a/proof_of_leadership/risc0/prover/src/lib.rs +++ b/proof_of_leadership/risc0/prover/src/lib.rs @@ -40,7 +40,7 @@ pub fn prove(leader_public: LeaderPublic, leader_private: LeaderPrivate) -> Resu #[cfg(test)] mod test { use super::*; - use cl::{note::NoteWitness, nullifier::NullifierSecret}; + use cl::{note::NoteWitness, nullifier::NullifierSecret, InputWitness}; use rand::thread_rng; const MAX_NOTE_COMMS: usize = 1 << 8; @@ -64,18 +64,23 @@ mod test { fn test_leader_prover() { let mut rng = thread_rng(); - let input = cl::InputWitness { - note: NoteWitness::basic(32, NMO_UNIT, &mut rng), - nf_sk: NullifierSecret::random(&mut rng), - }; + let nf_sk = NullifierSecret::random(&mut rng); + let note = NoteWitness::basic(32, NMO_UNIT, &mut rng); - let notes = vec![input.note_commitment()]; + let notes = vec![note.commit(nf_sk.commit())]; let epoch_nonce = [0u8; 32]; let slot = 0; let active_slot_coefficient = 0.05; let total_stake = 1000; let leaves = note_commitment_leaves(¬es); + + let input = InputWitness { + note, + nf_sk, + cm_path: cl::merkle::path(leaves, 0), + }; + let mut expected_public_inputs = LeaderPublic::new( cl::merkle::root(leaves), epoch_nonce, @@ -92,10 +97,7 @@ mod test { println!("slot={}", expected_public_inputs.slot); - let private_inputs = LeaderPrivate { - input: input.clone(), - input_cm_path: cl::merkle::path(leaves, 0), - }; + let private_inputs = LeaderPrivate { input }; let proof = prove(expected_public_inputs, private_inputs).unwrap(); assert!(proof 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 index 2e910a02..0e37388c 100644 --- 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 @@ -13,10 +13,7 @@ pub const NMO_UNIT: Unit = [ fn main() { let public_inputs: LeaderPublic = env::read(); - let LeaderPrivate { - input, - input_cm_path, - } = env::read(); + let LeaderPrivate { input } = env::read(); // Lottery checks assert!(public_inputs.check_winning(&input)); @@ -25,7 +22,7 @@ fn main() { assert_eq!(input.note.unit, NMO_UNIT); 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); + 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 diff --git a/tests/src/nodes/executor.rs b/tests/src/nodes/executor.rs index 49b4022a..9b6a2a69 100644 --- a/tests/src/nodes/executor.rs +++ b/tests/src/nodes/executor.rs @@ -166,7 +166,7 @@ pub fn create_executor_config(config: GeneralConfig) -> Config { }, }, cryptarchia: CryptarchiaSettings { - notes: config.consensus_config.notes, + leader_config: config.consensus_config.leader_config, config: config.consensus_config.ledger_config, genesis_state: config.consensus_config.genesis_state, time: config.consensus_config.time, diff --git a/tests/src/nodes/validator.rs b/tests/src/nodes/validator.rs index d09b2cfd..68fa5518 100644 --- a/tests/src/nodes/validator.rs +++ b/tests/src/nodes/validator.rs @@ -251,7 +251,7 @@ pub fn create_validator_config(config: GeneralConfig) -> Config { }, }, cryptarchia: CryptarchiaSettings { - notes: config.consensus_config.notes, + leader_config: config.consensus_config.leader_config, config: config.consensus_config.ledger_config, genesis_state: config.consensus_config.genesis_state, time: config.consensus_config.time, diff --git a/tests/src/topology/configs/consensus.rs b/tests/src/topology/configs/consensus.rs index f2668268..fd3ffc7c 100644 --- a/tests/src/topology/configs/consensus.rs +++ b/tests/src/topology/configs/consensus.rs @@ -1,8 +1,8 @@ use std::str::FromStr; use std::time::Duration; -use cl::{InputWitness, NoteWitness, NullifierSecret}; -use cryptarchia_consensus::TimeConfig; +use cl::{NoteWitness, NullifierSecret}; +use cryptarchia_consensus::{LeaderConfig, TimeConfig}; use nomos_core::staking::NMO_UNIT; use nomos_ledger::LedgerState; use rand::thread_rng; @@ -36,7 +36,7 @@ impl ConsensusParams { /// specific service or services configuration. #[derive(Clone)] pub struct GeneralConsensusConfig { - pub notes: Vec, + pub leader_config: LeaderConfig, pub ledger_config: nomos_ledger::Config, pub genesis_state: LedgerState, pub time: TimeConfig, @@ -46,21 +46,22 @@ pub fn create_consensus_configs( ids: &[[u8; 32]], consensus_params: ConsensusParams, ) -> Vec { - let notes = ids + let notes = (0..ids.len()) + .map(|_| NoteWitness::basic(1, NMO_UNIT, &mut thread_rng())) + .collect::>(); + + let sks = ids .iter() .map(|&id| { let mut sk = [0; 16]; sk.copy_from_slice(&id[0..16]); - InputWitness::new( - NoteWitness::basic(1, NMO_UNIT, &mut thread_rng()), - NullifierSecret(sk), - ) + NullifierSecret(sk) }) .collect::>(); // no commitments for now, proofs are not checked anyway let genesis_state = LedgerState::from_commitments( - notes.iter().map(|n| n.note_commitment()), + notes.iter().zip(&sks).map(|(n, sk)| n.commit(sk.commit())), (ids.len() as u32).into(), ); let ledger_config = nomos_ledger::Config { @@ -82,8 +83,12 @@ pub fn create_consensus_configs( notes .into_iter() - .map(|note| GeneralConsensusConfig { - notes: vec![note], + .zip(sks) + .map(|(note, nf_sk)| GeneralConsensusConfig { + leader_config: LeaderConfig { + notes: vec![note], + nf_sk, + }, ledger_config: ledger_config.clone(), genesis_state: genesis_state.clone(), time: time_config.clone(),