diff --git a/emmarin/apps/swapvm/app/src/lib.rs b/emmarin/apps/swapvm/app/src/lib.rs index 340f460..c85f19f 100644 --- a/emmarin/apps/swapvm/app/src/lib.rs +++ b/emmarin/apps/swapvm/app/src/lib.rs @@ -69,14 +69,14 @@ impl SwapArgs { } } -pub fn swap_goal_note(nonce: Nonce) -> OutputWitness { - OutputWitness { +pub fn swap_goal_note(nonce: Nonce) -> InputWitness { + InputWitness { state: [0u8; 32], value: 1, - unit: swap_goal_unit().unit(), + unit_witness: swap_goal_unit(), nonce, zone_id: ZONE_ID, - nf_pk: NullifierSecret::zero().commit(), + nf_sk: NullifierSecret::zero(), } } diff --git a/emmarin/apps/swapvm/app/tests/pricing.rs b/emmarin/apps/swapvm/app/tests/pricing.rs index 28542f8..e854cea 100644 --- a/emmarin/apps/swapvm/app/tests/pricing.rs +++ b/emmarin/apps/swapvm/app/tests/pricing.rs @@ -1,5 +1,5 @@ use app::ZoneData; -use cl::crust::{NullifierSecret, UnitWitness}; +use cl::crust::UnitWitness; fn nmo() -> UnitWitness { UnitWitness::nop(b"NMO") @@ -10,8 +10,6 @@ fn mem() -> UnitWitness { #[test] fn pair_price() { - let mut rng = rand::thread_rng(); - let mut swapvm_state = ZoneData::new(); // initially there is no NMO/MEM pair diff --git a/emmarin/apps/swapvm/stf/host/src/lib.rs b/emmarin/apps/swapvm/stf/host/src/lib.rs index 12f44f4..1ea39cf 100644 --- a/emmarin/apps/swapvm/stf/host/src/lib.rs +++ b/emmarin/apps/swapvm/stf/host/src/lib.rs @@ -1,16 +1,15 @@ use std::collections::BTreeMap; use app::{swap_goal_unit, SwapArgs, ZoneData}; -use cl::crust::{ - BundleWitness, InputWitness, Nonce, NoteCommitment, Nullifier, NullifierSecret, Tx, TxWitness, - Unit, -}; -use cl::ds::mmr::{MMRProof, MMR}; +use cl::crust::{BundleWitness, InputWitness, NoteCommitment, Nullifier, Tx, TxWitness, Unit}; +use cl::ds::mmr::{MMRFolds, MMRProof, MMR}; use cl::mantle::ledger::Ledger; use cl::mantle::ledger::LedgerState; +use cl::mantle::ZoneState; +use ledger::stf::{risc0_stf, StfProof}; use ledger_proof_statements::ledger::SyncLog; use methods::{STF_ELF, STF_ID}; -use risc0_zkvm::{ExecutorEnv, Prover, Receipt, Result}; +use risc0_zkvm::{ExecutorEnv, Prover, Result}; #[derive(Debug)] struct FundNote { @@ -33,31 +32,84 @@ pub struct ExecutorState { pub ledger: LedgerState, pub swapvm: ZoneData, fund_notes: BTreeMap, + goal_notes: Vec<(InputWitness, MMR, MMRProof)>, } impl ExecutorState { - pub fn observe_cms(&mut self, cms: impl IntoIterator) { - for cm in cms { - self.ledger.add_commitment(&cm); + pub fn zone_state(&self) -> ZoneState { + ZoneState { + stf: risc0_stf(STF_ID), + zone_data: self.swapvm.commit(), + ledger: self.ledger.to_witness().commit(), } } + pub fn observe_cm(&mut self, cm: &NoteCommitment) -> ((MMR, MMRProof), MMRFolds) { + let folds = self.ledger.commitments.folds(&cm.0); + + for (_, fund_note) in self.fund_notes.iter_mut() { + assert_eq!(fund_note.mmr, self.ledger.commitments); + fund_note + .path + .update(&fund_note.note.note_commitment().0, &folds); + } + + let proof = self.ledger.add_commitment(cm); + + for (_, fund_note) in self.fund_notes.iter_mut() { + fund_note.mmr = self.ledger.commitments.clone(); + } + + (proof, folds) + } + pub fn observe_nfs(&mut self, nfs: Vec) { self.ledger.add_nullifiers(nfs); } pub fn process_tx(&mut self, tx: &Tx) { + let Some(swapvm_update) = tx.updates.get(&self.swapvm.zone_id) else { + // this tx is not related to the swapvm zone + return; + }; + + let mut output_mmr_proofs = BTreeMap::::new(); + + for (cm, _) in &swapvm_update.outputs { + let (proof, folds) = self.observe_cm(cm); + + for (other_cm, other_cm_proof) in output_mmr_proofs.iter_mut() { + other_cm_proof.update(&other_cm.0, &folds); + } + + output_mmr_proofs.insert(*cm, proof.1); + + for (other_cm, other_cm_proof) in &output_mmr_proofs { + assert!(self + .ledger + .commitments + .verify_proof(&other_cm.0, &other_cm_proof)) + } + } + + for nf in &swapvm_update.inputs { + self.ledger.add_nullifiers(vec![*nf]); + } + if tx.balance.unit_balance(swap_goal_unit().unit()).is_neg() { // this is a SWAP - let (swap_cm, swap_args_bytes) = - &tx.updates.get(&self.swapvm.zone_id).unwrap().outputs[0]; + let (swap_goal_cm, swap_args_bytes) = &swapvm_update.outputs[0]; let swap_args: SwapArgs = cl::deserialize(&swap_args_bytes); // verify the user proved the correct swap goal note - assert_eq!( - swap_cm, - &app::swap_goal_note(swap_args.nonce).note_commitment() - ); + let swap_goal_witness = app::swap_goal_note(swap_args.nonce); + assert_eq!(swap_goal_cm, &swap_goal_witness.note_commitment()); + + self.goal_notes.push(( + swap_goal_witness, + self.ledger.commitments.clone(), + output_mmr_proofs[swap_goal_cm].clone(), + )); // assume there are only the goal unit and tokenIn units at play assert_eq!(tx.balance.balances.len(), 2); @@ -76,14 +128,6 @@ impl ExecutorState { let amount_in = balance_in.pos; self.swapvm.swap(token_in, amount_in, swap_args); } - for (cm, _) in tx - .updates - .get(&self.swapvm.zone_id) - .map(|u| u.outputs.iter()) - .unwrap_or_default() - { - self.ledger.add_commitment(cm); - } } pub fn update_and_get_executor_tx(&mut self) -> (TxWitness, Vec) { @@ -103,14 +147,7 @@ impl ExecutorState { let note = if let Some(note) = fund_notes.get(&unit) { note.evolve(value) } else { - InputWitness { - state: [0; 32], - value, - unit_witness: todo!(), - nonce: Nonce::from_bytes([0; 32]), - zone_id: self.swapvm.zone_id, - nf_sk: NullifierSecret([0; 16]), - } + panic!("dynamically created fund notes are not supported"); }; new_fund_notes.push(note); let output = note.to_output(); @@ -124,6 +161,10 @@ impl ExecutorState { tx = tx.add_input(note, (mmr, path)); } + for (goal_note, mmr, path) in std::mem::take(&mut self.goal_notes) { + tx = tx.add_input(goal_note, (mmr, path)); + } + (tx, new_fund_notes) } @@ -143,7 +184,7 @@ pub struct StfPrivate { } impl StfPrivate { - pub fn prove(&self, prover: &dyn Prover) -> Result { + pub fn prove(&self, prover: &dyn Prover) -> Result { let env = ExecutorEnv::builder() .write(&self.zone_data)? .write(&self.old_ledger)? @@ -156,6 +197,7 @@ impl StfPrivate { let prove_info = prover.prove(env, STF_ELF)?; debug_assert!(prove_info.receipt.verify(STF_ID).is_ok()); - Ok(prove_info.receipt) + + Ok(StfProof::from_risc0(risc0_stf(STF_ID), prove_info.receipt)) } } diff --git a/emmarin/apps/swapvm/stf/host/tests/swap.rs b/emmarin/apps/swapvm/stf/host/tests/swap.rs index cb5c262..c51c60c 100644 --- a/emmarin/apps/swapvm/stf/host/tests/swap.rs +++ b/emmarin/apps/swapvm/stf/host/tests/swap.rs @@ -1,7 +1,10 @@ use app::ZONE_ID; use cl::crust::{BundleWitness, InputWitness, Nonce, NullifierSecret, TxWitness, UnitWitness}; use cl::mantle::ledger::LedgerState; +use cl::mantle::update::{BatchUpdate, Update}; use host::{ExecutorState, StfPrivate}; +use ledger::ledger::ProvedLedgerTransition; +use ledger::update::ProvedBatchUpdate; use ledger::{bundle::ProvedBundle, tx::ProvedTx}; use rand::RngCore; @@ -25,9 +28,7 @@ fn setup_executor(mut rng: impl RngCore, ledger: LedgerState) -> ExecutorState { nf_sk: NullifierSecret::zero(), }; - let (mmr, mmr_proof) = exec_state - .ledger - .add_commitment(&nmo_fund.note_commitment()); + let ((mmr, mmr_proof), _) = exec_state.observe_cm(&nmo_fund.note_commitment()); exec_state.set_fund_note(nmo_fund, mmr, mmr_proof); let mem_fund = InputWitness { @@ -38,9 +39,7 @@ fn setup_executor(mut rng: impl RngCore, ledger: LedgerState) -> ExecutorState { zone_id: ZONE_ID, nf_sk: NullifierSecret::zero(), }; - let (mmr, mmr_proof) = exec_state - .ledger - .add_commitment(&mem_fund.note_commitment()); + let ((mmr, mmr_proof), _) = exec_state.observe_cm(&mem_fund.note_commitment()); exec_state.set_fund_note(mem_fund, mmr, mmr_proof); // HACK: we don't currently support liquidity notes, we directly hard code the corresponding liquidity @@ -77,11 +76,15 @@ fn simple_swap() { // Alice now has a valid 10 NMO note, she wants to swap it for 90 MEM // ---- begin swap ---- + let old_zone_state = exec_state.zone_state(); + + let mut temp_ledger_state = exec_state.ledger.clone(); + let swap_goal_nonce = Nonce::random(&mut rng); let swap_tx = TxWitness::default() .add_input(alice_in, alice_in_proof) .add_output( - app::swap_goal_note(swap_goal_nonce), + app::swap_goal_note(swap_goal_nonce).to_output(), app::SwapArgs { output: app::SwapOutput::basic(mem().unit(), ZONE_ID, alice_sk.commit(), &mut rng), limit: 90, @@ -101,16 +104,44 @@ fn simple_swap() { let (exec_tx, fund_notes) = exec_state.update_and_get_executor_tx(); let proved_exec_tx = ProvedTx::prove(exec_tx, vec![], vec![]).unwrap(); + let swap_bundle = BundleWitness { + txs: vec![swap_tx_proof.public(), proved_exec_tx.public()], + }; + + let _ = swap_bundle.clone().commit(); + + let swap_bundle_proof = ProvedBundle::prove(vec![swap_tx_proof, proved_exec_tx]); + // prove stf - StfPrivate { + let stf_proof = StfPrivate { zone_data: exec_state.swapvm.clone(), old_ledger: ledger.to_witness().commit(), new_ledger: exec_state.ledger.clone().to_witness().commit(), sync_logs: Vec::new(), fund_notes, - bundle: BundleWitness { - txs: vec![swap_tx_proof.public(), proved_exec_tx.public()], - }, + bundle: swap_bundle, } - .prove(risc0_zkvm::default_prover().as_ref()); + .prove(risc0_zkvm::default_prover().as_ref()) + .unwrap(); + + let ledger_proof = + ProvedLedgerTransition::prove(&mut temp_ledger_state, ZONE_ID, vec![swap_bundle_proof]); + + let new_zone_state = exec_state.zone_state(); + + assert_eq!(ledger_proof.public().old_ledger, old_zone_state.ledger); + assert_eq!(ledger_proof.public().ledger, new_zone_state.ledger); + + let zone_update = ProvedBatchUpdate { + batch: BatchUpdate { + updates: vec![Update { + old: old_zone_state, + new: new_zone_state, + }], + }, + ledger_proofs: vec![ledger_proof], + stf_proofs: vec![stf_proof], + }; + + assert!(zone_update.verify()) } diff --git a/emmarin/cl/cl/src/crust/tx.rs b/emmarin/cl/cl/src/crust/tx.rs index cf68769..274ae30 100644 --- a/emmarin/cl/cl/src/crust/tx.rs +++ b/emmarin/cl/cl/src/crust/tx.rs @@ -108,8 +108,20 @@ impl LedgerUpdate { impl TxWitness { pub fn add_input(mut self, input: InputWitness, input_cm_proof: (MMR, MMRProof)) -> Self { + assert!(input_cm_proof + .0 + .verify_proof(&input.note_commitment().0, &input_cm_proof.1)); + + for (i, other_input) in self.inputs.iter().enumerate() { + if other_input.zone_id == input.zone_id { + // ensure a single MMR per zone per tx + assert_eq!(self.frontier_paths[i].0, input_cm_proof.0); + } + } + self.inputs.push(input); self.frontier_paths.push(input_cm_proof); + self } diff --git a/emmarin/cl/cl/src/ds/mmr.rs b/emmarin/cl/cl/src/ds/mmr.rs index b385472..ea9265f 100644 --- a/emmarin/cl/cl/src/ds/mmr.rs +++ b/emmarin/cl/cl/src/ds/mmr.rs @@ -153,13 +153,13 @@ impl MMRProof { merkle::path_root(leaf, &self.path) } - pub fn update(&mut self, elem: &[u8], folds: MMRFolds) { - for (l, r) in folds.folds { + pub fn update(&mut self, elem: &[u8], folds: &MMRFolds) { + for (l, r) in &folds.folds { let root = self.root(elem); - if root == l { - self.path.push(merkle::PathNode::Right(r)) - } else if root == r { - self.path.push(merkle::PathNode::Left(l)) + if &root == l { + self.path.push(merkle::PathNode::Right(*r)) + } else if &root == r { + self.path.push(merkle::PathNode::Left(*l)) } } } diff --git a/emmarin/cl/risc0_images/src/BUNDLE_ELF b/emmarin/cl/risc0_images/src/BUNDLE_ELF index eca3d6e..a07a327 100755 Binary files a/emmarin/cl/risc0_images/src/BUNDLE_ELF and b/emmarin/cl/risc0_images/src/BUNDLE_ELF differ diff --git a/emmarin/cl/risc0_images/src/BUNDLE_ID b/emmarin/cl/risc0_images/src/BUNDLE_ID index 18fe39e..d486a2a 100644 --- a/emmarin/cl/risc0_images/src/BUNDLE_ID +++ b/emmarin/cl/risc0_images/src/BUNDLE_ID @@ -1 +1 @@ -48099ec33aa35c95b7bbe614fb6ca00accd456371a63b9c7720cd3a614301aa8 \ No newline at end of file +beb565f9da33f558659da6d9f9fd68ebe3d7d14a3e7e8bba8aaadd32e9836f93 \ No newline at end of file diff --git a/emmarin/cl/risc0_images/src/LEDGER_ELF b/emmarin/cl/risc0_images/src/LEDGER_ELF index eab258f..61c358c 100755 Binary files a/emmarin/cl/risc0_images/src/LEDGER_ELF and b/emmarin/cl/risc0_images/src/LEDGER_ELF differ diff --git a/emmarin/cl/risc0_images/src/LEDGER_ID b/emmarin/cl/risc0_images/src/LEDGER_ID index fa44a6f..f0889a7 100644 --- a/emmarin/cl/risc0_images/src/LEDGER_ID +++ b/emmarin/cl/risc0_images/src/LEDGER_ID @@ -1 +1 @@ -a1d3accfcff20421924c30f8195e956b522bda0847b3b3fab726c49e3205423b \ No newline at end of file +555127aec2abe879def82c31b50c2f2b19bf4e5456e7de31d9126fb77a9650d1 \ No newline at end of file diff --git a/emmarin/cl/risc0_images/src/STF_NOP_ELF b/emmarin/cl/risc0_images/src/STF_NOP_ELF index 1a393b9..d88c60d 100755 Binary files a/emmarin/cl/risc0_images/src/STF_NOP_ELF and b/emmarin/cl/risc0_images/src/STF_NOP_ELF differ diff --git a/emmarin/cl/risc0_images/src/STF_NOP_ID b/emmarin/cl/risc0_images/src/STF_NOP_ID index c6ac81c..dc8a847 100644 --- a/emmarin/cl/risc0_images/src/STF_NOP_ID +++ b/emmarin/cl/risc0_images/src/STF_NOP_ID @@ -1 +1 @@ -29a96bc98531b44e806051daff1be35fe84e00f4cd7f45de0213c1387184c12e \ No newline at end of file +557a0804bbf1220e66f4a695c24d57de02cb9fd52d646cb99dd43572561a6807 \ No newline at end of file diff --git a/emmarin/cl/risc0_images/src/TX_ELF b/emmarin/cl/risc0_images/src/TX_ELF index fd0ce67..30c4543 100755 Binary files a/emmarin/cl/risc0_images/src/TX_ELF and b/emmarin/cl/risc0_images/src/TX_ELF differ diff --git a/emmarin/cl/risc0_images/src/TX_ID b/emmarin/cl/risc0_images/src/TX_ID index 516b832..6e0ea5f 100644 --- a/emmarin/cl/risc0_images/src/TX_ID +++ b/emmarin/cl/risc0_images/src/TX_ID @@ -1 +1 @@ -327c489a2c52ea70bcf161ed4b90d1d681afb2bc82be7f4e5cf12ebbb5595f71 \ No newline at end of file +b332bcd5db82793c52664324cec76b67332d449bc4d7a2425a1e84c6d2282857 \ No newline at end of file