diff --git a/goas/atomic_asset_transfer/common/src/events.rs b/goas/atomic_asset_transfer/common/src/events.rs index b75a259..e116bc2 100644 --- a/goas/atomic_asset_transfer/common/src/events.rs +++ b/goas/atomic_asset_transfer/common/src/events.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Event { Spend(Spend), + Merge(Merge), } impl Event { @@ -10,6 +11,7 @@ impl Event { // TODO: add variant tag to byte encoding match self { Event::Spend(spend) => spend.to_bytes().to_vec(), + Event::Merge(merge) => merge.to_bytes().to_vec(), } } } @@ -34,3 +36,18 @@ impl Spend { bytes } } + +/// An event that authorizes spending zone funds to merge with other notes +/// Balancing of the transaction is done in the zone state death constraint +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Merge { + pub nf: cl::Nullifier, +} + +impl Merge { + pub fn to_bytes(&self) -> [u8; 32] { + let mut bytes = [0; 32]; + bytes.copy_from_slice(self.nf.as_bytes()); + bytes + } +} diff --git a/goas/atomic_asset_transfer/proof_statements/src/zone_funds.rs b/goas/atomic_asset_transfer/proof_statements/src/zone_funds.rs index 1a77d10..4ebc024 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/zone_funds.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/zone_funds.rs @@ -19,3 +19,19 @@ pub struct SpendFundsPrivate { /// Merkle root of balances in the zone pub balances_root: [u8; 32], } + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct MergePrivate { + // The note we're spending + pub funds_note: cl::PartialTxInputWitness, + // The zone note that is authorizing the spend + pub zone_note: cl::PartialTxOutputWitness, + // The event emitted by the zone that authorizes spending this note + pub merge_event: common::events::Merge, + // Path to the zone output events root + pub merge_event_state_path: Vec, + // Merkle root of txs included in the zone + pub txs_root: [u8; 32], + // Merkle root of balances in the zone + pub balances_root: [u8; 32], +} diff --git a/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml b/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml index e7446b1..b028011 100644 --- a/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml +++ b/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["spend_zone_funds", "zone_state"] +methods = ["spend_zone_funds", "zone_state", "zone_merge"] diff --git a/goas/atomic_asset_transfer/risc0_proofs/zone_merge/Cargo.toml b/goas/atomic_asset_transfer/risc0_proofs/zone_merge/Cargo.toml new file mode 100644 index 0000000..0f46c81 --- /dev/null +++ b/goas/atomic_asset_transfer/risc0_proofs/zone_merge/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "zone-merge" +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" } +ledger_proof_statements = { path = "../../../cl/ledger_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/atomic_asset_transfer/risc0_proofs/zone_merge/src/main.rs b/goas/atomic_asset_transfer/risc0_proofs/zone_merge/src/main.rs new file mode 100644 index 0000000..e5742b7 --- /dev/null +++ b/goas/atomic_asset_transfer/risc0_proofs/zone_merge/src/main.rs @@ -0,0 +1,44 @@ +use cl::{merkle, nullifier::Nullifier, PtxRoot}; +use goas_proof_statements::zone_funds::MergePrivate; +use ledger_proof_statements::death_constraint::DeathConstraintPublic; +use risc0_zkvm::guest::env; + +fn main() { + let MergePrivate { + funds_note, + zone_note, + merge_event, + merge_event_state_path, + txs_root, + balances_root, + } = env::read(); + + let input_root = funds_note.input_root(); + let output_root = zone_note.output_root(); + let nf = Nullifier::new(funds_note.input.nf_sk, funds_note.input.nonce); + // check the zone funds note is the one in the spend event + assert_eq!(nf, merge_event.nf); + + // ** Assert merge spent event was an output of the correct zone stf ** + // The zone state field is a merkle tree over: + // root + // / \ + // io state + // / \ / \ + // events txs zoneid balances + // We need to check that: + // 1) There is a valid path from the spend event to the events root + // 2) The zone id matches the one in the current funds note state + // 3) The witnesses for merge path, txs and balances allow to calculate the correct root + let zone_id = funds_note.input.note.state; // TODO: is there more state? + let merge_event_leaf = merkle::leaf(&merge_event.to_bytes()); + let event_root = merkle::path_root(merge_event_leaf, &merge_event_state_path); + + assert_eq!( + merkle::root([event_root, txs_root, zone_id, balances_root]), + zone_note.output.note.state + ); + let ptx_root = PtxRoot(merkle::node(input_root, output_root)); + + env::commit(&DeathConstraintPublic { ptx_root, nf }); +} diff --git a/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs b/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs index db36372..0061d19 100644 --- a/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs +++ b/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs @@ -94,6 +94,13 @@ fn deposit( .checked_add(amount) .expect("overflow when depositing"); + // authorize the merge of the zone funds + state + .output_events + .push(events::Event::Merge(events::Merge { + nf: Nullifier::new(zone_funds_in.nf_sk, zone_funds_in.nonce), + })); + state }