diff --git a/emmarin/apps/swapvm/Cargo.toml b/emmarin/apps/swapvm/Cargo.toml index 567f95f..c5fd5da 100644 --- a/emmarin/apps/swapvm/Cargo.toml +++ b/emmarin/apps/swapvm/Cargo.toml @@ -1,5 +1,11 @@ [workspace] resolver = "2" -members = ["app"] +members = ["app", "stf/host", "stf/methods"] +# Always optimize; building and running the guest takes much longer without optimization. +[profile.dev] +opt-level = 3 +[profile.release] +debug = 1 +lto = true diff --git a/emmarin/apps/swapvm/app/Cargo.toml b/emmarin/apps/swapvm/app/Cargo.toml index b976ef4..069b7d8 100644 --- a/emmarin/apps/swapvm/app/Cargo.toml +++ b/emmarin/apps/swapvm/app/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" [dependencies] cl = { path = "../../../cl/cl" } -risc0-zkvm = "1.2" \ No newline at end of file +risc0-zkvm = "1.2" +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/emmarin/apps/swapvm/app/src/lib.rs b/emmarin/apps/swapvm/app/src/lib.rs index a0b022c..be0c929 100644 --- a/emmarin/apps/swapvm/app/src/lib.rs +++ b/emmarin/apps/swapvm/app/src/lib.rs @@ -1,25 +1,14 @@ use cl::{ - crust::{ - balance::{UnitWitness, NOP_COVENANT}, - Nullifier, Tx, Unit, - }, - mantle::{ledger::Ledger, ZoneId, ZoneState}, + crust::{InputWitness, Nullifier, NullifierSecret, Tx, Unit}, + mantle::ZoneId, }; use risc0_zkvm::sha::rust_crypto::{Digest, Sha256}; +use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; -const SWAP_GOAL_UNIT: UnitWitness = UnitWitness { - spending_covenant: NOP_COVENANT, - minting_covenant: NOP_COVENANT, - burning_covenant: NOP_COVENANT, -}; - -pub struct SwapVmPrivate { - pub old: ZoneState, - pub new_ledger: Ledger, - pub data: ZoneData, -} +const FUNDS_SK: NullifierSecret = NullifierSecret([0; 16]); +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Swap { pair: Pair, t0_in: u64, @@ -28,36 +17,42 @@ pub struct Swap { t1_out: u64, } +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddLiquidity { - pair: Pair, - t0_in: u64, - t1_in: u64, + _pair: Pair, + _t0_in: u64, + _t1_in: u64, } +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct RemoveLiquidity { - shares: Unit, + _shares: Unit, } +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZoneData { - nfs: BTreeSet, - pools: BTreeMap, - zone_id: ZoneId, + pub nfs: BTreeSet, + pub pools: BTreeMap, + pub zone_id: ZoneId, } -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] pub struct Pair { pub t0: Unit, pub t1: Unit, } +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Pool { pub balance_0: u64, pub balance_1: u64, } /// Prove the data was part of the tx output +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct OutputDataProof; +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum ZoneOp { Swap { tx: Tx, @@ -97,7 +92,7 @@ impl ZoneData { let balance_1_final = balance_1_start + swap.t1_in as u128 - swap.t1_out as u128; (balance_0_final * 1000 - 3 * swap.t0_in as u128) - * (balance_0_final * 1000 - 3 * swap.t1_in as u128) + * (balance_1_final * 1000 - 3 * swap.t1_in as u128) == balance_0_start * balance_1_start } @@ -112,9 +107,7 @@ impl ZoneData { pub fn validate_op(&self, op: &ZoneOp) -> bool { match op { - ZoneOp::Swap { tx, swap, proof } => { - self.check_swap(&swap) && self.validate_no_pools(&tx) - } + ZoneOp::Swap { tx, swap, .. } => self.check_swap(&swap) && self.validate_no_pools(&tx), // TODO: check proof ZoneOp::AddLiquidity { tx, .. } => self.validate_no_pools(&tx), ZoneOp::RemoveLiquidity { tx, .. } => self.validate_no_pools(&tx), // should we check shares exist? ZoneOp::Ledger(tx) => { @@ -125,6 +118,37 @@ impl ZoneData { } } + pub fn pools_update(&mut self, tx: &Tx, notes: &[InputWitness]) { + // check all previous nullifiers are used + assert!(self.nfs.iter().all(|nf| tx + .updates + .iter() + .filter(|u| u.zone_id == self.zone_id) + .flat_map(|u| u.inputs.iter()) + .find(|nf2| *nf2 == nf) + .is_some())); + self.nfs.clear(); + + // check the exepected pool balances are reflected in the tx outputs + let outputs = tx + .updates + .iter() + .filter(|u| u.zone_id == self.zone_id) + .flat_map(|u| u.outputs.iter()) + .collect::>(); + + let expected_pool_balances = self.expected_pool_balances(); + for note in notes { + assert_eq!(note.nf_sk, FUNDS_SK); + // TODO: check nonce derivation + let output = note.to_output(); + let value = expected_pool_balances.get(&output.unit).unwrap(); + assert_eq!(note.value, *value); + assert!(outputs.contains(&&output.note_commitment())); + self.nfs.insert(note.nullifier()); + } + } + pub fn expected_pool_balances(&self) -> BTreeMap { let mut expected_pool_balances = BTreeMap::new(); for (Pair { t0, t1 }, pool) in self.pools.iter() { @@ -137,22 +161,15 @@ impl ZoneData { pub fn process_op(&mut self, op: &ZoneOp) { match op { - ZoneOp::Swap { tx, swap, proof } => { + ZoneOp::Swap { tx, swap, .. } => { self.swap(&swap); self.validate_no_pools(&tx); + // TODO: check the proof } - ZoneOp::AddLiquidity { - tx, - add_liquidity, - proof, - } => { + ZoneOp::AddLiquidity { .. } => { todo!() } - ZoneOp::RemoveLiquidity { - tx, - remove_liquidity, - proof, - } => { + ZoneOp::RemoveLiquidity { .. } => { todo!() } ZoneOp::Ledger(tx) => { @@ -163,6 +180,10 @@ impl ZoneData { } } + pub fn update_and_commit(self) -> ZoneData { + self + } + pub fn commit(&self) -> [u8; 32] { let mut hasher = Sha256::new(); for nf in &self.nfs { diff --git a/emmarin/apps/swapvm/stf/rust-toolchain.toml b/emmarin/apps/swapvm/rust-toolchain.toml similarity index 100% rename from emmarin/apps/swapvm/stf/rust-toolchain.toml rename to emmarin/apps/swapvm/rust-toolchain.toml diff --git a/emmarin/apps/swapvm/stf/Cargo.toml b/emmarin/apps/swapvm/stf/Cargo.toml deleted file mode 100644 index 09087b4..0000000 --- a/emmarin/apps/swapvm/stf/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[workspace] -resolver = "2" -members = ["host", "methods"] - -[dependencies] -app = { path = "../app" } - -# Always optimize; building and running the guest takes much longer without optimization. -[profile.dev] -opt-level = 3 - -[profile.release] -debug = 1 -lto = true diff --git a/emmarin/apps/swapvm/stf/host/Cargo.toml b/emmarin/apps/swapvm/stf/host/Cargo.toml index 20b716c..1e6d93d 100644 --- a/emmarin/apps/swapvm/stf/host/Cargo.toml +++ b/emmarin/apps/swapvm/stf/host/Cargo.toml @@ -8,3 +8,6 @@ methods = { path = "../methods" } risc0-zkvm = { version = "1.2.0" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } serde = "1.0" +ledger_proof_statements = { path = "../../../../cl/ledger_proof_statements" } +app = { path = "../../app" } +cl = { path = "../../../../cl/cl" } \ No newline at end of file diff --git a/emmarin/apps/swapvm/stf/host/src/lib.rs b/emmarin/apps/swapvm/stf/host/src/lib.rs new file mode 100644 index 0000000..47bc4e8 --- /dev/null +++ b/emmarin/apps/swapvm/stf/host/src/lib.rs @@ -0,0 +1,32 @@ +// These constants represent the RISC-V ELF and the image ID generated by risc0-build. +// The ELF is used for proving and the ID is used for verification. +use app::ZoneOp; +use cl::mantle::{ledger::Ledger, zone::ZoneData}; +use ledger_proof_statements::ledger::SyncLog; +use methods::{STF_ELF, STF_ID}; +use risc0_zkvm::{ExecutorEnv, Prover, Receipt, Result}; + +pub struct StfPrivate { + pub zone_data: ZoneData, + pub old_ledger: Ledger, + pub new_ledger: Ledger, + pub sync_logs: Vec, + pub ops: Vec, +} + +impl StfPrivate { + pub fn prove(&self, prover: &impl Prover) -> Result { + let env = ExecutorEnv::builder() + .write(&self.zone_data)? + .write(&self.old_ledger)? + .write(&self.new_ledger)? + .write(&self.sync_logs)? + .write(&STF_ID)? + .build()?; + + let prove_info = prover.prove(env, STF_ELF)?; + + debug_assert!(prove_info.receipt.verify(STF_ID).is_ok()); + Ok(prove_info.receipt) + } +} diff --git a/emmarin/apps/swapvm/stf/host/src/main.rs b/emmarin/apps/swapvm/stf/host/src/main.rs deleted file mode 100644 index 2672012..0000000 --- a/emmarin/apps/swapvm/stf/host/src/main.rs +++ /dev/null @@ -1,56 +0,0 @@ -// These constants represent the RISC-V ELF and the image ID generated by risc0-build. -// The ELF is used for proving and the ID is used for verification. -use methods::{ - STF_ELF, STF_ID -}; -use risc0_zkvm::{default_prover, ExecutorEnv}; - -fn main() { - // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) - .init(); - - // An executor environment describes the configurations for the zkVM - // including program inputs. - // A default ExecutorEnv can be created like so: - // `let env = ExecutorEnv::builder().build().unwrap();` - // However, this `env` does not have any inputs. - // - // To add guest input to the executor environment, use - // ExecutorEnvBuilder::write(). - // To access this method, you'll need to use ExecutorEnv::builder(), which - // creates an ExecutorEnvBuilder. When you're done adding input, call - // ExecutorEnvBuilder::build(). - - // For example: - let input: u32 = 15 * u32::pow(2, 27) + 1; - let env = ExecutorEnv::builder() - .write(&input) - .unwrap() - .build() - .unwrap(); - - // Obtain the default prover. - let prover = default_prover(); - - // Proof information by proving the specified ELF binary. - // This struct contains the receipt along with statistics about execution of the guest - let prove_info = prover - .prove(env, STF_ELF) - .unwrap(); - - // extract the receipt. - let receipt = prove_info.receipt; - - // TODO: Implement code for retrieving receipt journal here. - - // For example: - let _output: u32 = receipt.journal.decode().unwrap(); - - // The receipt was verified at the end of proving, but the below code is an - // example of how someone else could verify this receipt. - receipt - .verify(STF_ID) - .unwrap(); -} diff --git a/emmarin/apps/swapvm/stf/methods/Cargo.toml b/emmarin/apps/swapvm/stf/methods/Cargo.toml index aa1546b..6d6c14e 100644 --- a/emmarin/apps/swapvm/stf/methods/Cargo.toml +++ b/emmarin/apps/swapvm/stf/methods/Cargo.toml @@ -3,6 +3,9 @@ name = "methods" version = "0.1.0" edition = "2021" +[dependencies] +app = { path = "../../app" } + [build-dependencies] risc0-build = { version = "1.2.0" } diff --git a/emmarin/apps/swapvm/stf/methods/guest/Cargo.toml b/emmarin/apps/swapvm/stf/methods/guest/Cargo.toml index 2ce2deb..befd76c 100644 --- a/emmarin/apps/swapvm/stf/methods/guest/Cargo.toml +++ b/emmarin/apps/swapvm/stf/methods/guest/Cargo.toml @@ -7,3 +7,7 @@ edition = "2021" [dependencies] risc0-zkvm = { version = "1.2.0", default-features = false, features = ['std'] } +app = { path = "../../../app" } +ledger_proof_statements = { path = "../../../../../cl/ledger_proof_statements" } +cl = { path = "../../../../../cl/cl" } +ledger_validity_proof = { path = "../../../../../cl/ledger_validity_proof" } \ No newline at end of file diff --git a/emmarin/apps/swapvm/stf/methods/guest/src/main.rs b/emmarin/apps/swapvm/stf/methods/guest/src/main.rs index 3c7e289..e665588 100644 --- a/emmarin/apps/swapvm/stf/methods/guest/src/main.rs +++ b/emmarin/apps/swapvm/stf/methods/guest/src/main.rs @@ -1,50 +1,80 @@ +use app::{ZoneData, ZoneOp}; +use cl::{ + crust::Tx, + mantle::{ledger::Ledger, zone::ZoneState}, +}; +use ledger_proof_statements::{ + ledger::{LedgerProofPublic, SyncLog}, + stf::StfPublic, +}; use risc0_zkvm::guest::env; fn main() { - let mut inputs: SwapVmPrivate = env::read(); + let mut zone_data: ZoneData = env::read(); + let old_ledger: Ledger = env::read(); + let ledger: Ledger = env::read(); + let sync_logs: Vec = env::read(); + let stf: [u8; 32] = env::read(); + let ops: Vec = env::read(); - let zone_id = inputs.zone_data.zone_id; + let zone_id = zone_data.zone_id; - assert_eq!(inputs.zone_data.commit(), inputs.old.zone_data); + let old_zone_data = zone_data.commit(); - for op in ops { - zone_data.process_op(tx); + for op in &ops { + zone_data.process_op(op); } - let txs = ops + let txs: Vec<&Tx> = ops .iter() .map(|op| match op { - ZoneOp::Swap { tx, swap, proof } => tx, + ZoneOp::Swap { tx, .. } => tx, ZoneOp::AddLiquidity { tx, .. } => tx, ZoneOp::RemoveLiquidity { tx, .. } => tx, ZoneOp::Ledger(tx) => tx, }) .collect(); - let sync_logs = vec![]; // get this from outside - let outputs = txs .iter() - .flat_map(|tx| tx.outputs.clone()) - .filter(|o| o.zone_id == zone_id) + .flat_map(|tx| tx.updates.iter().filter(|u| u.zone_id == zone_id)) + .flat_map(|u| u.outputs.iter()) + .copied() .collect(); - let inputs = txs + // TODO: inputs missings from ledger proof public + let _inputs: Vec<_> = txs .iter() - .flat_map(|tx| tx.inputs.clone()) - .filter(|i| i.zone_id == zone_id) + .flat_map(|tx| tx.updates.iter().filter(|u| u.zone_id == zone_id)) + .flat_map(|u| u.inputs.iter()) + .copied() .collect(); let ledger_public = LedgerProofPublic { - old_ledger: inputs.old.ledger, - ledger: inputs.new_ledger, + old_ledger, + ledger, id: zone_id, - sync_log, + sync_logs, outputs, }; env::verify( ledger_validity_proof::LEDGER_ID, - &serde::to_vec(&ledger_public).unwrap(), + &risc0_zkvm::serde::to_vec(&ledger_public).unwrap(), ) .unwrap(); + + let public = StfPublic { + old: ZoneState { + ledger: old_ledger, + zone_data: old_zone_data, + stf, + }, + new: ZoneState { + ledger, + zone_data: zone_data.commit(), + stf, + }, + }; + + env::commit(&public); } diff --git a/emmarin/cl/cl/src/crust/iow.rs b/emmarin/cl/cl/src/crust/iow.rs index 88bcba9..07e92ce 100644 --- a/emmarin/cl/cl/src/crust/iow.rs +++ b/emmarin/cl/cl/src/crust/iow.rs @@ -73,6 +73,17 @@ impl InputWitness { self.nf_sk.commit(), ) } + + pub fn to_output(&self) -> OutputWitness { + OutputWitness { + state: self.state, + value: self.value, + unit: self.unit_witness.unit(), + nonce: self.nonce, + zone_id: self.zone_id, + nf_pk: self.nf_sk.commit(), + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]