diff --git a/goas/cl/cl/src/input.rs b/goas/cl/cl/src/input.rs index 445d8c8..dde6f36 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -51,8 +51,8 @@ impl InputWitness { } impl Input { - pub fn to_bytes(&self) -> [u8; 64] { - let mut bytes = [0u8; 64]; + pub fn to_bytes(&self) -> [u8; 96] { + let mut bytes = [0u8; 96]; bytes[..32].copy_from_slice(self.nullifier.as_bytes()); bytes[32..64].copy_from_slice(&self.balance.to_bytes()); bytes[64..96].copy_from_slice(&self.death_cm.0); diff --git a/goas/cl/cl/src/nullifier.rs b/goas/cl/cl/src/nullifier.rs index 76c3b06..9d55378 100644 --- a/goas/cl/cl/src/nullifier.rs +++ b/goas/cl/cl/src/nullifier.rs @@ -51,6 +51,10 @@ impl NullifierSecret { let commit_bytes: [u8; 32] = hasher.finalize().into(); NullifierCommitment(commit_bytes) } + + pub fn from_bytes(bytes: [u8; 16]) -> Self { + Self(bytes) + } } impl NullifierCommitment { @@ -73,6 +77,10 @@ impl NullifierNonce { pub fn as_bytes(&self) -> &[u8; 16] { &self.0 } + + pub fn from_bytes(bytes: [u8; 16]) -> Self { + Self(bytes) + } } impl Nullifier { @@ -86,7 +94,7 @@ impl Nullifier { Self(nf_bytes) } - pub(crate) fn as_bytes(&self) -> &[u8; 32] { + pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } } diff --git a/goas/cl/cl/src/partial_tx.rs b/goas/cl/cl/src/partial_tx.rs index 7b3abbd..814a70f 100644 --- a/goas/cl/cl/src/partial_tx.rs +++ b/goas/cl/cl/src/partial_tx.rs @@ -12,9 +12,15 @@ const MAX_OUTPUTS: usize = 8; /// The partial transaction commitment couples an input to a partial transaction. /// Prevents partial tx unbundling. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct PtxRoot(pub [u8; 32]); +impl From<[u8; 32]> for PtxRoot { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + impl PtxRoot { pub fn random(mut rng: impl RngCore) -> Self { let mut sk = [0u8; 32]; diff --git a/goas/cl/proof_statements/src/death_constraint.rs b/goas/cl/proof_statements/src/death_constraint.rs new file mode 100644 index 0000000..efcac9e --- /dev/null +++ b/goas/cl/proof_statements/src/death_constraint.rs @@ -0,0 +1,9 @@ +use cl::{Nullifier, PtxRoot}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DeathConstraintPublic { + pub cm_root: [u8; 32], + pub nf: Nullifier, + pub ptx_root: PtxRoot, +} diff --git a/goas/cl/proof_statements/src/lib.rs b/goas/cl/proof_statements/src/lib.rs index 7839bc5..8e58650 100644 --- a/goas/cl/proof_statements/src/lib.rs +++ b/goas/cl/proof_statements/src/lib.rs @@ -1 +1,3 @@ +pub mod death_constraint; pub mod input; +pub mod ptx; diff --git a/goas/cl/proof_statements/src/ptx.rs b/goas/cl/proof_statements/src/ptx.rs new file mode 100644 index 0000000..7c7f9d7 --- /dev/null +++ b/goas/cl/proof_statements/src/ptx.rs @@ -0,0 +1,35 @@ +use cl::{merkle, InputWitness, OutputWitness, PtxRoot}; +use serde::{Deserialize, Serialize}; +/// An input to a partial transaction +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTxInputPrivate { + pub input: InputWitness, + pub cm_path: Vec, + pub ptx_path: Vec, +} + +impl PartialTxInputPrivate { + pub fn ptx_root(&self) -> PtxRoot { + let leaf = merkle::leaf(&self.input.commit().to_bytes()); + PtxRoot(merkle::path_root(leaf, &self.ptx_path)) + } + + pub fn cm_root(&self) -> [u8; 32] { + let leaf = merkle::leaf(self.input.to_output_witness().commit_note().as_bytes()); + merkle::path_root(leaf, &self.cm_path) + } +} + +/// An output to a partial transaction +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTxOutputPrivate { + pub output: OutputWitness, + pub ptx_path: Vec, +} + +impl PartialTxOutputPrivate { + pub fn ptx_root(&self) -> PtxRoot { + let leaf = merkle::leaf(&self.output.commit().to_bytes()); + PtxRoot(merkle::path_root(leaf, &self.ptx_path)) + } +} diff --git a/goas/zone/Cargo.toml b/goas/zone/Cargo.toml index 229aa89..b8dd50d 100644 --- a/goas/zone/Cargo.toml +++ b/goas/zone/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [ "common","host", "methods"] +members = [ "common","host", "methods", "proof_statements", "risc0_proofs"] # Always optimize; building and running the guest takes much longer without optimization. [profile.dev] diff --git a/goas/zone/common/Cargo.toml b/goas/zone/common/Cargo.toml index aa48d9c..921f2c8 100644 --- a/goas/zone/common/Cargo.toml +++ b/goas/zone/common/Cargo.toml @@ -5,3 +5,5 @@ edition = "2021" [dependencies] serde = { version = "1", features = ["derive"] } +cl = { path = "../../cl/cl" } +proof_statements = { path = "../proof_statements", package = "goas_proof_statements" } \ No newline at end of file diff --git a/goas/zone/common/src/lib.rs b/goas/zone/common/src/lib.rs index 13a532f..cca9cdc 100644 --- a/goas/zone/common/src/lib.rs +++ b/goas/zone/common/src/lib.rs @@ -1,28 +1,106 @@ -use serde::{Serialize, Deserialize}; +use cl::nullifier::{Nullifier, NullifierCommitment}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +// TODO: sparse merkle tree +pub const MAX_BALANCES: usize = 1 << 8; +pub const MAX_TXS: usize = 1 << 8; +pub const MAX_EVENTS: usize = 1 << 8; + // state of the zone -pub type State = BTreeMap; -// list of all inputs that were executed up to this point -pub type Journal = Vec; - #[derive(Clone, Copy, Serialize, Deserialize)] -pub enum Input { - Transfer { from: u32, to: u32, amount: u32 }, - None, +pub struct StateCommitment([u8; 32]); + +#[derive(Clone, Serialize, Deserialize)] +pub struct StateWitness { + pub balances: BTreeMap, + pub included_txs: Vec, + pub output_events: Vec, } - -/// State transition function of the zone -pub fn stf(mut state: State, input: Input) -> State { - match input { - Input::Transfer { from, to, amount } => { - // compute transfer - let from = state.entry(from).or_insert(0); - *from = from.checked_sub(amount).unwrap(); - *state.entry(to).or_insert(0) += amount; - } - Input::None => {} +impl StateWitness { + pub fn commit(&self) -> StateCommitment { + let root = self.balances_root(); + let root = cl::merkle::node(self.events_root(), root); + let root = cl::merkle::node(self.included_txs_root(), root); + StateCommitment(root) + } + + fn events_root(&self) -> [u8; 32] { + let event_bytes = Vec::from_iter( + self.output_events + .iter() + .map(Event::to_bytes) + .map(Vec::from_iter), + ); + let event_merkle_leaves = cl::merkle::padded_leaves(&event_bytes); + cl::merkle::root::(event_merkle_leaves) + } + + fn included_txs_root(&self) -> [u8; 32] { + let tx_bytes = Vec::from_iter( + self.included_txs + .iter() + .map(Input::to_bytes) + .map(Vec::from_iter), + ); + let tx_merkle_leaves = cl::merkle::padded_leaves(&tx_bytes); + cl::merkle::root::(tx_merkle_leaves) + } + + fn balances_root(&self) -> [u8; 32] { + let balance_bytes = Vec::from_iter(self.balances.iter().map(|(k, v)| { + let mut bytes = [0; 8]; + bytes.copy_from_slice(&k.to_le_bytes()); + bytes[8..].copy_from_slice(&v.to_le_bytes()); + bytes.to_vec() + })); + let balance_merkle_leaves = cl::merkle::padded_leaves(&balance_bytes); + cl::merkle::root::(balance_merkle_leaves) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Withdraw { + pub from: u32, + pub amount: u32, + pub to: NullifierCommitment, + pub nf: Nullifier, +} + +impl Withdraw { + pub fn to_bytes(&self) -> [u8; 72] { + let mut bytes = [0; 72]; + bytes[0..4].copy_from_slice(&self.from.to_le_bytes()); + bytes[4..8].copy_from_slice(&self.amount.to_le_bytes()); + bytes[8..40].copy_from_slice(self.to.as_bytes()); + bytes[40..72].copy_from_slice(self.nf.as_bytes()); + bytes + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum Input { + Withdraw(Withdraw), +} + +impl Input { + pub fn to_bytes(&self) -> Vec { + match self { + Input::Withdraw(withdraw) => withdraw.to_bytes().to_vec(), + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum Event { + Spend(proof_statements::zone_funds::Spend), +} + +impl Event { + pub fn to_bytes(&self) -> Vec { + match self { + Event::Spend(spend) => spend.to_bytes().to_vec(), + } } - state } diff --git a/goas/zone/host/src/main.rs b/goas/zone/host/src/main.rs index b474b96..17cd386 100644 --- a/goas/zone/host/src/main.rs +++ b/goas/zone/host/src/main.rs @@ -1,77 +1,28 @@ // 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 blake2::{Blake2s256, Digest}; use common::*; use risc0_zkvm::{default_prover, ExecutorEnv}; +use std::path::PathBuf; use clap::Parser; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] enum Action { - Stf, + Stf { + // path to bincode-encoded state witness + state: PathBuf, + // path to bincode-encoded inputs + inputs: PathBuf, + }, } -fn stf_prove_stark() { - let mut rng = rand::thread_rng(); - - let state: State = [(0, 1000)].into_iter().collect(); - let journal = vec![]; - let zone_input = Input::Transfer { - from: 0, - to: 1, - amount: 10, - }; - - let in_state_cm = calculate_state_hash(&state); - let in_journal_cm = calculate_journal_hash(&journal); - let in_state_root = cl::merkle::node(in_state_cm, in_journal_cm); - let in_note = cl::NoteWitness::new(1, "ZONE", in_state_root, &mut rng); - - let mut out_journal = journal.clone(); - out_journal.push(zone_input); - - let out_state_cm = calculate_state_hash(&stf(state.clone(), zone_input)); - let out_journal_cm = calculate_journal_hash(&out_journal); - let out_state_root = cl::merkle::node(out_state_cm, out_journal_cm); - let out_note = cl::NoteWitness::new(1, "ZONE", out_state_root, &mut rng); - - let input = cl::InputWitness::random(in_note, &mut rng); - let output = cl::OutputWitness::random( - out_note, - cl::NullifierSecret::random(&mut rng).commit(), - &mut rng, - ); - let ptx = cl::PartialTx::from_witness(cl::PartialTxWitness { - inputs: vec![input.clone()], - outputs: vec![output.clone()], - }); - - let ptx_root = ptx.root().0; - let in_ptx_path = ptx.input_merkle_path(0); - let out_ptx_path = ptx.output_merkle_path(0); - +fn stf_prove_stark(state: StateWitness, inputs: Vec) { let env = ExecutorEnv::builder() - .write(&ptx_root) - .unwrap() - .write(&ptx.input_root()) - .unwrap() - .write(&ptx.output_root()) - .unwrap() - .write(&in_ptx_path) - .unwrap() - .write(&out_ptx_path) - .unwrap() - .write(&input) - .unwrap() - .write(&output) - .unwrap() - .write(&zone_input) + .write(&inputs) .unwrap() .write(&state) .unwrap() - .write(&journal) - .unwrap() .build() .unwrap(); @@ -109,16 +60,10 @@ fn main() { let action = Action::parse(); match action { - Action::Stf => stf_prove_stark(), + Action::Stf { state, inputs } => { + let state = bincode::deserialize(&std::fs::read(state).unwrap()).unwrap(); + let inputs = bincode::deserialize(&std::fs::read(inputs).unwrap()).unwrap(); + stf_prove_stark(state, inputs); + } } } - -fn calculate_state_hash(state: &State) -> [u8; 32] { - let bytes = bincode::serialize(state).unwrap(); - Blake2s256::digest(&bytes).into() -} - -fn calculate_journal_hash(journal: &Journal) -> [u8; 32] { - let bytes = bincode::serialize(journal).unwrap(); - Blake2s256::digest(&bytes).into() -} diff --git a/goas/zone/methods/guest/Cargo.toml b/goas/zone/methods/guest/Cargo.toml index c8f9784..9c7aea4 100644 --- a/goas/zone/methods/guest/Cargo.toml +++ b/goas/zone/methods/guest/Cargo.toml @@ -14,6 +14,7 @@ serde = { version = "1.0", features = ["derive"] } bincode = "1" common = { path = "../../common" } cl = { path = "../../../cl/cl" } +proof_statements = { path = "../../proof_statements", package = "goas_proof_statements" } [patch.crates-io] # Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint diff --git a/goas/zone/methods/guest/src/main.rs b/goas/zone/methods/guest/src/main.rs index 1465c86..ae9483f 100644 --- a/goas/zone/methods/guest/src/main.rs +++ b/goas/zone/methods/guest/src/main.rs @@ -1,113 +1,40 @@ -use blake2::{Blake2s256, Digest}; -use cl::input::InputWitness; -use cl::merkle; -use cl::output::OutputWitness; use common::*; +use proof_statements::zone_funds::Spend; use risc0_zkvm::guest::env; -/// Public Inputs: -/// * ptx_root: the root of the partial tx merkle tree of inputs/outputs -/// Private inputs: -/// TODO +fn withdraw(mut state: StateWitness, withdraw: Withdraw) -> StateWitness { + state.included_txs.push(Input::Withdraw(withdraw)); -fn execute( - ptx_root: [u8; 32], - input_root: [u8; 32], - output_root: [u8; 32], - in_ptx_path: Vec, - out_ptx_path: Vec, - in_note: InputWitness, - out_note: OutputWitness, - input: Input, - state: State, - mut journal: Journal, -) { - // verify ptx/cl preconditions - eprintln!("start exec: {}", env::cycle_count()); - assert_eq!(ptx_root, merkle::node(input_root, output_root)); - eprintln!("ptx_root: {}", env::cycle_count()); + let Withdraw { + from, + amount, + to, + nf, + } = withdraw; - // Glue the zone and the cl together, specifically, it verifies the note requesting - // a transfer is included as part of the same transaction in the cl - let in_comm = in_note.commit().to_bytes(); - eprintln!("input comm: {}", env::cycle_count()); + let from_balance = state.balances.entry(from).or_insert(0); + *from_balance = from.checked_sub(amount).expect("insufficient funds in account"); + let spend_auth = Spend { + amount: amount.into(), + to, + nf, + }; - assert_eq!( - merkle::path_root(merkle::leaf(&in_comm), &in_ptx_path), - input_root - ); - eprintln!("input merkle path: {}", env::cycle_count()); - - // check the commitments match the actual data - let state_cm = calculate_state_hash(&state); - let journal_cm = calculate_journal_hash(&journal); - let state_root = merkle::node(state_cm, journal_cm); - assert_eq!(state_root, in_note.note.state); - eprintln!("input state root: {}", env::cycle_count()); - - // then run the state transition function - let state = stf(state, input); - journal.push(input); - eprintln!("stf: {}", env::cycle_count()); - - // verifying ptx/cl postconditions - - let out_state_cm = calculate_state_hash(&state); - let out_journal_cm = calculate_journal_hash(&journal); - let out_state_root = merkle::node(out_state_cm, out_journal_cm); - // TODO: verify death constraints are propagated - assert_eq!(out_state_root, out_note.note.state); - eprintln!("out state root: {}", env::cycle_count()); - - // Glue the zone and the cl together, specifically, it verifies an output note - // containing the zone state is included as part of the same transaction in the cl - // (this is done in the death condition to disallow burning) - let out_comm = out_note.commit().to_bytes(); - eprintln!("output comm: {}", env::cycle_count()); - - assert_eq!( - merkle::path_root(merkle::leaf(&out_comm), &out_ptx_path), - output_root - ); - eprintln!("out merkle proof: {}", env::cycle_count()); + state.output_events.push(Event::Spend(spend_auth)); + state } fn main() { - // public input - let ptx_root: [u8; 32] = env::read(); + let inputs: Vec = env::read(); + let mut state: StateWitness = env::read(); - // private input - let input_root: [u8; 32] = env::read(); - let output_root: [u8; 32] = env::read(); - let in_ptx_path: Vec = env::read(); - let out_ptx_path: Vec = env::read(); - let in_note: InputWitness = env::read(); - let out_note: OutputWitness = env::read(); - let input: Input = env::read(); - let state: State = env::read(); - let journal: Journal = env::read(); + for input in inputs { + match input { + Input::Withdraw(input) => { + state = withdraw(state, input); + } + } + } - eprintln!("parse input: {}", env::cycle_count()); - execute( - ptx_root, - input_root, - output_root, - in_ptx_path, - out_ptx_path, - in_note, - out_note, - input, - state, - journal, - ); -} - -fn calculate_state_hash(state: &State) -> [u8; 32] { - let bytes = bincode::serialize(state).unwrap(); - Blake2s256::digest(&bytes).into() -} - -fn calculate_journal_hash(journal: &Journal) -> [u8; 32] { - let bytes = bincode::serialize(journal).unwrap(); - Blake2s256::digest(&bytes).into() + env::commit(&state.commit()); } diff --git a/goas/zone/proof_statements/Cargo.toml b/goas/zone/proof_statements/Cargo.toml new file mode 100644 index 0000000..d7b9b87 --- /dev/null +++ b/goas/zone/proof_statements/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "goas_proof_statements" +version = "0.1.0" +edition = "2021" + +[dependencies] +cl = { path = "../../cl/cl" } +proof_statements = { path = "../../cl/proof_statements" } +serde = { version = "1.0", features = ["derive"] } diff --git a/goas/zone/proof_statements/src/lib.rs b/goas/zone/proof_statements/src/lib.rs new file mode 100644 index 0000000..3eba170 --- /dev/null +++ b/goas/zone/proof_statements/src/lib.rs @@ -0,0 +1 @@ +pub mod zone_funds; diff --git a/goas/zone/proof_statements/src/zone_funds.rs b/goas/zone/proof_statements/src/zone_funds.rs new file mode 100644 index 0000000..5da4346 --- /dev/null +++ b/goas/zone/proof_statements/src/zone_funds.rs @@ -0,0 +1,40 @@ +use proof_statements::{ptx::PartialTxInputPrivate, ptx::PartialTxOutputPrivate}; +use serde::{Deserialize, Serialize}; + +/// An event that authorizes spending zone funds +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Spend { + pub amount: u64, + /// The public key of the recipient + pub to: cl::NullifierCommitment, + /// The nullifier of note that is being spent, this is to avoid using the spend event to + /// for multiple notes + pub nf: cl::Nullifier, +} + +impl Spend { + pub fn to_bytes(&self) -> [u8; 72] { + let mut bytes = [0; 72]; + bytes[0..8].copy_from_slice(&self.amount.to_le_bytes()); + bytes[8..40].copy_from_slice(self.to.as_bytes()); + bytes[40..72].copy_from_slice(self.nf.as_bytes()); + bytes + } +} + +/// There are two kind of paths +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SpendFundsPrivate { + /// The note we're spending + pub in_zone_funds: PartialTxInputPrivate, + /// The zone note that is authorizing the spend + pub zone_note: PartialTxOutputPrivate, + /// The note that is being created to send the change back to the zone + pub out_zone_funds: PartialTxOutputPrivate, + /// The spent funds note + pub spent_note: PartialTxOutputPrivate, + /// The event emitted by the zone that authorizes the spend + pub spend_event: Spend, + /// Path to the zone output state + pub spend_event_state_path: Vec, +} diff --git a/goas/zone/risc0_proofs/Cargo.toml b/goas/zone/risc0_proofs/Cargo.toml new file mode 100644 index 0000000..e73bbbb --- /dev/null +++ b/goas/zone/risc0_proofs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "goas_risc0_proofs" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "1.0" } + +[package.metadata.risc0] +methods = ["spend_zone_funds"] + diff --git a/goas/zone/risc0_proofs/build.rs b/goas/zone/risc0_proofs/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/goas/zone/risc0_proofs/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/goas/zone/risc0_proofs/spend_zone_funds/Cargo.toml b/goas/zone/risc0_proofs/spend_zone_funds/Cargo.toml new file mode 100644 index 0000000..4d65001 --- /dev/null +++ b/goas/zone/risc0_proofs/spend_zone_funds/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "spend-zone-funds" +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 = "../../../cl/cl" } +goas_proof_statements = { path = "../../proof_statements" } +proof_statements = { path = "../../../cl/proof_statements" } +sha2 = "0.10" + + +[patch.crates-io] +# add RISC Zero accelerator support for all downstream usages of the following crates. +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" } +crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" } +curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" } diff --git a/goas/zone/risc0_proofs/spend_zone_funds/src/main.rs b/goas/zone/risc0_proofs/spend_zone_funds/src/main.rs new file mode 100644 index 0000000..18a00ea --- /dev/null +++ b/goas/zone/risc0_proofs/spend_zone_funds/src/main.rs @@ -0,0 +1,89 @@ +/// Zone Funds Spend Proof +/// +/// Our goal: prove the zone authorized spending of funds +use cl::merkle; +use cl::nullifier::{Nullifier, NullifierNonce, NullifierSecret}; +use goas_proof_statements::zone_funds::SpendFundsPrivate; +use proof_statements::death_constraint::DeathConstraintPublic; +use risc0_zkvm::guest::env; +use sha2::{Digest, Sha256}; + +fn main() { + let SpendFundsPrivate { + in_zone_funds, + out_zone_funds, + zone_note, + spent_note, + spend_event, + spend_event_state_path, + } = env::read(); + + let cm_root = in_zone_funds.cm_root(); + let ptx_root = in_zone_funds.ptx_root(); + let nf = Nullifier::new(in_zone_funds.input.nf_sk, in_zone_funds.input.nonce); + // check the zone funds note is the one in the spend event + assert_eq!(nf, spend_event.nf); + + assert_eq!(ptx_root, zone_note.ptx_root()); + // assert the spent event was an output of the zone stf + let spend_event_leaf = merkle::leaf(&spend_event.to_bytes()); + // TODO: zones will have some more state + assert_eq!( + zone_note.output.note.state, + merkle::path_root(spend_event_leaf, &spend_event_state_path) + ); + + assert_eq!(ptx_root, out_zone_funds.ptx_root()); + + // Check we return the rest of the funds back to the zone + let change = in_zone_funds + .input + .note + .balance + .value + .checked_sub(spend_event.amount) + .unwrap(); + assert_eq!(out_zone_funds.output.note.balance.value, change); + // zone funds output should have the same death constraints as the zone funds input + assert_eq!( + out_zone_funds.output.note.death_constraint, + in_zone_funds.input.note.death_constraint + ); + assert_eq!( + out_zone_funds.output.note.balance.unit, + in_zone_funds.input.note.balance.unit + ); + // zone funds nullifier, nonce and value blinding should be public so that everybody can spend it + assert_eq!( + out_zone_funds.output.nf_pk, + NullifierSecret::from_bytes([0; 16]).commit() + ); + assert_eq!( + out_zone_funds.output.note.balance.blinding, + in_zone_funds.input.note.balance.blinding + ); + let mut evolved_nonce = [0; 16]; + evolved_nonce[..16] + .copy_from_slice(&Sha256::digest(&out_zone_funds.output.nonce.as_bytes())[..16]); + assert_eq!( + out_zone_funds.output.nonce, + NullifierNonce::from_bytes(evolved_nonce) + ); + + assert_eq!(ptx_root, spent_note.ptx_root()); + + // check the correct amount of funds is being spent + assert_eq!(spent_note.output.note.balance.value, spend_event.amount); + assert_eq!( + spent_note.output.note.balance.unit, + in_zone_funds.input.note.balance.unit + ); + // check the correct recipient is being paid + assert_eq!(spent_note.output.nf_pk, spend_event.to); + + env::commit(&DeathConstraintPublic { + cm_root, + ptx_root, + nf, + }); +} diff --git a/goas/zone/risc0_proofs/src/lib.rs b/goas/zone/risc0_proofs/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/goas/zone/risc0_proofs/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs"));