From c74fa88380f3936407a3712bc1d822f515c86163 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 11 Jul 2025 18:47:03 -0300 Subject: [PATCH 01/83] wip --- .gitignore | 5 +- risc0-selective-privacy-poc/Cargo.toml | 17 +++ risc0-selective-privacy-poc/core/Cargo.toml | 8 ++ risc0-selective-privacy-poc/core/src/lib.rs | 44 +++++++ .../outer_methods/Cargo.toml | 10 ++ .../outer_methods/build.rs | 3 + .../outer_methods/guest/Cargo.toml | 11 ++ .../outer_methods/guest/src/bin/outer.rs | 42 +++++++ .../outer_methods/src/lib.rs | 1 + .../rust-toolchain.toml | 4 + risc0-selective-privacy-poc/src/lib.rs | 119 ++++++++++++++++++ .../transfer_methods/Cargo.toml | 10 ++ .../transfer_methods/build.rs | 3 + .../transfer_methods/guest/Cargo.toml | 10 ++ .../guest/src/bin/transfer.rs | 18 +++ .../transfer_methods/src/lib.rs | 1 + 16 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 risc0-selective-privacy-poc/Cargo.toml create mode 100644 risc0-selective-privacy-poc/core/Cargo.toml create mode 100644 risc0-selective-privacy-poc/core/src/lib.rs create mode 100644 risc0-selective-privacy-poc/outer_methods/Cargo.toml create mode 100644 risc0-selective-privacy-poc/outer_methods/build.rs create mode 100644 risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml create mode 100644 risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs create mode 100644 risc0-selective-privacy-poc/outer_methods/src/lib.rs create mode 100644 risc0-selective-privacy-poc/rust-toolchain.toml create mode 100644 risc0-selective-privacy-poc/src/lib.rs create mode 100644 risc0-selective-privacy-poc/transfer_methods/Cargo.toml create mode 100644 risc0-selective-privacy-poc/transfer_methods/build.rs create mode 100644 risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml create mode 100644 risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs create mode 100644 risc0-selective-privacy-poc/transfer_methods/src/lib.rs diff --git a/.gitignore b/.gitignore index f2f9e58..d97316e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ target -Cargo.lock \ No newline at end of file +Cargo.lock +risc0-selective-privacy-poc/target +risc0-selective-privacy-poc/Cargo.lock +risc0-selective-privacy-poc/methods/guest/Cargo.lock \ No newline at end of file diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml new file mode 100644 index 0000000..e817b5d --- /dev/null +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tuki" +version = "0.12.0" +edition = "2021" + +[dependencies] +risc0-zkvm = "2.2" +toy-example-core = {path = "core"} +transfer-methods = {path = "transfer_methods"} +outer-methods = {path = "outer_methods"} +serde = "1.0" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[features] +cuda = ["risc0-zkvm/cuda"] +default = [] +prove = ["risc0-zkvm/prove"] \ No newline at end of file diff --git a/risc0-selective-privacy-poc/core/Cargo.toml b/risc0-selective-privacy-poc/core/Cargo.toml new file mode 100644 index 0000000..1d84937 --- /dev/null +++ b/risc0-selective-privacy-poc/core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "toy-example-core" +version = "0.12.0" +edition = "2021" + +[dependencies] +risc0-zkvm = "2.0.2" +serde = { version = "1.0", default-features = false } \ No newline at end of file diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs new file mode 100644 index 0000000..fc3da32 --- /dev/null +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -0,0 +1,44 @@ +#![cfg_attr(not(test), no_std)] + +use serde::{Serialize, Deserialize}; +use risc0_zkvm::{sha::{Impl, Sha256}, serde::to_vec}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct Account { + pub address: [u32; 8], + pub balance: u128, + pub nonce: [u32; 8] +} + +impl Account { + /// Creates a new account with address = hash(private_key) and balance = 0 + pub fn new(private_key: [u32; 8], nonce: [u32; 8]) -> Self { + let address = hash(&private_key); + Self { address, balance: 0, nonce } + } + + /// Returns Hash(Account) + pub fn commitment(&self) -> [u32; 8] { + hash(&to_vec(&self).unwrap()) + } + +} + + +pub fn hash(bytes: &[u32]) -> [u32; 8] { + Impl::hash_words(&bytes).as_words().try_into().unwrap() +} + +pub fn is_in_commitment_tree(_commitment: [u32; 8], _tree_root: [u32; 8]) -> bool { + // Dummy implementation + true +} + +/// Returns Hash(Commitment || private_key) +pub fn compute_nullifier(commitment: [u32; 8], private_key: [u32; 8]) -> [u32; 8] { + let mut bytes_to_hash = [0; 16]; + bytes_to_hash[..8].copy_from_slice(&commitment); + bytes_to_hash[8..].copy_from_slice(&private_key); + hash(&bytes_to_hash) +} + diff --git a/risc0-selective-privacy-poc/outer_methods/Cargo.toml b/risc0-selective-privacy-poc/outer_methods/Cargo.toml new file mode 100644 index 0000000..1f5e9ec --- /dev/null +++ b/risc0-selective-privacy-poc/outer_methods/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "outer-methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "2.2" } + +[package.metadata.risc0] +methods = ["guest"] diff --git a/risc0-selective-privacy-poc/outer_methods/build.rs b/risc0-selective-privacy-poc/outer_methods/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/risc0-selective-privacy-poc/outer_methods/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml new file mode 100644 index 0000000..ceb31ad --- /dev/null +++ b/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "toy_example" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] } +toy-example-core = {path = "../../core" } +transfer-methods = {path = "../../transfer_methods"} diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs new file mode 100644 index 0000000..d111bdd --- /dev/null +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -0,0 +1,42 @@ +use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; +use toy_example_core::{Account, hash, compute_nullifier, is_in_commitment_tree}; +use transfer_methods::TRANSFER_ID; + +fn main() { + // Read inputs + let sender_private_key: [u32; 8] = env::read(); + let sender: Account = env::read(); + let receiver: Account = env::read(); + let balance_to_move: u128 = env::read(); + let sender_post: Account = env::read(); + let receiver_post: Account = env::read(); + let commitment_tree_root: [u32; 8] = env::read(); + + // Assert receiver account is fresh + assert_eq!(receiver.balance, 0); + + // Prove ownership of sender account by proving + // knowledge of the pre-image of its address + assert_eq!(hash(&sender_private_key), sender.address); + + // Compute sender account commitment and prove it belongs to commitments tree + let sender_commitment = sender.commitment(); + assert!(is_in_commitment_tree(sender_commitment, commitment_tree_root)); + + // Compute nullifier of sender account + let sender_nullifier = compute_nullifier(sender_commitment, sender_private_key); + + // Compute receiver commitment + let receiver_commitment = receiver_post.commitment(); + + // Verify pre states and post states of accounts are consistent + // with the execution of the TRANSFER_ELF program + env::verify(TRANSFER_ID, &to_vec(&(sender.clone(), receiver.clone(), sender_post.clone(), receiver_post.clone())).unwrap()).unwrap(); + + // Assert TRANSFER_ELF program didn't modify address fields + assert_eq!(sender.address, sender_post.address); + assert_eq!(receiver.address, receiver_post.address); + + // Output nullifier + env::commit(&(sender_nullifier, receiver_commitment)); +} diff --git a/risc0-selective-privacy-poc/outer_methods/src/lib.rs b/risc0-selective-privacy-poc/outer_methods/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/risc0-selective-privacy-poc/outer_methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/risc0-selective-privacy-poc/rust-toolchain.toml b/risc0-selective-privacy-poc/rust-toolchain.toml new file mode 100644 index 0000000..36614c3 --- /dev/null +++ b/risc0-selective-privacy-poc/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "rust-src"] +profile = "minimal" diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs new file mode 100644 index 0000000..b2b85bb --- /dev/null +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -0,0 +1,119 @@ +use std::u128; + +// 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 transfer_methods::{ + TRANSFER_ELF, TRANSFER_ID +}; +use outer_methods::{ + OUTER_ELF, OUTER_ID +}; +use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, Receipt}; +use toy_example_core::Account; + +fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Receipt, Account, Account) { + let mut env_builder = ExecutorEnv::builder(); + env_builder.write(&sender).unwrap(); + env_builder.write(&receiver).unwrap(); + env_builder.write(&balance_to_move).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover + .prove(env, TRANSFER_ELF) + .unwrap(); + + let receipt = prove_info.receipt; + + let output: [Account; 4] = receipt.journal.decode().unwrap(); + let [_, _, sender_post, receiver_post] = output; + + println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); + println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); + + // Sanity check + receipt + .verify(TRANSFER_ID) + .unwrap(); + + (receipt, sender_post, receiver_post) +} + +pub fn run_private_execution_of_transfer_program() { + let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; + let sender_private_key = [0; 8]; + let mut sender = Account::new(sender_private_key, [1; 8]); + sender.balance = 150; + + let receiver_private_key = [99; 8]; + let receiver = Account::new(receiver_private_key, [1; 8]); + let balance_to_move: u128 = 3; + + // Prove inner + let (inner_receipt, sender_post, receiver_post) = prove_inner(sender.clone(), receiver.clone(), balance_to_move); + + // Prover outer + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&sender_private_key).unwrap(); + env_builder.write(&sender).unwrap(); + env_builder.write(&receiver) .unwrap(); + env_builder.write(&balance_to_move).unwrap(); + env_builder.write(&sender_post).unwrap(); + env_builder.write(&receiver_post).unwrap(); + env_builder.write(&commitment_tree_root).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover + .prove(env, OUTER_ELF) + .unwrap(); + + let receipt = prove_info.receipt; + + receipt.verify(OUTER_ID).unwrap(); + + let (nullifier, commitment): ([u32; 8], [u32; 8]) = receipt.journal.decode().unwrap(); + println!("nullifier: {:?}", nullifier); + println!("commitment: {:?}", commitment); +} + +pub fn run_public_execution_of_transfer_program() { + let sender_private_key = [0; 8]; + let mut sender = Account::new(sender_private_key, [1; 8]); + sender.balance = 150; + + let receiver_private_key = [99; 8]; + let mut receiver = Account::new(receiver_private_key, [1; 8]); + receiver.balance = 900; + + let balance_to_move: u128 = 3; + + let mut env_builder = ExecutorEnv::builder(); + env_builder.write(&sender).unwrap(); + env_builder.write(&receiver).unwrap(); + env_builder.write(&balance_to_move).unwrap(); + let env = env_builder.build().unwrap(); + + let executor = default_executor(); + let result: [Account; 4] = executor.execute(env, TRANSFER_ELF).unwrap().journal.decode().unwrap(); + let [_, _, sender_post, receiver_post] = result; + + println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); + println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_private() { + run_private_execution_of_transfer_program(); + } + + #[test] + fn test_public() { + run_public_execution_of_transfer_program(); + } +} diff --git a/risc0-selective-privacy-poc/transfer_methods/Cargo.toml b/risc0-selective-privacy-poc/transfer_methods/Cargo.toml new file mode 100644 index 0000000..523dc0a --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_methods/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "transfer-methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "2.2" } + +[package.metadata.risc0] +methods = ["guest"] diff --git a/risc0-selective-privacy-poc/transfer_methods/build.rs b/risc0-selective-privacy-poc/transfer_methods/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_methods/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml new file mode 100644 index 0000000..81e568f --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "toy_example" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] } +toy-example-core = {path = "../../core" } diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs new file mode 100644 index 0000000..1addb8d --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -0,0 +1,18 @@ +use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; +use toy_example_core::Account; + +fn main() { + let sender: Account = env::read(); + let receiver: Account = env::read(); + let balance_to_move: u128 = env::read(); + + assert!(sender.balance >= balance_to_move); + + let mut sender_post = sender.clone(); + let mut receiver_post = receiver.clone(); + + sender_post.balance -= balance_to_move; + receiver_post.balance += balance_to_move; + + env::commit(&(sender, receiver, sender_post, receiver_post)); +} diff --git a/risc0-selective-privacy-poc/transfer_methods/src/lib.rs b/risc0-selective-privacy-poc/transfer_methods/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); From f6f6f395ea86662bda3315e8e024e326a32e355f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 11 Jul 2025 19:06:16 -0300 Subject: [PATCH 02/83] generalize outer method --- .../outer_methods/guest/src/bin/outer.rs | 49 ++++++++++--------- risc0-selective-privacy-poc/src/lib.rs | 9 ++-- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index d111bdd..0743fa7 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,42 +1,43 @@ use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; use toy_example_core::{Account, hash, compute_nullifier, is_in_commitment_tree}; -use transfer_methods::TRANSFER_ID; fn main() { // Read inputs - let sender_private_key: [u32; 8] = env::read(); - let sender: Account = env::read(); - let receiver: Account = env::read(); + let account_1_private_key: [u32; 8] = env::read(); + let account_1: Account = env::read(); + let account_2: Account = env::read(); let balance_to_move: u128 = env::read(); - let sender_post: Account = env::read(); - let receiver_post: Account = env::read(); + let account_1_post: Account = env::read(); + let account_2_post: Account = env::read(); let commitment_tree_root: [u32; 8] = env::read(); + let program_id: [u32; 8] = env::read(); - // Assert receiver account is fresh - assert_eq!(receiver.balance, 0); + // Assert account_2 account is fresh + assert_eq!(account_2.balance, 0); - // Prove ownership of sender account by proving + // Prove ownership of account_1 account by proving // knowledge of the pre-image of its address - assert_eq!(hash(&sender_private_key), sender.address); + assert_eq!(hash(&account_1_private_key), account_1.address); - // Compute sender account commitment and prove it belongs to commitments tree - let sender_commitment = sender.commitment(); - assert!(is_in_commitment_tree(sender_commitment, commitment_tree_root)); + // Compute account_1 account commitment and prove it belongs to commitments tree + let account_1_commitment = account_1.commitment(); + assert!(is_in_commitment_tree(account_1_commitment, commitment_tree_root)); - // Compute nullifier of sender account - let sender_nullifier = compute_nullifier(sender_commitment, sender_private_key); + // Compute nullifier of account_1 account + let account_1_nullifier = compute_nullifier(account_1_commitment, account_1_private_key); - // Compute receiver commitment - let receiver_commitment = receiver_post.commitment(); + // Compute accounts post states commitments + let account_1_post_commitment = account_1_post.commitment(); + let account_2_post_commitment = account_2_post.commitment(); // Verify pre states and post states of accounts are consistent - // with the execution of the TRANSFER_ELF program - env::verify(TRANSFER_ID, &to_vec(&(sender.clone(), receiver.clone(), sender_post.clone(), receiver_post.clone())).unwrap()).unwrap(); + // with the execution of the `program_id`` program + env::verify(program_id, &to_vec(&(account_1.clone(), account_2.clone(), account_1_post.clone(), account_2_post.clone())).unwrap()).unwrap(); - // Assert TRANSFER_ELF program didn't modify address fields - assert_eq!(sender.address, sender_post.address); - assert_eq!(receiver.address, receiver_post.address); + // Assert `program_id` program didn't modify address fields + assert_eq!(account_1.address, account_1_post.address); + assert_eq!(account_2.address, account_2_post.address); - // Output nullifier - env::commit(&(sender_nullifier, receiver_commitment)); + // Output nullifier and commitments of new private accounts + env::commit(&(account_1_nullifier, account_1_post_commitment, account_2_post_commitment)); } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index b2b85bb..27e629a 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -62,6 +62,7 @@ pub fn run_private_execution_of_transfer_program() { env_builder.write(&sender_post).unwrap(); env_builder.write(&receiver_post).unwrap(); env_builder.write(&commitment_tree_root).unwrap(); + env_builder.write(&TRANSFER_ID).unwrap(); let env = env_builder.build().unwrap(); let prover = default_prover(); @@ -71,11 +72,13 @@ pub fn run_private_execution_of_transfer_program() { let receipt = prove_info.receipt; + // Sanity check receipt.verify(OUTER_ID).unwrap(); - let (nullifier, commitment): ([u32; 8], [u32; 8]) = receipt.journal.decode().unwrap(); - println!("nullifier: {:?}", nullifier); - println!("commitment: {:?}", commitment); + let output: [[u32; 8]; 3] = receipt.journal.decode().unwrap(); + println!("nullifier: {:?}", output[0]); + println!("commitment_1: {:?}", output[1]); + println!("commitment_2: {:?}", output[2]); } pub fn run_public_execution_of_transfer_program() { From be7da3410bfbd38dbdfca704aa4c59742f1f5f66 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 11 Jul 2025 19:21:06 -0300 Subject: [PATCH 03/83] refactor --- risc0-selective-privacy-poc/core/src/lib.rs | 2 +- .../outer_methods/guest/src/bin/outer.rs | 16 ++- risc0-selective-privacy-poc/src/lib.rs | 124 +----------------- .../src/private_execution.rs | 88 +++++++++++++ .../src/public_execution.rs | 38 ++++++ .../guest/src/bin/transfer.rs | 6 +- 6 files changed, 149 insertions(+), 125 deletions(-) create mode 100644 risc0-selective-privacy-poc/src/private_execution.rs create mode 100644 risc0-selective-privacy-poc/src/public_execution.rs diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index fc3da32..dcbe45d 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -12,7 +12,7 @@ pub struct Account { impl Account { /// Creates a new account with address = hash(private_key) and balance = 0 - pub fn new(private_key: [u32; 8], nonce: [u32; 8]) -> Self { + pub fn new_from_private_key(private_key: [u32; 8], nonce: [u32; 8]) -> Self { let address = hash(&private_key); Self { address, balance: 0, nonce } } diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index 0743fa7..85a4865 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,6 +1,20 @@ use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; use toy_example_core::{Account, hash, compute_nullifier, is_in_commitment_tree}; +/// Private execution logic. +/// Circuit for proving correct execution of some program with program id +/// equal to `program_id` (last input). +/// +/// Currently only supports private execution of a program with two inputs, one +/// of which must be a fresh new account (for example a private transfer function) +/// +/// This circuit checks: +/// - That accounts pre states and post states are consistent with the execution of the given `program_id`. +/// - That `program_id` execution didn't change addresses of the accounts. +/// +/// Outputs: +/// - The nullifier for the only existing input account (account_1) +/// - The commitments for the private accounts post states. fn main() { // Read inputs let account_1_private_key: [u32; 8] = env::read(); @@ -21,7 +35,7 @@ fn main() { // Compute account_1 account commitment and prove it belongs to commitments tree let account_1_commitment = account_1.commitment(); - assert!(is_in_commitment_tree(account_1_commitment, commitment_tree_root)); + assert!(is_in_commitment_tree(account_1_commitment, commitment_tree_root)); // <- Dummy implementation // Compute nullifier of account_1 account let account_1_nullifier = compute_nullifier(account_1_commitment, account_1_private_key); diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 27e629a..334d0c1 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,122 +1,2 @@ -use std::u128; - -// 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 transfer_methods::{ - TRANSFER_ELF, TRANSFER_ID -}; -use outer_methods::{ - OUTER_ELF, OUTER_ID -}; -use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, Receipt}; -use toy_example_core::Account; - -fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Receipt, Account, Account) { - let mut env_builder = ExecutorEnv::builder(); - env_builder.write(&sender).unwrap(); - env_builder.write(&receiver).unwrap(); - env_builder.write(&balance_to_move).unwrap(); - let env = env_builder.build().unwrap(); - - let prover = default_prover(); - let prove_info = prover - .prove(env, TRANSFER_ELF) - .unwrap(); - - let receipt = prove_info.receipt; - - let output: [Account; 4] = receipt.journal.decode().unwrap(); - let [_, _, sender_post, receiver_post] = output; - - println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); - println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); - - // Sanity check - receipt - .verify(TRANSFER_ID) - .unwrap(); - - (receipt, sender_post, receiver_post) -} - -pub fn run_private_execution_of_transfer_program() { - let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; - let sender_private_key = [0; 8]; - let mut sender = Account::new(sender_private_key, [1; 8]); - sender.balance = 150; - - let receiver_private_key = [99; 8]; - let receiver = Account::new(receiver_private_key, [1; 8]); - let balance_to_move: u128 = 3; - - // Prove inner - let (inner_receipt, sender_post, receiver_post) = prove_inner(sender.clone(), receiver.clone(), balance_to_move); - - // Prover outer - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); - env_builder.write(&sender_private_key).unwrap(); - env_builder.write(&sender).unwrap(); - env_builder.write(&receiver) .unwrap(); - env_builder.write(&balance_to_move).unwrap(); - env_builder.write(&sender_post).unwrap(); - env_builder.write(&receiver_post).unwrap(); - env_builder.write(&commitment_tree_root).unwrap(); - env_builder.write(&TRANSFER_ID).unwrap(); - let env = env_builder.build().unwrap(); - - let prover = default_prover(); - let prove_info = prover - .prove(env, OUTER_ELF) - .unwrap(); - - let receipt = prove_info.receipt; - - // Sanity check - receipt.verify(OUTER_ID).unwrap(); - - let output: [[u32; 8]; 3] = receipt.journal.decode().unwrap(); - println!("nullifier: {:?}", output[0]); - println!("commitment_1: {:?}", output[1]); - println!("commitment_2: {:?}", output[2]); -} - -pub fn run_public_execution_of_transfer_program() { - let sender_private_key = [0; 8]; - let mut sender = Account::new(sender_private_key, [1; 8]); - sender.balance = 150; - - let receiver_private_key = [99; 8]; - let mut receiver = Account::new(receiver_private_key, [1; 8]); - receiver.balance = 900; - - let balance_to_move: u128 = 3; - - let mut env_builder = ExecutorEnv::builder(); - env_builder.write(&sender).unwrap(); - env_builder.write(&receiver).unwrap(); - env_builder.write(&balance_to_move).unwrap(); - let env = env_builder.build().unwrap(); - - let executor = default_executor(); - let result: [Account; 4] = executor.execute(env, TRANSFER_ELF).unwrap().journal.decode().unwrap(); - let [_, _, sender_post, receiver_post] = result; - - println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); - println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_private() { - run_private_execution_of_transfer_program(); - } - - #[test] - fn test_public() { - run_public_execution_of_transfer_program(); - } -} +mod private_execution; +mod public_execution; diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs new file mode 100644 index 0000000..8030637 --- /dev/null +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -0,0 +1,88 @@ +use transfer_methods::{ + TRANSFER_ELF, TRANSFER_ID +}; +use outer_methods::{ + OUTER_ELF, OUTER_ID +}; +use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; +use toy_example_core::Account; + +fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Receipt, Account, Account) { + let mut env_builder = ExecutorEnv::builder(); + env_builder.write(&sender).unwrap(); + env_builder.write(&receiver).unwrap(); + env_builder.write(&balance_to_move).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover + .prove(env, TRANSFER_ELF) + .unwrap(); + + let receipt = prove_info.receipt; + + let output: [Account; 4] = receipt.journal.decode().unwrap(); + let [_, _, sender_post, receiver_post] = output; + + println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); + println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); + + // Sanity check + receipt + .verify(TRANSFER_ID) + .unwrap(); + + (receipt, sender_post, receiver_post) +} + +fn run_private_execution_of_transfer_program() { + let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; + let sender_private_key = [0; 8]; + let mut sender = Account::new_from_private_key(sender_private_key, [1; 8]); + sender.balance = 150; + + let receiver_private_key = [99; 8]; + let receiver = Account::new_from_private_key(receiver_private_key, [1; 8]); + let balance_to_move: u128 = 3; + + // Prove inner + let (inner_receipt, sender_post, receiver_post) = prove_inner(sender.clone(), receiver.clone(), balance_to_move); + + // Prover outer + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&sender_private_key).unwrap(); + env_builder.write(&sender).unwrap(); + env_builder.write(&receiver) .unwrap(); + env_builder.write(&balance_to_move).unwrap(); + env_builder.write(&sender_post).unwrap(); + env_builder.write(&receiver_post).unwrap(); + env_builder.write(&commitment_tree_root).unwrap(); + env_builder.write(&TRANSFER_ID).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover + .prove(env, OUTER_ELF) + .unwrap(); + + let receipt = prove_info.receipt; + + // Sanity check + receipt.verify(OUTER_ID).unwrap(); + + let output: [[u32; 8]; 3] = receipt.journal.decode().unwrap(); + println!("nullifier: {:?}", output[0]); + println!("commitment_1: {:?}", output[1]); + println!("commitment_2: {:?}", output[2]); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_private() { + run_private_execution_of_transfer_program(); + } +} \ No newline at end of file diff --git a/risc0-selective-privacy-poc/src/public_execution.rs b/risc0-selective-privacy-poc/src/public_execution.rs new file mode 100644 index 0000000..0b8096c --- /dev/null +++ b/risc0-selective-privacy-poc/src/public_execution.rs @@ -0,0 +1,38 @@ +use risc0_zkvm::{default_executor, ExecutorEnv}; +use toy_example_core::Account; +use transfer_methods::TRANSFER_ELF; + +pub fn run_public_execution_of_transfer_program() { + let sender_private_key = [0; 8]; + let mut sender = Account::new_from_private_key(sender_private_key, [1; 8]); + sender.balance = 150; + + let receiver_private_key = [99; 8]; + let mut receiver = Account::new_from_private_key(receiver_private_key, [1; 8]); + receiver.balance = 900; + + let balance_to_move: u128 = 3; + + let mut env_builder = ExecutorEnv::builder(); + env_builder.write(&sender).unwrap(); + env_builder.write(&receiver).unwrap(); + env_builder.write(&balance_to_move).unwrap(); + let env = env_builder.build().unwrap(); + + let executor = default_executor(); + let result: [Account; 4] = executor.execute(env, TRANSFER_ELF).unwrap().journal.decode().unwrap(); + let [_, _, sender_post, receiver_post] = result; + + println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); + println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_public() { + run_public_execution_of_transfer_program(); + } +} \ No newline at end of file diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs index 1addb8d..a37ede5 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -1,16 +1,20 @@ use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; use toy_example_core::Account; + +/// A transfer of balance program. +/// To be used both in public and private contexts fn main() { let sender: Account = env::read(); let receiver: Account = env::read(); let balance_to_move: u128 = env::read(); + // Check sender has enough balance assert!(sender.balance >= balance_to_move); + // Create accounts post states, with updated balances let mut sender_post = sender.clone(); let mut receiver_post = receiver.clone(); - sender_post.balance -= balance_to_move; receiver_post.balance += balance_to_move; From 296fc2c5bad50a27f510a5ff144916a86ce4d64e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 11 Jul 2025 19:35:54 -0300 Subject: [PATCH 04/83] add comments --- risc0-selective-privacy-poc/core/src/lib.rs | 4 + .../src/private_execution.rs | 93 ++++++++++--------- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index dcbe45d..2e8901c 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -17,6 +17,10 @@ impl Account { Self { address, balance: 0, nonce } } + pub fn new(address: [u32; 8], nonce: [u32; 8]) -> Self { + Self { address, balance: 0, nonce } + } + /// Returns Hash(Account) pub fn commitment(&self) -> [u32; 8] { hash(&to_vec(&self).unwrap()) diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index 8030637..fdea687 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -7,6 +7,57 @@ use outer_methods::{ use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; use toy_example_core::Account; +/// A private execution of the transfer function. +/// This actually "burns" a sender private account and "mints" two new private accounts: +/// one for the recepient with the transfered balance, and another owned by the sender with the remaining balance. +fn run_private_execution_of_transfer_program() { + let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; + // This is supposed to be an existing private account (UTXO) with balance equal to 150. + // And it is supposed to be a private account of the user running this private execution (hence the access to the private key) + let sender_private_key = [0; 8]; + let mut sender = Account::new_from_private_key(sender_private_key, [1; 8]); + sender.balance = 150; + let balance_to_move: u128 = 3; + + // This is the new private account (UTXO) being minted by this private execution. + // (The `receiver_address` would be in UTXO's terminology) + let receiver_address = [99; 8]; + let receiver = Account::new(receiver_address, [1; 8]); + + // Prove inner program and get post state of the accounts + let (inner_receipt, sender_post, receiver_post) = prove_inner(sender.clone(), receiver.clone(), balance_to_move); + + // Prove outer program. + // This computes the nullifier for the input account + // and commitments for the accounts post states. + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&sender_private_key).unwrap(); + env_builder.write(&sender).unwrap(); + env_builder.write(&receiver) .unwrap(); + env_builder.write(&balance_to_move).unwrap(); + env_builder.write(&sender_post).unwrap(); + env_builder.write(&receiver_post).unwrap(); + env_builder.write(&commitment_tree_root).unwrap(); + env_builder.write(&TRANSFER_ID).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover + .prove(env, OUTER_ELF) + .unwrap(); + + let receipt = prove_info.receipt; + + // Sanity check + receipt.verify(OUTER_ID).unwrap(); + + let output: [[u32; 8]; 3] = receipt.journal.decode().unwrap(); + println!("nullifier: {:?}", output[0]); + println!("commitment_1: {:?}", output[1]); + println!("commitment_2: {:?}", output[2]); +} + fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Receipt, Account, Account) { let mut env_builder = ExecutorEnv::builder(); env_builder.write(&sender).unwrap(); @@ -35,48 +86,6 @@ fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Re (receipt, sender_post, receiver_post) } -fn run_private_execution_of_transfer_program() { - let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; - let sender_private_key = [0; 8]; - let mut sender = Account::new_from_private_key(sender_private_key, [1; 8]); - sender.balance = 150; - - let receiver_private_key = [99; 8]; - let receiver = Account::new_from_private_key(receiver_private_key, [1; 8]); - let balance_to_move: u128 = 3; - - // Prove inner - let (inner_receipt, sender_post, receiver_post) = prove_inner(sender.clone(), receiver.clone(), balance_to_move); - - // Prover outer - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); - env_builder.write(&sender_private_key).unwrap(); - env_builder.write(&sender).unwrap(); - env_builder.write(&receiver) .unwrap(); - env_builder.write(&balance_to_move).unwrap(); - env_builder.write(&sender_post).unwrap(); - env_builder.write(&receiver_post).unwrap(); - env_builder.write(&commitment_tree_root).unwrap(); - env_builder.write(&TRANSFER_ID).unwrap(); - let env = env_builder.build().unwrap(); - - let prover = default_prover(); - let prove_info = prover - .prove(env, OUTER_ELF) - .unwrap(); - - let receipt = prove_info.receipt; - - // Sanity check - receipt.verify(OUTER_ID).unwrap(); - - let output: [[u32; 8]; 3] = receipt.journal.decode().unwrap(); - println!("nullifier: {:?}", output[0]); - println!("commitment_1: {:?}", output[1]); - println!("commitment_2: {:?}", output[2]); -} - #[cfg(test)] mod tests { use super::*; From 2175fcd4971d35bab48b401e825062b1fb48d6b6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 11 Jul 2025 19:44:43 -0300 Subject: [PATCH 05/83] minor changes --- .../outer_methods/guest/src/bin/outer.rs | 8 +++---- .../src/private_execution.rs | 11 ++++++---- .../src/public_execution.rs | 21 +++++++++++++------ .../guest/src/bin/transfer.rs | 2 +- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index 85a4865..4b0f448 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -5,11 +5,12 @@ use toy_example_core::{Account, hash, compute_nullifier, is_in_commitment_tree}; /// Circuit for proving correct execution of some program with program id /// equal to `program_id` (last input). /// -/// Currently only supports private execution of a program with two inputs, one -/// of which must be a fresh new account (for example a private transfer function) +/// Currently only supports private execution of a program with two input accounts, one +/// of which must be a fresh new account (`account_2`) (for example a private transfer function). /// /// This circuit checks: /// - That accounts pre states and post states are consistent with the execution of the given `program_id`. +/// - That `account_2` is fresh (meaning, for this toy example, that it has 0 balance). /// - That `program_id` execution didn't change addresses of the accounts. /// /// Outputs: @@ -20,13 +21,12 @@ fn main() { let account_1_private_key: [u32; 8] = env::read(); let account_1: Account = env::read(); let account_2: Account = env::read(); - let balance_to_move: u128 = env::read(); let account_1_post: Account = env::read(); let account_2_post: Account = env::read(); let commitment_tree_root: [u32; 8] = env::read(); let program_id: [u32; 8] = env::read(); - // Assert account_2 account is fresh + // Assert account_2 is a fresh account assert_eq!(account_2.balance, 0); // Prove ownership of account_1 account by proving diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index fdea687..fc06248 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -9,14 +9,18 @@ use toy_example_core::Account; /// A private execution of the transfer function. /// This actually "burns" a sender private account and "mints" two new private accounts: -/// one for the recepient with the transfered balance, and another owned by the sender with the remaining balance. +/// one for the recipient with the transferred balance, and another owned by the sender with the remaining balance. fn run_private_execution_of_transfer_program() { let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; // This is supposed to be an existing private account (UTXO) with balance equal to 150. // And it is supposed to be a private account of the user running this private execution (hence the access to the private key) let sender_private_key = [0; 8]; - let mut sender = Account::new_from_private_key(sender_private_key, [1; 8]); - sender.balance = 150; + let sender = { + // Creating it now but it's supposed to be already created by other previous transactions. + let mut account = Account::new_from_private_key(sender_private_key, [1; 8]); + account.balance = 150; + account + }; let balance_to_move: u128 = 3; // This is the new private account (UTXO) being minted by this private execution. @@ -35,7 +39,6 @@ fn run_private_execution_of_transfer_program() { env_builder.write(&sender_private_key).unwrap(); env_builder.write(&sender).unwrap(); env_builder.write(&receiver) .unwrap(); - env_builder.write(&balance_to_move).unwrap(); env_builder.write(&sender_post).unwrap(); env_builder.write(&receiver_post).unwrap(); env_builder.write(&commitment_tree_root).unwrap(); diff --git a/risc0-selective-privacy-poc/src/public_execution.rs b/risc0-selective-privacy-poc/src/public_execution.rs index 0b8096c..366eb25 100644 --- a/risc0-selective-privacy-poc/src/public_execution.rs +++ b/risc0-selective-privacy-poc/src/public_execution.rs @@ -2,14 +2,23 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; use toy_example_core::Account; use transfer_methods::TRANSFER_ELF; +/// A public execution. +/// This would be executed by the runtime after checking that +/// the initiating transaction includes the sender's signature. pub fn run_public_execution_of_transfer_program() { - let sender_private_key = [0; 8]; - let mut sender = Account::new_from_private_key(sender_private_key, [1; 8]); - sender.balance = 150; + // Account fetched from the chain state with 150 in its balance. + let sender = { + let mut account = Account::new([5; 8], [98; 8]); + account.balance = 150; + account + }; - let receiver_private_key = [99; 8]; - let mut receiver = Account::new_from_private_key(receiver_private_key, [1; 8]); - receiver.balance = 900; + // Account fetched from the chain state with 900 in its balance. + let receiver = { + let mut account = Account::new([6; 8], [99; 8]); + account.balance = 900; + account + }; let balance_to_move: u128 = 3; diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs index a37ede5..ddd7d0d 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -3,7 +3,7 @@ use toy_example_core::Account; /// A transfer of balance program. -/// To be used both in public and private contexts +/// To be used both in public and private contexts. fn main() { let sender: Account = env::read(); let receiver: Account = env::read(); From 0431b640f0ac5335b53f7e45162dc521f5bf1634 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 14 Jul 2025 16:47:43 -0300 Subject: [PATCH 06/83] move code to account.rs --- .../core/src/account.rs | 47 ++++++++++++++++++ risc0-selective-privacy-poc/core/src/lib.rs | 49 +------------------ .../outer_methods/guest/src/bin/outer.rs | 2 +- .../src/private_execution.rs | 4 +- .../src/public_execution.rs | 4 +- .../guest/src/bin/transfer.rs | 2 +- 6 files changed, 54 insertions(+), 54 deletions(-) create mode 100644 risc0-selective-privacy-poc/core/src/account.rs diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs new file mode 100644 index 0000000..d6946de --- /dev/null +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -0,0 +1,47 @@ +#![cfg_attr(not(test), no_std)] + +use serde::{Serialize, Deserialize}; +use risc0_zkvm::{sha::{Impl, Sha256}, serde::to_vec}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct Account { + pub address: [u32; 8], + pub balance: u128, + pub nonce: [u32; 8] +} + +impl Account { + /// Creates a new account with address = hash(private_key) and balance = 0 + pub fn new_from_private_key(private_key: [u32; 8], nonce: [u32; 8]) -> Self { + let address = hash(&private_key); + Self { address, balance: 0, nonce } + } + + pub fn new(address: [u32; 8], nonce: [u32; 8]) -> Self { + Self { address, balance: 0, nonce } + } + + /// Returns Hash(Account) + pub fn commitment(&self) -> [u32; 8] { + hash(&to_vec(&self).unwrap()) + } + +} + +pub fn hash(bytes: &[u32]) -> [u32; 8] { + Impl::hash_words(&bytes).as_words().try_into().unwrap() +} + +pub fn is_in_commitment_tree(_commitment: [u32; 8], _tree_root: [u32; 8]) -> bool { + // Dummy implementation + true +} + +/// Returns Hash(Commitment || private_key) +pub fn compute_nullifier(commitment: [u32; 8], private_key: [u32; 8]) -> [u32; 8] { + let mut bytes_to_hash = [0; 16]; + bytes_to_hash[..8].copy_from_slice(&commitment); + bytes_to_hash[8..].copy_from_slice(&private_key); + hash(&bytes_to_hash) +} + diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 2e8901c..b0edc6c 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -1,48 +1 @@ -#![cfg_attr(not(test), no_std)] - -use serde::{Serialize, Deserialize}; -use risc0_zkvm::{sha::{Impl, Sha256}, serde::to_vec}; - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct Account { - pub address: [u32; 8], - pub balance: u128, - pub nonce: [u32; 8] -} - -impl Account { - /// Creates a new account with address = hash(private_key) and balance = 0 - pub fn new_from_private_key(private_key: [u32; 8], nonce: [u32; 8]) -> Self { - let address = hash(&private_key); - Self { address, balance: 0, nonce } - } - - pub fn new(address: [u32; 8], nonce: [u32; 8]) -> Self { - Self { address, balance: 0, nonce } - } - - /// Returns Hash(Account) - pub fn commitment(&self) -> [u32; 8] { - hash(&to_vec(&self).unwrap()) - } - -} - - -pub fn hash(bytes: &[u32]) -> [u32; 8] { - Impl::hash_words(&bytes).as_words().try_into().unwrap() -} - -pub fn is_in_commitment_tree(_commitment: [u32; 8], _tree_root: [u32; 8]) -> bool { - // Dummy implementation - true -} - -/// Returns Hash(Commitment || private_key) -pub fn compute_nullifier(commitment: [u32; 8], private_key: [u32; 8]) -> [u32; 8] { - let mut bytes_to_hash = [0; 16]; - bytes_to_hash[..8].copy_from_slice(&commitment); - bytes_to_hash[8..].copy_from_slice(&private_key); - hash(&bytes_to_hash) -} - +pub mod account; diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index 4b0f448..c76aa71 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,5 +1,5 @@ use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; -use toy_example_core::{Account, hash, compute_nullifier, is_in_commitment_tree}; +use toy_example_core::account::{Account, hash, compute_nullifier, is_in_commitment_tree}; /// Private execution logic. /// Circuit for proving correct execution of some program with program id diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index fc06248..656f323 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -5,7 +5,7 @@ use outer_methods::{ OUTER_ELF, OUTER_ID }; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; -use toy_example_core::Account; +use toy_example_core::account::Account; /// A private execution of the transfer function. /// This actually "burns" a sender private account and "mints" two new private accounts: @@ -97,4 +97,4 @@ mod tests { fn test_private() { run_private_execution_of_transfer_program(); } -} \ No newline at end of file +} diff --git a/risc0-selective-privacy-poc/src/public_execution.rs b/risc0-selective-privacy-poc/src/public_execution.rs index 366eb25..bc0c536 100644 --- a/risc0-selective-privacy-poc/src/public_execution.rs +++ b/risc0-selective-privacy-poc/src/public_execution.rs @@ -1,5 +1,5 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; -use toy_example_core::Account; +use toy_example_core::account::Account; use transfer_methods::TRANSFER_ELF; /// A public execution. @@ -44,4 +44,4 @@ mod tests { fn test_public() { run_public_execution_of_transfer_program(); } -} \ No newline at end of file +} diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs index ddd7d0d..2f33c0f 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -1,5 +1,5 @@ use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; -use toy_example_core::Account; +use toy_example_core::account::Account; /// A transfer of balance program. From 1f1031cca5abb1226ba9eaebe8e06d9d6b704955 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 14 Jul 2025 19:25:41 -0300 Subject: [PATCH 07/83] add variable number of inputs/outputs with visibility --- .../core/src/account.rs | 38 ++++--- risc0-selective-privacy-poc/core/src/input.rs | 12 +++ risc0-selective-privacy-poc/core/src/lib.rs | 2 + .../outer_methods/guest/src/bin/outer.rs | 99 ++++++++++++------- .../src/private_execution.rs | 84 ++++++++++------ 5 files changed, 156 insertions(+), 79 deletions(-) create mode 100644 risc0-selective-privacy-poc/core/src/input.rs diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index d6946de..9cfe075 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -1,47 +1,59 @@ -#![cfg_attr(not(test), no_std)] - -use serde::{Serialize, Deserialize}; -use risc0_zkvm::{sha::{Impl, Sha256}, serde::to_vec}; +use risc0_zkvm::{ + serde::to_vec, + sha::{Impl, Sha256}, +}; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Account { pub address: [u32; 8], pub balance: u128, - pub nonce: [u32; 8] + pub nonce: [u32; 8], } impl Account { /// Creates a new account with address = hash(private_key) and balance = 0 pub fn new_from_private_key(private_key: [u32; 8], nonce: [u32; 8]) -> Self { let address = hash(&private_key); - Self { address, balance: 0, nonce } + Self { + address, + balance: 0, + nonce, + } } pub fn new(address: [u32; 8], nonce: [u32; 8]) -> Self { - Self { address, balance: 0, nonce } + Self { + address, + balance: 0, + nonce, + } } /// Returns Hash(Account) pub fn commitment(&self) -> [u32; 8] { hash(&to_vec(&self).unwrap()) } - } pub fn hash(bytes: &[u32]) -> [u32; 8] { - Impl::hash_words(&bytes).as_words().try_into().unwrap() + Impl::hash_words(bytes).as_words().try_into().unwrap() } +/// Dummy implementation pub fn is_in_commitment_tree(_commitment: [u32; 8], _tree_root: [u32; 8]) -> bool { - // Dummy implementation true } /// Returns Hash(Commitment || private_key) -pub fn compute_nullifier(commitment: [u32; 8], private_key: [u32; 8]) -> [u32; 8] { +pub fn compute_nullifier(commitment: &[u32; 8], private_key: &[u32; 8]) -> [u32; 8] { let mut bytes_to_hash = [0; 16]; - bytes_to_hash[..8].copy_from_slice(&commitment); - bytes_to_hash[8..].copy_from_slice(&private_key); + bytes_to_hash[..8].copy_from_slice(commitment); + bytes_to_hash[8..].copy_from_slice(private_key); hash(&bytes_to_hash) } +/// Dummy implementation +pub fn new_random_nonce() -> [u32; 8] { + [0xcc, 0xaa, 0xff, 0xee, 0xcc, 0xaa, 0xff, 0xff] +} diff --git a/risc0-selective-privacy-poc/core/src/input.rs b/risc0-selective-privacy-poc/core/src/input.rs new file mode 100644 index 0000000..61809a5 --- /dev/null +++ b/risc0-selective-privacy-poc/core/src/input.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +pub type PrivateKey = [u32; 8]; + +#[derive(Serialize, Deserialize)] +pub enum InputVisibiility { + // A public account + Public, + // A private account + Private(Option), +} + diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index b0edc6c..873929a 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -1 +1,3 @@ pub mod account; +pub mod input; + diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index c76aa71..dc5a1b4 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,57 +1,88 @@ -use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; -use toy_example_core::account::{Account, hash, compute_nullifier, is_in_commitment_tree}; +use risc0_zkvm::{ + guest::env, + serde::to_vec, + sha::{Impl, Sha256}, +}; +use toy_example_core::{ + account::{compute_nullifier, hash, is_in_commitment_tree, Account}, + input::InputVisibiility, +}; /// Private execution logic. /// Circuit for proving correct execution of some program with program id /// equal to `program_id` (last input). -/// +/// /// Currently only supports private execution of a program with two input accounts, one /// of which must be a fresh new account (`account_2`) (for example a private transfer function). -/// +/// /// This circuit checks: /// - That accounts pre states and post states are consistent with the execution of the given `program_id`. /// - That `account_2` is fresh (meaning, for this toy example, that it has 0 balance). /// - That `program_id` execution didn't change addresses of the accounts. -/// +/// /// Outputs: /// - The nullifier for the only existing input account (account_1) /// - The commitments for the private accounts post states. fn main() { - // Read inputs - let account_1_private_key: [u32; 8] = env::read(); - let account_1: Account = env::read(); - let account_2: Account = env::read(); - let account_1_post: Account = env::read(); - let account_2_post: Account = env::read(); + let num_inputs: u32 = env::read(); + // Read inputs and outputs + let mut inputs_outputs = Vec::new(); + for _ in 0..(2 * num_inputs) { + let account: Account = env::read(); + inputs_outputs.push(account); + } + + // Read visibilities + let mut input_visibilities = Vec::new(); + for _ in 0..num_inputs { + let input_visibility: InputVisibiility = env::read(); + input_visibilities.push(input_visibility); + } + let commitment_tree_root: [u32; 8] = env::read(); let program_id: [u32; 8] = env::read(); - // Assert account_2 is a fresh account - assert_eq!(account_2.balance, 0); + let inputs = inputs_outputs.iter().take(num_inputs as usize); + let mut nullifiers = Vec::new(); + for (visibility, input_account) in input_visibilities.iter().zip(inputs) { + match visibility { + InputVisibiility::Private(Some(private_key)) => { + // Prove ownership of input accounts by proving + // knowledge of the pre-image of their addresses. + assert_eq!(hash(private_key), input_account.address); + // Check the input account was created by a previous transaction + // by checking it belongs to the commitments tree. + let commitment = input_account.commitment(); + assert!(is_in_commitment_tree(commitment, commitment_tree_root)); + // Compute nullifier to nullify this private input account. + let nullifier = compute_nullifier(&commitment, &private_key); + nullifiers.push(nullifier); + } + InputVisibiility::Private(None) => { + // Private accounts without a companion private key are + // enforced to have default values + assert_eq!(input_account.balance, 0); + } + // No checks on public accounts + InputVisibiility::Public => continue, + } + } + let outputs = inputs_outputs.iter().skip(num_inputs as usize); + let output_commitments: Vec<_> = outputs.map(|account| account.commitment()).collect(); - // Prove ownership of account_1 account by proving - // knowledge of the pre-image of its address - assert_eq!(hash(&account_1_private_key), account_1.address); - - // Compute account_1 account commitment and prove it belongs to commitments tree - let account_1_commitment = account_1.commitment(); - assert!(is_in_commitment_tree(account_1_commitment, commitment_tree_root)); // <- Dummy implementation - - // Compute nullifier of account_1 account - let account_1_nullifier = compute_nullifier(account_1_commitment, account_1_private_key); - - // Compute accounts post states commitments - let account_1_post_commitment = account_1_post.commitment(); - let account_2_post_commitment = account_2_post.commitment(); + // Assert `program_id` program didn't modify address fields + for (account_pre, account_post) in inputs_outputs + .iter() + .take(num_inputs as usize) + .zip(inputs_outputs.iter().skip(num_inputs as usize)) + { + assert_eq!(account_pre.address, account_post.address); + } // Verify pre states and post states of accounts are consistent // with the execution of the `program_id`` program - env::verify(program_id, &to_vec(&(account_1.clone(), account_2.clone(), account_1_post.clone(), account_2_post.clone())).unwrap()).unwrap(); + env::verify(program_id, &to_vec(&inputs_outputs).unwrap()).unwrap(); - // Assert `program_id` program didn't modify address fields - assert_eq!(account_1.address, account_1_post.address); - assert_eq!(account_2.address, account_2_post.address); - - // Output nullifier and commitments of new private accounts - env::commit(&(account_1_nullifier, account_1_post_commitment, account_2_post_commitment)); + // Output nullifier of consumed input accounts and commitments of new output private accounts + env::commit(&(nullifiers, output_commitments)); } diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index 656f323..a4b11c6 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -1,17 +1,22 @@ -use transfer_methods::{ - TRANSFER_ELF, TRANSFER_ID -}; -use outer_methods::{ - OUTER_ELF, OUTER_ID -}; +use outer_methods::{OUTER_ELF, OUTER_ID}; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; -use toy_example_core::account::Account; +use toy_example_core::{ + account::{new_random_nonce, Account}, + input::InputVisibiility, +}; +use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; + +const COMMITMENT_TREE_ROOT: [u32; 8] = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; + +fn mint_fresh_account(address: [u32; 8]) -> Account { + let nonce = new_random_nonce(); + Account::new(address, nonce) +} /// A private execution of the transfer function. /// This actually "burns" a sender private account and "mints" two new private accounts: /// one for the recipient with the transferred balance, and another owned by the sender with the remaining balance. fn run_private_execution_of_transfer_program() { - let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; // This is supposed to be an existing private account (UTXO) with balance equal to 150. // And it is supposed to be a private account of the user running this private execution (hence the access to the private key) let sender_private_key = [0; 8]; @@ -21,47 +26,60 @@ fn run_private_execution_of_transfer_program() { account.balance = 150; account }; + let balance_to_move: u128 = 3; // This is the new private account (UTXO) being minted by this private execution. // (The `receiver_address` would be in UTXO's terminology) - let receiver_address = [99; 8]; - let receiver = Account::new(receiver_address, [1; 8]); + let receiver_address = [99; 8]; + let receiver = mint_fresh_account(receiver_address); // Prove inner program and get post state of the accounts - let (inner_receipt, sender_post, receiver_post) = prove_inner(sender.clone(), receiver.clone(), balance_to_move); + let (inner_receipt, outputs) = prove_inner(&sender, &receiver, balance_to_move); + + let visibilities = vec![ + InputVisibiility::Private(Some(sender_private_key)), + InputVisibiility::Private(None), + ]; + + let inputs_outputs = { + let mut vec = vec![sender, receiver]; + vec.extend_from_slice(&outputs); + vec + }; + let num_inputs: u32 = inputs_outputs.len() as u32 / 2; // Prove outer program. // This computes the nullifier for the input account // and commitments for the accounts post states. let mut env_builder = ExecutorEnv::builder(); env_builder.add_assumption(inner_receipt); - env_builder.write(&sender_private_key).unwrap(); - env_builder.write(&sender).unwrap(); - env_builder.write(&receiver) .unwrap(); - env_builder.write(&sender_post).unwrap(); - env_builder.write(&receiver_post).unwrap(); - env_builder.write(&commitment_tree_root).unwrap(); + env_builder.write(&num_inputs).unwrap(); + env_builder.write(&inputs_outputs).unwrap(); + env_builder.write(&visibilities).unwrap(); + env_builder.write(&COMMITMENT_TREE_ROOT).unwrap(); env_builder.write(&TRANSFER_ID).unwrap(); let env = env_builder.build().unwrap(); let prover = default_prover(); - let prove_info = prover - .prove(env, OUTER_ELF) - .unwrap(); + let prove_info = prover.prove(env, OUTER_ELF).unwrap(); let receipt = prove_info.receipt; - + // Sanity check receipt.verify(OUTER_ID).unwrap(); - + let output: [[u32; 8]; 3] = receipt.journal.decode().unwrap(); println!("nullifier: {:?}", output[0]); println!("commitment_1: {:?}", output[1]); println!("commitment_2: {:?}", output[2]); } -fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Receipt, Account, Account) { +fn prove_inner( + sender: &Account, + receiver: &Account, + balance_to_move: u128, +) -> (Receipt, Vec) { let mut env_builder = ExecutorEnv::builder(); env_builder.write(&sender).unwrap(); env_builder.write(&receiver).unwrap(); @@ -69,24 +87,26 @@ fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Re let env = env_builder.build().unwrap(); let prover = default_prover(); - let prove_info = prover - .prove(env, TRANSFER_ELF) - .unwrap(); + let prove_info = prover.prove(env, TRANSFER_ELF).unwrap(); let receipt = prove_info.receipt; let output: [Account; 4] = receipt.journal.decode().unwrap(); let [_, _, sender_post, receiver_post] = output; - println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); - println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); + println!( + "sender_before: {:?}, sender_after: {:?}", + sender, sender_post + ); + println!( + "receiver_before: {:?}, receiver_after: {:?}", + receiver, receiver_post + ); // Sanity check - receipt - .verify(TRANSFER_ID) - .unwrap(); + receipt.verify(TRANSFER_ID).unwrap(); - (receipt, sender_post, receiver_post) + (receipt, vec![sender_post, receiver_post]) } #[cfg(test)] From 04def6e82b9fb1a91cae0f06f92f36980ee1aec0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 08:07:27 -0300 Subject: [PATCH 08/83] add public pre and post states to the output of the outer program --- risc0-selective-privacy-poc/Cargo.toml | 10 +- .../core/src/account.rs | 5 - .../outer_methods/guest/src/bin/outer.rs | 91 +++++++++++++------ .../src/private_execution.rs | 39 ++++---- .../src/public_execution.rs | 18 +++- .../guest/src/bin/transfer.rs | 2 +- 6 files changed, 103 insertions(+), 62 deletions(-) diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index e817b5d..a32e53f 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -5,13 +5,15 @@ edition = "2021" [dependencies] risc0-zkvm = "2.2" -toy-example-core = {path = "core"} -transfer-methods = {path = "transfer_methods"} -outer-methods = {path = "outer_methods"} +toy-example-core = { path = "core" } +transfer-methods = { path = "transfer_methods" } +outer-methods = { path = "outer_methods" } serde = "1.0" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +rand = "0.8" [features] cuda = ["risc0-zkvm/cuda"] default = [] -prove = ["risc0-zkvm/prove"] \ No newline at end of file +prove = ["risc0-zkvm/prove"] + diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index 9cfe075..c4da8dd 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -52,8 +52,3 @@ pub fn compute_nullifier(commitment: &[u32; 8], private_key: &[u32; 8]) -> [u32; bytes_to_hash[8..].copy_from_slice(private_key); hash(&bytes_to_hash) } - -/// Dummy implementation -pub fn new_random_nonce() -> [u32; 8] { - [0xcc, 0xaa, 0xff, 0xee, 0xcc, 0xaa, 0xff, 0xff] -} diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index dc5a1b4..cc93293 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,8 +1,4 @@ -use risc0_zkvm::{ - guest::env, - serde::to_vec, - sha::{Impl, Sha256}, -}; +use risc0_zkvm::{guest::env, serde::to_vec}; use toy_example_core::{ account::{compute_nullifier, hash, is_in_commitment_tree, Account}, input::InputVisibiility, @@ -26,25 +22,32 @@ use toy_example_core::{ fn main() { let num_inputs: u32 = env::read(); // Read inputs and outputs - let mut inputs_outputs = Vec::new(); - for _ in 0..(2 * num_inputs) { - let account: Account = env::read(); - inputs_outputs.push(account); - } + let mut inputs_outputs: Vec = env::read(); + assert_eq!(inputs_outputs.len() as u32, num_inputs * 2); // Read visibilities - let mut input_visibilities = Vec::new(); - for _ in 0..num_inputs { - let input_visibility: InputVisibiility = env::read(); - input_visibilities.push(input_visibility); - } + let input_visibilities: Vec = env::read(); + assert_eq!(input_visibilities.len() as u32, num_inputs); + + // Read nonces for outputs + let output_nonces: Vec<[u32; 8]> = env::read(); + assert_eq!(output_nonces.len() as u32, num_inputs); let commitment_tree_root: [u32; 8] = env::read(); let program_id: [u32; 8] = env::read(); - let inputs = inputs_outputs.iter().take(num_inputs as usize); + // Verify pre states and post states of accounts are consistent + // with the execution of the `program_id`` program + env::verify(program_id, &to_vec(&inputs_outputs).unwrap()).unwrap(); + + // Split inputs_outputs into two separate vectors + let (inputs, mut outputs) = { + let outputs = inputs_outputs.split_off(num_inputs as usize); + (inputs_outputs, outputs) + }; + let mut nullifiers = Vec::new(); - for (visibility, input_account) in input_visibilities.iter().zip(inputs) { + for (visibility, input_account) in input_visibilities.iter().zip(inputs.iter()) { match visibility { InputVisibiility::Private(Some(private_key)) => { // Prove ownership of input accounts by proving @@ -55,7 +58,7 @@ fn main() { let commitment = input_account.commitment(); assert!(is_in_commitment_tree(commitment, commitment_tree_root)); // Compute nullifier to nullify this private input account. - let nullifier = compute_nullifier(&commitment, &private_key); + let nullifier = compute_nullifier(&commitment, private_key); nullifiers.push(nullifier); } InputVisibiility::Private(None) => { @@ -67,22 +70,52 @@ fn main() { InputVisibiility::Public => continue, } } - let outputs = inputs_outputs.iter().skip(num_inputs as usize); - let output_commitments: Vec<_> = outputs.map(|account| account.commitment()).collect(); // Assert `program_id` program didn't modify address fields - for (account_pre, account_post) in inputs_outputs - .iter() - .take(num_inputs as usize) - .zip(inputs_outputs.iter().skip(num_inputs as usize)) - { + for (account_pre, account_post) in inputs.iter().zip(outputs.iter()) { assert_eq!(account_pre.address, account_post.address); } - // Verify pre states and post states of accounts are consistent - // with the execution of the `program_id`` program - env::verify(program_id, &to_vec(&inputs_outputs).unwrap()).unwrap(); + // Insert new nonces in outputs (including public ones (?!)) + outputs + .iter_mut() + .zip(output_nonces) + .for_each(|(account, new_nonce)| account.nonce = new_nonce); + + // Compute private outputs commitments + let mut private_outputs = Vec::new(); + for (output, visibility) in outputs.iter().zip(input_visibilities.iter()) { + match visibility { + InputVisibiility::Public => continue, + InputVisibiility::Private(_) => private_outputs.push(output), + } + } + + // Get the list of public inputs pre states and their post states + let mut public_inputs_outputs = Vec::new(); + for (account, visibility) in inputs + .iter() + .chain(outputs.iter()) + .zip(input_visibilities.iter().chain(input_visibilities.iter())) + { + match visibility { + InputVisibiility::Public => { + public_inputs_outputs.push(account); + } + InputVisibiility::Private(_) => continue, + } + } + + // Compute commitments for every private output + let private_output_commitments: Vec<_> = private_outputs + .iter() + .map(|account| account.commitment()) + .collect(); // Output nullifier of consumed input accounts and commitments of new output private accounts - env::commit(&(nullifiers, output_commitments)); + env::commit(&( + public_inputs_outputs, + nullifiers, + private_output_commitments, + )); } diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index a4b11c6..65e1a97 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -1,13 +1,16 @@ use outer_methods::{OUTER_ELF, OUTER_ID}; +use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; -use toy_example_core::{ - account::{new_random_nonce, Account}, - input::InputVisibiility, -}; +use toy_example_core::{account::Account, input::InputVisibiility}; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; const COMMITMENT_TREE_ROOT: [u32; 8] = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; +pub fn new_random_nonce() -> [u32; 8] { + let mut rng = OsRng; + std::array::from_fn(|_| rng.gen()) +} + fn mint_fresh_account(address: [u32; 8]) -> Account { let nonce = new_random_nonce(); Account::new(address, nonce) @@ -35,19 +38,16 @@ fn run_private_execution_of_transfer_program() { let receiver = mint_fresh_account(receiver_address); // Prove inner program and get post state of the accounts - let (inner_receipt, outputs) = prove_inner(&sender, &receiver, balance_to_move); + let (inner_receipt, inputs_outputs) = prove_inner(&sender, &receiver, balance_to_move); let visibilities = vec![ InputVisibiility::Private(Some(sender_private_key)), InputVisibiility::Private(None), ]; - let inputs_outputs = { - let mut vec = vec![sender, receiver]; - vec.extend_from_slice(&outputs); - vec - }; let num_inputs: u32 = inputs_outputs.len() as u32 / 2; + let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); + println!("output nonces {output_nonces:?}"); // Prove outer program. // This computes the nullifier for the input account @@ -57,6 +57,7 @@ fn run_private_execution_of_transfer_program() { env_builder.write(&num_inputs).unwrap(); env_builder.write(&inputs_outputs).unwrap(); env_builder.write(&visibilities).unwrap(); + env_builder.write(&output_nonces).unwrap(); env_builder.write(&COMMITMENT_TREE_ROOT).unwrap(); env_builder.write(&TRANSFER_ID).unwrap(); let env = env_builder.build().unwrap(); @@ -69,10 +70,10 @@ fn run_private_execution_of_transfer_program() { // Sanity check receipt.verify(OUTER_ID).unwrap(); - let output: [[u32; 8]; 3] = receipt.journal.decode().unwrap(); - println!("nullifier: {:?}", output[0]); - println!("commitment_1: {:?}", output[1]); - println!("commitment_2: {:?}", output[2]); + let output: (Vec, Vec<[u32; 8]>, Vec<[u32; 8]>) = receipt.journal.decode().unwrap(); + println!("public_outputs: {:?}", output.0); + println!("nullifiers: {:?}", output.1); + println!("commitments: {:?}", output.2); } fn prove_inner( @@ -91,22 +92,22 @@ fn prove_inner( let receipt = prove_info.receipt; - let output: [Account; 4] = receipt.journal.decode().unwrap(); - let [_, _, sender_post, receiver_post] = output; + let inputs_outputs: Vec = receipt.journal.decode().unwrap(); + assert_eq!(inputs_outputs.len(), 4); println!( "sender_before: {:?}, sender_after: {:?}", - sender, sender_post + inputs_outputs[0], inputs_outputs[2] ); println!( "receiver_before: {:?}, receiver_after: {:?}", - receiver, receiver_post + inputs_outputs[1], inputs_outputs[3] ); // Sanity check receipt.verify(TRANSFER_ID).unwrap(); - (receipt, vec![sender_post, receiver_post]) + (receipt, inputs_outputs) } #[cfg(test)] diff --git a/risc0-selective-privacy-poc/src/public_execution.rs b/risc0-selective-privacy-poc/src/public_execution.rs index bc0c536..6a2889c 100644 --- a/risc0-selective-privacy-poc/src/public_execution.rs +++ b/risc0-selective-privacy-poc/src/public_execution.rs @@ -29,11 +29,21 @@ pub fn run_public_execution_of_transfer_program() { let env = env_builder.build().unwrap(); let executor = default_executor(); - let result: [Account; 4] = executor.execute(env, TRANSFER_ELF).unwrap().journal.decode().unwrap(); - let [_, _, sender_post, receiver_post] = result; + let inputs_outputs: Vec = executor + .execute(env, TRANSFER_ELF) + .unwrap() + .journal + .decode() + .unwrap(); - println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post); - println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post); + println!( + "sender_before: {:?}, sender_after: {:?}", + inputs_outputs[0], inputs_outputs[2] + ); + println!( + "receiver_before: {:?}, receiver_after: {:?}", + inputs_outputs[1], inputs_outputs[3], + ); } #[cfg(test)] diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs index 2f33c0f..1fe7de8 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -18,5 +18,5 @@ fn main() { sender_post.balance -= balance_to_move; receiver_post.balance += balance_to_move; - env::commit(&(sender, receiver, sender_post, receiver_post)); + env::commit(&vec![sender, receiver, sender_post, receiver_post]); } From caa00a667fe25047820706813964a6a04f1cf759 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 09:21:55 -0300 Subject: [PATCH 09/83] make commitment a u32 value for the POC --- .../core/src/account.rs | 18 ++++++++++-------- .../src/private_execution.rs | 9 +++++++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index c4da8dd..7bf9c5a 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -4,6 +4,8 @@ use risc0_zkvm::{ }; use serde::{Deserialize, Serialize}; +pub type Commitment = u32; + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Account { pub address: [u32; 8], @@ -30,9 +32,9 @@ impl Account { } } - /// Returns Hash(Account) - pub fn commitment(&self) -> [u32; 8] { - hash(&to_vec(&self).unwrap()) + /// Returns Hash(Account)[0] (only first word for this POC) + pub fn commitment(&self) -> Commitment { + hash(&to_vec(&self).unwrap())[0] } } @@ -41,14 +43,14 @@ pub fn hash(bytes: &[u32]) -> [u32; 8] { } /// Dummy implementation -pub fn is_in_commitment_tree(_commitment: [u32; 8], _tree_root: [u32; 8]) -> bool { +pub fn is_in_commitment_tree(_commitment: Commitment, _tree_root: [u32; 8]) -> bool { true } /// Returns Hash(Commitment || private_key) -pub fn compute_nullifier(commitment: &[u32; 8], private_key: &[u32; 8]) -> [u32; 8] { - let mut bytes_to_hash = [0; 16]; - bytes_to_hash[..8].copy_from_slice(commitment); - bytes_to_hash[8..].copy_from_slice(private_key); +pub fn compute_nullifier(commitment: &Commitment, private_key: &[u32; 8]) -> [u32; 8] { + let mut bytes_to_hash = [0; 9]; // <- 1 word for the commitment, 8 words for the private key + bytes_to_hash[..1].copy_from_slice(&[*commitment]); + bytes_to_hash[1..].copy_from_slice(private_key); hash(&bytes_to_hash) } diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index 65e1a97..fae0ed6 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -1,7 +1,10 @@ use outer_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; -use toy_example_core::{account::Account, input::InputVisibiility}; +use toy_example_core::{ + account::{Account, Commitment}, + input::InputVisibiility, +}; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; const COMMITMENT_TREE_ROOT: [u32; 8] = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; @@ -46,6 +49,8 @@ fn run_private_execution_of_transfer_program() { ]; let num_inputs: u32 = inputs_outputs.len() as u32 / 2; + + // Sample fresh random nonces for the outputs of this execution let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); println!("output nonces {output_nonces:?}"); @@ -70,7 +75,7 @@ fn run_private_execution_of_transfer_program() { // Sanity check receipt.verify(OUTER_ID).unwrap(); - let output: (Vec, Vec<[u32; 8]>, Vec<[u32; 8]>) = receipt.journal.decode().unwrap(); + let output: (Vec, Vec<[u32; 8]>, Vec) = receipt.journal.decode().unwrap(); println!("public_outputs: {:?}", output.0); println!("nullifiers: {:?}", output.1); println!("commitments: {:?}", output.2); From 5c954d3d454a268f50d0fb0660755c10be0b7217 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 09:26:34 -0300 Subject: [PATCH 10/83] name types --- risc0-selective-privacy-poc/core/src/account.rs | 14 +++++++++----- risc0-selective-privacy-poc/core/src/input.rs | 6 ++---- .../outer_methods/guest/src/bin/outer.rs | 4 ++-- .../src/private_execution.rs | 10 +++++----- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index 7bf9c5a..1c0b67b 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -5,17 +5,21 @@ use risc0_zkvm::{ use serde::{Deserialize, Serialize}; pub type Commitment = u32; +pub type Nullifier = [u32; 8]; +pub type Address = [u32; 8]; +pub type Nonce = [u32; 8]; +pub type Key = [u32; 8]; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Account { - pub address: [u32; 8], + pub address: Address, pub balance: u128, - pub nonce: [u32; 8], + pub nonce: Nonce, } impl Account { /// Creates a new account with address = hash(private_key) and balance = 0 - pub fn new_from_private_key(private_key: [u32; 8], nonce: [u32; 8]) -> Self { + pub fn new_from_private_key(private_key: Address, nonce: Nonce) -> Self { let address = hash(&private_key); Self { address, @@ -24,7 +28,7 @@ impl Account { } } - pub fn new(address: [u32; 8], nonce: [u32; 8]) -> Self { + pub fn new(address: Address, nonce: Nonce) -> Self { Self { address, balance: 0, @@ -48,7 +52,7 @@ pub fn is_in_commitment_tree(_commitment: Commitment, _tree_root: [u32; 8]) -> b } /// Returns Hash(Commitment || private_key) -pub fn compute_nullifier(commitment: &Commitment, private_key: &[u32; 8]) -> [u32; 8] { +pub fn compute_nullifier(commitment: &Commitment, private_key: &Key) -> Nullifier { let mut bytes_to_hash = [0; 9]; // <- 1 word for the commitment, 8 words for the private key bytes_to_hash[..1].copy_from_slice(&[*commitment]); bytes_to_hash[1..].copy_from_slice(private_key); diff --git a/risc0-selective-privacy-poc/core/src/input.rs b/risc0-selective-privacy-poc/core/src/input.rs index 61809a5..9c303f0 100644 --- a/risc0-selective-privacy-poc/core/src/input.rs +++ b/risc0-selective-privacy-poc/core/src/input.rs @@ -1,12 +1,10 @@ +use crate::account::Key; use serde::{Deserialize, Serialize}; -pub type PrivateKey = [u32; 8]; - #[derive(Serialize, Deserialize)] pub enum InputVisibiility { // A public account Public, // A private account - Private(Option), + Private(Option), } - diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index cc93293..c64a3f6 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,6 +1,6 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use toy_example_core::{ - account::{compute_nullifier, hash, is_in_commitment_tree, Account}, + account::{compute_nullifier, hash, is_in_commitment_tree, Account, Nonce}, input::InputVisibiility, }; @@ -30,7 +30,7 @@ fn main() { assert_eq!(input_visibilities.len() as u32, num_inputs); // Read nonces for outputs - let output_nonces: Vec<[u32; 8]> = env::read(); + let output_nonces: Vec = env::read(); assert_eq!(output_nonces.len() as u32, num_inputs); let commitment_tree_root: [u32; 8] = env::read(); diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index fae0ed6..c41c1a4 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -2,19 +2,19 @@ use outer_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; use toy_example_core::{ - account::{Account, Commitment}, + account::{Account, Address, Commitment, Nonce, Nullifier}, input::InputVisibiility, }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; const COMMITMENT_TREE_ROOT: [u32; 8] = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; -pub fn new_random_nonce() -> [u32; 8] { +pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) } -fn mint_fresh_account(address: [u32; 8]) -> Account { +fn mint_fresh_account(address: Address) -> Account { let nonce = new_random_nonce(); Account::new(address, nonce) } @@ -25,7 +25,7 @@ fn mint_fresh_account(address: [u32; 8]) -> Account { fn run_private_execution_of_transfer_program() { // This is supposed to be an existing private account (UTXO) with balance equal to 150. // And it is supposed to be a private account of the user running this private execution (hence the access to the private key) - let sender_private_key = [0; 8]; + let sender_private_key = [1, 2, 3, 4, 4, 3, 2, 1]; let sender = { // Creating it now but it's supposed to be already created by other previous transactions. let mut account = Account::new_from_private_key(sender_private_key, [1; 8]); @@ -75,7 +75,7 @@ fn run_private_execution_of_transfer_program() { // Sanity check receipt.verify(OUTER_ID).unwrap(); - let output: (Vec, Vec<[u32; 8]>, Vec) = receipt.journal.decode().unwrap(); + let output: (Vec, Vec, Vec) = receipt.journal.decode().unwrap(); println!("public_outputs: {:?}", output.0); println!("nullifiers: {:?}", output.1); println!("commitments: {:?}", output.2); From fed74bd5efffe3cc0b904e5447e468ddf5ffe6b6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 12:30:23 -0300 Subject: [PATCH 11/83] add sparse merkle tree impl --- risc0-selective-privacy-poc/Cargo.toml | 1 + .../sparse_merkle_tree/Cargo.toml | 7 + .../sparse_merkle_tree/src/default_hashes.rs | 142 ++++++++++++ .../sparse_merkle_tree/src/lib.rs | 203 ++++++++++++++++++ 4 files changed, 353 insertions(+) create mode 100644 risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml create mode 100644 risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs create mode 100644 risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index a32e53f..a236a11 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -11,6 +11,7 @@ outer-methods = { path = "outer_methods" } serde = "1.0" tracing-subscriber = { version = "0.3", features = ["env-filter"] } rand = "0.8" +sparse-merkle-tree = {path="./sparse_merkle_tree/"} [features] cuda = ["risc0-zkvm/cuda"] diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml b/risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml new file mode 100644 index 0000000..5e893be --- /dev/null +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sparse-merkle-tree" +version = "0.1.0" +edition = "2021" + +[dependencies] +sha2 = "0.10.9" diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs new file mode 100644 index 0000000..a465f59 --- /dev/null +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs @@ -0,0 +1,142 @@ +// Values computed as follows +// +// fn default_hashes() -> Vec<[u8; 32]> { +// let mut defaults = vec![ZERO_HASH]; +// for i in 1..TREE_DEPTH { +// let h = hash_node(&defaults[i - 1], &defaults[i - 1]); +// defaults.push(h); +// } +// defaults.into_iter().rev().collect() +// } +// +// +pub(crate) const DEFAULT_HASHES: [[u8; 32]; 32] = [ + [ + 157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, 64, 171, + 100, 192, 44, 121, 46, 78, 53, 190, 198, 191, 82, 85, 16, + ], + [ + 244, 159, 70, 55, 214, 43, 252, 179, 167, 206, 218, 26, 170, 158, 22, 127, 12, 155, 130, + 224, 1, 98, 105, 28, 59, 57, 233, 189, 41, 50, 63, 115, + ], + [ + 204, 67, 6, 158, 244, 197, 238, 10, 26, 69, 178, 150, 78, 185, 192, 92, 5, 224, 36, 51, + 115, 21, 130, 142, 229, 223, 102, 195, 57, 198, 17, 27, + ], + [ + 49, 193, 198, 248, 157, 86, 23, 193, 111, 194, 178, 234, 70, 158, 128, 133, 165, 168, 43, + 60, 43, 15, 129, 209, 237, 51, 21, 134, 74, 209, 147, 88, + ], + [ + 219, 156, 83, 145, 70, 138, 195, 224, 161, 165, 121, 148, 215, 154, 104, 234, 219, 0, 91, + 255, 134, 28, 229, 108, 126, 225, 184, 6, 104, 156, 105, 140, + ], + [ + 196, 49, 181, 107, 231, 60, 253, 69, 245, 236, 7, 145, 32, 86, 58, 140, 47, 241, 0, 236, + 104, 61, 176, 23, 170, 95, 128, 66, 179, 134, 192, 209, + ], + [ + 155, 28, 109, 240, 82, 104, 71, 197, 186, 104, 178, 17, 138, 195, 57, 194, 194, 216, 96, + 246, 131, 233, 26, 84, 7, 124, 175, 159, 223, 60, 187, 161, + ], + [ + 183, 22, 254, 169, 215, 123, 104, 4, 156, 9, 23, 45, 110, 238, 115, 162, 108, 188, 142, + 141, 151, 185, 20, 199, 63, 150, 94, 146, 124, 30, 53, 145, + ], + [ + 122, 148, 43, 150, 236, 64, 36, 158, 18, 108, 140, 219, 34, 52, 143, 194, 69, 12, 185, 195, + 88, 206, 30, 249, 126, 255, 18, 221, 99, 72, 18, 91, + ], + [ + 51, 0, 157, 127, 41, 170, 190, 201, 194, 188, 222, 202, 115, 37, 229, 84, 111, 185, 104, + 69, 151, 66, 69, 34, 201, 161, 159, 139, 200, 11, 135, 67, + ], + [ + 186, 234, 98, 18, 205, 31, 46, 119, 118, 209, 66, 20, 180, 72, 129, 169, 242, 250, 48, 128, + 81, 175, 108, 228, 250, 226, 170, 123, 227, 21, 242, 221, + ], + [ + 27, 207, 232, 194, 77, 200, 137, 234, 233, 209, 180, 73, 180, 248, 193, 243, 50, 118, 191, + 199, 245, 30, 142, 242, 28, 234, 249, 134, 195, 154, 138, 162, + ], + [ + 199, 222, 136, 204, 114, 129, 19, 245, 177, 223, 179, 178, 201, 1, 202, 99, 26, 55, 146, + 90, 166, 193, 206, 36, 34, 171, 170, 245, 236, 35, 142, 161, + ], + [ + 121, 214, 101, 193, 197, 86, 227, 248, 59, 227, 3, 15, 20, 191, 124, 129, 209, 226, 93, + 128, 155, 137, 229, 66, 156, 221, 29, 179, 227, 120, 78, 59, + ], + [ + 118, 250, 222, 147, 174, 99, 105, 0, 241, 223, 160, 108, 11, 209, 143, 124, 59, 56, 11, + 164, 127, 2, 3, 18, 236, 149, 4, 176, 167, 196, 138, 245, + ], + [ + 204, 148, 248, 102, 164, 48, 65, 245, 219, 189, 191, 120, 157, 122, 63, 66, 228, 30, 143, + 166, 50, 157, 68, 187, 191, 110, 195, 83, 158, 2, 133, 52, + ], + [ + 179, 199, 88, 222, 194, 63, 148, 195, 88, 33, 190, 181, 102, 109, 100, 199, 212, 19, 198, + 123, 91, 167, 50, 157, 151, 242, 194, 103, 171, 143, 88, 198, + ], + [ + 209, 77, 39, 86, 1, 182, 123, 170, 109, 89, 182, 199, 89, 116, 244, 69, 49, 192, 149, 31, + 156, 226, 106, 73, 2, 112, 161, 78, 75, 153, 68, 189, + ], + [ + 158, 75, 216, 188, 35, 5, 86, 141, 82, 160, 215, 125, 16, 116, 45, 129, 224, 201, 105, 239, + 127, 37, 135, 136, 159, 255, 91, 222, 78, 64, 60, 246, + ], + [ + 121, 98, 24, 197, 183, 169, 52, 200, 156, 241, 142, 73, 241, 171, 113, 215, 133, 250, 13, + 105, 112, 253, 80, 197, 118, 105, 228, 77, 237, 254, 195, 66, + ], + [ + 188, 30, 229, 197, 205, 48, 162, 67, 206, 188, 130, 44, 72, 150, 168, 221, 170, 202, 59, + 110, 83, 205, 9, 10, 130, 11, 129, 79, 5, 218, 164, 97, + ], + [ + 182, 25, 214, 145, 150, 6, 219, 238, 38, 49, 166, 24, 255, 75, 56, 6, 31, 46, 163, 172, + 120, 213, 141, 74, 137, 21, 191, 169, 116, 50, 172, 71, + ], + [ + 52, 69, 229, 255, 230, 237, 127, 41, 223, 116, 249, 52, 228, 220, 231, 233, 38, 66, 188, + 188, 141, 176, 216, 204, 129, 209, 214, 199, 116, 203, 218, 0, + ], + [ + 34, 210, 93, 96, 203, 255, 15, 139, 0, 56, 109, 64, 224, 255, 168, 143, 235, 238, 144, 247, + 57, 237, 244, 210, 215, 160, 98, 250, 108, 101, 127, 130, + ], + [ + 57, 218, 36, 154, 181, 246, 243, 88, 152, 87, 31, 19, 81, 50, 15, 16, 66, 65, 78, 191, 194, + 47, 162, 102, 108, 254, 215, 38, 131, 209, 233, 88, + ], + [ + 155, 30, 22, 245, 84, 30, 111, 118, 197, 124, 53, 108, 138, 34, 183, 149, 93, 161, 54, 20, + 81, 52, 135, 241, 96, 199, 21, 156, 123, 208, 105, 244, + ], + [ + 173, 212, 84, 212, 76, 106, 120, 123, 235, 152, 249, 21, 121, 57, 137, 70, 8, 109, 9, 102, + 153, 66, 109, 61, 116, 176, 20, 123, 52, 240, 173, 143, + ], + [ + 148, 174, 121, 229, 202, 140, 51, 7, 46, 210, 185, 87, 169, 223, 189, 164, 252, 59, 133, + 226, 4, 99, 142, 243, 43, 14, 151, 8, 159, 60, 235, 60, + ], + [ + 175, 132, 242, 248, 185, 9, 188, 62, 34, 213, 240, 199, 176, 177, 75, 99, 187, 215, 70, + 226, 72, 67, 45, 66, 103, 218, 50, 31, 1, 52, 216, 168, + ], + [ + 248, 211, 204, 204, 180, 196, 230, 213, 226, 254, 251, 255, 140, 104, 170, 245, 141, 86, + 82, 142, 59, 109, 142, 191, 7, 180, 33, 12, 239, 230, 161, 241, + ], + [ + 178, 137, 222, 169, 44, 165, 171, 165, 242, 225, 137, 26, 26, 241, 27, 226, 121, 20, 196, + 136, 84, 219, 15, 229, 180, 187, 149, 193, 55, 224, 242, 214, + ], + [ + 110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, + 63, 179, 55, 56, 118, 133, 17, 163, 6, 23, 175, 160, 29, + ], +]; diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs new file mode 100644 index 0000000..483981f --- /dev/null +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs @@ -0,0 +1,203 @@ +use sha2::{Digest, Sha256}; +use std::collections::{HashMap, HashSet}; + +mod default_hashes; +use default_hashes::DEFAULT_HASHES; + +const TREE_DEPTH: usize = 32; +const ZERO_HASH: [u8; 32] = [ + 110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, 63, + 179, 55, 56, 118, 133, 17, 163, 6, 23, 175, 160, 29, +]; +const ONE_HASH: [u8; 32] = [ + 75, 245, 18, 47, 52, 69, 84, 197, 59, 222, 46, 187, 140, 210, 183, 227, 209, 96, 10, 214, 49, + 195, 133, 165, 215, 204, 226, 60, 119, 133, 69, 154, +]; + +/// Hash a leaf from arbitrary bytes +fn hash_leaf(data: &[u8]) -> [u8; 32] { + Sha256::digest(data).into() +} + +/// Hash two child nodes +fn hash_node(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(left); + hasher.update(right); + hasher.finalize().into() +} + +/// Sparse Merkle Tree with 2^32 leaves +pub struct SparseMerkleTree { + values: HashSet, // store leaf hashes +} + +impl SparseMerkleTree { + pub fn new(values: HashSet) -> Self { + Self { values } + } + + pub fn new_empty() -> Self { + Self { + values: HashSet::new(), + } + } + + fn node_map(&self) -> HashMap<(usize, u32), [u8; 32]> { + let mut nodes: HashMap<(usize, u32), [u8; 32]> = HashMap::new(); + + // Start from occupied leaves + for &leaf_index in &self.values { + nodes.insert((TREE_DEPTH, leaf_index), ONE_HASH); + } + + // Build tree bottom-up + for depth in (0..TREE_DEPTH).rev() { + let mut next_level = HashMap::new(); + let indices: Vec = nodes + .keys() + .filter(|(d, _)| *d == depth + 1) + .map(|(_, i)| i >> 1) // parent index + .collect(); + + for &parent_index in indices.iter() { + let left_index = parent_index << 1; + let right_index = left_index | 1; + + let left = nodes + .get(&(depth + 1, left_index)) + .unwrap_or(&DEFAULT_HASHES[depth]); + let right = nodes + .get(&(depth + 1, right_index)) + .unwrap_or(&DEFAULT_HASHES[depth]); + + if left != &DEFAULT_HASHES[depth] || right != &DEFAULT_HASHES[depth] { + let h = hash_node(left, right); + next_level.insert((depth, parent_index), h); + } + } + + nodes.extend(next_level); + } + nodes + } + + pub fn root(&self) -> [u8; 32] { + let nodes = self.node_map(); + nodes.get(&(0, 0)).cloned().unwrap_or(DEFAULT_HASHES[0]) + } + + pub fn get_authentication_path_for_index(&self, mut index: u32) -> [[u8; 32]; 32] { + let mut path = [[0u8; 32]; 32]; + + let nodes = self.node_map(); + let mut current_index = index; + + for depth in (0..32).rev() { + let sibling_index = current_index ^ 1; + + let sibling_hash = nodes + .get(&(depth + 1, sibling_index)) + .cloned() + .unwrap_or(DEFAULT_HASHES[depth]); + + path[31 - depth] = sibling_hash; + current_index >>= 1; + } + + path + } + + pub fn verify_index_set(mut index: u32, path: [[u8; 32]; 32], root: [u8; 32]) -> bool { + let mut hash = ONE_HASH; + for path_value in path.iter() { + if index & 1 == 0 { + hash = hash_node(&hash, path_value); + } else { + hash = hash_node(path_value, &hash); + } + index >>= 1; + } + root == hash + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_default_hashes() { + assert_eq!(DEFAULT_HASHES[TREE_DEPTH - 1], ZERO_HASH); + assert_eq!( + DEFAULT_HASHES[0], + [ + 157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, + 64, 171, 100, 192, 44, 121, 46, 78, 53, 190, 198, 191, 82, 85, 16 + ] + ); + } + + #[test] + fn test_empty_tree() { + let empty_tree = SparseMerkleTree::new_empty(); + assert_eq!(empty_tree.root(), DEFAULT_HASHES[0]); + } + + #[test] + fn test_tree_1() { + let values: HashSet = vec![0, 1, 2, 3].into_iter().collect(); + let tree = SparseMerkleTree::new(values); + assert_eq!( + tree.root(), + [ + 109, 94, 224, 93, 195, 77, 137, 36, 108, 105, 177, 22, 212, 17, 160, 255, 224, 61, + 191, 17, 129, 10, 26, 76, 197, 42, 230, 160, 80, 44, 101, 184 + ] + ); + } + + #[test] + fn test_tree_2() { + let values: HashSet = vec![2147483648, 2147483649, 2147483650, 2147483651] + .into_iter() + .collect(); + let tree = SparseMerkleTree::new(values); + + assert_eq!( + tree.root(), + [ + 36, 178, 159, 245, 165, 76, 242, 85, 25, 218, 149, 135, 194, 127, 130, 201, 219, + 187, 167, 216, 1, 222, 234, 197, 152, 156, 243, 174, 68, 27, 114, 8 + ] + ); + } + + #[test] + fn test_tree_3() { + let values: HashSet = vec![2147483648, 0, 1, 2147483649].into_iter().collect(); + let tree = SparseMerkleTree::new(values); + + assert_eq!( + tree.root(), + [ + 148, 76, 190, 191, 248, 243, 89, 40, 197, 157, 206, 23, 58, 197, 86, 169, 225, 217, + 110, 166, 54, 10, 245, 175, 168, 4, 145, 220, 30, 210, 67, 113 + ] + ); + } + + #[test] + fn test_auth_path_1() { + let values: HashSet = vec![0, 1, 2, 3, 1337].into_iter().collect(); + let tree = SparseMerkleTree::new(values); + let root = tree.root(); + let path = tree.get_authentication_path_for_index(0); + assert!(SparseMerkleTree::verify_index_set(0, path, root)); + let path = tree.get_authentication_path_for_index(1); + assert!(SparseMerkleTree::verify_index_set(1, path, root)); + let path = tree.get_authentication_path_for_index(1337); + assert!(SparseMerkleTree::verify_index_set(1337, path, root)); + let path = tree.get_authentication_path_for_index(1338); + assert!(!SparseMerkleTree::verify_index_set(1338, path, root)); + } +} From a11ea0d9aabcee7159d973a170528f5925c61ebf Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 12:51:39 -0300 Subject: [PATCH 12/83] add add_value --- .../sparse_merkle_tree/src/lib.rs | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs index 483981f..fd53197 100644 --- a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs @@ -14,12 +14,7 @@ const ONE_HASH: [u8; 32] = [ 195, 133, 165, 215, 204, 226, 60, 119, 133, 69, 154, ]; -/// Hash a leaf from arbitrary bytes -fn hash_leaf(data: &[u8]) -> [u8; 32] { - Sha256::digest(data).into() -} - -/// Hash two child nodes +/// Compute parent as the hash of two child nodes fn hash_node(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { let mut hasher = Sha256::new(); hasher.update(left); @@ -29,25 +24,31 @@ fn hash_node(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { /// Sparse Merkle Tree with 2^32 leaves pub struct SparseMerkleTree { - values: HashSet, // store leaf hashes + values: HashSet, + node_map: HashMap<(usize, u32), [u8; 32]>, } impl SparseMerkleTree { pub fn new(values: HashSet) -> Self { - Self { values } + let node_map = Self::node_map(&values); + Self { values, node_map } } pub fn new_empty() -> Self { - Self { - values: HashSet::new(), + Self::new(HashSet::new()) + } + + pub fn add_value(&mut self, new_value: u32) { + if self.values.insert(new_value) { + self.node_map = Self::node_map(&self.values); } } - fn node_map(&self) -> HashMap<(usize, u32), [u8; 32]> { + fn node_map(values: &HashSet) -> HashMap<(usize, u32), [u8; 32]> { let mut nodes: HashMap<(usize, u32), [u8; 32]> = HashMap::new(); // Start from occupied leaves - for &leaf_index in &self.values { + for &leaf_index in values { nodes.insert((TREE_DEPTH, leaf_index), ONE_HASH); } @@ -83,20 +84,22 @@ impl SparseMerkleTree { } pub fn root(&self) -> [u8; 32] { - let nodes = self.node_map(); - nodes.get(&(0, 0)).cloned().unwrap_or(DEFAULT_HASHES[0]) + self.node_map + .get(&(0, 0)) + .cloned() + .unwrap_or(DEFAULT_HASHES[0]) } - pub fn get_authentication_path_for_index(&self, mut index: u32) -> [[u8; 32]; 32] { + pub fn get_authentication_path_for_value(&self, value: u32) -> [[u8; 32]; 32] { let mut path = [[0u8; 32]; 32]; - let nodes = self.node_map(); - let mut current_index = index; + let mut current_index = value; for depth in (0..32).rev() { let sibling_index = current_index ^ 1; - let sibling_hash = nodes + let sibling_hash = self + .node_map .get(&(depth + 1, sibling_index)) .cloned() .unwrap_or(DEFAULT_HASHES[depth]); @@ -108,15 +111,16 @@ impl SparseMerkleTree { path } - pub fn verify_index_set(mut index: u32, path: [[u8; 32]; 32], root: [u8; 32]) -> bool { + pub fn verify_value_is_in_set(value: u32, path: [[u8; 32]; 32], root: [u8; 32]) -> bool { let mut hash = ONE_HASH; + let mut current_index = value; for path_value in path.iter() { - if index & 1 == 0 { + if current_index & 1 == 0 { hash = hash_node(&hash, path_value); } else { hash = hash_node(path_value, &hash); } - index >>= 1; + current_index >>= 1; } root == hash } @@ -187,17 +191,22 @@ mod tests { } #[test] - fn test_auth_path_1() { + fn test_auth_path() { let values: HashSet = vec![0, 1, 2, 3, 1337].into_iter().collect(); - let tree = SparseMerkleTree::new(values); + let mut tree = SparseMerkleTree::new(values); let root = tree.root(); - let path = tree.get_authentication_path_for_index(0); - assert!(SparseMerkleTree::verify_index_set(0, path, root)); - let path = tree.get_authentication_path_for_index(1); - assert!(SparseMerkleTree::verify_index_set(1, path, root)); - let path = tree.get_authentication_path_for_index(1337); - assert!(SparseMerkleTree::verify_index_set(1337, path, root)); - let path = tree.get_authentication_path_for_index(1338); - assert!(!SparseMerkleTree::verify_index_set(1338, path, root)); + let path = tree.get_authentication_path_for_value(0); + assert!(SparseMerkleTree::verify_value_is_in_set(0, path, root)); + let path = tree.get_authentication_path_for_value(1); + assert!(SparseMerkleTree::verify_value_is_in_set(1, path, root)); + let path = tree.get_authentication_path_for_value(1337); + assert!(SparseMerkleTree::verify_value_is_in_set(1337, path, root)); + let path = tree.get_authentication_path_for_value(1338); + assert!(!SparseMerkleTree::verify_value_is_in_set(1338, path, root)); + + tree.add_value(1338); + let path = tree.get_authentication_path_for_value(1338); + let root = tree.root(); + assert!(SparseMerkleTree::verify_value_is_in_set(1338, path, root)); } } From 7870d7ab4c6224fe60fe9c954b20935e55c98c9e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 13:08:13 -0300 Subject: [PATCH 13/83] dummy --- risc0-selective-privacy-poc/core/src/account.rs | 15 ++++++++++++++- risc0-selective-privacy-poc/core/src/input.rs | 4 ++-- risc0-selective-privacy-poc/core/src/lib.rs | 2 ++ .../outer_methods/guest/src/bin/outer.rs | 6 +++--- .../src/private_execution.rs | 12 +++++++----- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index 1c0b67b..f76e82c 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -9,6 +9,7 @@ pub type Nullifier = [u32; 8]; pub type Address = [u32; 8]; pub type Nonce = [u32; 8]; pub type Key = [u32; 8]; +pub type AuthenticationPath = [[u32; 8]; 32]; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Account { @@ -47,7 +48,11 @@ pub fn hash(bytes: &[u32]) -> [u32; 8] { } /// Dummy implementation -pub fn is_in_commitment_tree(_commitment: Commitment, _tree_root: [u32; 8]) -> bool { +pub fn is_in_tree( + _commitment: Commitment, + _auth_path: &AuthenticationPath, + _tree_root: [u32; 8], +) -> bool { true } @@ -58,3 +63,11 @@ pub fn compute_nullifier(commitment: &Commitment, private_key: &Key) -> Nullifie bytes_to_hash[1..].copy_from_slice(private_key); hash(&bytes_to_hash) } + +pub fn bytes_to_words(bytes: [u8; 32]) -> [u32; 8] { + let mut words = [0; 8]; + for (i, chunk) in bytes.chunks(4).enumerate() { + words[i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + words +} diff --git a/risc0-selective-privacy-poc/core/src/input.rs b/risc0-selective-privacy-poc/core/src/input.rs index 9c303f0..fc7930f 100644 --- a/risc0-selective-privacy-poc/core/src/input.rs +++ b/risc0-selective-privacy-poc/core/src/input.rs @@ -1,4 +1,4 @@ -use crate::account::Key; +use crate::account::{AuthenticationPath, Key}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -6,5 +6,5 @@ pub enum InputVisibiility { // A public account Public, // A private account - Private(Option), + Private(Option<(Key, AuthenticationPath)>), } diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 873929a..2b84523 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -1,3 +1,5 @@ pub mod account; pub mod input; + + diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index c64a3f6..bcfa30e 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,6 +1,6 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use toy_example_core::{ - account::{compute_nullifier, hash, is_in_commitment_tree, Account, Nonce}, + account::{compute_nullifier, hash, is_in_tree, Account, Nonce}, input::InputVisibiility, }; @@ -49,14 +49,14 @@ fn main() { let mut nullifiers = Vec::new(); for (visibility, input_account) in input_visibilities.iter().zip(inputs.iter()) { match visibility { - InputVisibiility::Private(Some(private_key)) => { + InputVisibiility::Private(Some((private_key, auth_path))) => { // Prove ownership of input accounts by proving // knowledge of the pre-image of their addresses. assert_eq!(hash(private_key), input_account.address); // Check the input account was created by a previous transaction // by checking it belongs to the commitments tree. let commitment = input_account.commitment(); - assert!(is_in_commitment_tree(commitment, commitment_tree_root)); + assert!(is_in_tree(commitment, auth_path, commitment_tree_root)); // Compute nullifier to nullify this private input account. let nullifier = compute_nullifier(&commitment, private_key); nullifiers.push(nullifier); diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index c41c1a4..2d476f2 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -1,14 +1,13 @@ use outer_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; +use sparse_merkle_tree::SparseMerkleTree; use toy_example_core::{ - account::{Account, Address, Commitment, Nonce, Nullifier}, + account::{bytes_to_words, Account, Address, Commitment, Nonce, Nullifier}, input::InputVisibiility, }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -const COMMITMENT_TREE_ROOT: [u32; 8] = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff]; - pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) @@ -32,6 +31,9 @@ fn run_private_execution_of_transfer_program() { account.balance = 150; account }; + let commitment_tree = SparseMerkleTree::new([sender.commitment()].into_iter().collect()); + let root = bytes_to_words(commitment_tree.root()); + let auth_path = commitment_tree.get_authentication_path_for_value(sender.commitment()); let balance_to_move: u128 = 3; @@ -44,7 +46,7 @@ fn run_private_execution_of_transfer_program() { let (inner_receipt, inputs_outputs) = prove_inner(&sender, &receiver, balance_to_move); let visibilities = vec![ - InputVisibiility::Private(Some(sender_private_key)), + InputVisibiility::Private(Some((sender_private_key, [[0; 8]; 32]))), InputVisibiility::Private(None), ]; @@ -63,7 +65,7 @@ fn run_private_execution_of_transfer_program() { env_builder.write(&inputs_outputs).unwrap(); env_builder.write(&visibilities).unwrap(); env_builder.write(&output_nonces).unwrap(); - env_builder.write(&COMMITMENT_TREE_ROOT).unwrap(); + env_builder.write(&root).unwrap(); env_builder.write(&TRANSFER_ID).unwrap(); let env = env_builder.build().unwrap(); From 0b908c842a01ba2fb5723b6fdd0ac9c13b74a317 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 13:54:59 -0300 Subject: [PATCH 14/83] add implementation of is_in_tree --- .../core/src/account.rs | 33 ++++++++++++++----- .../src/private_execution.rs | 14 +++++--- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index f76e82c..1083ca6 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -47,13 +47,23 @@ pub fn hash(bytes: &[u32]) -> [u32; 8] { Impl::hash_words(bytes).as_words().try_into().unwrap() } -/// Dummy implementation -pub fn is_in_tree( - _commitment: Commitment, - _auth_path: &AuthenticationPath, - _tree_root: [u32; 8], -) -> bool { - true +pub fn is_in_tree(commitment: Commitment, path: &AuthenticationPath, root: [u32; 8]) -> bool { + const HASH_ONE: [u32; 8] = [ + 789771595, 3310634292, 3140410939, 3820475020, 3591004369, 2777006897, 1021496535, + 2588247415, + ]; + + let mut hash = HASH_ONE; + let mut current_index = commitment; + for path_value in path.iter() { + if current_index & 1 == 0 { + hash = hash_two(&hash, path_value); + } else { + hash = hash_two(path_value, &hash); + } + current_index >>= 1; + } + root == hash } /// Returns Hash(Commitment || private_key) @@ -64,7 +74,14 @@ pub fn compute_nullifier(commitment: &Commitment, private_key: &Key) -> Nullifie hash(&bytes_to_hash) } -pub fn bytes_to_words(bytes: [u8; 32]) -> [u32; 8] { +fn hash_two(left: &[u32; 8], right: &[u32; 8]) -> [u32; 8] { + let mut bytes_to_hash = [0; 16]; + bytes_to_hash[..8].copy_from_slice(left); + bytes_to_hash[8..].copy_from_slice(right); + hash(&bytes_to_hash) +} + +pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { let mut words = [0; 8]; for (i, chunk) in bytes.chunks(4).enumerate() { words[i] = u32::from_le_bytes(chunk.try_into().unwrap()); diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index 2d476f2..60a52c1 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -3,7 +3,7 @@ use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; use sparse_merkle_tree::SparseMerkleTree; use toy_example_core::{ - account::{bytes_to_words, Account, Address, Commitment, Nonce, Nullifier}, + account::{bytes_to_words, Account, Address, AuthenticationPath, Commitment, Nonce, Nullifier}, input::InputVisibiility, }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; @@ -31,9 +31,15 @@ fn run_private_execution_of_transfer_program() { account.balance = 150; account }; + let commitment_tree = SparseMerkleTree::new([sender.commitment()].into_iter().collect()); - let root = bytes_to_words(commitment_tree.root()); - let auth_path = commitment_tree.get_authentication_path_for_value(sender.commitment()); + let root = bytes_to_words(&commitment_tree.root()); + let auth_path: Vec<[u32; 8]> = commitment_tree + .get_authentication_path_for_value(sender.commitment()) + .iter() + .map(bytes_to_words) + .collect(); + let auth_path: AuthenticationPath = auth_path.try_into().unwrap(); let balance_to_move: u128 = 3; @@ -46,7 +52,7 @@ fn run_private_execution_of_transfer_program() { let (inner_receipt, inputs_outputs) = prove_inner(&sender, &receiver, balance_to_move); let visibilities = vec![ - InputVisibiility::Private(Some((sender_private_key, [[0; 8]; 32]))), + InputVisibiility::Private(Some((sender_private_key, auth_path))), InputVisibiility::Private(None), ]; From c322f937757a226bc024c1905b6ae7dde60b17bc Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 15:37:49 -0300 Subject: [PATCH 15/83] refactor --- .../core/src/account.rs | 66 ++----------------- risc0-selective-privacy-poc/core/src/input.rs | 2 +- risc0-selective-privacy-poc/core/src/lib.rs | 48 ++++++++++++++ .../outer_methods/guest/src/bin/outer.rs | 3 +- .../sparse_merkle_tree/src/lib.rs | 6 +- .../src/private_execution.rs | 4 +- 6 files changed, 61 insertions(+), 68 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index 1083ca6..85f6251 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -1,16 +1,10 @@ -use risc0_zkvm::{ - serde::to_vec, - sha::{Impl, Sha256}, +use crate::{ + hash, + types::{Address, Commitment, Nonce}, }; +use risc0_zkvm::{serde::to_vec, sha::Impl}; use serde::{Deserialize, Serialize}; -pub type Commitment = u32; -pub type Nullifier = [u32; 8]; -pub type Address = [u32; 8]; -pub type Nonce = [u32; 8]; -pub type Key = [u32; 8]; -pub type AuthenticationPath = [[u32; 8]; 32]; - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Account { pub address: Address, @@ -22,11 +16,7 @@ impl Account { /// Creates a new account with address = hash(private_key) and balance = 0 pub fn new_from_private_key(private_key: Address, nonce: Nonce) -> Self { let address = hash(&private_key); - Self { - address, - balance: 0, - nonce, - } + Self::new(address, nonce) } pub fn new(address: Address, nonce: Nonce) -> Self { @@ -42,49 +32,3 @@ impl Account { hash(&to_vec(&self).unwrap())[0] } } - -pub fn hash(bytes: &[u32]) -> [u32; 8] { - Impl::hash_words(bytes).as_words().try_into().unwrap() -} - -pub fn is_in_tree(commitment: Commitment, path: &AuthenticationPath, root: [u32; 8]) -> bool { - const HASH_ONE: [u32; 8] = [ - 789771595, 3310634292, 3140410939, 3820475020, 3591004369, 2777006897, 1021496535, - 2588247415, - ]; - - let mut hash = HASH_ONE; - let mut current_index = commitment; - for path_value in path.iter() { - if current_index & 1 == 0 { - hash = hash_two(&hash, path_value); - } else { - hash = hash_two(path_value, &hash); - } - current_index >>= 1; - } - root == hash -} - -/// Returns Hash(Commitment || private_key) -pub fn compute_nullifier(commitment: &Commitment, private_key: &Key) -> Nullifier { - let mut bytes_to_hash = [0; 9]; // <- 1 word for the commitment, 8 words for the private key - bytes_to_hash[..1].copy_from_slice(&[*commitment]); - bytes_to_hash[1..].copy_from_slice(private_key); - hash(&bytes_to_hash) -} - -fn hash_two(left: &[u32; 8], right: &[u32; 8]) -> [u32; 8] { - let mut bytes_to_hash = [0; 16]; - bytes_to_hash[..8].copy_from_slice(left); - bytes_to_hash[8..].copy_from_slice(right); - hash(&bytes_to_hash) -} - -pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { - let mut words = [0; 8]; - for (i, chunk) in bytes.chunks(4).enumerate() { - words[i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - words -} diff --git a/risc0-selective-privacy-poc/core/src/input.rs b/risc0-selective-privacy-poc/core/src/input.rs index fc7930f..944164a 100644 --- a/risc0-selective-privacy-poc/core/src/input.rs +++ b/risc0-selective-privacy-poc/core/src/input.rs @@ -1,5 +1,5 @@ -use crate::account::{AuthenticationPath, Key}; use serde::{Deserialize, Serialize}; +use crate::types::{AuthenticationPath, Key}; #[derive(Serialize, Deserialize)] pub enum InputVisibiility { diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 2b84523..87d255f 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -1,5 +1,53 @@ pub mod account; pub mod input; +pub mod types; +use crate::types::{AuthenticationPath, Commitment, Key, Nullifier}; +use risc0_zkvm::sha::{Impl, Sha256}; +use serde::{Deserialize, Serialize}; +pub fn hash(bytes: &[u32]) -> [u32; 8] { + Impl::hash_words(bytes).as_words().try_into().unwrap() +} +pub fn is_in_tree(commitment: Commitment, path: &AuthenticationPath, root: [u32; 8]) -> bool { + const HASH_ONE: [u32; 8] = [ + 789771595, 3310634292, 3140410939, 3820475020, 3591004369, 2777006897, 1021496535, + 2588247415, + ]; + + let mut hash = HASH_ONE; + let mut current_index = commitment; + for path_value in path.iter() { + if current_index & 1 == 0 { + hash = hash_two(&hash, path_value); + } else { + hash = hash_two(path_value, &hash); + } + current_index >>= 1; + } + root == hash +} + +/// Returns Hash(Commitment || private_key) +pub fn compute_nullifier(commitment: &Commitment, private_key: &Key) -> Nullifier { + let mut bytes_to_hash = [0; 9]; // <- 1 word for the commitment, 8 words for the private key + bytes_to_hash[..1].copy_from_slice(&[*commitment]); + bytes_to_hash[1..].copy_from_slice(private_key); + hash(&bytes_to_hash) +} + +fn hash_two(left: &[u32; 8], right: &[u32; 8]) -> [u32; 8] { + let mut bytes_to_hash = [0; 16]; + bytes_to_hash[..8].copy_from_slice(left); + bytes_to_hash[8..].copy_from_slice(right); + hash(&bytes_to_hash) +} + +pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { + let mut words = [0; 8]; + for (i, chunk) in bytes.chunks(4).enumerate() { + words[i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + words +} diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index bcfa30e..186b357 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,7 +1,6 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use toy_example_core::{ - account::{compute_nullifier, hash, is_in_tree, Account, Nonce}, - input::InputVisibiility, + account::Account, compute_nullifier, hash, input::InputVisibiility, is_in_tree, types::Nonce, }; /// Private execution logic. diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs index fd53197..8d09d9c 100644 --- a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs @@ -1,9 +1,9 @@ +mod default_hashes; + +use default_hashes::DEFAULT_HASHES; use sha2::{Digest, Sha256}; use std::collections::{HashMap, HashSet}; -mod default_hashes; -use default_hashes::DEFAULT_HASHES; - const TREE_DEPTH: usize = 32; const ZERO_HASH: [u8; 32] = [ 110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, 63, diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index 60a52c1..1390d7b 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -3,8 +3,10 @@ use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; use sparse_merkle_tree::SparseMerkleTree; use toy_example_core::{ - account::{bytes_to_words, Account, Address, AuthenticationPath, Commitment, Nonce, Nullifier}, + account::Account, + bytes_to_words, input::InputVisibiility, + types::{Address, AuthenticationPath, Commitment, Nonce, Nullifier}, }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; From b6bec23ac2138ec0de8ffd7f4aa54694a0705b2f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 16:45:07 -0300 Subject: [PATCH 16/83] add program trait --- risc0-selective-privacy-poc/core/src/types.rs | 6 ++ .../outer_methods/guest/src/bin/outer.rs | 13 ++-- risc0-selective-privacy-poc/src/lib.rs | 15 +++++ .../src/private_execution.rs | 48 +++------------ risc0-selective-privacy-poc/src/program.rs | 60 +++++++++++++++++++ .../src/public_execution.rs | 16 +---- 6 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 risc0-selective-privacy-poc/core/src/types.rs create mode 100644 risc0-selective-privacy-poc/src/program.rs diff --git a/risc0-selective-privacy-poc/core/src/types.rs b/risc0-selective-privacy-poc/core/src/types.rs new file mode 100644 index 0000000..a9608dc --- /dev/null +++ b/risc0-selective-privacy-poc/core/src/types.rs @@ -0,0 +1,6 @@ +pub type Commitment = u32; +pub type Nullifier = [u32; 8]; +pub type Address = [u32; 8]; +pub type Nonce = [u32; 8]; +pub type Key = [u32; 8]; +pub type AuthenticationPath = [[u32; 8]; 32]; diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index 186b357..a8ba142 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -49,11 +49,9 @@ fn main() { for (visibility, input_account) in input_visibilities.iter().zip(inputs.iter()) { match visibility { InputVisibiility::Private(Some((private_key, auth_path))) => { - // Prove ownership of input accounts by proving - // knowledge of the pre-image of their addresses. + // Prove ownership of input accounts by proving knowledge of the pre-image of their addresses. assert_eq!(hash(private_key), input_account.address); - // Check the input account was created by a previous transaction - // by checking it belongs to the commitments tree. + // Check the input account was created by a previous transaction by checking it belongs to the commitments tree. let commitment = input_account.commitment(); assert!(is_in_tree(commitment, auth_path, commitment_tree_root)); // Compute nullifier to nullify this private input account. @@ -61,18 +59,19 @@ fn main() { nullifiers.push(nullifier); } InputVisibiility::Private(None) => { - // Private accounts without a companion private key are - // enforced to have default values + // Private accounts without a companion private key are enforced to have default values assert_eq!(input_account.balance, 0); + assert_eq!(input_account.nonce, [0; 8]); } // No checks on public accounts InputVisibiility::Public => continue, } } - // Assert `program_id` program didn't modify address fields + // Assert `program_id` program didn't modify address fields or nonces for (account_pre, account_post) in inputs.iter().zip(outputs.iter()) { assert_eq!(account_pre.address, account_post.address); + assert_eq!(account_pre.nonce, account_post.nonce); } // Insert new nonces in outputs (including public ones (?!)) diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 334d0c1..ce39e62 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,2 +1,17 @@ mod private_execution; +mod program; mod public_execution; + +use program::Program; +use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; + +struct TransferProgram; + +impl Program for TransferProgram { + const PROGRAM_ID: [u32; 8] = TRANSFER_ID; + + const PROGRAM_ELF: &[u8] = TRANSFER_ELF; + + // Amount to transfer + type InstructionData = u128; +} diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index 1390d7b..f536727 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -10,13 +10,16 @@ use toy_example_core::{ }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; +use crate::program::Program; +use crate::TransferProgram; + pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) } fn mint_fresh_account(address: Address) -> Account { - let nonce = new_random_nonce(); + let nonce = [0; 8]; Account::new(address, nonce) } @@ -45,13 +48,13 @@ fn run_private_execution_of_transfer_program() { let balance_to_move: u128 = 3; - // This is the new private account (UTXO) being minted by this private execution. - // (The `receiver_address` would be in UTXO's terminology) + // This is the new private account (UTXO) being minted by this private execution. (The `receiver_address` would be in UTXO's terminology) let receiver_address = [99; 8]; let receiver = mint_fresh_account(receiver_address); // Prove inner program and get post state of the accounts - let (inner_receipt, inputs_outputs) = prove_inner(&sender, &receiver, balance_to_move); + let (inner_receipt, inputs_outputs) = + TransferProgram::execute_and_prove(&[sender, receiver], &balance_to_move).unwrap(); let visibilities = vec![ InputVisibiility::Private(Some((sender_private_key, auth_path))), @@ -65,8 +68,7 @@ fn run_private_execution_of_transfer_program() { println!("output nonces {output_nonces:?}"); // Prove outer program. - // This computes the nullifier for the input account - // and commitments for the accounts post states. + // This computes the nullifier for the input account and commitments for the accounts post states. let mut env_builder = ExecutorEnv::builder(); env_builder.add_assumption(inner_receipt); env_builder.write(&num_inputs).unwrap(); @@ -91,40 +93,6 @@ fn run_private_execution_of_transfer_program() { println!("commitments: {:?}", output.2); } -fn prove_inner( - sender: &Account, - receiver: &Account, - balance_to_move: u128, -) -> (Receipt, Vec) { - let mut env_builder = ExecutorEnv::builder(); - env_builder.write(&sender).unwrap(); - env_builder.write(&receiver).unwrap(); - env_builder.write(&balance_to_move).unwrap(); - let env = env_builder.build().unwrap(); - - let prover = default_prover(); - let prove_info = prover.prove(env, TRANSFER_ELF).unwrap(); - - let receipt = prove_info.receipt; - - let inputs_outputs: Vec = receipt.journal.decode().unwrap(); - assert_eq!(inputs_outputs.len(), 4); - - println!( - "sender_before: {:?}, sender_after: {:?}", - inputs_outputs[0], inputs_outputs[2] - ); - println!( - "receiver_before: {:?}, receiver_after: {:?}", - inputs_outputs[1], inputs_outputs[3] - ); - - // Sanity check - receipt.verify(TRANSFER_ID).unwrap(); - - (receipt, inputs_outputs) -} - #[cfg(test)] mod tests { use super::*; diff --git a/risc0-selective-privacy-poc/src/program.rs b/risc0-selective-privacy-poc/src/program.rs new file mode 100644 index 0000000..45988ac --- /dev/null +++ b/risc0-selective-privacy-poc/src/program.rs @@ -0,0 +1,60 @@ +use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; +use serde::{Deserialize, Serialize}; +use toy_example_core::account::Account; + +pub(crate) trait Program { + const PROGRAM_ID: [u32; 8]; + const PROGRAM_ELF: &[u8]; + type InstructionData: Serialize + for<'de> Deserialize<'de>; + + fn write_inputs( + input_accounts: &[Account], + instruction_data: &Self::InstructionData, + env_builder: &mut ExecutorEnvBuilder, + ) -> Result<(), ()> { + for account in input_accounts { + env_builder.write(&account).map_err(|_| ())?; + } + env_builder.write(&instruction_data).map_err(|_| ())?; + Ok(()) + } + + fn execute_and_prove( + input_accounts: &[Account], + instruction_data: &Self::InstructionData, + ) -> Result<(Receipt, Vec), ()> { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + Self::write_inputs(input_accounts, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Prove the program + let prover = default_prover(); + let prove_info = prover.prove(env, Self::PROGRAM_ELF).map_err(|_| ())?; + let receipt = prove_info.receipt; + + // Get proof and (inputs and) outputs + let inputs_outputs: Vec = receipt.journal.decode().map_err(|_| ())?; + + Ok((receipt, inputs_outputs)) + } + + fn execute( + input_accounts: &[Account], + instruction_data: &Self::InstructionData, + ) -> Result, ()> { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + Self::write_inputs(input_accounts, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Execute the program (without proving) + let executor = default_executor(); + let session_info = executor.execute(env, Self::PROGRAM_ELF).map_err(|_| ())?; + + // Get proof and (inputs and) outputs + let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; + + Ok(inputs_outputs) + } +} diff --git a/risc0-selective-privacy-poc/src/public_execution.rs b/risc0-selective-privacy-poc/src/public_execution.rs index 6a2889c..3917ca8 100644 --- a/risc0-selective-privacy-poc/src/public_execution.rs +++ b/risc0-selective-privacy-poc/src/public_execution.rs @@ -2,6 +2,8 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; use toy_example_core::account::Account; use transfer_methods::TRANSFER_ELF; +use crate::{program::Program, TransferProgram}; + /// A public execution. /// This would be executed by the runtime after checking that /// the initiating transaction includes the sender's signature. @@ -22,19 +24,7 @@ pub fn run_public_execution_of_transfer_program() { let balance_to_move: u128 = 3; - let mut env_builder = ExecutorEnv::builder(); - env_builder.write(&sender).unwrap(); - env_builder.write(&receiver).unwrap(); - env_builder.write(&balance_to_move).unwrap(); - let env = env_builder.build().unwrap(); - - let executor = default_executor(); - let inputs_outputs: Vec = executor - .execute(env, TRANSFER_ELF) - .unwrap() - .journal - .decode() - .unwrap(); + let inputs_outputs = TransferProgram::execute(&[sender, receiver], &balance_to_move).unwrap(); println!( "sender_before: {:?}, sender_after: {:?}", From b622cb8040d7797a0b68500de6da31577fee9740 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 17:01:50 -0300 Subject: [PATCH 17/83] generalize code to prove any privacy execution --- .../src/private_execution.rs | 66 +++++++----- risc0-selective-privacy-poc/src/program.rs | 102 +++++++++--------- .../src/public_execution.rs | 7 +- 3 files changed, 95 insertions(+), 80 deletions(-) diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index f536727..1576cef 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -1,6 +1,6 @@ use outer_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; -use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; +use risc0_zkvm::{default_prover, ExecutorEnv, ProveInfo, Receipt}; use sparse_merkle_tree::SparseMerkleTree; use toy_example_core::{ account::Account, @@ -10,7 +10,7 @@ use toy_example_core::{ }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -use crate::program::Program; +use crate::program::{execute_and_prove, Program}; use crate::TransferProgram; pub fn new_random_nonce() -> Nonce { @@ -23,6 +23,36 @@ fn mint_fresh_account(address: Address) -> Account { Account::new(address, nonce) } +fn prove_privacy_execution( + inputs: &[Account], + instruction_data: &P::InstructionData, + visibilities: &[InputVisibiility], + commitment_tree_root: [u32; 8], +) -> Result { + // Prove inner program and get post state of the accounts + let num_inputs = inputs.len(); + let (inner_receipt, inputs_outputs) = execute_and_prove::

(inputs, instruction_data)?; + + // Sample fresh random nonces for the outputs of this execution + let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); + + // Prove outer program. + // This computes the nullifiers for the input accounts and commitments for the output accounts. + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&(num_inputs as u32)).unwrap(); + env_builder.write(&inputs_outputs).unwrap(); + env_builder.write(&visibilities).unwrap(); + env_builder.write(&output_nonces).unwrap(); + env_builder.write(&commitment_tree_root).unwrap(); + env_builder.write(&TRANSFER_ID).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover.prove(env, OUTER_ELF).unwrap(); + Ok(prove_info) +} + /// A private execution of the transfer function. /// This actually "burns" a sender private account and "mints" two new private accounts: /// one for the recipient with the transferred balance, and another owned by the sender with the remaining balance. @@ -52,35 +82,17 @@ fn run_private_execution_of_transfer_program() { let receiver_address = [99; 8]; let receiver = mint_fresh_account(receiver_address); - // Prove inner program and get post state of the accounts - let (inner_receipt, inputs_outputs) = - TransferProgram::execute_and_prove(&[sender, receiver], &balance_to_move).unwrap(); - let visibilities = vec![ InputVisibiility::Private(Some((sender_private_key, auth_path))), InputVisibiility::Private(None), ]; - - let num_inputs: u32 = inputs_outputs.len() as u32 / 2; - - // Sample fresh random nonces for the outputs of this execution - let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); - println!("output nonces {output_nonces:?}"); - - // Prove outer program. - // This computes the nullifier for the input account and commitments for the accounts post states. - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); - env_builder.write(&num_inputs).unwrap(); - env_builder.write(&inputs_outputs).unwrap(); - env_builder.write(&visibilities).unwrap(); - env_builder.write(&output_nonces).unwrap(); - env_builder.write(&root).unwrap(); - env_builder.write(&TRANSFER_ID).unwrap(); - let env = env_builder.build().unwrap(); - - let prover = default_prover(); - let prove_info = prover.prove(env, OUTER_ELF).unwrap(); + let prove_info = prove_privacy_execution::( + &[sender, receiver], + &balance_to_move, + &visibilities, + root, + ) + .unwrap(); let receipt = prove_info.receipt; diff --git a/risc0-selective-privacy-poc/src/program.rs b/risc0-selective-privacy-poc/src/program.rs index 45988ac..bf31cf1 100644 --- a/risc0-selective-privacy-poc/src/program.rs +++ b/risc0-selective-privacy-poc/src/program.rs @@ -6,55 +6,55 @@ pub(crate) trait Program { const PROGRAM_ID: [u32; 8]; const PROGRAM_ELF: &[u8]; type InstructionData: Serialize + for<'de> Deserialize<'de>; - - fn write_inputs( - input_accounts: &[Account], - instruction_data: &Self::InstructionData, - env_builder: &mut ExecutorEnvBuilder, - ) -> Result<(), ()> { - for account in input_accounts { - env_builder.write(&account).map_err(|_| ())?; - } - env_builder.write(&instruction_data).map_err(|_| ())?; - Ok(()) - } - - fn execute_and_prove( - input_accounts: &[Account], - instruction_data: &Self::InstructionData, - ) -> Result<(Receipt, Vec), ()> { - // Write inputs to the program - let mut env_builder = ExecutorEnv::builder(); - Self::write_inputs(input_accounts, instruction_data, &mut env_builder)?; - let env = env_builder.build().unwrap(); - - // Prove the program - let prover = default_prover(); - let prove_info = prover.prove(env, Self::PROGRAM_ELF).map_err(|_| ())?; - let receipt = prove_info.receipt; - - // Get proof and (inputs and) outputs - let inputs_outputs: Vec = receipt.journal.decode().map_err(|_| ())?; - - Ok((receipt, inputs_outputs)) - } - - fn execute( - input_accounts: &[Account], - instruction_data: &Self::InstructionData, - ) -> Result, ()> { - // Write inputs to the program - let mut env_builder = ExecutorEnv::builder(); - Self::write_inputs(input_accounts, instruction_data, &mut env_builder)?; - let env = env_builder.build().unwrap(); - - // Execute the program (without proving) - let executor = default_executor(); - let session_info = executor.execute(env, Self::PROGRAM_ELF).map_err(|_| ())?; - - // Get proof and (inputs and) outputs - let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; - - Ok(inputs_outputs) - } +} + +pub(crate) fn write_inputs( + input_accounts: &[Account], + instruction_data: &P::InstructionData, + env_builder: &mut ExecutorEnvBuilder, +) -> Result<(), ()> { + for account in input_accounts { + env_builder.write(&account).map_err(|_| ())?; + } + env_builder.write(&instruction_data).map_err(|_| ())?; + Ok(()) +} + +pub(crate) fn execute_and_prove( + input_accounts: &[Account], + instruction_data: &P::InstructionData, +) -> Result<(Receipt, Vec), ()> { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Prove the program + let prover = default_prover(); + let prove_info = prover.prove(env, P::PROGRAM_ELF).map_err(|_| ())?; + let receipt = prove_info.receipt; + + // Get proof and (inputs and) outputs + let inputs_outputs: Vec = receipt.journal.decode().map_err(|_| ())?; + + Ok((receipt, inputs_outputs)) +} + +pub(crate) fn execute( + input_accounts: &[Account], + instruction_data: &P::InstructionData, +) -> Result, ()> { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Execute the program (without proving) + let executor = default_executor(); + let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; + + // Get proof and (inputs and) outputs + let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; + + Ok(inputs_outputs) } diff --git a/risc0-selective-privacy-poc/src/public_execution.rs b/risc0-selective-privacy-poc/src/public_execution.rs index 3917ca8..44395a9 100644 --- a/risc0-selective-privacy-poc/src/public_execution.rs +++ b/risc0-selective-privacy-poc/src/public_execution.rs @@ -2,7 +2,10 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; use toy_example_core::account::Account; use transfer_methods::TRANSFER_ELF; -use crate::{program::Program, TransferProgram}; +use crate::{ + program::{execute, Program}, + TransferProgram, +}; /// A public execution. /// This would be executed by the runtime after checking that @@ -24,7 +27,7 @@ pub fn run_public_execution_of_transfer_program() { let balance_to_move: u128 = 3; - let inputs_outputs = TransferProgram::execute(&[sender, receiver], &balance_to_move).unwrap(); + let inputs_outputs = execute::(&[sender, receiver], &balance_to_move).unwrap(); println!( "sender_before: {:?}, sender_after: {:?}", From 8feb17db1a3d373b5f84871c832b522fc7dc9d0e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 17:13:26 -0300 Subject: [PATCH 18/83] refactor --- risc0-selective-privacy-poc/src/lib.rs | 2 - .../src/private_execution.rs | 32 +-------------- risc0-selective-privacy-poc/src/program.rs | 39 ++++++++++++++++++- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index ce39e62..7d9f777 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -9,9 +9,7 @@ struct TransferProgram; impl Program for TransferProgram { const PROGRAM_ID: [u32; 8] = TRANSFER_ID; - const PROGRAM_ELF: &[u8] = TRANSFER_ELF; - // Amount to transfer type InstructionData = u128; } diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/src/private_execution.rs index 1576cef..1cb3ceb 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/src/private_execution.rs @@ -10,7 +10,7 @@ use toy_example_core::{ }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -use crate::program::{execute_and_prove, Program}; +use crate::program::{execute_and_prove, prove_privacy_execution, Program}; use crate::TransferProgram; pub fn new_random_nonce() -> Nonce { @@ -23,36 +23,6 @@ fn mint_fresh_account(address: Address) -> Account { Account::new(address, nonce) } -fn prove_privacy_execution( - inputs: &[Account], - instruction_data: &P::InstructionData, - visibilities: &[InputVisibiility], - commitment_tree_root: [u32; 8], -) -> Result { - // Prove inner program and get post state of the accounts - let num_inputs = inputs.len(); - let (inner_receipt, inputs_outputs) = execute_and_prove::

(inputs, instruction_data)?; - - // Sample fresh random nonces for the outputs of this execution - let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); - - // Prove outer program. - // This computes the nullifiers for the input accounts and commitments for the output accounts. - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); - env_builder.write(&(num_inputs as u32)).unwrap(); - env_builder.write(&inputs_outputs).unwrap(); - env_builder.write(&visibilities).unwrap(); - env_builder.write(&output_nonces).unwrap(); - env_builder.write(&commitment_tree_root).unwrap(); - env_builder.write(&TRANSFER_ID).unwrap(); - let env = env_builder.build().unwrap(); - - let prover = default_prover(); - let prove_info = prover.prove(env, OUTER_ELF).unwrap(); - Ok(prove_info) -} - /// A private execution of the transfer function. /// This actually "burns" a sender private account and "mints" two new private accounts: /// one for the recipient with the transferred balance, and another owned by the sender with the remaining balance. diff --git a/risc0-selective-privacy-poc/src/program.rs b/risc0-selective-privacy-poc/src/program.rs index bf31cf1..ade94c0 100644 --- a/risc0-selective-privacy-poc/src/program.rs +++ b/risc0-selective-privacy-poc/src/program.rs @@ -1,6 +1,11 @@ -use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; +use outer_methods::OUTER_ELF; +use risc0_zkvm::{ + default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, ProveInfo, Receipt, +}; use serde::{Deserialize, Serialize}; -use toy_example_core::account::Account; +use toy_example_core::{account::Account, input::InputVisibiility}; + +use crate::private_execution::new_random_nonce; pub(crate) trait Program { const PROGRAM_ID: [u32; 8]; @@ -58,3 +63,33 @@ pub(crate) fn execute( Ok(inputs_outputs) } + +pub(crate) fn prove_privacy_execution( + inputs: &[Account], + instruction_data: &P::InstructionData, + visibilities: &[InputVisibiility], + commitment_tree_root: [u32; 8], +) -> Result { + // Prove inner program and get post state of the accounts + let num_inputs = inputs.len(); + let (inner_receipt, inputs_outputs) = execute_and_prove::

(inputs, instruction_data)?; + + // Sample fresh random nonces for the outputs of this execution + let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); + + // Prove outer program. + // This computes the nullifiers for the input accounts and commitments for the output accounts. + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&(num_inputs as u32)).unwrap(); + env_builder.write(&inputs_outputs).unwrap(); + env_builder.write(&visibilities).unwrap(); + env_builder.write(&output_nonces).unwrap(); + env_builder.write(&commitment_tree_root).unwrap(); + env_builder.write(&P::PROGRAM_ID).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover.prove(env, OUTER_ELF).unwrap(); + Ok(prove_info) +} From d4a306d67ad225d746600d639f0d3d15936d076d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 17:25:03 -0300 Subject: [PATCH 19/83] moved examples to examples dir --- .../{src => examples}/private_execution.rs | 25 ++++--------------- .../examples/programs/mod.rs | 11 ++++++++ .../{src => examples}/public_execution.rs | 21 +++++----------- risc0-selective-privacy-poc/src/lib.rs | 15 +---------- risc0-selective-privacy-poc/src/program.rs | 14 +++++++---- 5 files changed, 32 insertions(+), 54 deletions(-) rename risc0-selective-privacy-poc/{src => examples}/private_execution.rs (85%) create mode 100644 risc0-selective-privacy-poc/examples/programs/mod.rs rename risc0-selective-privacy-poc/{src => examples}/public_execution.rs (80%) diff --git a/risc0-selective-privacy-poc/src/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs similarity index 85% rename from risc0-selective-privacy-poc/src/private_execution.rs rename to risc0-selective-privacy-poc/examples/private_execution.rs index 1cb3ceb..c580c78 100644 --- a/risc0-selective-privacy-poc/src/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,5 +1,7 @@ +mod programs; + use outer_methods::{OUTER_ELF, OUTER_ID}; -use rand::{rngs::OsRng, Rng}; +use programs::TransferProgram; use risc0_zkvm::{default_prover, ExecutorEnv, ProveInfo, Receipt}; use sparse_merkle_tree::SparseMerkleTree; use toy_example_core::{ @@ -9,14 +11,7 @@ use toy_example_core::{ types::{Address, AuthenticationPath, Commitment, Nonce, Nullifier}, }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; - -use crate::program::{execute_and_prove, prove_privacy_execution, Program}; -use crate::TransferProgram; - -pub fn new_random_nonce() -> Nonce { - let mut rng = OsRng; - std::array::from_fn(|_| rng.gen()) -} +use tuki::program::{prove_privacy_execution, Program}; fn mint_fresh_account(address: Address) -> Account { let nonce = [0; 8]; @@ -26,7 +21,7 @@ fn mint_fresh_account(address: Address) -> Account { /// A private execution of the transfer function. /// This actually "burns" a sender private account and "mints" two new private accounts: /// one for the recipient with the transferred balance, and another owned by the sender with the remaining balance. -fn run_private_execution_of_transfer_program() { +fn main() { // This is supposed to be an existing private account (UTXO) with balance equal to 150. // And it is supposed to be a private account of the user running this private execution (hence the access to the private key) let sender_private_key = [1, 2, 3, 4, 4, 3, 2, 1]; @@ -74,13 +69,3 @@ fn run_private_execution_of_transfer_program() { println!("nullifiers: {:?}", output.1); println!("commitments: {:?}", output.2); } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_private() { - run_private_execution_of_transfer_program(); - } -} diff --git a/risc0-selective-privacy-poc/examples/programs/mod.rs b/risc0-selective-privacy-poc/examples/programs/mod.rs new file mode 100644 index 0000000..1bb7219 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/programs/mod.rs @@ -0,0 +1,11 @@ +use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; +use tuki::program::Program; + +pub struct TransferProgram; + +impl Program for TransferProgram { + const PROGRAM_ID: [u32; 8] = TRANSFER_ID; + const PROGRAM_ELF: &[u8] = TRANSFER_ELF; + // Amount to transfer + type InstructionData = u128; +} diff --git a/risc0-selective-privacy-poc/src/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs similarity index 80% rename from risc0-selective-privacy-poc/src/public_execution.rs rename to risc0-selective-privacy-poc/examples/public_execution.rs index 44395a9..c7cb566 100644 --- a/risc0-selective-privacy-poc/src/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,16 +1,17 @@ +mod programs; + use risc0_zkvm::{default_executor, ExecutorEnv}; use toy_example_core::account::Account; use transfer_methods::TRANSFER_ELF; -use crate::{ - program::{execute, Program}, - TransferProgram, -}; +use tuki::program::{execute, Program}; + +use crate::programs::TransferProgram; /// A public execution. /// This would be executed by the runtime after checking that /// the initiating transaction includes the sender's signature. -pub fn run_public_execution_of_transfer_program() { +pub fn main() { // Account fetched from the chain state with 150 in its balance. let sender = { let mut account = Account::new([5; 8], [98; 8]); @@ -38,13 +39,3 @@ pub fn run_public_execution_of_transfer_program() { inputs_outputs[1], inputs_outputs[3], ); } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_public() { - run_public_execution_of_transfer_program(); - } -} diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 7d9f777..5dc5d07 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,15 +1,2 @@ -mod private_execution; -mod program; -mod public_execution; +pub mod program; -use program::Program; -use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; - -struct TransferProgram; - -impl Program for TransferProgram { - const PROGRAM_ID: [u32; 8] = TRANSFER_ID; - const PROGRAM_ELF: &[u8] = TRANSFER_ELF; - // Amount to transfer - type InstructionData = u128; -} diff --git a/risc0-selective-privacy-poc/src/program.rs b/risc0-selective-privacy-poc/src/program.rs index ade94c0..d83813a 100644 --- a/risc0-selective-privacy-poc/src/program.rs +++ b/risc0-selective-privacy-poc/src/program.rs @@ -1,13 +1,17 @@ use outer_methods::OUTER_ELF; +use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{ default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, ProveInfo, Receipt, }; use serde::{Deserialize, Serialize}; -use toy_example_core::{account::Account, input::InputVisibiility}; +use toy_example_core::{account::Account, input::InputVisibiility, types::Nonce}; -use crate::private_execution::new_random_nonce; +pub fn new_random_nonce() -> Nonce { + let mut rng = OsRng; + std::array::from_fn(|_| rng.gen()) +} -pub(crate) trait Program { +pub trait Program { const PROGRAM_ID: [u32; 8]; const PROGRAM_ELF: &[u8]; type InstructionData: Serialize + for<'de> Deserialize<'de>; @@ -45,7 +49,7 @@ pub(crate) fn execute_and_prove( Ok((receipt, inputs_outputs)) } -pub(crate) fn execute( +pub fn execute( input_accounts: &[Account], instruction_data: &P::InstructionData, ) -> Result, ()> { @@ -64,7 +68,7 @@ pub(crate) fn execute( Ok(inputs_outputs) } -pub(crate) fn prove_privacy_execution( +pub fn prove_privacy_execution( inputs: &[Account], instruction_data: &P::InstructionData, visibilities: &[InputVisibiility], From 3d582d491fa851cbb1dca7b87f97f93151327411 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 17:28:45 -0300 Subject: [PATCH 20/83] refactor --- .../examples/private_execution.rs | 2 +- .../examples/programs/mod.rs | 2 +- .../examples/public_execution.rs | 2 +- risc0-selective-privacy-poc/src/lib.rs | 99 ++++++++++++++++++- risc0-selective-privacy-poc/src/program.rs | 99 ------------------- 5 files changed, 101 insertions(+), 103 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index c580c78..a19b5bb 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -11,7 +11,7 @@ use toy_example_core::{ types::{Address, AuthenticationPath, Commitment, Nonce, Nullifier}, }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -use tuki::program::{prove_privacy_execution, Program}; +use tuki::{prove_privacy_execution, Program}; fn mint_fresh_account(address: Address) -> Account { let nonce = [0; 8]; diff --git a/risc0-selective-privacy-poc/examples/programs/mod.rs b/risc0-selective-privacy-poc/examples/programs/mod.rs index 1bb7219..b61b7ba 100644 --- a/risc0-selective-privacy-poc/examples/programs/mod.rs +++ b/risc0-selective-privacy-poc/examples/programs/mod.rs @@ -1,5 +1,5 @@ use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -use tuki::program::Program; +use tuki::Program; pub struct TransferProgram; diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index c7cb566..9ede169 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -4,7 +4,7 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; use toy_example_core::account::Account; use transfer_methods::TRANSFER_ELF; -use tuki::program::{execute, Program}; +use tuki::{execute, Program}; use crate::programs::TransferProgram; diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 5dc5d07..d83813a 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,2 +1,99 @@ -pub mod program; +use outer_methods::OUTER_ELF; +use rand::{rngs::OsRng, Rng}; +use risc0_zkvm::{ + default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, ProveInfo, Receipt, +}; +use serde::{Deserialize, Serialize}; +use toy_example_core::{account::Account, input::InputVisibiility, types::Nonce}; +pub fn new_random_nonce() -> Nonce { + let mut rng = OsRng; + std::array::from_fn(|_| rng.gen()) +} + +pub trait Program { + const PROGRAM_ID: [u32; 8]; + const PROGRAM_ELF: &[u8]; + type InstructionData: Serialize + for<'de> Deserialize<'de>; +} + +pub(crate) fn write_inputs( + input_accounts: &[Account], + instruction_data: &P::InstructionData, + env_builder: &mut ExecutorEnvBuilder, +) -> Result<(), ()> { + for account in input_accounts { + env_builder.write(&account).map_err(|_| ())?; + } + env_builder.write(&instruction_data).map_err(|_| ())?; + Ok(()) +} + +pub(crate) fn execute_and_prove( + input_accounts: &[Account], + instruction_data: &P::InstructionData, +) -> Result<(Receipt, Vec), ()> { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Prove the program + let prover = default_prover(); + let prove_info = prover.prove(env, P::PROGRAM_ELF).map_err(|_| ())?; + let receipt = prove_info.receipt; + + // Get proof and (inputs and) outputs + let inputs_outputs: Vec = receipt.journal.decode().map_err(|_| ())?; + + Ok((receipt, inputs_outputs)) +} + +pub fn execute( + input_accounts: &[Account], + instruction_data: &P::InstructionData, +) -> Result, ()> { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Execute the program (without proving) + let executor = default_executor(); + let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; + + // Get proof and (inputs and) outputs + let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; + + Ok(inputs_outputs) +} + +pub fn prove_privacy_execution( + inputs: &[Account], + instruction_data: &P::InstructionData, + visibilities: &[InputVisibiility], + commitment_tree_root: [u32; 8], +) -> Result { + // Prove inner program and get post state of the accounts + let num_inputs = inputs.len(); + let (inner_receipt, inputs_outputs) = execute_and_prove::

(inputs, instruction_data)?; + + // Sample fresh random nonces for the outputs of this execution + let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); + + // Prove outer program. + // This computes the nullifiers for the input accounts and commitments for the output accounts. + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&(num_inputs as u32)).unwrap(); + env_builder.write(&inputs_outputs).unwrap(); + env_builder.write(&visibilities).unwrap(); + env_builder.write(&output_nonces).unwrap(); + env_builder.write(&commitment_tree_root).unwrap(); + env_builder.write(&P::PROGRAM_ID).unwrap(); + let env = env_builder.build().unwrap(); + + let prover = default_prover(); + let prove_info = prover.prove(env, OUTER_ELF).unwrap(); + Ok(prove_info) +} diff --git a/risc0-selective-privacy-poc/src/program.rs b/risc0-selective-privacy-poc/src/program.rs index d83813a..e69de29 100644 --- a/risc0-selective-privacy-poc/src/program.rs +++ b/risc0-selective-privacy-poc/src/program.rs @@ -1,99 +0,0 @@ -use outer_methods::OUTER_ELF; -use rand::{rngs::OsRng, Rng}; -use risc0_zkvm::{ - default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, ProveInfo, Receipt, -}; -use serde::{Deserialize, Serialize}; -use toy_example_core::{account::Account, input::InputVisibiility, types::Nonce}; - -pub fn new_random_nonce() -> Nonce { - let mut rng = OsRng; - std::array::from_fn(|_| rng.gen()) -} - -pub trait Program { - const PROGRAM_ID: [u32; 8]; - const PROGRAM_ELF: &[u8]; - type InstructionData: Serialize + for<'de> Deserialize<'de>; -} - -pub(crate) fn write_inputs( - input_accounts: &[Account], - instruction_data: &P::InstructionData, - env_builder: &mut ExecutorEnvBuilder, -) -> Result<(), ()> { - for account in input_accounts { - env_builder.write(&account).map_err(|_| ())?; - } - env_builder.write(&instruction_data).map_err(|_| ())?; - Ok(()) -} - -pub(crate) fn execute_and_prove( - input_accounts: &[Account], - instruction_data: &P::InstructionData, -) -> Result<(Receipt, Vec), ()> { - // Write inputs to the program - let mut env_builder = ExecutorEnv::builder(); - write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; - let env = env_builder.build().unwrap(); - - // Prove the program - let prover = default_prover(); - let prove_info = prover.prove(env, P::PROGRAM_ELF).map_err(|_| ())?; - let receipt = prove_info.receipt; - - // Get proof and (inputs and) outputs - let inputs_outputs: Vec = receipt.journal.decode().map_err(|_| ())?; - - Ok((receipt, inputs_outputs)) -} - -pub fn execute( - input_accounts: &[Account], - instruction_data: &P::InstructionData, -) -> Result, ()> { - // Write inputs to the program - let mut env_builder = ExecutorEnv::builder(); - write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; - let env = env_builder.build().unwrap(); - - // Execute the program (without proving) - let executor = default_executor(); - let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; - - // Get proof and (inputs and) outputs - let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; - - Ok(inputs_outputs) -} - -pub fn prove_privacy_execution( - inputs: &[Account], - instruction_data: &P::InstructionData, - visibilities: &[InputVisibiility], - commitment_tree_root: [u32; 8], -) -> Result { - // Prove inner program and get post state of the accounts - let num_inputs = inputs.len(); - let (inner_receipt, inputs_outputs) = execute_and_prove::

(inputs, instruction_data)?; - - // Sample fresh random nonces for the outputs of this execution - let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); - - // Prove outer program. - // This computes the nullifiers for the input accounts and commitments for the output accounts. - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); - env_builder.write(&(num_inputs as u32)).unwrap(); - env_builder.write(&inputs_outputs).unwrap(); - env_builder.write(&visibilities).unwrap(); - env_builder.write(&output_nonces).unwrap(); - env_builder.write(&commitment_tree_root).unwrap(); - env_builder.write(&P::PROGRAM_ID).unwrap(); - let env = env_builder.build().unwrap(); - - let prover = default_prover(); - let prove_info = prover.prove(env, OUTER_ELF).unwrap(); - Ok(prove_info) -} From 0e704cd51a06a19a7d9c1803029c4d1dabe281a6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 17:33:58 -0300 Subject: [PATCH 21/83] refactor --- risc0-selective-privacy-poc/Cargo.toml | 5 ++--- .../examples/private_execution.rs | 6 +++--- risc0-selective-privacy-poc/examples/programs/mod.rs | 11 ----------- .../examples/public_execution.rs | 7 ++++--- risc0-selective-privacy-poc/src/lib.rs | 2 ++ risc0-selective-privacy-poc/src/program.rs | 10 ++++++++++ 6 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 risc0-selective-privacy-poc/examples/programs/mod.rs diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index a236a11..a90a0e3 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tuki" +name = "nssa" version = "0.12.0" edition = "2021" @@ -11,10 +11,9 @@ outer-methods = { path = "outer_methods" } serde = "1.0" tracing-subscriber = { version = "0.3", features = ["env-filter"] } rand = "0.8" -sparse-merkle-tree = {path="./sparse_merkle_tree/"} +sparse-merkle-tree = { path = "./sparse_merkle_tree/" } [features] cuda = ["risc0-zkvm/cuda"] default = [] prove = ["risc0-zkvm/prove"] - diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index a19b5bb..0296a83 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,7 +1,8 @@ mod programs; +use nssa; +use nssa::program::TransferProgram; use outer_methods::{OUTER_ELF, OUTER_ID}; -use programs::TransferProgram; use risc0_zkvm::{default_prover, ExecutorEnv, ProveInfo, Receipt}; use sparse_merkle_tree::SparseMerkleTree; use toy_example_core::{ @@ -11,7 +12,6 @@ use toy_example_core::{ types::{Address, AuthenticationPath, Commitment, Nonce, Nullifier}, }; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -use tuki::{prove_privacy_execution, Program}; fn mint_fresh_account(address: Address) -> Account { let nonce = [0; 8]; @@ -51,7 +51,7 @@ fn main() { InputVisibiility::Private(Some((sender_private_key, auth_path))), InputVisibiility::Private(None), ]; - let prove_info = prove_privacy_execution::( + let prove_info = nssa::prove_privacy_execution::( &[sender, receiver], &balance_to_move, &visibilities, diff --git a/risc0-selective-privacy-poc/examples/programs/mod.rs b/risc0-selective-privacy-poc/examples/programs/mod.rs deleted file mode 100644 index b61b7ba..0000000 --- a/risc0-selective-privacy-poc/examples/programs/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -use tuki::Program; - -pub struct TransferProgram; - -impl Program for TransferProgram { - const PROGRAM_ID: [u32; 8] = TRANSFER_ID; - const PROGRAM_ELF: &[u8] = TRANSFER_ELF; - // Amount to transfer - type InstructionData = u128; -} diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 9ede169..670ab68 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -4,9 +4,9 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; use toy_example_core::account::Account; use transfer_methods::TRANSFER_ELF; -use tuki::{execute, Program}; +use nssa; -use crate::programs::TransferProgram; +use nssa::program::TransferProgram; /// A public execution. /// This would be executed by the runtime after checking that @@ -28,7 +28,8 @@ pub fn main() { let balance_to_move: u128 = 3; - let inputs_outputs = execute::(&[sender, receiver], &balance_to_move).unwrap(); + let inputs_outputs = + nssa::execute::(&[sender, receiver], &balance_to_move).unwrap(); println!( "sender_before: {:?}, sender_after: {:?}", diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index d83813a..f5326a4 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -6,6 +6,8 @@ use risc0_zkvm::{ use serde::{Deserialize, Serialize}; use toy_example_core::{account::Account, input::InputVisibiility, types::Nonce}; +pub mod program; + pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) diff --git a/risc0-selective-privacy-poc/src/program.rs b/risc0-selective-privacy-poc/src/program.rs index e69de29..52ec5e4 100644 --- a/risc0-selective-privacy-poc/src/program.rs +++ b/risc0-selective-privacy-poc/src/program.rs @@ -0,0 +1,10 @@ +use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; + +pub struct TransferProgram; + +impl crate::Program for TransferProgram { + const PROGRAM_ID: [u32; 8] = TRANSFER_ID; + const PROGRAM_ELF: &[u8] = TRANSFER_ELF; + // Amount to transfer + type InstructionData = u128; +} From 8ecfa9887bfae8b6068f917fe555ba253d36a617 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 17:40:22 -0300 Subject: [PATCH 22/83] refactor --- .../examples/private_execution.rs | 3 --- .../examples/public_execution.rs | 2 -- risc0-selective-privacy-poc/src/lib.rs | 8 +------- .../src/{program.rs => program/mod.rs} | 10 ++++++++-- 4 files changed, 9 insertions(+), 14 deletions(-) rename risc0-selective-privacy-poc/src/{program.rs => program/mod.rs} (51%) diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 0296a83..2686596 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,6 +1,3 @@ -mod programs; - -use nssa; use nssa::program::TransferProgram; use outer_methods::{OUTER_ELF, OUTER_ID}; use risc0_zkvm::{default_prover, ExecutorEnv, ProveInfo, Receipt}; diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 670ab68..7deb9a2 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,5 +1,3 @@ -mod programs; - use risc0_zkvm::{default_executor, ExecutorEnv}; use toy_example_core::account::Account; use transfer_methods::TRANSFER_ELF; diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index f5326a4..711663e 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,4 +1,5 @@ use outer_methods::OUTER_ELF; +use program::Program; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{ default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, ProveInfo, Receipt, @@ -12,13 +13,6 @@ pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) } - -pub trait Program { - const PROGRAM_ID: [u32; 8]; - const PROGRAM_ELF: &[u8]; - type InstructionData: Serialize + for<'de> Deserialize<'de>; -} - pub(crate) fn write_inputs( input_accounts: &[Account], instruction_data: &P::InstructionData, diff --git a/risc0-selective-privacy-poc/src/program.rs b/risc0-selective-privacy-poc/src/program/mod.rs similarity index 51% rename from risc0-selective-privacy-poc/src/program.rs rename to risc0-selective-privacy-poc/src/program/mod.rs index 52ec5e4..a9af5fc 100644 --- a/risc0-selective-privacy-poc/src/program.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -1,8 +1,14 @@ +use serde::{Deserialize, Serialize}; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; -pub struct TransferProgram; +pub trait Program { + const PROGRAM_ID: [u32; 8]; + const PROGRAM_ELF: &[u8]; + type InstructionData: Serialize + for<'de> Deserialize<'de>; +} -impl crate::Program for TransferProgram { +pub struct TransferProgram; +impl Program for TransferProgram { const PROGRAM_ID: [u32; 8] = TRANSFER_ID; const PROGRAM_ELF: &[u8] = TRANSFER_ELF; // Amount to transfer From e081031580d8042bc504cf3a6955eaec079c13d1 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 09:20:03 -0300 Subject: [PATCH 23/83] add verification function --- .../examples/private_execution.rs | 21 +++++----- .../outer_methods/guest/src/bin/outer.rs | 1 + risc0-selective-privacy-poc/src/lib.rs | 42 +++++++++++++++---- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 2686596..d77e700 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,14 +1,12 @@ use nssa::program::TransferProgram; -use outer_methods::{OUTER_ELF, OUTER_ID}; -use risc0_zkvm::{default_prover, ExecutorEnv, ProveInfo, Receipt}; +use outer_methods::OUTER_ID; use sparse_merkle_tree::SparseMerkleTree; use toy_example_core::{ account::Account, bytes_to_words, input::InputVisibiility, - types::{Address, AuthenticationPath, Commitment, Nonce, Nullifier}, + types::{Address, AuthenticationPath, Commitment, Nullifier}, }; -use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; fn mint_fresh_account(address: Address) -> Account { let nonce = [0; 8]; @@ -48,7 +46,7 @@ fn main() { InputVisibiility::Private(Some((sender_private_key, auth_path))), InputVisibiility::Private(None), ]; - let prove_info = nssa::prove_privacy_execution::( + let receipt = nssa::prove_privacy_execution::( &[sender, receiver], &balance_to_move, &visibilities, @@ -56,13 +54,14 @@ fn main() { ) .unwrap(); - let receipt = prove_info.receipt; - - // Sanity check - receipt.verify(OUTER_ID).unwrap(); - - let output: (Vec, Vec, Vec) = receipt.journal.decode().unwrap(); + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); println!("public_outputs: {:?}", output.0); println!("nullifiers: {:?}", output.1); println!("commitments: {:?}", output.2); + println!("commitment_tree_root: {:?}", output.3); + + assert!( + nssa::verify_privacy_execution(receipt, &output.0, &output.1, &output.2, &output.3).is_ok() + ); } diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index a8ba142..62b4b33 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -115,5 +115,6 @@ fn main() { public_inputs_outputs, nullifiers, private_output_commitments, + commitment_tree_root, )); } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 711663e..f8e095e 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,11 +1,12 @@ -use outer_methods::OUTER_ELF; +use outer_methods::{OUTER_ELF, OUTER_ID}; use program::Program; use rand::{rngs::OsRng, Rng}; -use risc0_zkvm::{ - default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, ProveInfo, Receipt, +use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; +use toy_example_core::{ + account::Account, + input::InputVisibiility, + types::{Commitment, Nonce, Nullifier}, }; -use serde::{Deserialize, Serialize}; -use toy_example_core::{account::Account, input::InputVisibiility, types::Nonce}; pub mod program; @@ -13,7 +14,8 @@ pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) } -pub(crate) fn write_inputs( + +fn write_inputs( input_accounts: &[Account], instruction_data: &P::InstructionData, env_builder: &mut ExecutorEnvBuilder, @@ -25,7 +27,7 @@ pub(crate) fn write_inputs( Ok(()) } -pub(crate) fn execute_and_prove( +fn execute_and_prove( input_accounts: &[Account], instruction_data: &P::InstructionData, ) -> Result<(Receipt, Vec), ()> { @@ -69,7 +71,7 @@ pub fn prove_privacy_execution( instruction_data: &P::InstructionData, visibilities: &[InputVisibiility], commitment_tree_root: [u32; 8], -) -> Result { +) -> Result { // Prove inner program and get post state of the accounts let num_inputs = inputs.len(); let (inner_receipt, inputs_outputs) = execute_and_prove::

(inputs, instruction_data)?; @@ -91,5 +93,27 @@ pub fn prove_privacy_execution( let prover = default_prover(); let prove_info = prover.prove(env, OUTER_ELF).unwrap(); - Ok(prove_info) + Ok(prove_info.receipt) +} + +pub fn verify_privacy_execution( + receipt: Receipt, + public_accounts_inputs_outputs: &[Account], + nullifiers: &[Nullifier], + private_output_commitments: &[Commitment], + commitment_tree_root: &[u32; 8], +) -> Result<(), ()> { + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + let expected_output = ( + public_accounts_inputs_outputs.to_vec(), + nullifiers.to_vec(), + private_output_commitments.to_vec(), + commitment_tree_root.to_owned(), + ); + if output != expected_output { + return Err(()); + } else { + receipt.verify(OUTER_ID).map_err(|_| ()) + } } From c051f4e9ea196341b92c64671f8e0d8f7af1b0e7 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 09:43:44 -0300 Subject: [PATCH 24/83] rename --- risc0-selective-privacy-poc/examples/private_execution.rs | 3 ++- risc0-selective-privacy-poc/src/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index d77e700..86dd25f 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -46,7 +46,8 @@ fn main() { InputVisibiility::Private(Some((sender_private_key, auth_path))), InputVisibiility::Private(None), ]; - let receipt = nssa::prove_privacy_execution::( + + let receipt = nssa::execute_and_prove_privacy_execution::( &[sender, receiver], &balance_to_move, &visibilities, diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index f8e095e..d1f98dd 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -27,7 +27,7 @@ fn write_inputs( Ok(()) } -fn execute_and_prove( +fn execute_and_prove_inner( input_accounts: &[Account], instruction_data: &P::InstructionData, ) -> Result<(Receipt, Vec), ()> { @@ -66,7 +66,7 @@ pub fn execute( Ok(inputs_outputs) } -pub fn prove_privacy_execution( +pub fn execute_and_prove_privacy_execution( inputs: &[Account], instruction_data: &P::InstructionData, visibilities: &[InputVisibiility], @@ -74,7 +74,7 @@ pub fn prove_privacy_execution( ) -> Result { // Prove inner program and get post state of the accounts let num_inputs = inputs.len(); - let (inner_receipt, inputs_outputs) = execute_and_prove::

(inputs, instruction_data)?; + let (inner_receipt, inputs_outputs) = execute_and_prove_inner::

(inputs, instruction_data)?; // Sample fresh random nonces for the outputs of this execution let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); From 9e153b9ebd68c809a8c7aabf1fa676b3fe4af7ca Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 10:10:39 -0300 Subject: [PATCH 25/83] rename crates --- risc0-selective-privacy-poc/Cargo.toml | 2 +- risc0-selective-privacy-poc/core/Cargo.toml | 5 +++-- .../examples/private_execution.rs | 2 +- .../examples/public_execution.rs | 2 +- .../outer_methods/guest/Cargo.toml | 4 ++-- .../outer_methods/guest/src/bin/outer.rs | 2 +- risc0-selective-privacy-poc/src/lib.rs | 10 +++++----- .../transfer_methods/guest/Cargo.toml | 4 ++-- .../transfer_methods/guest/src/bin/transfer.rs | 9 ++++++--- 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index a90a0e3..e159416 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] risc0-zkvm = "2.2" -toy-example-core = { path = "core" } +core = { path = "core" } transfer-methods = { path = "transfer_methods" } outer-methods = { path = "outer_methods" } serde = "1.0" diff --git a/risc0-selective-privacy-poc/core/Cargo.toml b/risc0-selective-privacy-poc/core/Cargo.toml index 1d84937..4e57901 100644 --- a/risc0-selective-privacy-poc/core/Cargo.toml +++ b/risc0-selective-privacy-poc/core/Cargo.toml @@ -1,8 +1,9 @@ [package] -name = "toy-example-core" +name = "core" version = "0.12.0" edition = "2021" [dependencies] risc0-zkvm = "2.0.2" -serde = { version = "1.0", default-features = false } \ No newline at end of file +serde = { version = "1.0", default-features = false } + diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 86dd25f..501bcff 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,7 +1,7 @@ use nssa::program::TransferProgram; use outer_methods::OUTER_ID; use sparse_merkle_tree::SparseMerkleTree; -use toy_example_core::{ +use core::{ account::Account, bytes_to_words, input::InputVisibiility, diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 7deb9a2..1095886 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,5 +1,5 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; -use toy_example_core::account::Account; +use core::account::Account; use transfer_methods::TRANSFER_ELF; use nssa; diff --git a/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml index ceb31ad..4585a49 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml +++ b/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "toy_example" +name = "outer" version = "0.1.0" edition = "2021" @@ -7,5 +7,5 @@ edition = "2021" [dependencies] risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] } -toy-example-core = {path = "../../core" } +core = {path = "../../core" } transfer-methods = {path = "../../transfer_methods"} diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs index 62b4b33..0f67546 100644 --- a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs @@ -1,5 +1,5 @@ use risc0_zkvm::{guest::env, serde::to_vec}; -use toy_example_core::{ +use core::{ account::Account, compute_nullifier, hash, input::InputVisibiility, is_in_tree, types::Nonce, }; diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index d1f98dd..f9b167f 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,12 +1,12 @@ -use outer_methods::{OUTER_ELF, OUTER_ID}; -use program::Program; -use rand::{rngs::OsRng, Rng}; -use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; -use toy_example_core::{ +use core::{ account::Account, input::InputVisibiility, types::{Commitment, Nonce, Nullifier}, }; +use outer_methods::{OUTER_ELF, OUTER_ID}; +use program::Program; +use rand::{rngs::OsRng, Rng}; +use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; pub mod program; diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml index 81e568f..f1dffe8 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml +++ b/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "toy_example" +name = "transfer" version = "0.1.0" edition = "2021" @@ -7,4 +7,4 @@ edition = "2021" [dependencies] risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] } -toy-example-core = {path = "../../core" } +core = { path = "../../core" } diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs index 1fe7de8..f3f19b4 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -1,6 +1,9 @@ -use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec}; -use toy_example_core::account::Account; - +use core::account::Account; +use risc0_zkvm::{ + guest::env, + serde::to_vec, + sha::{Impl, Sha256}, +}; /// A transfer of balance program. /// To be used both in public and private contexts. From f6a8a11f98799433c0a5f3d35e245a556c3801dc Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 10:22:24 -0300 Subject: [PATCH 26/83] make inputs a vector to allow variable number of input accounts --- risc0-selective-privacy-poc/src/lib.rs | 5 ++--- .../transfer_methods/guest/src/bin/transfer.rs | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index f9b167f..2d07b59 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -20,9 +20,8 @@ fn write_inputs( instruction_data: &P::InstructionData, env_builder: &mut ExecutorEnvBuilder, ) -> Result<(), ()> { - for account in input_accounts { - env_builder.write(&account).map_err(|_| ())?; - } + let input_accounts = input_accounts.to_vec(); + env_builder.write(&input_accounts).map_err(|_| ())?; env_builder.write(&instruction_data).map_err(|_| ())?; Ok(()) } diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs index f3f19b4..1d786ee 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -8,10 +8,12 @@ use risc0_zkvm::{ /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { - let sender: Account = env::read(); - let receiver: Account = env::read(); + let mut input_accounts: Vec = env::read(); let balance_to_move: u128 = env::read(); + assert_eq!(input_accounts.len(), 2); + let [sender, receiver] = input_accounts.try_into().unwrap(); + // Check sender has enough balance assert!(sender.balance >= balance_to_move); From 8234c0cb68fb6c53b6a8419195b7b74bf5c37a3b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 11:07:18 -0300 Subject: [PATCH 27/83] add tansfer multiple program --- risc0-selective-privacy-poc/Cargo.toml | 1 + .../examples/private_execution.rs | 20 +++++---- .../examples/public_execution.rs | 25 +++++++---- .../src/program/mod.rs | 9 ++++ .../transfer_multiple_methods/Cargo.toml | 10 +++++ .../transfer_multiple_methods/build.rs | 3 ++ .../guest/Cargo.toml | 10 +++++ .../guest/src/bin/transfer_multiple.rs | 42 +++++++++++++++++++ .../transfer_multiple_methods/src/lib.rs | 1 + 9 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml create mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/build.rs create mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml create mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/guest/src/bin/transfer_multiple.rs create mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index e159416..0228bc8 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" risc0-zkvm = "2.2" core = { path = "core" } transfer-methods = { path = "transfer_methods" } +transfer-multiple-methods = { path = "transfer_multiple_methods" } outer-methods = { path = "outer_methods" } serde = "1.0" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 501bcff..36f25c4 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,12 +1,12 @@ -use nssa::program::TransferProgram; -use outer_methods::OUTER_ID; -use sparse_merkle_tree::SparseMerkleTree; use core::{ account::Account, bytes_to_words, input::InputVisibiility, types::{Address, AuthenticationPath, Commitment, Nullifier}, }; +use nssa::program::TransferMultipleProgram; +use outer_methods::OUTER_ID; +use sparse_merkle_tree::SparseMerkleTree; fn mint_fresh_account(address: Address) -> Account { let nonce = [0; 8]; @@ -39,17 +39,21 @@ fn main() { let balance_to_move: u128 = 3; // This is the new private account (UTXO) being minted by this private execution. (The `receiver_address` would be in UTXO's terminology) - let receiver_address = [99; 8]; - let receiver = mint_fresh_account(receiver_address); + let receiver_address_1 = [99; 8]; + let receiver_1 = mint_fresh_account(receiver_address_1); + + let receiver_address_2 = [100; 8]; + let receiver_2 = mint_fresh_account(receiver_address_2); let visibilities = vec![ InputVisibiility::Private(Some((sender_private_key, auth_path))), InputVisibiility::Private(None), + InputVisibiility::Private(None), ]; - let receipt = nssa::execute_and_prove_privacy_execution::( - &[sender, receiver], - &balance_to_move, + let receipt = nssa::execute_and_prove_privacy_execution::( + &[sender, receiver_1, receiver_2], + &vec![30, 40], &visibilities, root, ) diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 1095886..03d3256 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,10 +1,9 @@ use risc0_zkvm::{default_executor, ExecutorEnv}; use core::account::Account; -use transfer_methods::TRANSFER_ELF; use nssa; -use nssa::program::TransferProgram; +use nssa::program::TransferMultipleProgram; /// A public execution. /// This would be executed by the runtime after checking that @@ -18,23 +17,33 @@ pub fn main() { }; // Account fetched from the chain state with 900 in its balance. - let receiver = { + let receiver_1 = { let mut account = Account::new([6; 8], [99; 8]); account.balance = 900; account }; - let balance_to_move: u128 = 3; + let receiver_2 = { + let mut account = Account::new([6; 8], [99; 8]); + account.balance = 500; + account + }; + + let balance_to_move = vec![10, 20]; let inputs_outputs = - nssa::execute::(&[sender, receiver], &balance_to_move).unwrap(); + nssa::execute::(&[sender, receiver_1, receiver_2], &balance_to_move).unwrap(); println!( "sender_before: {:?}, sender_after: {:?}", - inputs_outputs[0], inputs_outputs[2] + inputs_outputs[0], inputs_outputs[3] ); println!( - "receiver_before: {:?}, receiver_after: {:?}", - inputs_outputs[1], inputs_outputs[3], + "receiver_1_before: {:?}, receiver_1_after: {:?}", + inputs_outputs[1], inputs_outputs[4], + ); + println!( + "receiver_2_before: {:?}, receiver_2_after: {:?}", + inputs_outputs[2], inputs_outputs[5], ); } diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index a9af5fc..aafe0c7 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; +use transfer_multiple_methods::{TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}; pub trait Program { const PROGRAM_ID: [u32; 8]; @@ -14,3 +15,11 @@ impl Program for TransferProgram { // Amount to transfer type InstructionData = u128; } + +pub struct TransferMultipleProgram; +impl Program for TransferMultipleProgram { + const PROGRAM_ID: [u32; 8] = TRANSFER_MULTIPLE_ID; + const PROGRAM_ELF: &[u8] = TRANSFER_MULTIPLE_ELF; + // Amounts to transfer + type InstructionData = Vec; +} diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml b/risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml new file mode 100644 index 0000000..6510d09 --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "transfer-multiple-methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "2.2" } + +[package.metadata.risc0] +methods = ["guest"] diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/build.rs b/risc0-selective-privacy-poc/transfer_multiple_methods/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_multiple_methods/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml new file mode 100644 index 0000000..2a7395e --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "transfer-multiple" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] } +core = { path = "../../core" } diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/guest/src/bin/transfer_multiple.rs b/risc0-selective-privacy-poc/transfer_multiple_methods/guest/src/bin/transfer_multiple.rs new file mode 100644 index 0000000..03ef52b --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_multiple_methods/guest/src/bin/transfer_multiple.rs @@ -0,0 +1,42 @@ +use core::account::Account; +use risc0_zkvm::{ + guest::env, + serde::to_vec, + sha::{Impl, Sha256}, +}; + +/// A transfer of balance program with multiple recipients. +/// To be used both in public and private contexts. +fn main() { + let mut input_accounts: Vec = env::read(); + let target_balances: Vec = env::read(); + + assert!(input_accounts.len() > 1); + assert_eq!(target_balances.len() + 1, input_accounts.len()); + + let receivers = input_accounts.split_off(1); + let sender = input_accounts.pop().unwrap(); + let total_balance_to_move = target_balances.iter().sum(); + + // Check sender has enough balance + assert!(sender.balance >= total_balance_to_move); + + // Create accounts post states, with updated balances + let mut sender_post = sender.clone(); + let mut receivers_post = receivers.clone(); + + sender_post.balance -= total_balance_to_move; + for (receiver, balance_for_receiver) in receivers_post.iter_mut().zip(target_balances) { + receiver.balance += balance_for_receiver; + } + + // Flatten pre and post states for output + let inputs_outputs: Vec = vec![sender] + .into_iter() + .chain(receivers) + .chain(vec![sender_post]) + .chain(receivers_post) + .collect(); + + env::commit(&inputs_outputs); +} diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs b/risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); From d56298e42c371ff2c1ad09c4f4849c82438353e3 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 11:29:35 -0300 Subject: [PATCH 28/83] =?UTF-8?q?add=20pi=C3=B1ata=20program?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/program/mod.rs | 14 ++++++-- .../transfer_methods/guest/src/bin/pinata.rs | 33 +++++++++++++++++++ .../guest/src/bin/transfer.rs | 8 ++--- 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 risc0-selective-privacy-poc/transfer_methods/guest/src/bin/pinata.rs diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index aafe0c7..bc0d6a0 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use transfer_methods::{TRANSFER_ELF, TRANSFER_ID}; +use transfer_methods::{PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID}; use transfer_multiple_methods::{TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}; pub trait Program { @@ -12,7 +12,7 @@ pub struct TransferProgram; impl Program for TransferProgram { const PROGRAM_ID: [u32; 8] = TRANSFER_ID; const PROGRAM_ELF: &[u8] = TRANSFER_ELF; - // Amount to transfer + /// Amount to transfer type InstructionData = u128; } @@ -20,6 +20,14 @@ pub struct TransferMultipleProgram; impl Program for TransferMultipleProgram { const PROGRAM_ID: [u32; 8] = TRANSFER_MULTIPLE_ID; const PROGRAM_ELF: &[u8] = TRANSFER_MULTIPLE_ELF; - // Amounts to transfer + /// Amounts to transfer type InstructionData = Vec; } + +pub struct PinataProgram; +impl Program for PinataProgram { + const PROGRAM_ID: [u32; 8] = TRANSFER_MULTIPLE_ID; + const PROGRAM_ELF: &[u8] = TRANSFER_MULTIPLE_ELF; + /// Preimage of target hash to win price + type InstructionData = [u32; 8]; +} diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/pinata.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/pinata.rs new file mode 100644 index 0000000..95c1f55 --- /dev/null +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/pinata.rs @@ -0,0 +1,33 @@ +use core::{account::Account, hash}; +use risc0_zkvm::guest::env; + +const TARGET_HASH: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +const PINATA_ACCOUNT_ADDR: [u32; 8] = [0xcafe; 8]; +const PINATA_PRICE: u128 = 100; + +/// A Piñata program +/// To be used both in public and private contexts. +fn main() { + let mut input_accounts: Vec = env::read(); + let preimage: Vec = env::read(); + + assert_eq!(input_accounts.len(), 2); + let [pinata_account] = input_accounts.split_off(1).try_into().unwrap(); + let [winner_account] = input_accounts.try_into().unwrap(); + + assert_eq!(pinata_account.address, PINATA_ACCOUNT_ADDR); + assert_eq!(pinata_account.balance, PINATA_PRICE); + assert_eq!(hash(&preimage), TARGET_HASH); + + let mut winner_account_post = winner_account.clone(); + let mut pinata_account_post = pinata_account.clone(); + pinata_account_post.balance -= PINATA_PRICE; + winner_account_post.balance += PINATA_PRICE; + + env::commit(&vec![ + winner_account, + pinata_account, + winner_account_post, + pinata_account_post, + ]); +} diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs index 1d786ee..26b3d1e 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs @@ -1,14 +1,10 @@ use core::account::Account; -use risc0_zkvm::{ - guest::env, - serde::to_vec, - sha::{Impl, Sha256}, -}; +use risc0_zkvm::guest::env; /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { - let mut input_accounts: Vec = env::read(); + let input_accounts: Vec = env::read(); let balance_to_move: u128 = env::read(); assert_eq!(input_accounts.len(), 2); From 876e525c9210955a3c3646709572ff5a4158f8f2 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 11:31:04 -0300 Subject: [PATCH 29/83] wip --- .../guest/src/bin/transfer_multiple.rs | 6 +----- .../transfer_multiple_methods/Cargo.toml | 10 ---------- .../transfer_multiple_methods/build.rs | 3 --- .../transfer_multiple_methods/guest/Cargo.toml | 10 ---------- .../transfer_multiple_methods/src/lib.rs | 1 - 5 files changed, 1 insertion(+), 29 deletions(-) rename risc0-selective-privacy-poc/{transfer_multiple_methods => transfer_methods}/guest/src/bin/transfer_multiple.rs (93%) delete mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml delete mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/build.rs delete mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml delete mode 100644 risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/guest/src/bin/transfer_multiple.rs b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer_multiple.rs similarity index 93% rename from risc0-selective-privacy-poc/transfer_multiple_methods/guest/src/bin/transfer_multiple.rs rename to risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer_multiple.rs index 03ef52b..60b5c34 100644 --- a/risc0-selective-privacy-poc/transfer_multiple_methods/guest/src/bin/transfer_multiple.rs +++ b/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer_multiple.rs @@ -1,9 +1,5 @@ use core::account::Account; -use risc0_zkvm::{ - guest::env, - serde::to_vec, - sha::{Impl, Sha256}, -}; +use risc0_zkvm::guest::env; /// A transfer of balance program with multiple recipients. /// To be used both in public and private contexts. diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml b/risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml deleted file mode 100644 index 6510d09..0000000 --- a/risc0-selective-privacy-poc/transfer_multiple_methods/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "transfer-multiple-methods" -version = "0.1.0" -edition = "2021" - -[build-dependencies] -risc0-build = { version = "2.2" } - -[package.metadata.risc0] -methods = ["guest"] diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/build.rs b/risc0-selective-privacy-poc/transfer_multiple_methods/build.rs deleted file mode 100644 index 08a8a4e..0000000 --- a/risc0-selective-privacy-poc/transfer_multiple_methods/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - risc0_build::embed_methods(); -} diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml deleted file mode 100644 index 2a7395e..0000000 --- a/risc0-selective-privacy-poc/transfer_multiple_methods/guest/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "transfer-multiple" -version = "0.1.0" -edition = "2021" - -[workspace] - -[dependencies] -risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] } -core = { path = "../../core" } diff --git a/risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs b/risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs deleted file mode 100644 index 1bdb308..0000000 --- a/risc0-selective-privacy-poc/transfer_multiple_methods/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/methods.rs")); From 196c375d213890acb57cdea106aaa282409ecad7 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 11:34:56 -0300 Subject: [PATCH 30/83] wip --- risc0-selective-privacy-poc/Cargo.toml | 4 +--- .../examples/private_execution.rs | 2 +- .../examples/public_execution.rs | 9 ++++++--- .../outer_methods/guest/Cargo.toml | 11 ----------- .../{outer_methods => program_methods}/Cargo.toml | 2 +- .../{outer_methods => program_methods}/build.rs | 0 .../guest/Cargo.toml | 2 +- .../guest/src/bin/outer.rs | 0 .../guest/src/bin/pinata.rs | 0 .../guest/src/bin/transfer.rs | 0 .../guest/src/bin/transfer_multiple.rs | 0 .../{outer_methods => program_methods}/src/lib.rs | 0 risc0-selective-privacy-poc/src/lib.rs | 2 +- risc0-selective-privacy-poc/src/program/mod.rs | 5 +++-- .../transfer_methods/Cargo.toml | 10 ---------- risc0-selective-privacy-poc/transfer_methods/build.rs | 3 --- .../transfer_methods/src/lib.rs | 1 - 17 files changed, 14 insertions(+), 37 deletions(-) delete mode 100644 risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml rename risc0-selective-privacy-poc/{outer_methods => program_methods}/Cargo.toml (85%) rename risc0-selective-privacy-poc/{outer_methods => program_methods}/build.rs (100%) rename risc0-selective-privacy-poc/{transfer_methods => program_methods}/guest/Cargo.toml (91%) rename risc0-selective-privacy-poc/{outer_methods => program_methods}/guest/src/bin/outer.rs (100%) rename risc0-selective-privacy-poc/{transfer_methods => program_methods}/guest/src/bin/pinata.rs (100%) rename risc0-selective-privacy-poc/{transfer_methods => program_methods}/guest/src/bin/transfer.rs (100%) rename risc0-selective-privacy-poc/{transfer_methods => program_methods}/guest/src/bin/transfer_multiple.rs (100%) rename risc0-selective-privacy-poc/{outer_methods => program_methods}/src/lib.rs (100%) delete mode 100644 risc0-selective-privacy-poc/transfer_methods/Cargo.toml delete mode 100644 risc0-selective-privacy-poc/transfer_methods/build.rs delete mode 100644 risc0-selective-privacy-poc/transfer_methods/src/lib.rs diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index 0228bc8..055271f 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -6,9 +6,7 @@ edition = "2021" [dependencies] risc0-zkvm = "2.2" core = { path = "core" } -transfer-methods = { path = "transfer_methods" } -transfer-multiple-methods = { path = "transfer_multiple_methods" } -outer-methods = { path = "outer_methods" } +program-methods = { path = "program_methods" } serde = "1.0" tracing-subscriber = { version = "0.3", features = ["env-filter"] } rand = "0.8" diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 36f25c4..c8ee654 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -5,8 +5,8 @@ use core::{ types::{Address, AuthenticationPath, Commitment, Nullifier}, }; use nssa::program::TransferMultipleProgram; -use outer_methods::OUTER_ID; use sparse_merkle_tree::SparseMerkleTree; +use program_methods::OUTER_ID; fn mint_fresh_account(address: Address) -> Account { let nonce = [0; 8]; diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 03d3256..a72a490 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,5 +1,5 @@ -use risc0_zkvm::{default_executor, ExecutorEnv}; use core::account::Account; +use risc0_zkvm::{default_executor, ExecutorEnv}; use nssa; @@ -31,8 +31,11 @@ pub fn main() { let balance_to_move = vec![10, 20]; - let inputs_outputs = - nssa::execute::(&[sender, receiver_1, receiver_2], &balance_to_move).unwrap(); + let inputs_outputs = nssa::execute::( + &[sender, receiver_1, receiver_2], + &balance_to_move, + ) + .unwrap(); println!( "sender_before: {:?}, sender_after: {:?}", diff --git a/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml deleted file mode 100644 index 4585a49..0000000 --- a/risc0-selective-privacy-poc/outer_methods/guest/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "outer" -version = "0.1.0" -edition = "2021" - -[workspace] - -[dependencies] -risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] } -core = {path = "../../core" } -transfer-methods = {path = "../../transfer_methods"} diff --git a/risc0-selective-privacy-poc/outer_methods/Cargo.toml b/risc0-selective-privacy-poc/program_methods/Cargo.toml similarity index 85% rename from risc0-selective-privacy-poc/outer_methods/Cargo.toml rename to risc0-selective-privacy-poc/program_methods/Cargo.toml index 1f5e9ec..24c192c 100644 --- a/risc0-selective-privacy-poc/outer_methods/Cargo.toml +++ b/risc0-selective-privacy-poc/program_methods/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "outer-methods" +name = "program-methods" version = "0.1.0" edition = "2021" diff --git a/risc0-selective-privacy-poc/outer_methods/build.rs b/risc0-selective-privacy-poc/program_methods/build.rs similarity index 100% rename from risc0-selective-privacy-poc/outer_methods/build.rs rename to risc0-selective-privacy-poc/program_methods/build.rs diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml b/risc0-selective-privacy-poc/program_methods/guest/Cargo.toml similarity index 91% rename from risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml rename to risc0-selective-privacy-poc/program_methods/guest/Cargo.toml index f1dffe8..5263dd9 100644 --- a/risc0-selective-privacy-poc/transfer_methods/guest/Cargo.toml +++ b/risc0-selective-privacy-poc/program_methods/guest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "transfer" +name = "programs" version = "0.1.0" edition = "2021" diff --git a/risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs similarity index 100% rename from risc0-selective-privacy-poc/outer_methods/guest/src/bin/outer.rs rename to risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/pinata.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs similarity index 100% rename from risc0-selective-privacy-poc/transfer_methods/guest/src/bin/pinata.rs rename to risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs similarity index 100% rename from risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer.rs rename to risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs diff --git a/risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer_multiple.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs similarity index 100% rename from risc0-selective-privacy-poc/transfer_methods/guest/src/bin/transfer_multiple.rs rename to risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs diff --git a/risc0-selective-privacy-poc/outer_methods/src/lib.rs b/risc0-selective-privacy-poc/program_methods/src/lib.rs similarity index 100% rename from risc0-selective-privacy-poc/outer_methods/src/lib.rs rename to risc0-selective-privacy-poc/program_methods/src/lib.rs diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 2d07b59..a44ae55 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -3,10 +3,10 @@ use core::{ input::InputVisibiility, types::{Commitment, Nonce, Nullifier}, }; -use outer_methods::{OUTER_ELF, OUTER_ID}; use program::Program; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; +use program_methods::{OUTER_ELF, OUTER_ID}; pub mod program; diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index bc0d6a0..af1ede3 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; -use transfer_methods::{PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID}; -use transfer_multiple_methods::{TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}; +use program_methods::{ + PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, {TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}, +}; pub trait Program { const PROGRAM_ID: [u32; 8]; diff --git a/risc0-selective-privacy-poc/transfer_methods/Cargo.toml b/risc0-selective-privacy-poc/transfer_methods/Cargo.toml deleted file mode 100644 index 523dc0a..0000000 --- a/risc0-selective-privacy-poc/transfer_methods/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "transfer-methods" -version = "0.1.0" -edition = "2021" - -[build-dependencies] -risc0-build = { version = "2.2" } - -[package.metadata.risc0] -methods = ["guest"] diff --git a/risc0-selective-privacy-poc/transfer_methods/build.rs b/risc0-selective-privacy-poc/transfer_methods/build.rs deleted file mode 100644 index 08a8a4e..0000000 --- a/risc0-selective-privacy-poc/transfer_methods/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - risc0_build::embed_methods(); -} diff --git a/risc0-selective-privacy-poc/transfer_methods/src/lib.rs b/risc0-selective-privacy-poc/transfer_methods/src/lib.rs deleted file mode 100644 index 1bdb308..0000000 --- a/risc0-selective-privacy-poc/transfer_methods/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/methods.rs")); From 34731a99504f64b057e4ba12f9d0a96c2ca79781 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 12:46:57 -0300 Subject: [PATCH 31/83] sequencer wip --- risc0-selective-privacy-poc/core/src/types.rs | 1 + .../examples/private_execution.rs | 2 +- .../examples/sequencer.rs | 7 ++ .../examples/sequencer_mock/mod.rs | 102 ++++++++++++++++++ .../program_methods/guest/src/bin/outer.rs | 10 +- .../src/program/mod.rs | 12 ++- 6 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 risc0-selective-privacy-poc/examples/sequencer.rs create mode 100644 risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs diff --git a/risc0-selective-privacy-poc/core/src/types.rs b/risc0-selective-privacy-poc/core/src/types.rs index a9608dc..8aa04db 100644 --- a/risc0-selective-privacy-poc/core/src/types.rs +++ b/risc0-selective-privacy-poc/core/src/types.rs @@ -4,3 +4,4 @@ pub type Address = [u32; 8]; pub type Nonce = [u32; 8]; pub type Key = [u32; 8]; pub type AuthenticationPath = [[u32; 8]; 32]; +pub type ProgramId = [u32; 8]; diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index c8ee654..a6b6721 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -5,8 +5,8 @@ use core::{ types::{Address, AuthenticationPath, Commitment, Nullifier}, }; use nssa::program::TransferMultipleProgram; -use sparse_merkle_tree::SparseMerkleTree; use program_methods::OUTER_ID; +use sparse_merkle_tree::SparseMerkleTree; fn mint_fresh_account(address: Address) -> Account { let nonce = [0; 8]; diff --git a/risc0-selective-privacy-poc/examples/sequencer.rs b/risc0-selective-privacy-poc/examples/sequencer.rs new file mode 100644 index 0000000..da98a47 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/sequencer.rs @@ -0,0 +1,7 @@ +use crate::sequencer_mock::MockedSequencer; + +mod sequencer_mock; + +fn main() { + let sequencer = MockedSequencer::new(); +} diff --git a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs new file mode 100644 index 0000000..53b9985 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs @@ -0,0 +1,102 @@ +use core::{ + account::Account, + types::{Address, Key, Nullifier, ProgramId}, +}; +use std::collections::{HashMap, HashSet}; + +use nssa; +use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; +use sparse_merkle_tree::SparseMerkleTree; + +pub struct MockedSequencer { + accounts: HashMap, + commitment_tree: SparseMerkleTree, + nullifier_set: HashSet, + deployed_program_ids: HashSet, +} + +const ACCOUNTS_PRIVATE_KEYS: [Key; 5] = [[1; 8], [2; 8], [3; 8], [4; 8], [5; 8]]; +const ACCOUNTS_INITIAL_BALANCES: [u128; 5] = [100, 3, 900, 44, 0]; +const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; + +impl MockedSequencer { + pub fn new() -> Self { + let mut accounts: HashMap = ACCOUNTS_PRIVATE_KEYS + .iter() + .cloned() + .zip(ACCOUNTS_INITIAL_BALANCES) + .map(|(key, initial_balance)| { + let mut this = Account::new_from_private_key(key, [0; 8]); + this.balance = initial_balance; + this + }) + .map(|account| (account.address, account)) + .collect(); + + let pinata_account = { + let mut this = Account::new([0xcafe; 8], [0; 8]); + this.balance = 100; + this + }; + accounts.insert(pinata_account.address, pinata_account); + + let commitment_tree = SparseMerkleTree::new_empty(); + let nullifier_set = HashSet::new(); + Self { + accounts, + commitment_tree, + nullifier_set, + deployed_program_ids: DEPLOYED_PROGRAM_IDS.iter().collect(), + } + } + + pub fn get_account(&self, address: &Address) -> Option { + self.accounts.get(address).cloned() + } + + pub fn invoke_program_public( + &mut self, + program_id: ProgramId, + input_account_addresses: &[Address], + instruction_data: &P::InstructionData, + ) -> Result<(), ()> { + let input_accounts: Vec = input_account_addresses + .iter() + .map(|address| self.get_account(address).ok_or(|_| ())?) + .collect(); + let inputs_outputs = nssa::execute(input_accounts, instruction_data)?; + + self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs)? + } + + fn inputs_outputs_are_consistent( + &self, + input_accounts: &[Account], + inputs_outputs: &[Account], + ) -> Result<(), ()> { + let num_inputs = input_accounts.len(); + if inputs_outputs.len() != num_inputs * 2 { + return Err(()); + } + + let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); + if claimed_accounts_pre != input_accounts { + return Err(()); + } + + for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { + if account_pre.address != account_post.address { + return Err(()); + } + if account_pre.nonce != account_post.nonce { + return Err(()); + } + } + let accounts_pre_total_balance = input_accounts.iter().map(|account| account.balance).sum(); + let accounts_post_total_balance = accounts_post.iter().map(|account| account.balance).sum(); + if accounts_pre_total_balance != accounts_post_total_balance { + return Err(()); + } + return Ok(()); + } +} diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 0f67546..8fadb3e 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,7 +1,11 @@ -use risc0_zkvm::{guest::env, serde::to_vec}; use core::{ - account::Account, compute_nullifier, hash, input::InputVisibiility, is_in_tree, types::Nonce, + account::Account, + compute_nullifier, hash, + input::InputVisibiility, + is_in_tree, + types::{Nonce, ProgramId}, }; +use risc0_zkvm::{guest::env, serde::to_vec}; /// Private execution logic. /// Circuit for proving correct execution of some program with program id @@ -33,7 +37,7 @@ fn main() { assert_eq!(output_nonces.len() as u32, num_inputs); let commitment_tree_root: [u32; 8] = env::read(); - let program_id: [u32; 8] = env::read(); + let program_id: ProgramId = env::read(); // Verify pre states and post states of accounts are consistent // with the execution of the `program_id`` program diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index af1ede3..ea6f896 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -1,17 +1,19 @@ -use serde::{Deserialize, Serialize}; +use core::types::ProgramId; + use program_methods::{ PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, {TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}, }; +use serde::{Deserialize, Serialize}; pub trait Program { - const PROGRAM_ID: [u32; 8]; + const PROGRAM_ID: ProgramId; const PROGRAM_ELF: &[u8]; type InstructionData: Serialize + for<'de> Deserialize<'de>; } pub struct TransferProgram; impl Program for TransferProgram { - const PROGRAM_ID: [u32; 8] = TRANSFER_ID; + const PROGRAM_ID: ProgramId = TRANSFER_ID; const PROGRAM_ELF: &[u8] = TRANSFER_ELF; /// Amount to transfer type InstructionData = u128; @@ -19,7 +21,7 @@ impl Program for TransferProgram { pub struct TransferMultipleProgram; impl Program for TransferMultipleProgram { - const PROGRAM_ID: [u32; 8] = TRANSFER_MULTIPLE_ID; + const PROGRAM_ID: ProgramId = TRANSFER_MULTIPLE_ID; const PROGRAM_ELF: &[u8] = TRANSFER_MULTIPLE_ELF; /// Amounts to transfer type InstructionData = Vec; @@ -27,7 +29,7 @@ impl Program for TransferMultipleProgram { pub struct PinataProgram; impl Program for PinataProgram { - const PROGRAM_ID: [u32; 8] = TRANSFER_MULTIPLE_ID; + const PROGRAM_ID: ProgramId = TRANSFER_MULTIPLE_ID; const PROGRAM_ELF: &[u8] = TRANSFER_MULTIPLE_ELF; /// Preimage of target hash to win price type InstructionData = [u32; 8]; From 1f5eeba2a34cffcc3607a8b3b90a4cbbc074c42a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 13:34:12 -0300 Subject: [PATCH 32/83] wip --- risc0-selective-privacy-poc/Cargo.toml | 1 + .../examples/sequencer.rs | 21 +++++- .../examples/sequencer_mock/mod.rs | 67 ++++++++++++++----- .../sparse_merkle_tree/src/lib.rs | 4 ++ risc0-selective-privacy-poc/src/lib.rs | 4 +- 5 files changed, 80 insertions(+), 17 deletions(-) diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index 055271f..06b2f83 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -16,3 +16,4 @@ sparse-merkle-tree = { path = "./sparse_merkle_tree/" } cuda = ["risc0-zkvm/cuda"] default = [] prove = ["risc0-zkvm/prove"] + diff --git a/risc0-selective-privacy-poc/examples/sequencer.rs b/risc0-selective-privacy-poc/examples/sequencer.rs index da98a47..e1e2506 100644 --- a/risc0-selective-privacy-poc/examples/sequencer.rs +++ b/risc0-selective-privacy-poc/examples/sequencer.rs @@ -1,7 +1,26 @@ +use nssa::program::TransferProgram; + use crate::sequencer_mock::MockedSequencer; mod sequencer_mock; fn main() { - let sequencer = MockedSequencer::new(); + let mut sequencer = MockedSequencer::new(); + let addresses = sequencer.addresses(); + println!("Initial balances"); + sequencer.print(); + + // A public execution of the Transfer Program + let sender_addr = addresses[0]; + let receiver_addr = addresses[1]; + sequencer + .invoke_public::(&[sender_addr, receiver_addr], 10) + .unwrap(); + + println!("Balances after transfer"); + sequencer.print(); + + + // A private execution of the Transfer Program + } diff --git a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs index 53b9985..b74e73f 100644 --- a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs +++ b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs @@ -2,26 +2,26 @@ use core::{ account::Account, types::{Address, Key, Nullifier, ProgramId}, }; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use nssa; use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; use sparse_merkle_tree::SparseMerkleTree; pub struct MockedSequencer { - accounts: HashMap, + accounts: BTreeMap, commitment_tree: SparseMerkleTree, nullifier_set: HashSet, deployed_program_ids: HashSet, } -const ACCOUNTS_PRIVATE_KEYS: [Key; 5] = [[1; 8], [2; 8], [3; 8], [4; 8], [5; 8]]; -const ACCOUNTS_INITIAL_BALANCES: [u128; 5] = [100, 3, 900, 44, 0]; +const ACCOUNTS_PRIVATE_KEYS: [Key; 3] = [[1; 8], [2; 8], [3; 8]]; +const ACCOUNTS_INITIAL_BALANCES: [u128; 3] = [100, 1337, 0]; const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; impl MockedSequencer { pub fn new() -> Self { - let mut accounts: HashMap = ACCOUNTS_PRIVATE_KEYS + let mut accounts: BTreeMap = ACCOUNTS_PRIVATE_KEYS .iter() .cloned() .zip(ACCOUNTS_INITIAL_BALANCES) @@ -46,7 +46,7 @@ impl MockedSequencer { accounts, commitment_tree, nullifier_set, - deployed_program_ids: DEPLOYED_PROGRAM_IDS.iter().collect(), + deployed_program_ids: DEPLOYED_PROGRAM_IDS.iter().cloned().collect(), } } @@ -54,19 +54,32 @@ impl MockedSequencer { self.accounts.get(address).cloned() } - pub fn invoke_program_public( + pub fn invoke_public( &mut self, - program_id: ProgramId, input_account_addresses: &[Address], - instruction_data: &P::InstructionData, + instruction_data: P::InstructionData, ) -> Result<(), ()> { + // Fetch accounts let input_accounts: Vec = input_account_addresses .iter() - .map(|address| self.get_account(address).ok_or(|_| ())?) - .collect(); - let inputs_outputs = nssa::execute(input_accounts, instruction_data)?; + .map(|address| self.get_account(address).ok_or(())) + .collect::>()?; - self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs)? + // Execute + let inputs_outputs = nssa::execute::

(&input_accounts, &instruction_data)?; + + // Consistency checks + self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs)?; + + // Update accounts + inputs_outputs + .into_iter() + .skip(input_accounts.len()) + .for_each(|account_post_state| { + self.accounts + .insert(account_post_state.address, account_post_state); + }); + Ok(()) } fn inputs_outputs_are_consistent( @@ -91,12 +104,36 @@ impl MockedSequencer { if account_pre.nonce != account_post.nonce { return Err(()); } + // Redundant with previous checks, but better make it explicit. + if !self.accounts.contains_key(&account_post.address) { + return Err(()); + } } - let accounts_pre_total_balance = input_accounts.iter().map(|account| account.balance).sum(); - let accounts_post_total_balance = accounts_post.iter().map(|account| account.balance).sum(); + let accounts_pre_total_balance: u128 = + input_accounts.iter().map(|account| account.balance).sum(); + let accounts_post_total_balance: u128 = + accounts_post.iter().map(|account| account.balance).sum(); if accounts_pre_total_balance != accounts_post_total_balance { return Err(()); } return Ok(()); } + + pub fn addresses(&self) -> Vec

{ + self.accounts.keys().cloned().collect() + } + + pub fn print(&self) { + println!("{:<20} | {:>10}", "Address (first u32)", "Balance"); + println!("{:-<20}-+-{:-<10}", "", ""); + + for account in self.accounts.values() { + println!("{:<20x} | {:>10}", account.address[0], account.balance); + } + println!("{:-<20}-+-{:-<10}", "", ""); + println!("Commitments: {:?}", self.commitment_tree.values()); + println!("Nullifiers: {:?}", self.nullifier_set); + println!(""); + println!(""); + } } diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs index 8d09d9c..3822116 100644 --- a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs @@ -111,6 +111,10 @@ impl SparseMerkleTree { path } + pub fn values(&self) -> HashSet { + self.values.clone() + } + pub fn verify_value_is_in_set(value: u32, path: [[u8; 32]; 32], root: [u8; 32]) -> bool { let mut hash = ONE_HASH; let mut current_index = value; diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index a44ae55..c7f4f15 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -3,13 +3,15 @@ use core::{ input::InputVisibiility, types::{Commitment, Nonce, Nullifier}, }; -use program::Program; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; use program_methods::{OUTER_ELF, OUTER_ID}; pub mod program; +pub use program::Program; + + pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) From 7ae7135ff719818bb02b39930ae3d322c9d6d104 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 14:02:54 -0300 Subject: [PATCH 33/83] add verify privacy method to mocked sequencer --- .../examples/sequencer_mock/mod.rs | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs index b74e73f..2a4398d 100644 --- a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs +++ b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs @@ -1,11 +1,13 @@ use core::{ account::Account, - types::{Address, Key, Nullifier, ProgramId}, + bytes_to_words, + types::{Address, Commitment, Key, Nullifier, ProgramId}, }; use std::collections::{BTreeMap, HashSet}; use nssa; use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; +use risc0_zkvm::Receipt; use sparse_merkle_tree::SparseMerkleTree; pub struct MockedSequencer { @@ -54,6 +56,64 @@ impl MockedSequencer { self.accounts.get(address).cloned() } + pub fn invoke_privacy_execution( + &mut self, + receipt: Receipt, + public_inputs_outputs: &[Account], + nullifiers: &[Nullifier], + commitments: &[Commitment], + ) -> Result<(), ()> { + let commitments_tree_root = bytes_to_words(&self.commitment_tree.root()); + if public_inputs_outputs.len() % 2 != 0 { + return Err(()); + } + + let num_input_public = public_inputs_outputs.len() >> 1; + for account in public_inputs_outputs.iter() { + let current_account = self.get_account(&account.address).ok_or(())?; + if ¤t_account != account { + return Err(()); + } + } + + // Check that nullifiers have not been added before + if nullifiers + .iter() + .any(|nullifier| self.nullifier_set.contains(nullifier)) + { + return Err(()); + } + + // Check that commitments are new too + if commitments + .iter() + .any(|commitment| self.commitment_tree.values().contains(commitment)) + { + return Err(()); + } + + // Verify consistency between public accounts, nullifiers and commitments + nssa::verify_privacy_execution( + receipt, + public_inputs_outputs, + nullifiers, + commitments, + &commitments_tree_root, + )?; + + // Update accounts + public_inputs_outputs + .iter() + .cloned() + .skip(num_input_public) + .for_each(|account_post_state| { + self.accounts + .insert(account_post_state.address, account_post_state); + }); + + Ok(()) + } + pub fn invoke_public( &mut self, input_account_addresses: &[Address], From 1bcb3a05e74118ca1c7325d941ca130dd2774536 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 15:34:35 -0300 Subject: [PATCH 34/83] add tx types --- .../examples/private_execution.rs | 2 +- .../examples/sequencer.rs | 154 +++++++++++++++++- .../examples/sequencer_mock/mod.rs | 38 ++++- risc0-selective-privacy-poc/src/lib.rs | 9 +- 4 files changed, 186 insertions(+), 17 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index a6b6721..433fac3 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -51,7 +51,7 @@ fn main() { InputVisibiility::Private(None), ]; - let receipt = nssa::execute_and_prove_privacy_execution::( + let (receipt, _) = nssa::invoke_privacy_execution::( &[sender, receiver_1, receiver_2], &vec![30, 40], &visibilities, diff --git a/risc0-selective-privacy-poc/examples/sequencer.rs b/risc0-selective-privacy-poc/examples/sequencer.rs index e1e2506..e947f6c 100644 --- a/risc0-selective-privacy-poc/examples/sequencer.rs +++ b/risc0-selective-privacy-poc/examples/sequencer.rs @@ -1,26 +1,170 @@ -use nssa::program::TransferProgram; +use core::{ + account::Account, + bytes_to_words, hash, + input::InputVisibiility, + types::{Address, Commitment, Key, Nullifier}, +}; -use crate::sequencer_mock::MockedSequencer; +use nssa::program::TransferProgram; +use risc0_zkvm::Receipt; + +use crate::sequencer_mock::{MockedSequencer, ACCOUNTS_PRIVATE_KEYS}; mod sequencer_mock; fn main() { let mut sequencer = MockedSequencer::new(); let addresses = sequencer.addresses(); + println!("addresses: {:?}", addresses); println!("Initial balances"); sequencer.print(); // A public execution of the Transfer Program - let sender_addr = addresses[0]; - let receiver_addr = addresses[1]; + let sender_addr = addresses[1]; + let receiver_addr = addresses[2]; sequencer .invoke_public::(&[sender_addr, receiver_addr], 10) .unwrap(); - println!("Balances after transfer"); sequencer.print(); + // A shielded execution of the Transfer Program + let private_account_2 = send_shielded(&addresses[1], &addresses[2], 15, &mut sequencer); + println!("Balances after shielded execution"); + sequencer.print(); // A private execution of the Transfer Program + let private_account_1 = send_private( + &private_account_2, + &ACCOUNTS_PRIVATE_KEYS[1], // <-- this is shifted 🫠 + &addresses[3], + 8, + &mut sequencer, + ); + println!("Balances after shielded execution"); + sequencer.print(); + // A deshielded execution of the Transfer Program + send_deshielded( + &private_account_1, + &ACCOUNTS_PRIVATE_KEYS[0], + &addresses[0], + 1, + &mut sequencer, + ); + + println!("Balances after deshielded execution"); + sequencer.print(); +} + +fn mint_fresh_account(address: Address) -> Account { + let nonce = [0; 8]; + Account::new(address, nonce) +} + +/// A shielded execution of the Transfer program +fn send_shielded( + from_address: &Address, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, +) -> Account { + // All of this is executed locally by the sender + let sender_account = sequencer.get_account(&from_address).unwrap(); + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let mut receiver_account = mint_fresh_account(*receiver_addr); + let visibilities = vec![InputVisibiility::Public, InputVisibiility::Private(None)]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[sender_account, receiver_account.clone()], + &balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + // Assemble the private account + receiver_account.nonce = nonces[1]; + receiver_account.balance = balance_to_move; + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + receiver_account +} + +/// A private execution of the Transfer program +fn send_private( + from_account: &Account, + from_account_pk: &Key, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, +) -> Account { + // All of this is executed locally by the sender + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let sender_commitment_auth_path = + sequencer.get_authentication_path_for(&from_account.commitment()); + let mut receiver_account = mint_fresh_account(*receiver_addr); + let visibilities = vec![ + InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Private(None), + ]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[from_account.clone(), receiver_account.clone()], + &balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + // Assemble the private account + receiver_account.nonce = nonces[1]; + receiver_account.balance = balance_to_move; + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + receiver_account +} + +fn send_deshielded( + from_account: &Account, + from_account_pk: &Key, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, +) { + // All of this is executed locally by the sender + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let sender_commitment_auth_path = + sequencer.get_authentication_path_for(&from_account.commitment()); + let to_account = sequencer.get_account(&to_address).unwrap(); + let visibilities = vec![ + InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Public, + ]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[from_account.clone(), to_account], + &balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); } diff --git a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs index 2a4398d..79b5c25 100644 --- a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs +++ b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs @@ -1,7 +1,7 @@ use core::{ account::Account, bytes_to_words, - types::{Address, Commitment, Key, Nullifier, ProgramId}, + types::{Address, AuthenticationPath, Commitment, Key, Nullifier, ProgramId}, }; use std::collections::{BTreeMap, HashSet}; @@ -17,8 +17,8 @@ pub struct MockedSequencer { deployed_program_ids: HashSet, } -const ACCOUNTS_PRIVATE_KEYS: [Key; 3] = [[1; 8], [2; 8], [3; 8]]; -const ACCOUNTS_INITIAL_BALANCES: [u128; 3] = [100, 1337, 0]; +pub const ACCOUNTS_PRIVATE_KEYS: [Key; 3] = [[1; 8], [2; 8], [3; 8]]; +const ACCOUNTS_INITIAL_BALANCES: [u128; 3] = [100, 1337, 37]; const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; impl MockedSequencer { @@ -63,13 +63,13 @@ impl MockedSequencer { nullifiers: &[Nullifier], commitments: &[Commitment], ) -> Result<(), ()> { - let commitments_tree_root = bytes_to_words(&self.commitment_tree.root()); + let commitments_tree_root = self.get_commitment_tree_root(); if public_inputs_outputs.len() % 2 != 0 { return Err(()); } let num_input_public = public_inputs_outputs.len() >> 1; - for account in public_inputs_outputs.iter() { + for account in public_inputs_outputs.iter().take(num_input_public) { let current_account = self.get_account(&account.address).ok_or(())?; if ¤t_account != account { return Err(()); @@ -111,6 +111,14 @@ impl MockedSequencer { .insert(account_post_state.address, account_post_state); }); + // Add nullifiers + self.nullifier_set.extend(nullifiers); + + // Add nullifiers + for commitment in commitments.iter() { + self.commitment_tree.add_value(*commitment); + } + Ok(()) } @@ -192,8 +200,26 @@ impl MockedSequencer { } println!("{:-<20}-+-{:-<10}", "", ""); println!("Commitments: {:?}", self.commitment_tree.values()); - println!("Nullifiers: {:?}", self.nullifier_set); + let formatted: Vec = self.nullifier_set + .iter() + .map(|arr| format!("0x{:x}", arr[0])) + .collect(); + println!("Nullifiers: [{}]", formatted.join(", ")); println!(""); println!(""); } + + pub fn get_commitment_tree_root(&self) -> [u32; 8] { + bytes_to_words(&self.commitment_tree.root()) + } + + pub fn get_authentication_path_for(&self, commitment: &Commitment) -> AuthenticationPath { + self.commitment_tree + .get_authentication_path_for_value(*commitment) + .iter() + .map(bytes_to_words) + .collect::>() + .try_into() + .unwrap() + } } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index c7f4f15..5e9e10c 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -3,15 +3,14 @@ use core::{ input::InputVisibiility, types::{Commitment, Nonce, Nullifier}, }; +use program_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; -use program_methods::{OUTER_ELF, OUTER_ID}; pub mod program; pub use program::Program; - pub fn new_random_nonce() -> Nonce { let mut rng = OsRng; std::array::from_fn(|_| rng.gen()) @@ -67,12 +66,12 @@ pub fn execute( Ok(inputs_outputs) } -pub fn execute_and_prove_privacy_execution( +pub fn invoke_privacy_execution( inputs: &[Account], instruction_data: &P::InstructionData, visibilities: &[InputVisibiility], commitment_tree_root: [u32; 8], -) -> Result { +) -> Result<(Receipt, Vec), ()> { // Prove inner program and get post state of the accounts let num_inputs = inputs.len(); let (inner_receipt, inputs_outputs) = execute_and_prove_inner::

(inputs, instruction_data)?; @@ -94,7 +93,7 @@ pub fn execute_and_prove_privacy_execution( let prover = default_prover(); let prove_info = prover.prove(env, OUTER_ELF).unwrap(); - Ok(prove_info.receipt) + Ok((prove_info.receipt, output_nonces)) } pub fn verify_privacy_execution( From bdea1a21373616cab4859e98f4aa27844fac72ea Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 16:05:24 -0300 Subject: [PATCH 35/83] add happy paths --- .../examples/private_execution.rs | 2 +- .../examples/public_execution.rs | 2 +- .../examples/sequencer.rs | 17 ++++++++++++----- .../examples/sequencer_mock/mod.rs | 2 +- .../program_methods/guest/src/bin/pinata.rs | 15 +++++++++------ risc0-selective-privacy-poc/src/lib.rs | 10 +++++----- risc0-selective-privacy-poc/src/program/mod.rs | 6 +++--- 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 433fac3..6a6edf2 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -53,7 +53,7 @@ fn main() { let (receipt, _) = nssa::invoke_privacy_execution::( &[sender, receiver_1, receiver_2], - &vec![30, 40], + vec![30, 40], &visibilities, root, ) diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index a72a490..e2fada7 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -33,7 +33,7 @@ pub fn main() { let inputs_outputs = nssa::execute::( &[sender, receiver_1, receiver_2], - &balance_to_move, + balance_to_move, ) .unwrap(); diff --git a/risc0-selective-privacy-poc/examples/sequencer.rs b/risc0-selective-privacy-poc/examples/sequencer.rs index e947f6c..e8a9957 100644 --- a/risc0-selective-privacy-poc/examples/sequencer.rs +++ b/risc0-selective-privacy-poc/examples/sequencer.rs @@ -5,7 +5,7 @@ use core::{ types::{Address, Commitment, Key, Nullifier}, }; -use nssa::program::TransferProgram; +use nssa::program::{PinataProgram, TransferProgram}; use risc0_zkvm::Receipt; use crate::sequencer_mock::{MockedSequencer, ACCOUNTS_PRIVATE_KEYS}; @@ -52,9 +52,16 @@ fn main() { 1, &mut sequencer, ); - println!("Balances after deshielded execution"); sequencer.print(); + + // A public execution of the Pinata program + let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); + sequencer + .invoke_public::(&[addresses[0], addresses[3]], preimage) + .unwrap(); + println!("Balances after public piñata execution"); + sequencer.print(); } fn mint_fresh_account(address: Address) -> Account { @@ -77,7 +84,7 @@ fn send_shielded( let visibilities = vec![InputVisibiility::Public, InputVisibiility::Private(None)]; let (receipt, nonces) = nssa::invoke_privacy_execution::( &[sender_account, receiver_account.clone()], - &balance_to_move, + balance_to_move, &visibilities, commitment_tree_root, ) @@ -116,7 +123,7 @@ fn send_private( ]; let (receipt, nonces) = nssa::invoke_privacy_execution::( &[from_account.clone(), receiver_account.clone()], - &balance_to_move, + balance_to_move, &visibilities, commitment_tree_root, ) @@ -154,7 +161,7 @@ fn send_deshielded( ]; let (receipt, nonces) = nssa::invoke_privacy_execution::( &[from_account.clone(), to_account], - &balance_to_move, + balance_to_move, &visibilities, commitment_tree_root, ) diff --git a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs index 79b5c25..e9b0680 100644 --- a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs +++ b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs @@ -134,7 +134,7 @@ impl MockedSequencer { .collect::>()?; // Execute - let inputs_outputs = nssa::execute::

(&input_accounts, &instruction_data)?; + let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; // Consistency checks self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs)?; diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs index 95c1f55..ddcc45a 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs @@ -1,7 +1,10 @@ use core::{account::Account, hash}; use risc0_zkvm::guest::env; -const TARGET_HASH: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +// preimage is b"NSSA Selective privacy is great!" +const TARGET_HASH: [u32; 8] = [ + 1363824975, 720119575, 717909014, 2043925380, 717793160, 1495780600, 1253022833, 116132328, +]; const PINATA_ACCOUNT_ADDR: [u32; 8] = [0xcafe; 8]; const PINATA_PRICE: u128 = 100; @@ -12,11 +15,11 @@ fn main() { let preimage: Vec = env::read(); assert_eq!(input_accounts.len(), 2); - let [pinata_account] = input_accounts.split_off(1).try_into().unwrap(); - let [winner_account] = input_accounts.try_into().unwrap(); + let [winner_account] = input_accounts.split_off(1).try_into().unwrap(); + let [pinata_account] = input_accounts.try_into().unwrap(); assert_eq!(pinata_account.address, PINATA_ACCOUNT_ADDR); - assert_eq!(pinata_account.balance, PINATA_PRICE); + assert!(pinata_account.balance >= PINATA_PRICE); assert_eq!(hash(&preimage), TARGET_HASH); let mut winner_account_post = winner_account.clone(); @@ -25,9 +28,9 @@ fn main() { winner_account_post.balance += PINATA_PRICE; env::commit(&vec![ - winner_account, pinata_account, - winner_account_post, + winner_account, pinata_account_post, + winner_account_post, ]); } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 5e9e10c..1bef79c 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -18,7 +18,7 @@ pub fn new_random_nonce() -> Nonce { fn write_inputs( input_accounts: &[Account], - instruction_data: &P::InstructionData, + instruction_data: P::InstructionData, env_builder: &mut ExecutorEnvBuilder, ) -> Result<(), ()> { let input_accounts = input_accounts.to_vec(); @@ -29,7 +29,7 @@ fn write_inputs( fn execute_and_prove_inner( input_accounts: &[Account], - instruction_data: &P::InstructionData, + instruction_data: P::InstructionData, ) -> Result<(Receipt, Vec), ()> { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); @@ -49,7 +49,7 @@ fn execute_and_prove_inner( pub fn execute( input_accounts: &[Account], - instruction_data: &P::InstructionData, + instruction_data: P::InstructionData, ) -> Result, ()> { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); @@ -60,7 +60,7 @@ pub fn execute( let executor = default_executor(); let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; - // Get proof and (inputs and) outputs + // Get (inputs and) outputs let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; Ok(inputs_outputs) @@ -68,7 +68,7 @@ pub fn execute( pub fn invoke_privacy_execution( inputs: &[Account], - instruction_data: &P::InstructionData, + instruction_data: P::InstructionData, visibilities: &[InputVisibiility], commitment_tree_root: [u32; 8], ) -> Result<(Receipt, Vec), ()> { diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index ea6f896..984a5cb 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -29,8 +29,8 @@ impl Program for TransferMultipleProgram { pub struct PinataProgram; impl Program for PinataProgram { - const PROGRAM_ID: ProgramId = TRANSFER_MULTIPLE_ID; - const PROGRAM_ELF: &[u8] = TRANSFER_MULTIPLE_ELF; + const PROGRAM_ID: ProgramId = PINATA_ID; + const PROGRAM_ELF: &[u8] = PINATA_ELF; /// Preimage of target hash to win price - type InstructionData = [u32; 8]; + type InstructionData = Vec; } From 2c098f904709bd043ccbcaccb1b5691bec61abe4 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 16:22:04 -0300 Subject: [PATCH 36/83] nit --- risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs index e9b0680..b15fea9 100644 --- a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs +++ b/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs @@ -114,7 +114,7 @@ impl MockedSequencer { // Add nullifiers self.nullifier_set.extend(nullifiers); - // Add nullifiers + // Add commitments for commitment in commitments.iter() { self.commitment_tree.add_value(*commitment); } From 3823c6809a86da99c0190b6309551e012166574a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 17:03:18 -0300 Subject: [PATCH 37/83] refactor --- .../examples/mocked_components/client.rs | 125 ++++++++++++++++++ .../examples/mocked_components/mod.rs | 2 + .../mod.rs => mocked_components/sequencer.rs} | 1 + .../examples/sequencer.rs | 124 +---------------- 4 files changed, 135 insertions(+), 117 deletions(-) create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/client.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/mod.rs rename risc0-selective-privacy-poc/examples/{sequencer_mock/mod.rs => mocked_components/sequencer.rs} (99%) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client.rs b/risc0-selective-privacy-poc/examples/mocked_components/client.rs new file mode 100644 index 0000000..6389479 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/client.rs @@ -0,0 +1,125 @@ +use core::{ + account::Account, + input::InputVisibiility, + types::{Address, Commitment, Key, Nullifier}, +}; + +use nssa::program::TransferProgram; + +use crate::mocked_components::sequencer::MockedSequencer; + +pub struct MockedClient; + +impl MockedClient { + pub fn fresh_account_for_mint(address: Address) -> Account { + let nonce = [0; 8]; + Account::new(address, nonce) + } + + /// A shielded execution of the Transfer program + pub fn send_shielded( + from_address: &Address, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, + ) -> Account { + // All of this is executed locally by the sender + let sender_account = sequencer.get_account(&from_address).unwrap(); + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); + let visibilities = vec![InputVisibiility::Public, InputVisibiility::Private(None)]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[sender_account, receiver_account.clone()], + balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + // Assemble the private account + receiver_account.nonce = nonces[1]; + receiver_account.balance = balance_to_move; + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + receiver_account + } + + /// A private execution of the Transfer program + pub fn send_private( + from_account: &Account, + from_account_pk: &Key, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, + ) -> Account { + // All of this is executed locally by the sender + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let sender_commitment_auth_path = + sequencer.get_authentication_path_for(&from_account.commitment()); + let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); + let visibilities = vec![ + InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Private(None), + ]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[from_account.clone(), receiver_account.clone()], + balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + // Assemble the private account + receiver_account.nonce = nonces[1]; + receiver_account.balance = balance_to_move; + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + receiver_account + } + + pub fn send_deshielded( + from_account: &Account, + from_account_pk: &Key, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, + ) { + // All of this is executed locally by the sender + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let sender_commitment_auth_path = + sequencer.get_authentication_path_for(&from_account.commitment()); + let to_account = sequencer.get_account(&to_address).unwrap(); + let visibilities = vec![ + InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Public, + ]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[from_account.clone(), to_account], + balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs new file mode 100644 index 0000000..49cb55f --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs @@ -0,0 +1,2 @@ +pub mod sequencer; +pub mod client; diff --git a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer.rs similarity index 99% rename from risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs rename to risc0-selective-privacy-poc/examples/mocked_components/sequencer.rs index b15fea9..ef0be79 100644 --- a/risc0-selective-privacy-poc/examples/sequencer_mock/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer.rs @@ -223,3 +223,4 @@ impl MockedSequencer { .unwrap() } } + diff --git a/risc0-selective-privacy-poc/examples/sequencer.rs b/risc0-selective-privacy-poc/examples/sequencer.rs index e8a9957..fb9c28f 100644 --- a/risc0-selective-privacy-poc/examples/sequencer.rs +++ b/risc0-selective-privacy-poc/examples/sequencer.rs @@ -8,9 +8,10 @@ use core::{ use nssa::program::{PinataProgram, TransferProgram}; use risc0_zkvm::Receipt; -use crate::sequencer_mock::{MockedSequencer, ACCOUNTS_PRIVATE_KEYS}; +use crate::mocked_components::client::MockedClient; +use crate::mocked_components::sequencer::{MockedSequencer, ACCOUNTS_PRIVATE_KEYS}; -mod sequencer_mock; +mod mocked_components; fn main() { let mut sequencer = MockedSequencer::new(); @@ -29,12 +30,13 @@ fn main() { sequencer.print(); // A shielded execution of the Transfer Program - let private_account_2 = send_shielded(&addresses[1], &addresses[2], 15, &mut sequencer); + let private_account_2 = + MockedClient::send_shielded(&addresses[1], &addresses[2], 15, &mut sequencer); println!("Balances after shielded execution"); sequencer.print(); // A private execution of the Transfer Program - let private_account_1 = send_private( + let private_account_1 = MockedClient::send_private( &private_account_2, &ACCOUNTS_PRIVATE_KEYS[1], // <-- this is shifted 🫠 &addresses[3], @@ -45,7 +47,7 @@ fn main() { sequencer.print(); // A deshielded execution of the Transfer Program - send_deshielded( + MockedClient::send_deshielded( &private_account_1, &ACCOUNTS_PRIVATE_KEYS[0], &addresses[0], @@ -63,115 +65,3 @@ fn main() { println!("Balances after public piñata execution"); sequencer.print(); } - -fn mint_fresh_account(address: Address) -> Account { - let nonce = [0; 8]; - Account::new(address, nonce) -} - -/// A shielded execution of the Transfer program -fn send_shielded( - from_address: &Address, - to_address: &Address, - balance_to_move: u128, - sequencer: &mut MockedSequencer, -) -> Account { - // All of this is executed locally by the sender - let sender_account = sequencer.get_account(&from_address).unwrap(); - let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let mut receiver_account = mint_fresh_account(*receiver_addr); - let visibilities = vec![InputVisibiility::Public, InputVisibiility::Private(None)]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[sender_account, receiver_account.clone()], - balance_to_move, - &visibilities, - commitment_tree_root, - ) - .unwrap(); - - // Assemble the private account - receiver_account.nonce = nonces[1]; - receiver_account.balance = balance_to_move; - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); - receiver_account -} - -/// A private execution of the Transfer program -fn send_private( - from_account: &Account, - from_account_pk: &Key, - to_address: &Address, - balance_to_move: u128, - sequencer: &mut MockedSequencer, -) -> Account { - // All of this is executed locally by the sender - let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&from_account.commitment()); - let mut receiver_account = mint_fresh_account(*receiver_addr); - let visibilities = vec![ - InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), - InputVisibiility::Private(None), - ]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[from_account.clone(), receiver_account.clone()], - balance_to_move, - &visibilities, - commitment_tree_root, - ) - .unwrap(); - - // Assemble the private account - receiver_account.nonce = nonces[1]; - receiver_account.balance = balance_to_move; - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); - receiver_account -} - -fn send_deshielded( - from_account: &Account, - from_account_pk: &Key, - to_address: &Address, - balance_to_move: u128, - sequencer: &mut MockedSequencer, -) { - // All of this is executed locally by the sender - let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&from_account.commitment()); - let to_account = sequencer.get_account(&to_address).unwrap(); - let visibilities = vec![ - InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), - InputVisibiility::Public, - ]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[from_account.clone(), to_account], - balance_to_move, - &visibilities, - commitment_tree_root, - ) - .unwrap(); - - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); -} From 88740c29c2d6d90f65671c214d98bbee2dd49df3 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 17 Jul 2025 17:08:16 -0300 Subject: [PATCH 38/83] refactor --- .../examples/{sequencer.rs => happy_path.rs} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename risc0-selective-privacy-poc/examples/{sequencer.rs => happy_path.rs} (86%) diff --git a/risc0-selective-privacy-poc/examples/sequencer.rs b/risc0-selective-privacy-poc/examples/happy_path.rs similarity index 86% rename from risc0-selective-privacy-poc/examples/sequencer.rs rename to risc0-selective-privacy-poc/examples/happy_path.rs index fb9c28f..ed0bd0f 100644 --- a/risc0-selective-privacy-poc/examples/sequencer.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -17,7 +17,7 @@ fn main() { let mut sequencer = MockedSequencer::new(); let addresses = sequencer.addresses(); println!("addresses: {:?}", addresses); - println!("Initial balances"); + println!("1️⃣ 🚀 Initial balances"); sequencer.print(); // A public execution of the Transfer Program @@ -26,7 +26,7 @@ fn main() { sequencer .invoke_public::(&[sender_addr, receiver_addr], 10) .unwrap(); - println!("Balances after transfer"); + println!("2️⃣ 🚀 Balances after transfer"); sequencer.print(); // A shielded execution of the Transfer Program @@ -43,7 +43,7 @@ fn main() { 8, &mut sequencer, ); - println!("Balances after shielded execution"); + println!("3️⃣ 🚀 Balances after shielded execution"); sequencer.print(); // A deshielded execution of the Transfer Program @@ -54,7 +54,7 @@ fn main() { 1, &mut sequencer, ); - println!("Balances after deshielded execution"); + println!("4️⃣ 🚀 Balances after deshielded execution"); sequencer.print(); // A public execution of the Pinata program @@ -62,6 +62,6 @@ fn main() { sequencer .invoke_public::(&[addresses[0], addresses[3]], preimage) .unwrap(); - println!("Balances after public piñata execution"); + println!("5️⃣ 🚀 Balances after public piñata execution"); sequencer.print(); } From f66f62c041b8627570bb74b17f73d87b3d3309de Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 08:18:56 -0300 Subject: [PATCH 39/83] refactor into files --- .../examples/mocked_components/mod.rs | 4 +- .../examples/mocked_components/sequencer.rs | 226 ------------------ .../sequencer/invoke_privacy_execution.rs | 76 ++++++ .../sequencer/invoke_public_execution.rs | 71 ++++++ .../mocked_components/sequencer/mod.rs | 99 ++++++++ .../src/program/mod.rs | 2 +- 6 files changed, 250 insertions(+), 228 deletions(-) delete mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs diff --git a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs index 49cb55f..aa3ae1c 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs @@ -1,2 +1,4 @@ -pub mod sequencer; pub mod client; +pub mod sequencer; + +pub const ACCOUNTS_PRIVATE_KEYS: [Key; 3] = [[1; 8], [2; 8], [3; 8]]; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer.rs deleted file mode 100644 index ef0be79..0000000 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer.rs +++ /dev/null @@ -1,226 +0,0 @@ -use core::{ - account::Account, - bytes_to_words, - types::{Address, AuthenticationPath, Commitment, Key, Nullifier, ProgramId}, -}; -use std::collections::{BTreeMap, HashSet}; - -use nssa; -use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; -use risc0_zkvm::Receipt; -use sparse_merkle_tree::SparseMerkleTree; - -pub struct MockedSequencer { - accounts: BTreeMap, - commitment_tree: SparseMerkleTree, - nullifier_set: HashSet, - deployed_program_ids: HashSet, -} - -pub const ACCOUNTS_PRIVATE_KEYS: [Key; 3] = [[1; 8], [2; 8], [3; 8]]; -const ACCOUNTS_INITIAL_BALANCES: [u128; 3] = [100, 1337, 37]; -const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; - -impl MockedSequencer { - pub fn new() -> Self { - let mut accounts: BTreeMap = ACCOUNTS_PRIVATE_KEYS - .iter() - .cloned() - .zip(ACCOUNTS_INITIAL_BALANCES) - .map(|(key, initial_balance)| { - let mut this = Account::new_from_private_key(key, [0; 8]); - this.balance = initial_balance; - this - }) - .map(|account| (account.address, account)) - .collect(); - - let pinata_account = { - let mut this = Account::new([0xcafe; 8], [0; 8]); - this.balance = 100; - this - }; - accounts.insert(pinata_account.address, pinata_account); - - let commitment_tree = SparseMerkleTree::new_empty(); - let nullifier_set = HashSet::new(); - Self { - accounts, - commitment_tree, - nullifier_set, - deployed_program_ids: DEPLOYED_PROGRAM_IDS.iter().cloned().collect(), - } - } - - pub fn get_account(&self, address: &Address) -> Option { - self.accounts.get(address).cloned() - } - - pub fn invoke_privacy_execution( - &mut self, - receipt: Receipt, - public_inputs_outputs: &[Account], - nullifiers: &[Nullifier], - commitments: &[Commitment], - ) -> Result<(), ()> { - let commitments_tree_root = self.get_commitment_tree_root(); - if public_inputs_outputs.len() % 2 != 0 { - return Err(()); - } - - let num_input_public = public_inputs_outputs.len() >> 1; - for account in public_inputs_outputs.iter().take(num_input_public) { - let current_account = self.get_account(&account.address).ok_or(())?; - if ¤t_account != account { - return Err(()); - } - } - - // Check that nullifiers have not been added before - if nullifiers - .iter() - .any(|nullifier| self.nullifier_set.contains(nullifier)) - { - return Err(()); - } - - // Check that commitments are new too - if commitments - .iter() - .any(|commitment| self.commitment_tree.values().contains(commitment)) - { - return Err(()); - } - - // Verify consistency between public accounts, nullifiers and commitments - nssa::verify_privacy_execution( - receipt, - public_inputs_outputs, - nullifiers, - commitments, - &commitments_tree_root, - )?; - - // Update accounts - public_inputs_outputs - .iter() - .cloned() - .skip(num_input_public) - .for_each(|account_post_state| { - self.accounts - .insert(account_post_state.address, account_post_state); - }); - - // Add nullifiers - self.nullifier_set.extend(nullifiers); - - // Add commitments - for commitment in commitments.iter() { - self.commitment_tree.add_value(*commitment); - } - - Ok(()) - } - - pub fn invoke_public( - &mut self, - input_account_addresses: &[Address], - instruction_data: P::InstructionData, - ) -> Result<(), ()> { - // Fetch accounts - let input_accounts: Vec = input_account_addresses - .iter() - .map(|address| self.get_account(address).ok_or(())) - .collect::>()?; - - // Execute - let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; - - // Consistency checks - self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs)?; - - // Update accounts - inputs_outputs - .into_iter() - .skip(input_accounts.len()) - .for_each(|account_post_state| { - self.accounts - .insert(account_post_state.address, account_post_state); - }); - Ok(()) - } - - fn inputs_outputs_are_consistent( - &self, - input_accounts: &[Account], - inputs_outputs: &[Account], - ) -> Result<(), ()> { - let num_inputs = input_accounts.len(); - if inputs_outputs.len() != num_inputs * 2 { - return Err(()); - } - - let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); - if claimed_accounts_pre != input_accounts { - return Err(()); - } - - for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { - if account_pre.address != account_post.address { - return Err(()); - } - if account_pre.nonce != account_post.nonce { - return Err(()); - } - // Redundant with previous checks, but better make it explicit. - if !self.accounts.contains_key(&account_post.address) { - return Err(()); - } - } - let accounts_pre_total_balance: u128 = - input_accounts.iter().map(|account| account.balance).sum(); - let accounts_post_total_balance: u128 = - accounts_post.iter().map(|account| account.balance).sum(); - if accounts_pre_total_balance != accounts_post_total_balance { - return Err(()); - } - return Ok(()); - } - - pub fn addresses(&self) -> Vec

{ - self.accounts.keys().cloned().collect() - } - - pub fn print(&self) { - println!("{:<20} | {:>10}", "Address (first u32)", "Balance"); - println!("{:-<20}-+-{:-<10}", "", ""); - - for account in self.accounts.values() { - println!("{:<20x} | {:>10}", account.address[0], account.balance); - } - println!("{:-<20}-+-{:-<10}", "", ""); - println!("Commitments: {:?}", self.commitment_tree.values()); - let formatted: Vec = self.nullifier_set - .iter() - .map(|arr| format!("0x{:x}", arr[0])) - .collect(); - println!("Nullifiers: [{}]", formatted.join(", ")); - println!(""); - println!(""); - } - - pub fn get_commitment_tree_root(&self) -> [u32; 8] { - bytes_to_words(&self.commitment_tree.root()) - } - - pub fn get_authentication_path_for(&self, commitment: &Commitment) -> AuthenticationPath { - self.commitment_tree - .get_authentication_path_for_value(*commitment) - .iter() - .map(bytes_to_words) - .collect::>() - .try_into() - .unwrap() - } -} - diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs new file mode 100644 index 0000000..046e0b5 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs @@ -0,0 +1,76 @@ +use core::{ + account::Account, + types::{Commitment, Nullifier}, +}; + +use risc0_zkvm::Receipt; + +use super::MockedSequencer; + +impl MockedSequencer { + pub fn invoke_privacy_execution( + &mut self, + receipt: Receipt, + public_inputs_outputs: &[Account], + nullifiers: &[Nullifier], + commitments: &[Commitment], + ) -> Result<(), ()> { + let commitments_tree_root = self.get_commitment_tree_root(); + if public_inputs_outputs.len() % 2 != 0 { + return Err(()); + } + + let num_input_public = public_inputs_outputs.len() >> 1; + for account in public_inputs_outputs.iter().take(num_input_public) { + let current_account = self.get_account(&account.address).ok_or(())?; + if ¤t_account != account { + return Err(()); + } + } + + // Check that nullifiers have not been added before + if nullifiers + .iter() + .any(|nullifier| self.nullifier_set.contains(nullifier)) + { + return Err(()); + } + + // Check that commitments are new too + if commitments + .iter() + .any(|commitment| self.commitment_tree.values().contains(commitment)) + { + return Err(()); + } + + // Verify consistency between public accounts, nullifiers and commitments + nssa::verify_privacy_execution( + receipt, + public_inputs_outputs, + nullifiers, + commitments, + &commitments_tree_root, + )?; + + // Update accounts + public_inputs_outputs + .iter() + .cloned() + .skip(num_input_public) + .for_each(|account_post_state| { + self.accounts + .insert(account_post_state.address, account_post_state); + }); + + // Add nullifiers + self.nullifier_set.extend(nullifiers); + + // Add commitments + for commitment in commitments.iter() { + self.commitment_tree.add_value(*commitment); + } + + Ok(()) + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs new file mode 100644 index 0000000..bd3385c --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs @@ -0,0 +1,71 @@ +use core::{account::Account, types::Address}; + +use super::MockedSequencer; + +impl MockedSequencer { + pub fn invoke_public( + &mut self, + input_account_addresses: &[Address], + instruction_data: P::InstructionData, + ) -> Result<(), ()> { + // Fetch accounts + let input_accounts: Vec = input_account_addresses + .iter() + .map(|address| self.get_account(address).ok_or(())) + .collect::>()?; + + // Execute + let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; + + // Consistency checks + self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs)?; + + // Update accounts + inputs_outputs + .into_iter() + .skip(input_accounts.len()) + .for_each(|account_post_state| { + self.accounts + .insert(account_post_state.address, account_post_state); + }); + Ok(()) + } + + fn inputs_outputs_are_consistent( + &self, + input_accounts: &[Account], + inputs_outputs: &[Account], + ) -> Result<(), ()> { + let num_inputs = input_accounts.len(); + if inputs_outputs.len() != num_inputs * 2 { + return Err(()); + } + + let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); + if claimed_accounts_pre != input_accounts { + return Err(()); + } + + for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { + if account_pre.address != account_post.address { + return Err(()); + } + if account_pre.nonce != account_post.nonce { + return Err(()); + } + // Redundant with previous checks, but better make it explicit. + if !self.accounts.contains_key(&account_post.address) { + return Err(()); + } + } + let accounts_pre_total_balance: u128 = + input_accounts.iter().map(|account| account.balance).sum(); + let accounts_post_total_balance: u128 = + accounts_post.iter().map(|account| account.balance).sum(); + if accounts_pre_total_balance != accounts_post_total_balance { + return Err(()); + } + return Ok(()); + } +} + diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs new file mode 100644 index 0000000..16a0b0b --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -0,0 +1,99 @@ +use core::{ + account::Account, + bytes_to_words, + types::{Address, AuthenticationPath, Commitment, Key, Nullifier, ProgramId}, +}; +use std::collections::{BTreeMap, HashSet}; + +use nssa; +use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; +use risc0_zkvm::Receipt; +use sparse_merkle_tree::SparseMerkleTree; + +use crate::mocked_components::ACCOUNTS_PRIVATE_KEYS; + +pub mod invoke_privacy_execution; +pub mod invoke_public_execution; + +pub struct MockedSequencer { + accounts: BTreeMap, + commitment_tree: SparseMerkleTree, + nullifier_set: HashSet, + deployed_program_ids: HashSet, +} + +const ACCOUNTS_INITIAL_BALANCES: [u128; 3] = [100, 1337, 37]; +const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; + +impl MockedSequencer { + pub fn new() -> Self { + let mut accounts: BTreeMap = ACCOUNTS_PRIVATE_KEYS + .iter() + .cloned() + .zip(ACCOUNTS_INITIAL_BALANCES) + .map(|(key, initial_balance)| { + let mut this = Account::new_from_private_key(key, [0; 8]); + this.balance = initial_balance; + this + }) + .map(|account| (account.address, account)) + .collect(); + + let pinata_account = { + let mut this = Account::new([0xcafe; 8], [0; 8]); + this.balance = 100; + this + }; + accounts.insert(pinata_account.address, pinata_account); + + let commitment_tree = SparseMerkleTree::new_empty(); + let nullifier_set = HashSet::new(); + Self { + accounts, + commitment_tree, + nullifier_set, + deployed_program_ids: DEPLOYED_PROGRAM_IDS.iter().cloned().collect(), + } + } + + pub fn get_account(&self, address: &Address) -> Option { + self.accounts.get(address).cloned() + } + + pub fn get_commitment_tree_root(&self) -> [u32; 8] { + bytes_to_words(&self.commitment_tree.root()) + } + + pub fn get_authentication_path_for(&self, commitment: &Commitment) -> AuthenticationPath { + self.commitment_tree + .get_authentication_path_for_value(*commitment) + .iter() + .map(bytes_to_words) + .collect::>() + .try_into() + .unwrap() + } + + pub fn addresses(&self) -> Vec

{ + self.accounts.keys().cloned().collect() + } + + pub fn print(&self) { + println!("{:<20} | {:>10}", "Address (first u32)", "Balance"); + println!("{:-<20}-+-{:-<10}", "", ""); + + for account in self.accounts.values() { + println!("{:<20x} | {:>10}", account.address[0], account.balance); + } + println!("{:-<20}-+-{:-<10}", "", ""); + println!("Commitments: {:?}", self.commitment_tree.values()); + let formatted: Vec = self + .nullifier_set + .iter() + .map(|arr| format!("0x{:x}", arr[0])) + .collect(); + println!("Nullifiers: [{}]", formatted.join(", ")); + println!(""); + println!(""); + } +} diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index 984a5cb..8fe64b6 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -1,7 +1,7 @@ use core::types::ProgramId; use program_methods::{ - PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, {TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}, + PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID, }; use serde::{Deserialize, Serialize}; From 5de2fd05a812a359f5bfb78cc3bf3bc2470289c5 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 08:24:50 -0300 Subject: [PATCH 40/83] refactor --- .../examples/happy_path.rs | 4 ++-- .../examples/mocked_components/mod.rs | 2 ++ .../sequencer/invoke_public_execution.rs | 23 ++++++++++--------- .../mocked_components/sequencer/mod.rs | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index ed0bd0f..194ac8b 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -8,8 +8,8 @@ use core::{ use nssa::program::{PinataProgram, TransferProgram}; use risc0_zkvm::Receipt; -use crate::mocked_components::client::MockedClient; -use crate::mocked_components::sequencer::{MockedSequencer, ACCOUNTS_PRIVATE_KEYS}; +use crate::mocked_components::sequencer::MockedSequencer; +use crate::mocked_components::{client::MockedClient, ACCOUNTS_PRIVATE_KEYS}; mod mocked_components; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs index aa3ae1c..68ee005 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs @@ -1,3 +1,5 @@ +use core::types::Key; + pub mod client; pub mod sequencer; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs index bd3385c..c06adba 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs @@ -17,8 +17,10 @@ impl MockedSequencer { // Execute let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; - // Consistency checks - self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs)?; + // Perform consistency checks + if !self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs) { + return Err(()); + } // Update accounts inputs_outputs @@ -35,27 +37,27 @@ impl MockedSequencer { &self, input_accounts: &[Account], inputs_outputs: &[Account], - ) -> Result<(), ()> { + ) -> bool { let num_inputs = input_accounts.len(); if inputs_outputs.len() != num_inputs * 2 { - return Err(()); + return false; } let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); if claimed_accounts_pre != input_accounts { - return Err(()); + return false; } for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { if account_pre.address != account_post.address { - return Err(()); + return false; } if account_pre.nonce != account_post.nonce { - return Err(()); + return false; } // Redundant with previous checks, but better make it explicit. if !self.accounts.contains_key(&account_post.address) { - return Err(()); + return false; } } let accounts_pre_total_balance: u128 = @@ -63,9 +65,8 @@ impl MockedSequencer { let accounts_post_total_balance: u128 = accounts_post.iter().map(|account| account.balance).sum(); if accounts_pre_total_balance != accounts_post_total_balance { - return Err(()); + return false; } - return Ok(()); + return true; } } - diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 16a0b0b..3a0fafd 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -10,7 +10,7 @@ use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; use risc0_zkvm::Receipt; use sparse_merkle_tree::SparseMerkleTree; -use crate::mocked_components::ACCOUNTS_PRIVATE_KEYS; +use super::ACCOUNTS_PRIVATE_KEYS; pub mod invoke_privacy_execution; pub mod invoke_public_execution; From 2fecd7da72193ce298430bd35a4cdd623b9cb29f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 08:56:12 -0300 Subject: [PATCH 41/83] refactor mocked client --- .../examples/happy_path.rs | 10 +- .../examples/mocked_components/client.rs | 125 ------------------ .../examples/mocked_components/client/mod.rs | 23 ++++ .../client/transfer_deshielded.rs | 43 ++++++ .../client/transfer_private.rs | 48 +++++++ .../client/transfer_public.rs | 0 .../client/transfer_shielded.rs | 43 ++++++ .../sequencer/invoke_public_execution.rs | 2 +- 8 files changed, 163 insertions(+), 131 deletions(-) delete mode 100644 risc0-selective-privacy-poc/examples/mocked_components/client.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 194ac8b..2ea921d 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -24,19 +24,19 @@ fn main() { let sender_addr = addresses[1]; let receiver_addr = addresses[2]; sequencer - .invoke_public::(&[sender_addr, receiver_addr], 10) + .invoke_public_execution::(&[sender_addr, receiver_addr], 10) .unwrap(); println!("2️⃣ 🚀 Balances after transfer"); sequencer.print(); // A shielded execution of the Transfer Program let private_account_2 = - MockedClient::send_shielded(&addresses[1], &addresses[2], 15, &mut sequencer); + MockedClient::transfer_shielded(&addresses[1], &addresses[2], 15, &mut sequencer); println!("Balances after shielded execution"); sequencer.print(); // A private execution of the Transfer Program - let private_account_1 = MockedClient::send_private( + let private_account_1 = MockedClient::transfer_private( &private_account_2, &ACCOUNTS_PRIVATE_KEYS[1], // <-- this is shifted 🫠 &addresses[3], @@ -47,7 +47,7 @@ fn main() { sequencer.print(); // A deshielded execution of the Transfer Program - MockedClient::send_deshielded( + MockedClient::transfer_deshielded( &private_account_1, &ACCOUNTS_PRIVATE_KEYS[0], &addresses[0], @@ -60,7 +60,7 @@ fn main() { // A public execution of the Pinata program let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); sequencer - .invoke_public::(&[addresses[0], addresses[3]], preimage) + .invoke_public_execution::(&[addresses[0], addresses[3]], preimage) .unwrap(); println!("5️⃣ 🚀 Balances after public piñata execution"); sequencer.print(); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client.rs b/risc0-selective-privacy-poc/examples/mocked_components/client.rs deleted file mode 100644 index 6389479..0000000 --- a/risc0-selective-privacy-poc/examples/mocked_components/client.rs +++ /dev/null @@ -1,125 +0,0 @@ -use core::{ - account::Account, - input::InputVisibiility, - types::{Address, Commitment, Key, Nullifier}, -}; - -use nssa::program::TransferProgram; - -use crate::mocked_components::sequencer::MockedSequencer; - -pub struct MockedClient; - -impl MockedClient { - pub fn fresh_account_for_mint(address: Address) -> Account { - let nonce = [0; 8]; - Account::new(address, nonce) - } - - /// A shielded execution of the Transfer program - pub fn send_shielded( - from_address: &Address, - to_address: &Address, - balance_to_move: u128, - sequencer: &mut MockedSequencer, - ) -> Account { - // All of this is executed locally by the sender - let sender_account = sequencer.get_account(&from_address).unwrap(); - let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); - let visibilities = vec![InputVisibiility::Public, InputVisibiility::Private(None)]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[sender_account, receiver_account.clone()], - balance_to_move, - &visibilities, - commitment_tree_root, - ) - .unwrap(); - - // Assemble the private account - receiver_account.nonce = nonces[1]; - receiver_account.balance = balance_to_move; - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); - receiver_account - } - - /// A private execution of the Transfer program - pub fn send_private( - from_account: &Account, - from_account_pk: &Key, - to_address: &Address, - balance_to_move: u128, - sequencer: &mut MockedSequencer, - ) -> Account { - // All of this is executed locally by the sender - let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&from_account.commitment()); - let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); - let visibilities = vec![ - InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), - InputVisibiility::Private(None), - ]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[from_account.clone(), receiver_account.clone()], - balance_to_move, - &visibilities, - commitment_tree_root, - ) - .unwrap(); - - // Assemble the private account - receiver_account.nonce = nonces[1]; - receiver_account.balance = balance_to_move; - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); - receiver_account - } - - pub fn send_deshielded( - from_account: &Account, - from_account_pk: &Key, - to_address: &Address, - balance_to_move: u128, - sequencer: &mut MockedSequencer, - ) { - // All of this is executed locally by the sender - let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&from_account.commitment()); - let to_account = sequencer.get_account(&to_address).unwrap(); - let visibilities = vec![ - InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), - InputVisibiility::Public, - ]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[from_account.clone(), to_account], - balance_to_move, - &visibilities, - commitment_tree_root, - ) - .unwrap(); - - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); - } -} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs new file mode 100644 index 0000000..04bea16 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -0,0 +1,23 @@ +use core::{ + account::Account, + input::InputVisibiility, + types::{Address, Commitment, Key, Nullifier}, +}; + +use nssa::program::TransferProgram; + +use crate::mocked_components::sequencer::MockedSequencer; + +pub mod transfer_deshielded; +pub mod transfer_private; +pub mod transfer_public; +pub mod transfer_shielded; + +pub struct MockedClient; + +impl MockedClient { + pub fn fresh_account_for_mint(address: Address) -> Account { + let nonce = [0; 8]; + Account::new(address, nonce) + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs new file mode 100644 index 0000000..c89d4ad --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -0,0 +1,43 @@ +use core::account::Account; +use core::input::InputVisibiility; +use core::types::{Address, Commitment, Key, Nullifier}; + +use nssa::program::TransferProgram; + +use super::{MockedSequencer, MockedClient}; + +impl MockedClient { + /// A shielded execution of the Transfer program + pub fn transfer_shielded( + from_address: &Address, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, + ) -> Account { + // All of this is executed locally by the sender + let sender_account = sequencer.get_account(&from_address).unwrap(); + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); + let visibilities = vec![InputVisibiility::Public, InputVisibiility::Private(None)]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[sender_account, receiver_account.clone()], + balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + // Assemble the private account + receiver_account.nonce = nonces[1]; + receiver_account.balance = balance_to_move; + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + receiver_account + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs new file mode 100644 index 0000000..6bf6a6f --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -0,0 +1,48 @@ +use core::account::Account; +use core::input::InputVisibiility; +use core::types::{Address, Commitment, Key, Nullifier}; + +use nssa::program::TransferProgram; + +use super::{MockedClient, MockedSequencer}; + +impl MockedClient { + /// A private execution of the Transfer program + pub fn transfer_private( + from_account: &Account, + from_account_pk: &Key, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, + ) -> Account { + // All of this is executed locally by the sender + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let sender_commitment_auth_path = + sequencer.get_authentication_path_for(&from_account.commitment()); + let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); + let visibilities = vec![ + InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Private(None), + ]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[from_account.clone(), receiver_account.clone()], + balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + + // Assemble the private account + receiver_account.nonce = nonces[1]; + receiver_account.balance = balance_to_move; + receiver_account + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs new file mode 100644 index 0000000..e69de29 diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs new file mode 100644 index 0000000..03b61ce --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -0,0 +1,43 @@ +use core::account::Account; +use core::input::InputVisibiility; +use core::types::{Address, Commitment, Key, Nullifier}; + +use nssa::program::TransferProgram; + +use super::{MockedClient, MockedSequencer}; + +impl MockedClient { + pub fn transfer_deshielded( + from_account: &Account, + from_account_pk: &Key, + to_address: &Address, + balance_to_move: u128, + sequencer: &mut MockedSequencer, + ) { + // All of this is executed locally by the sender + let commitment_tree_root = sequencer.get_commitment_tree_root(); + let receiver_addr = to_address; + let sender_commitment_auth_path = + sequencer.get_authentication_path_for(&from_account.commitment()); + let to_account = sequencer.get_account(&to_address).unwrap(); + let visibilities = vec![ + InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Public, + ]; + let (receipt, nonces) = nssa::invoke_privacy_execution::( + &[from_account.clone(), to_account], + balance_to_move, + &visibilities, + commitment_tree_root, + ) + .unwrap(); + + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs index c06adba..0e6a46f 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs @@ -3,7 +3,7 @@ use core::{account::Account, types::Address}; use super::MockedSequencer; impl MockedSequencer { - pub fn invoke_public( + pub fn invoke_public_execution( &mut self, input_account_addresses: &[Address], instruction_data: P::InstructionData, From e1f24f3411089aaf3ad59d77feee7fdef96b92cf Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 09:55:23 -0300 Subject: [PATCH 42/83] refactor client --- .../examples/happy_path.rs | 16 +++++----- .../examples/mocked_components/client/mod.rs | 25 +++++++++++++++ .../client/transfer_deshielded.rs | 27 ++++++---------- .../client/transfer_private.rs | 25 +++++---------- .../client/transfer_shielded.rs | 18 +++-------- .../mocked_components/sequencer/mod.rs | 2 -- risc0-selective-privacy-poc/src/lib.rs | 32 +++++++++++++++++-- 7 files changed, 84 insertions(+), 61 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 2ea921d..13402dc 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -17,7 +17,7 @@ fn main() { let mut sequencer = MockedSequencer::new(); let addresses = sequencer.addresses(); println!("addresses: {:?}", addresses); - println!("1️⃣ 🚀 Initial balances"); + println!("🚀 Initial balances"); sequencer.print(); // A public execution of the Transfer Program @@ -26,7 +26,7 @@ fn main() { sequencer .invoke_public_execution::(&[sender_addr, receiver_addr], 10) .unwrap(); - println!("2️⃣ 🚀 Balances after transfer"); + println!("🚀 Balances after transfer"); sequencer.print(); // A shielded execution of the Transfer Program @@ -36,25 +36,25 @@ fn main() { sequencer.print(); // A private execution of the Transfer Program - let private_account_1 = MockedClient::transfer_private( - &private_account_2, + let [_, private_account_1] = MockedClient::transfer_private( + private_account_2, &ACCOUNTS_PRIVATE_KEYS[1], // <-- this is shifted 🫠 &addresses[3], 8, &mut sequencer, ); - println!("3️⃣ 🚀 Balances after shielded execution"); + println!("🚀 Balances after shielded execution"); sequencer.print(); // A deshielded execution of the Transfer Program MockedClient::transfer_deshielded( - &private_account_1, + private_account_1, &ACCOUNTS_PRIVATE_KEYS[0], &addresses[0], 1, &mut sequencer, ); - println!("4️⃣ 🚀 Balances after deshielded execution"); + println!("🚀 Balances after deshielded execution"); sequencer.print(); // A public execution of the Pinata program @@ -62,6 +62,6 @@ fn main() { sequencer .invoke_public_execution::(&[addresses[0], addresses[3]], preimage) .unwrap(); - println!("5️⃣ 🚀 Balances after public piñata execution"); + println!("🚀 Balances after public piñata execution"); sequencer.print(); } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index 04bea16..54d3edb 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -20,4 +20,29 @@ impl MockedClient { let nonce = [0; 8]; Account::new(address, nonce) } + + fn prove_and_send_to_sequencer( + input_accounts: &[Account], + instruction_data: P::InstructionData, + visibilities: &[InputVisibiility], + commitment_tree_root: [u32; 8], + sequencer: &mut MockedSequencer, + ) -> Vec { + let (receipt, private_outputs) = nssa::invoke_privacy_execution::

( + input_accounts, + instruction_data, + visibilities, + commitment_tree_root, + ) + .unwrap(); + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + + // Send to te sequencer + sequencer + .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .unwrap(); + + private_outputs + } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index c89d4ad..919b775 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -4,7 +4,7 @@ use core::types::{Address, Commitment, Key, Nullifier}; use nssa::program::TransferProgram; -use super::{MockedSequencer, MockedClient}; +use super::{MockedClient, MockedSequencer}; impl MockedClient { /// A shielded execution of the Transfer program @@ -19,25 +19,16 @@ impl MockedClient { let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); - let visibilities = vec![InputVisibiility::Public, InputVisibiility::Private(None)]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[sender_account, receiver_account.clone()], + let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; + let private_outputs = Self::prove_and_send_to_sequencer::( + &[sender_account, receiver_account], balance_to_move, &visibilities, commitment_tree_root, - ) - .unwrap(); - - // Assemble the private account - receiver_account.nonce = nonces[1]; - receiver_account.balance = balance_to_move; - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); - receiver_account + sequencer, + ); + let [receiver_private_account] = private_outputs.try_into().unwrap(); + receiver_private_account } + } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index 6bf6a6f..c84d68a 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -9,12 +9,12 @@ use super::{MockedClient, MockedSequencer}; impl MockedClient { /// A private execution of the Transfer program pub fn transfer_private( - from_account: &Account, + from_account: Account, from_account_pk: &Key, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Account { + ) -> [Account; 2] { // All of this is executed locally by the sender let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; @@ -25,24 +25,15 @@ impl MockedClient { InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), InputVisibiility::Private(None), ]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[from_account.clone(), receiver_account.clone()], + + let private_outputs = Self::prove_and_send_to_sequencer::( + &[from_account, receiver_account], balance_to_move, &visibilities, commitment_tree_root, - ) - .unwrap(); - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); + sequencer, + ); - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); - - // Assemble the private account - receiver_account.nonce = nonces[1]; - receiver_account.balance = balance_to_move; - receiver_account + private_outputs.try_into().unwrap() } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index 03b61ce..69f9193 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -8,7 +8,7 @@ use super::{MockedClient, MockedSequencer}; impl MockedClient { pub fn transfer_deshielded( - from_account: &Account, + from_account: Account, from_account_pk: &Key, to_address: &Address, balance_to_move: u128, @@ -24,20 +24,12 @@ impl MockedClient { InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), InputVisibiility::Public, ]; - let (receipt, nonces) = nssa::invoke_privacy_execution::( - &[from_account.clone(), to_account], + let private_outputs = Self::prove_and_send_to_sequencer::( + &[from_account, to_account], balance_to_move, &visibilities, commitment_tree_root, - ) - .unwrap(); - - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - - // Send to te sequencer - sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); + sequencer, + ); } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 3a0fafd..26a457a 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -5,9 +5,7 @@ use core::{ }; use std::collections::{BTreeMap, HashSet}; -use nssa; use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; -use risc0_zkvm::Receipt; use sparse_merkle_tree::SparseMerkleTree; use super::ACCOUNTS_PRIVATE_KEYS; diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 1bef79c..abce4f0 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,7 +1,7 @@ use core::{ account::Account, input::InputVisibiility, - types::{Commitment, Nonce, Nullifier}, + types::{AuthenticationPath, Commitment, Key, Nonce, Nullifier}, }; use program_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; @@ -66,12 +66,32 @@ pub fn execute( Ok(inputs_outputs) } +pub fn extract_private_outputs_from_inner_results( + inputs_outputs: &[Account], + num_inputs: usize, + visibilities: &[InputVisibiility], + nonces: &[Nonce], +) -> Vec { + inputs_outputs + .iter() + .skip(num_inputs) + .zip(visibilities) + .zip(nonces) + .filter(|((_, visibility), _)| matches!(visibility, InputVisibiility::Private(_))) + .map(|((account, _), nonce)| { + let mut this = account.clone(); + this.nonce = *nonce; + this + }) + .collect() +} + pub fn invoke_privacy_execution( inputs: &[Account], instruction_data: P::InstructionData, visibilities: &[InputVisibiility], commitment_tree_root: [u32; 8], -) -> Result<(Receipt, Vec), ()> { +) -> Result<(Receipt, Vec), ()> { // Prove inner program and get post state of the accounts let num_inputs = inputs.len(); let (inner_receipt, inputs_outputs) = execute_and_prove_inner::

(inputs, instruction_data)?; @@ -93,7 +113,13 @@ pub fn invoke_privacy_execution( let prover = default_prover(); let prove_info = prover.prove(env, OUTER_ELF).unwrap(); - Ok((prove_info.receipt, output_nonces)) + let private_outputs = extract_private_outputs_from_inner_results( + &inputs_outputs, + num_inputs, + &visibilities, + &output_nonces, + ); + Ok((prove_info.receipt, private_outputs)) } pub fn verify_privacy_execution( From 25dd3cb93155767c1579cddd3ca3ab67887521e6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 10:09:12 -0300 Subject: [PATCH 43/83] rename --- .../examples/happy_path.rs | 4 +- .../examples/mocked_components/client/mod.rs | 12 +-- .../sequencer/invoke_privacy_execution.rs | 76 ------------------- .../sequencer/invoke_public_execution.rs | 72 ------------------ .../mocked_components/sequencer/mod.rs | 4 +- 5 files changed, 10 insertions(+), 158 deletions(-) delete mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs delete mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 13402dc..107c24d 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -24,7 +24,7 @@ fn main() { let sender_addr = addresses[1]; let receiver_addr = addresses[2]; sequencer - .invoke_public_execution::(&[sender_addr, receiver_addr], 10) + .process_public_execution::(&[sender_addr, receiver_addr], 10) .unwrap(); println!("🚀 Balances after transfer"); sequencer.print(); @@ -60,7 +60,7 @@ fn main() { // A public execution of the Pinata program let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); sequencer - .invoke_public_execution::(&[addresses[0], addresses[3]], preimage) + .process_public_execution::(&[addresses[0], addresses[3]], preimage) .unwrap(); println!("🚀 Balances after public piñata execution"); sequencer.print(); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index 54d3edb..d5d4136 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -16,11 +16,6 @@ pub mod transfer_shielded; pub struct MockedClient; impl MockedClient { - pub fn fresh_account_for_mint(address: Address) -> Account { - let nonce = [0; 8]; - Account::new(address, nonce) - } - fn prove_and_send_to_sequencer( input_accounts: &[Account], instruction_data: P::InstructionData, @@ -40,9 +35,14 @@ impl MockedClient { // Send to te sequencer sequencer - .invoke_privacy_execution(receipt, &output.0, &output.1, &output.2) + .process_privacy_execution(receipt, &output.0, &output.1, &output.2) .unwrap(); private_outputs } + + pub fn fresh_account_for_mint(address: Address) -> Account { + let nonce = [0; 8]; + Account::new(address, nonce) + } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs deleted file mode 100644 index 046e0b5..0000000 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_privacy_execution.rs +++ /dev/null @@ -1,76 +0,0 @@ -use core::{ - account::Account, - types::{Commitment, Nullifier}, -}; - -use risc0_zkvm::Receipt; - -use super::MockedSequencer; - -impl MockedSequencer { - pub fn invoke_privacy_execution( - &mut self, - receipt: Receipt, - public_inputs_outputs: &[Account], - nullifiers: &[Nullifier], - commitments: &[Commitment], - ) -> Result<(), ()> { - let commitments_tree_root = self.get_commitment_tree_root(); - if public_inputs_outputs.len() % 2 != 0 { - return Err(()); - } - - let num_input_public = public_inputs_outputs.len() >> 1; - for account in public_inputs_outputs.iter().take(num_input_public) { - let current_account = self.get_account(&account.address).ok_or(())?; - if ¤t_account != account { - return Err(()); - } - } - - // Check that nullifiers have not been added before - if nullifiers - .iter() - .any(|nullifier| self.nullifier_set.contains(nullifier)) - { - return Err(()); - } - - // Check that commitments are new too - if commitments - .iter() - .any(|commitment| self.commitment_tree.values().contains(commitment)) - { - return Err(()); - } - - // Verify consistency between public accounts, nullifiers and commitments - nssa::verify_privacy_execution( - receipt, - public_inputs_outputs, - nullifiers, - commitments, - &commitments_tree_root, - )?; - - // Update accounts - public_inputs_outputs - .iter() - .cloned() - .skip(num_input_public) - .for_each(|account_post_state| { - self.accounts - .insert(account_post_state.address, account_post_state); - }); - - // Add nullifiers - self.nullifier_set.extend(nullifiers); - - // Add commitments - for commitment in commitments.iter() { - self.commitment_tree.add_value(*commitment); - } - - Ok(()) - } -} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs deleted file mode 100644 index 0e6a46f..0000000 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/invoke_public_execution.rs +++ /dev/null @@ -1,72 +0,0 @@ -use core::{account::Account, types::Address}; - -use super::MockedSequencer; - -impl MockedSequencer { - pub fn invoke_public_execution( - &mut self, - input_account_addresses: &[Address], - instruction_data: P::InstructionData, - ) -> Result<(), ()> { - // Fetch accounts - let input_accounts: Vec = input_account_addresses - .iter() - .map(|address| self.get_account(address).ok_or(())) - .collect::>()?; - - // Execute - let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; - - // Perform consistency checks - if !self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs) { - return Err(()); - } - - // Update accounts - inputs_outputs - .into_iter() - .skip(input_accounts.len()) - .for_each(|account_post_state| { - self.accounts - .insert(account_post_state.address, account_post_state); - }); - Ok(()) - } - - fn inputs_outputs_are_consistent( - &self, - input_accounts: &[Account], - inputs_outputs: &[Account], - ) -> bool { - let num_inputs = input_accounts.len(); - if inputs_outputs.len() != num_inputs * 2 { - return false; - } - - let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); - if claimed_accounts_pre != input_accounts { - return false; - } - - for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { - if account_pre.address != account_post.address { - return false; - } - if account_pre.nonce != account_post.nonce { - return false; - } - // Redundant with previous checks, but better make it explicit. - if !self.accounts.contains_key(&account_post.address) { - return false; - } - } - let accounts_pre_total_balance: u128 = - input_accounts.iter().map(|account| account.balance).sum(); - let accounts_post_total_balance: u128 = - accounts_post.iter().map(|account| account.balance).sum(); - if accounts_pre_total_balance != accounts_post_total_balance { - return false; - } - return true; - } -} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 26a457a..c88d98a 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -10,8 +10,8 @@ use sparse_merkle_tree::SparseMerkleTree; use super::ACCOUNTS_PRIVATE_KEYS; -pub mod invoke_privacy_execution; -pub mod invoke_public_execution; +pub mod process_privacy_execution; +pub mod process_public_execution; pub struct MockedSequencer { accounts: BTreeMap, From 1dbc4bef729a9d005b466e1dc3f5f804aab604ce Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 10:16:26 -0300 Subject: [PATCH 44/83] move output decode to process function --- .../examples/mocked_components/client/mod.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index d5d4136..9121c20 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -30,13 +30,8 @@ impl MockedClient { commitment_tree_root, ) .unwrap(); - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - // Send to te sequencer - sequencer - .process_privacy_execution(receipt, &output.0, &output.1, &output.2) - .unwrap(); + sequencer.process_privacy_execution(receipt).unwrap(); private_outputs } From c02a80440422ac948746265fe6812c0067d4fe5c Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 10:29:41 -0300 Subject: [PATCH 45/83] add missing files --- .../client/transfer_deshielded.rs | 1 + .../sequencer/process_privacy_execution.rs | 77 +++++++++++++++++++ .../sequencer/process_public_execution.rs | 72 +++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index 919b775..e47b861 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -20,6 +20,7 @@ impl MockedClient { let receiver_addr = to_address; let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; + let private_outputs = Self::prove_and_send_to_sequencer::( &[sender_account, receiver_account], balance_to_move, diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs new file mode 100644 index 0000000..d620d37 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -0,0 +1,77 @@ +use core::{ + account::Account, + types::{Commitment, Nullifier}, +}; + +use risc0_zkvm::Receipt; + +use super::MockedSequencer; + +impl MockedSequencer { + pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), ()> { + let output: (Vec, Vec, Vec, [u32; 8]) = + receipt.journal.decode().unwrap(); + let (public_inputs_outputs, nullifiers, commitments, commitment_tree_root) = output; + + if commitment_tree_root != self.get_commitment_tree_root() { + return Err(()); + } + + if public_inputs_outputs.len() % 2 != 0 { + return Err(()); + } + + let num_input_public = public_inputs_outputs.len() >> 1; + for account in public_inputs_outputs.iter().take(num_input_public) { + let current_account = self.get_account(&account.address).ok_or(())?; + if ¤t_account != account { + return Err(()); + } + } + + // Check that nullifiers have not been added before + if nullifiers + .iter() + .any(|nullifier| self.nullifier_set.contains(nullifier)) + { + return Err(()); + } + + // Check that commitments are new too + if commitments + .iter() + .any(|commitment| self.commitment_tree.values().contains(commitment)) + { + return Err(()); + } + + // Verify consistency between public accounts, nullifiers and commitments + nssa::verify_privacy_execution( + receipt, + &public_inputs_outputs, + &nullifiers, + &commitments, + &commitment_tree_root, + )?; + + // Update accounts + public_inputs_outputs + .iter() + .cloned() + .skip(num_input_public) + .for_each(|account_post_state| { + self.accounts + .insert(account_post_state.address, account_post_state); + }); + + // Add nullifiers + self.nullifier_set.extend(nullifiers); + + // Add commitments + for commitment in commitments.iter() { + self.commitment_tree.add_value(*commitment); + } + + Ok(()) + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs new file mode 100644 index 0000000..b05e35d --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -0,0 +1,72 @@ +use core::{account::Account, types::Address}; + +use super::MockedSequencer; + +impl MockedSequencer { + pub fn process_public_execution( + &mut self, + input_account_addresses: &[Address], + instruction_data: P::InstructionData, + ) -> Result<(), ()> { + // Fetch accounts + let input_accounts: Vec = input_account_addresses + .iter() + .map(|address| self.get_account(address).ok_or(())) + .collect::>()?; + + // Execute + let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; + + // Perform consistency checks + if !self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs) { + return Err(()); + } + + // Update accounts + inputs_outputs + .into_iter() + .skip(input_accounts.len()) + .for_each(|account_post_state| { + self.accounts + .insert(account_post_state.address, account_post_state); + }); + Ok(()) + } + + fn inputs_outputs_are_consistent( + &self, + input_accounts: &[Account], + inputs_outputs: &[Account], + ) -> bool { + let num_inputs = input_accounts.len(); + if inputs_outputs.len() != num_inputs * 2 { + return false; + } + + let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); + if claimed_accounts_pre != input_accounts { + return false; + } + + for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { + if account_pre.address != account_post.address { + return false; + } + if account_pre.nonce != account_post.nonce { + return false; + } + // Redundant with previous checks, but better make it explicit. + if !self.accounts.contains_key(&account_post.address) { + return false; + } + } + let accounts_pre_total_balance: u128 = + input_accounts.iter().map(|account| account.balance).sum(); + let accounts_post_total_balance: u128 = + accounts_post.iter().map(|account| account.balance).sum(); + if accounts_pre_total_balance != accounts_post_total_balance { + return false; + } + return true; + } +} From 097bf54782b5c0424bfcd460b8544d84c78747d1 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 11:19:08 -0300 Subject: [PATCH 46/83] propagate errors --- risc0-selective-privacy-poc/examples/happy_path.rs | 8 +++++--- .../examples/mocked_components/client/mod.rs | 6 +++--- .../mocked_components/client/transfer_deshielded.rs | 7 +++---- .../mocked_components/client/transfer_private.rs | 6 +++--- .../mocked_components/client/transfer_public.rs | 11 +++++++++++ .../mocked_components/client/transfer_shielded.rs | 5 +++-- 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 107c24d..3280f2a 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -31,7 +31,7 @@ fn main() { // A shielded execution of the Transfer Program let private_account_2 = - MockedClient::transfer_shielded(&addresses[1], &addresses[2], 15, &mut sequencer); + MockedClient::transfer_shielded(&addresses[1], &addresses[2], 15, &mut sequencer).unwrap(); println!("Balances after shielded execution"); sequencer.print(); @@ -42,7 +42,8 @@ fn main() { &addresses[3], 8, &mut sequencer, - ); + ) + .unwrap(); println!("🚀 Balances after shielded execution"); sequencer.print(); @@ -53,7 +54,8 @@ fn main() { &addresses[0], 1, &mut sequencer, - ); + ) + .unwrap(); println!("🚀 Balances after deshielded execution"); sequencer.print(); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index 9121c20..d03447c 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -22,7 +22,7 @@ impl MockedClient { visibilities: &[InputVisibiility], commitment_tree_root: [u32; 8], sequencer: &mut MockedSequencer, - ) -> Vec { + ) -> Result, ()> { let (receipt, private_outputs) = nssa::invoke_privacy_execution::

( input_accounts, instruction_data, @@ -31,9 +31,9 @@ impl MockedClient { ) .unwrap(); // Send to te sequencer - sequencer.process_privacy_execution(receipt).unwrap(); + sequencer.process_privacy_execution(receipt)?; - private_outputs + Ok(private_outputs) } pub fn fresh_account_for_mint(address: Address) -> Account { diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index e47b861..8a8e104 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -13,7 +13,7 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Account { + ) -> Result { // All of this is executed locally by the sender let sender_account = sequencer.get_account(&from_address).unwrap(); let commitment_tree_root = sequencer.get_commitment_tree_root(); @@ -27,9 +27,8 @@ impl MockedClient { &visibilities, commitment_tree_root, sequencer, - ); + )?; let [receiver_private_account] = private_outputs.try_into().unwrap(); - receiver_private_account + Ok(receiver_private_account) } - } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index c84d68a..19abd23 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -14,7 +14,7 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> [Account; 2] { + ) -> Result<[Account; 2], ()> { // All of this is executed locally by the sender let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; @@ -32,8 +32,8 @@ impl MockedClient { &visibilities, commitment_tree_root, sequencer, - ); + )?; - private_outputs.try_into().unwrap() + Ok(private_outputs.try_into().unwrap()) } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs index e69de29..2d58c22 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs @@ -0,0 +1,11 @@ +use core::types::Address; + +use crate::mocked_components::client::MockedClient; + + + +impl MockedClient { + fn transfer_public(receiver_address: &Address, amount_to_transfer: u128) -> Result<(), ()> { + todo!() + } +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index 69f9193..2fb4bdf 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -13,7 +13,7 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) { + ) -> Result<(), ()> { // All of this is executed locally by the sender let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; @@ -30,6 +30,7 @@ impl MockedClient { &visibilities, commitment_tree_root, sequencer, - ); + )?; + Ok(()) } } From f93c481be326099f9c3e4ef8866397f5d0aa9bc3 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 11:24:15 -0300 Subject: [PATCH 47/83] add public transfer method to mocked client --- .../examples/happy_path.rs | 6 +----- .../mocked_components/client/transfer_public.rs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 3280f2a..ae3dddc 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -21,11 +21,7 @@ fn main() { sequencer.print(); // A public execution of the Transfer Program - let sender_addr = addresses[1]; - let receiver_addr = addresses[2]; - sequencer - .process_public_execution::(&[sender_addr, receiver_addr], 10) - .unwrap(); + MockedClient::transfer_public(&addresses[1], &addresses[2], 10, &mut sequencer).unwrap(); println!("🚀 Balances after transfer"); sequencer.print(); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs index 2d58c22..db10db3 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs @@ -1,11 +1,19 @@ use core::types::Address; -use crate::mocked_components::client::MockedClient; - +use nssa::program::TransferProgram; +use crate::mocked_components::{client::MockedClient, sequencer::MockedSequencer}; impl MockedClient { - fn transfer_public(receiver_address: &Address, amount_to_transfer: u128) -> Result<(), ()> { - todo!() + pub fn transfer_public( + sender_address: &Address, + receiver_address: &Address, + amount_to_transfer: u128, + sequencer: &mut MockedSequencer, + ) -> Result<(), ()> { + sequencer.process_public_execution::( + &[*sender_address, *receiver_address], + amount_to_transfer, + ) } } From 486822842fce9a199a9407017d08916a95e45d2b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 12:04:43 -0300 Subject: [PATCH 48/83] make client carry the private key of the user --- .../core/src/account.rs | 10 +++-- .../examples/happy_path.rs | 38 ++++++++----------- .../examples/mocked_components/client/mod.rs | 13 ++++++- .../client/transfer_deshielded.rs | 4 +- .../client/transfer_private.rs | 5 ++- .../client/transfer_public.rs | 4 +- .../client/transfer_shielded.rs | 5 ++- .../examples/mocked_components/mod.rs | 8 +++- .../mocked_components/sequencer/mod.rs | 10 ++--- 9 files changed, 57 insertions(+), 40 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index 85f6251..cdc56f2 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -1,6 +1,6 @@ use crate::{ hash, - types::{Address, Commitment, Nonce}, + types::{Address, Commitment, Key, Nonce}, }; use risc0_zkvm::{serde::to_vec, sha::Impl}; use serde::{Deserialize, Serialize}; @@ -14,11 +14,15 @@ pub struct Account { impl Account { /// Creates a new account with address = hash(private_key) and balance = 0 - pub fn new_from_private_key(private_key: Address, nonce: Nonce) -> Self { - let address = hash(&private_key); + pub fn new_from_private_key(private_key: Key, nonce: Nonce) -> Self { + let address = Self::address_for_key(&private_key); Self::new(address, nonce) } + pub fn address_for_key(private_key: &Key) -> Address { + hash(private_key) + } + pub fn new(address: Address, nonce: Nonce) -> Self { Self { address, diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index ae3dddc..580b2e1 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -9,56 +9,50 @@ use nssa::program::{PinataProgram, TransferProgram}; use risc0_zkvm::Receipt; use crate::mocked_components::sequencer::MockedSequencer; -use crate::mocked_components::{client::MockedClient, ACCOUNTS_PRIVATE_KEYS}; +use crate::mocked_components::{client::MockedClient, USER_CLIENTS}; mod mocked_components; fn main() { let mut sequencer = MockedSequencer::new(); - let addresses = sequencer.addresses(); + let addresses: [Address; 3] = USER_CLIENTS.map(|client| client.user_address()); + println!("addresses: {:?}", addresses); println!("🚀 Initial balances"); sequencer.print(); // A public execution of the Transfer Program - MockedClient::transfer_public(&addresses[1], &addresses[2], 10, &mut sequencer).unwrap(); + USER_CLIENTS[0] + .transfer_public(&addresses[1], 10, &mut sequencer) + .unwrap(); println!("🚀 Balances after transfer"); sequencer.print(); // A shielded execution of the Transfer Program - let private_account_2 = - MockedClient::transfer_shielded(&addresses[1], &addresses[2], 15, &mut sequencer).unwrap(); + let private_account_1 = USER_CLIENTS[0] + .transfer_shielded(&addresses[1], 15, &mut sequencer) + .unwrap(); println!("Balances after shielded execution"); sequencer.print(); // A private execution of the Transfer Program - let [_, private_account_1] = MockedClient::transfer_private( - private_account_2, - &ACCOUNTS_PRIVATE_KEYS[1], // <-- this is shifted 🫠 - &addresses[3], - 8, - &mut sequencer, - ) - .unwrap(); + let [_, private_account_2] = USER_CLIENTS[1] + .transfer_private(private_account_1, &addresses[2], 8, &mut sequencer) + .unwrap(); println!("🚀 Balances after shielded execution"); sequencer.print(); // A deshielded execution of the Transfer Program - MockedClient::transfer_deshielded( - private_account_1, - &ACCOUNTS_PRIVATE_KEYS[0], - &addresses[0], - 1, - &mut sequencer, - ) - .unwrap(); + USER_CLIENTS[2] + .transfer_deshielded(private_account_2, &addresses[0], 1, &mut sequencer) + .unwrap(); println!("🚀 Balances after deshielded execution"); sequencer.print(); // A public execution of the Pinata program let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); sequencer - .process_public_execution::(&[addresses[0], addresses[3]], preimage) + .process_public_execution::(&[[0xcafe; 8], addresses[2]], preimage) .unwrap(); println!("🚀 Balances after public piñata execution"); sequencer.print(); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index d03447c..1fd754f 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -13,9 +13,20 @@ pub mod transfer_private; pub mod transfer_public; pub mod transfer_shielded; -pub struct MockedClient; +pub struct MockedClient { + user_private_key: Key, +} impl MockedClient { + pub const fn new(user_private_key: Key) -> Self { + Self { user_private_key } + } + + pub fn user_address(&self) -> Address { + let address = Account::address_for_key(&self.user_private_key); + address + } + fn prove_and_send_to_sequencer( input_accounts: &[Account], instruction_data: P::InstructionData, diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index 8a8e104..df62ce4 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -9,13 +9,13 @@ use super::{MockedClient, MockedSequencer}; impl MockedClient { /// A shielded execution of the Transfer program pub fn transfer_shielded( - from_address: &Address, + &self, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, ) -> Result { // All of this is executed locally by the sender - let sender_account = sequencer.get_account(&from_address).unwrap(); + let sender_account = sequencer.get_account(&self.user_address()).ok_or(())?; let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index 19abd23..92fe405 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -9,8 +9,8 @@ use super::{MockedClient, MockedSequencer}; impl MockedClient { /// A private execution of the Transfer program pub fn transfer_private( + &self, from_account: Account, - from_account_pk: &Key, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, @@ -18,11 +18,12 @@ impl MockedClient { // All of this is executed locally by the sender let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; + // let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; let sender_commitment_auth_path = sequencer.get_authentication_path_for(&from_account.commitment()); let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); let visibilities = vec![ - InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), InputVisibiility::Private(None), ]; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs index db10db3..a7d9374 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs @@ -6,13 +6,13 @@ use crate::mocked_components::{client::MockedClient, sequencer::MockedSequencer} impl MockedClient { pub fn transfer_public( - sender_address: &Address, + &self, receiver_address: &Address, amount_to_transfer: u128, sequencer: &mut MockedSequencer, ) -> Result<(), ()> { sequencer.process_public_execution::( - &[*sender_address, *receiver_address], + &[self.user_address(), *receiver_address], amount_to_transfer, ) } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index 2fb4bdf..816ff26 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -8,8 +8,8 @@ use super::{MockedClient, MockedSequencer}; impl MockedClient { pub fn transfer_deshielded( + &self, from_account: Account, - from_account_pk: &Key, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, @@ -17,11 +17,12 @@ impl MockedClient { // All of this is executed locally by the sender let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; + // let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; let sender_commitment_auth_path = sequencer.get_authentication_path_for(&from_account.commitment()); let to_account = sequencer.get_account(&to_address).unwrap(); let visibilities = vec![ - InputVisibiility::Private(Some((from_account_pk.clone(), sender_commitment_auth_path))), + InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), InputVisibiility::Public, ]; let private_outputs = Self::prove_and_send_to_sequencer::( diff --git a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs index 68ee005..b0dcc4f 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs @@ -1,6 +1,12 @@ use core::types::Key; +use crate::mocked_components::client::MockedClient; + pub mod client; pub mod sequencer; -pub const ACCOUNTS_PRIVATE_KEYS: [Key; 3] = [[1; 8], [2; 8], [3; 8]]; +pub const USER_CLIENTS: [MockedClient; 3] = [ + MockedClient::new([1; 8]), + MockedClient::new([2; 8]), + MockedClient::new([3; 8]), +]; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index c88d98a..358d2ed 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -8,7 +8,7 @@ use std::collections::{BTreeMap, HashSet}; use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; use sparse_merkle_tree::SparseMerkleTree; -use super::ACCOUNTS_PRIVATE_KEYS; +use crate::mocked_components::USER_CLIENTS; pub mod process_privacy_execution; pub mod process_public_execution; @@ -25,12 +25,12 @@ const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, impl MockedSequencer { pub fn new() -> Self { - let mut accounts: BTreeMap = ACCOUNTS_PRIVATE_KEYS + let mut accounts: BTreeMap = USER_CLIENTS .iter() - .cloned() + .map(|client| client.user_address()) .zip(ACCOUNTS_INITIAL_BALANCES) - .map(|(key, initial_balance)| { - let mut this = Account::new_from_private_key(key, [0; 8]); + .map(|(address, initial_balance)| { + let mut this = Account::new(address, [0; 8]); this.balance = initial_balance; this }) From 6db06722c165c09fb9e4a91f5133ebd3d67b5a3b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 15:56:41 -0300 Subject: [PATCH 49/83] refactor --- .../core/src/account.rs | 28 +++--- risc0-selective-privacy-poc/core/src/lib.rs | 2 +- .../core/src/{input.rs => visibility.rs} | 2 +- .../examples/happy_path.rs | 85 ++++++++++++----- .../examples/mocked_components/client/mod.rs | 16 ++-- .../client/transfer_deshielded.rs | 24 +++-- .../client/transfer_private.rs | 9 +- .../client/transfer_shielded.rs | 25 +++-- .../mocked_components/sequencer/mod.rs | 91 ++++++++++++------- .../examples/private_execution.rs | 4 +- .../examples/public_execution.rs | 19 +--- .../program_methods/guest/src/bin/outer.rs | 5 +- risc0-selective-privacy-poc/src/lib.rs | 2 +- 13 files changed, 185 insertions(+), 127 deletions(-) rename risc0-selective-privacy-poc/core/src/{input.rs => visibility.rs} (100%) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index cdc56f2..fd0549a 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -5,6 +5,7 @@ use crate::{ use risc0_zkvm::{serde::to_vec, sha::Impl}; use serde::{Deserialize, Serialize}; +/// Account to be used both in public and private contexts #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Account { pub address: Address, @@ -13,25 +14,26 @@ pub struct Account { } impl Account { - /// Creates a new account with address = hash(private_key) and balance = 0 - pub fn new_from_private_key(private_key: Key, nonce: Nonce) -> Self { - let address = Self::address_for_key(&private_key); - Self::new(address, nonce) + pub fn new(address: Address, balance: u128) -> Self { + Self { + address, + balance, + nonce: [0; 8], + } } + /// Creates a new account with address = hash(private_key) and balance = 0 + pub fn new_from_private_key(private_key: Key) -> Self { + let address = Self::address_for_key(&private_key); + Self::new(address, 0) + } + + /// Computes the address corresponding to the given private key pub fn address_for_key(private_key: &Key) -> Address { hash(private_key) } - pub fn new(address: Address, nonce: Nonce) -> Self { - Self { - address, - balance: 0, - nonce, - } - } - - /// Returns Hash(Account)[0] (only first word for this POC) + /// Returns (first 8 bytes of) SHA256(Account) pub fn commitment(&self) -> Commitment { hash(&to_vec(&self).unwrap())[0] } diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 87d255f..57f40bc 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -1,6 +1,6 @@ pub mod account; -pub mod input; pub mod types; +pub mod visibility; use crate::types::{AuthenticationPath, Commitment, Key, Nullifier}; use risc0_zkvm::sha::{Impl, Sha256}; diff --git a/risc0-selective-privacy-poc/core/src/input.rs b/risc0-selective-privacy-poc/core/src/visibility.rs similarity index 100% rename from risc0-selective-privacy-poc/core/src/input.rs rename to risc0-selective-privacy-poc/core/src/visibility.rs index 944164a..dc04fd0 100644 --- a/risc0-selective-privacy-poc/core/src/input.rs +++ b/risc0-selective-privacy-poc/core/src/visibility.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use crate::types::{AuthenticationPath, Key}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub enum InputVisibiility { diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 580b2e1..d25abd3 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -1,14 +1,14 @@ use core::{ account::Account, bytes_to_words, hash, - input::InputVisibiility, types::{Address, Commitment, Key, Nullifier}, + visibility::InputVisibiility, }; use nssa::program::{PinataProgram, TransferProgram}; use risc0_zkvm::Receipt; -use crate::mocked_components::sequencer::MockedSequencer; +use crate::mocked_components::sequencer::{print_accounts, MockedSequencer}; use crate::mocked_components::{client::MockedClient, USER_CLIENTS}; mod mocked_components; @@ -16,44 +16,81 @@ mod mocked_components; fn main() { let mut sequencer = MockedSequencer::new(); let addresses: [Address; 3] = USER_CLIENTS.map(|client| client.user_address()); - - println!("addresses: {:?}", addresses); - println!("🚀 Initial balances"); - sequencer.print(); + println!("📝 Initial balances"); + print_accounts(&sequencer, &[]); // A public execution of the Transfer Program - USER_CLIENTS[0] - .transfer_public(&addresses[1], 10, &mut sequencer) + USER_CLIENTS[1] + .transfer_public(&[0xcafe; 8], 51, &mut sequencer) .unwrap(); - println!("🚀 Balances after transfer"); - sequencer.print(); + println!("📝 Balances after transfer"); + print_accounts(&sequencer, &[]); // A shielded execution of the Transfer Program - let private_account_1 = USER_CLIENTS[0] + let private_account_user_1 = USER_CLIENTS[0] .transfer_shielded(&addresses[1], 15, &mut sequencer) .unwrap(); - println!("Balances after shielded execution"); - sequencer.print(); + println!("📝 Balances after shielded execution"); + print_accounts(&sequencer, &[&private_account_user_1]); // A private execution of the Transfer Program - let [_, private_account_2] = USER_CLIENTS[1] - .transfer_private(private_account_1, &addresses[2], 8, &mut sequencer) + let [private_account_user_1, private_account_user_2] = USER_CLIENTS[1] + .transfer_private(private_account_user_1, &addresses[2], 8, &mut sequencer) .unwrap(); - println!("🚀 Balances after shielded execution"); - sequencer.print(); + println!("📝 Balances after shielded execution"); + print_accounts( + &sequencer, + &[&private_account_user_1, &private_account_user_2], + ); // A deshielded execution of the Transfer Program - USER_CLIENTS[2] - .transfer_deshielded(private_account_2, &addresses[0], 1, &mut sequencer) + let private_acount_user_2 = USER_CLIENTS[2] + .transfer_deshielded(private_account_user_2, &addresses[0], 1, &mut sequencer) .unwrap(); - println!("🚀 Balances after deshielded execution"); - sequencer.print(); + println!("📝 Balances after deshielded execution"); + print_accounts( + &sequencer, + &[&private_account_user_1, &private_acount_user_2], + ); - // A public execution of the Pinata program + // A public execution of the Piñata program let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); sequencer .process_public_execution::(&[[0xcafe; 8], addresses[2]], preimage) .unwrap(); - println!("🚀 Balances after public piñata execution"); - sequencer.print(); + println!("📝 Balances after public piñata execution"); + print_accounts( + &sequencer, + &[&private_account_user_1, &private_acount_user_2], + ); + + // A deshielded execution of the Piñata program + let private_account_user_0 = { + // All of this is executed locally by the sender + let receiver_addr = USER_CLIENTS[1].user_address(); + let pinata_account = sequencer.get_account(&[0xcafe; 8]).unwrap(); + let mut receiver_account = MockedClient::fresh_account_for_mint(receiver_addr); + let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; + let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); + + let private_outputs = MockedClient::prove_and_send_to_sequencer::( + &[pinata_account, receiver_account], + preimage, + &visibilities, + sequencer.get_commitment_tree_root(), + &mut sequencer, + ) + .unwrap(); + let [private_account_user_0] = private_outputs.try_into().unwrap(); + private_account_user_0 + }; + println!("📝 Balances after private piñata execution"); + print_accounts( + &sequencer, + &[ + &private_account_user_1, + &private_acount_user_2, + &private_account_user_0, + ], + ); } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index 1fd754f..a6a9cdc 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -1,7 +1,7 @@ use core::{ account::Account, - input::InputVisibiility, types::{Address, Commitment, Key, Nullifier}, + visibility::InputVisibiility, }; use nssa::program::TransferProgram; @@ -15,19 +15,22 @@ pub mod transfer_shielded; pub struct MockedClient { user_private_key: Key, + private_accounts: Vec, } impl MockedClient { pub const fn new(user_private_key: Key) -> Self { - Self { user_private_key } + Self { + user_private_key, + private_accounts: Vec::new(), + } } pub fn user_address(&self) -> Address { - let address = Account::address_for_key(&self.user_private_key); - address + Account::address_for_key(&self.user_private_key) } - fn prove_and_send_to_sequencer( + pub fn prove_and_send_to_sequencer( input_accounts: &[Account], instruction_data: P::InstructionData, visibilities: &[InputVisibiility], @@ -48,7 +51,6 @@ impl MockedClient { } pub fn fresh_account_for_mint(address: Address) -> Account { - let nonce = [0; 8]; - Account::new(address, nonce) + Account::new(address, 0) } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index df62ce4..b09c173 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -1,5 +1,5 @@ use core::account::Account; -use core::input::InputVisibiility; +use core::visibility::InputVisibiility; use core::types::{Address, Commitment, Key, Nullifier}; use nssa::program::TransferProgram; @@ -7,28 +7,32 @@ use nssa::program::TransferProgram; use super::{MockedClient, MockedSequencer}; impl MockedClient { - /// A shielded execution of the Transfer program - pub fn transfer_shielded( + pub fn transfer_deshielded( &self, + from_account: Account, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, ) -> Result { // All of this is executed locally by the sender - let sender_account = sequencer.get_account(&self.user_address()).ok_or(())?; let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; - let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); - let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; - + // let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; + let sender_commitment_auth_path = + sequencer.get_authentication_path_for(&from_account.commitment()); + let to_account = sequencer.get_account(&to_address).unwrap(); + let visibilities = vec![ + InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), + InputVisibiility::Public, + ]; let private_outputs = Self::prove_and_send_to_sequencer::( - &[sender_account, receiver_account], + &[from_account, to_account], balance_to_move, &visibilities, commitment_tree_root, sequencer, )?; - let [receiver_private_account] = private_outputs.try_into().unwrap(); - Ok(receiver_private_account) + let [sender_private_account] = private_outputs.try_into().unwrap(); + Ok(sender_private_account) } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index 92fe405..7e8a6f8 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -1,5 +1,5 @@ use core::account::Account; -use core::input::InputVisibiility; +use core::visibility::InputVisibiility; use core::types::{Address, Commitment, Key, Nullifier}; use nssa::program::TransferProgram; @@ -10,7 +10,7 @@ impl MockedClient { /// A private execution of the Transfer program pub fn transfer_private( &self, - from_account: Account, + owned_private_account: Account, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, @@ -18,9 +18,8 @@ impl MockedClient { // All of this is executed locally by the sender let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; - // let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&from_account.commitment()); + sequencer.get_authentication_path_for(&owned_private_account.commitment()); let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); let visibilities = vec![ InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), @@ -28,7 +27,7 @@ impl MockedClient { ]; let private_outputs = Self::prove_and_send_to_sequencer::( - &[from_account, receiver_account], + &[owned_private_account, receiver_account], balance_to_move, &visibilities, commitment_tree_root, diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index 816ff26..1aecf3b 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -1,37 +1,34 @@ use core::account::Account; -use core::input::InputVisibiility; use core::types::{Address, Commitment, Key, Nullifier}; +use core::visibility::InputVisibiility; use nssa::program::TransferProgram; use super::{MockedClient, MockedSequencer}; impl MockedClient { - pub fn transfer_deshielded( + /// A shielded execution of the Transfer program + pub fn transfer_shielded( &self, - from_account: Account, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Result<(), ()> { + ) -> Result { // All of this is executed locally by the sender + let sender_account = sequencer.get_account(&self.user_address()).ok_or(())?; let commitment_tree_root = sequencer.get_commitment_tree_root(); let receiver_addr = to_address; - // let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; - let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&from_account.commitment()); - let to_account = sequencer.get_account(&to_address).unwrap(); - let visibilities = vec![ - InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), - InputVisibiility::Public, - ]; + let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); + let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; + let private_outputs = Self::prove_and_send_to_sequencer::( - &[from_account, to_account], + &[sender_account, receiver_account], balance_to_move, &visibilities, commitment_tree_root, sequencer, )?; - Ok(()) + let [receiver_private_account] = private_outputs.try_into().unwrap(); + Ok(receiver_private_account) } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 358d2ed..0880c75 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -20,28 +20,20 @@ pub struct MockedSequencer { deployed_program_ids: HashSet, } -const ACCOUNTS_INITIAL_BALANCES: [u128; 3] = [100, 1337, 37]; const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; +const INITIAL_BALANCE: u128 = 150; +const PINATA_ADDRESS: Address = [0xcafe; 8]; impl MockedSequencer { pub fn new() -> Self { let mut accounts: BTreeMap = USER_CLIENTS .iter() .map(|client| client.user_address()) - .zip(ACCOUNTS_INITIAL_BALANCES) - .map(|(address, initial_balance)| { - let mut this = Account::new(address, [0; 8]); - this.balance = initial_balance; - this - }) + .map(|address| Account::new(address, INITIAL_BALANCE)) .map(|account| (account.address, account)) .collect(); - let pinata_account = { - let mut this = Account::new([0xcafe; 8], [0; 8]); - this.balance = 100; - this - }; + let pinata_account = Account::new(PINATA_ADDRESS, INITIAL_BALANCE); accounts.insert(pinata_account.address, pinata_account); let commitment_tree = SparseMerkleTree::new_empty(); @@ -75,23 +67,60 @@ impl MockedSequencer { pub fn addresses(&self) -> Vec

{ self.accounts.keys().cloned().collect() } - - pub fn print(&self) { - println!("{:<20} | {:>10}", "Address (first u32)", "Balance"); - println!("{:-<20}-+-{:-<10}", "", ""); - - for account in self.accounts.values() { - println!("{:<20x} | {:>10}", account.address[0], account.balance); - } - println!("{:-<20}-+-{:-<10}", "", ""); - println!("Commitments: {:?}", self.commitment_tree.values()); - let formatted: Vec = self - .nullifier_set - .iter() - .map(|arr| format!("0x{:x}", arr[0])) - .collect(); - println!("Nullifiers: [{}]", formatted.join(", ")); - println!(""); - println!(""); - } +} + +pub fn print_accounts(sequencer: &MockedSequencer, private_accounts: &[&Account]) { + println!("\n====================== ACCOUNT SNAPSHOT ======================\n"); + + println!(">> Public Accounts:"); + println!("{:<20} | {:>10} |", "Address (first u32)", "Balance"); + println!("{:-<20}-+-{:-<10}", "", ""); + + for account in sequencer.accounts.values() { + println!("0x{:<20x} | {:>10} |", account.address[0], account.balance); + } + + println!("{:-<20}-+-{:-<10}\n", "", ""); + + println!(">> Commitments:"); + println!("{:-<20}", ""); + + for commitment in sequencer.commitment_tree.values().iter() { + println!("{:<20x}", commitment); + } + + println!("{:-<20}\n", ""); + + let formatted: Vec = sequencer + .nullifier_set + .iter() + .map(|nullifier| format!("0x{:x}", nullifier[0])) + .collect(); + + println!(">> Nullifiers (first u32):"); + println!("{:-<20}", ""); + + for entry in formatted { + println!("{:<20}", entry); + } + + println!("{:-<20}\n", ""); + + println!(">> Private Accounts:"); + println!( + "{:<20} | {:>10} | {:>10}", + "Address (first u32)", "Nonce", "Balance" + ); + println!("{:-<20}-+-{:-<10}-+-{:-<10}", "", "", ""); + + for account in private_accounts.iter() { + println!( + "{:<20x} | {:>10x}| {:>10} | ", + account.address[0], account.nonce[0], account.balance, + ); + } + + println!("{:-<20}-+-{:-<10}-+-{:-<10}", "", "", ""); + + println!("\n=============================================================\n"); } diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 6a6edf2..b32fc9c 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,7 +1,7 @@ use core::{ account::Account, bytes_to_words, - input::InputVisibiility, + visibility::InputVisibiility, types::{Address, AuthenticationPath, Commitment, Nullifier}, }; use nssa::program::TransferMultipleProgram; @@ -22,7 +22,7 @@ fn main() { let sender_private_key = [1, 2, 3, 4, 4, 3, 2, 1]; let sender = { // Creating it now but it's supposed to be already created by other previous transactions. - let mut account = Account::new_from_private_key(sender_private_key, [1; 8]); + let mut account = Account::new_from_private_key(sender_private_key); account.balance = 150; account }; diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index e2fada7..0b22a3a 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -10,24 +10,13 @@ use nssa::program::TransferMultipleProgram; /// the initiating transaction includes the sender's signature. pub fn main() { // Account fetched from the chain state with 150 in its balance. - let sender = { - let mut account = Account::new([5; 8], [98; 8]); - account.balance = 150; - account - }; + let sender = Account::new([5; 8], 150); // Account fetched from the chain state with 900 in its balance. - let receiver_1 = { - let mut account = Account::new([6; 8], [99; 8]); - account.balance = 900; - account - }; + let receiver_1 = Account::new([6; 8], 900); - let receiver_2 = { - let mut account = Account::new([6; 8], [99; 8]); - account.balance = 500; - account - }; + // Account fetched from the chain state with 500 in its balance. + let receiver_2 = Account::new([6; 8], 500); let balance_to_move = vec![10, 20]; diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 8fadb3e..72f8831 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,9 +1,8 @@ use core::{ account::Account, - compute_nullifier, hash, - input::InputVisibiility, - is_in_tree, + compute_nullifier, hash, is_in_tree, types::{Nonce, ProgramId}, + visibility::InputVisibiility, }; use risc0_zkvm::{guest::env, serde::to_vec}; diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index abce4f0..ab0cabd 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,7 +1,7 @@ use core::{ account::Account, - input::InputVisibiility, types::{AuthenticationPath, Commitment, Key, Nonce, Nullifier}, + visibility::InputVisibiility, }; use program_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; From ce107906dab679bda6f36a76fc4143be9657192a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 16:42:55 -0300 Subject: [PATCH 50/83] add comments --- risc0-selective-privacy-poc/core/src/types.rs | 1 + .../examples/mocked_components/client/mod.rs | 23 ++++++----- .../client/transfer_deshielded.rs | 22 +++++++--- .../client/transfer_private.rs | 23 +++++++---- .../client/transfer_public.rs | 8 ++-- .../client/transfer_shielded.rs | 22 +++++++--- .../mocked_components/sequencer/mod.rs | 13 ++++-- .../sequencer/process_privacy_execution.rs | 36 ++++++++++++----- .../sequencer/process_public_execution.rs | 40 +++++++++++-------- 9 files changed, 124 insertions(+), 64 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/types.rs b/risc0-selective-privacy-poc/core/src/types.rs index 8aa04db..ff05508 100644 --- a/risc0-selective-privacy-poc/core/src/types.rs +++ b/risc0-selective-privacy-poc/core/src/types.rs @@ -1,3 +1,4 @@ +/// For this POC we consider 32-bit commitments pub type Commitment = u32; pub type Nullifier = [u32; 8]; pub type Address = [u32; 8]; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index a6a9cdc..9bbd1f9 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -1,18 +1,17 @@ +use crate::mocked_components::sequencer::MockedSequencer; use core::{ account::Account, types::{Address, Commitment, Key, Nullifier}, visibility::InputVisibiility, }; - use nssa::program::TransferProgram; -use crate::mocked_components::sequencer::MockedSequencer; - pub mod transfer_deshielded; pub mod transfer_private; pub mod transfer_public; pub mod transfer_shielded; +/// A client that creates and submits transfer transactions pub struct MockedClient { user_private_key: Key, private_accounts: Vec, @@ -30,6 +29,8 @@ impl MockedClient { Account::address_for_key(&self.user_private_key) } + /// Runs the outer program and submits the proof to the sequencer. + /// Returns the output private accounts of the execution. pub fn prove_and_send_to_sequencer( input_accounts: &[Account], instruction_data: P::InstructionData, @@ -37,19 +38,19 @@ impl MockedClient { commitment_tree_root: [u32; 8], sequencer: &mut MockedSequencer, ) -> Result, ()> { - let (receipt, private_outputs) = nssa::invoke_privacy_execution::

( - input_accounts, - instruction_data, - visibilities, - commitment_tree_root, - ) - .unwrap(); - // Send to te sequencer + // Execute and generate proof of the outer program + let (receipt, private_outputs) = + nssa::invoke_privacy_execution::

(input_accounts, instruction_data, visibilities, commitment_tree_root) + .unwrap(); + + // Send proof to the sequencer sequencer.process_privacy_execution(receipt)?; + // Return private outputs Ok(private_outputs) } + /// Returns a new account used in privacy executions that mint private accounts pub fn fresh_account_for_mint(address: Address) -> Account { Account::new(address, 0) } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index b09c173..0d0bb2c 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -1,12 +1,14 @@ use core::account::Account; -use core::visibility::InputVisibiility; use core::types::{Address, Commitment, Key, Nullifier}; +use core::visibility::InputVisibiility; use nssa::program::TransferProgram; use super::{MockedClient, MockedSequencer}; impl MockedClient { + /// A deshielded transaction of the Transfer program. + /// All of this is executed locally by the sender pub fn transfer_deshielded( &self, from_account: Account, @@ -14,17 +16,22 @@ impl MockedClient { balance_to_move: u128, sequencer: &mut MockedSequencer, ) -> Result { - // All of this is executed locally by the sender + // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - // let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; - let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&from_account.commitment()); + // Compute authenticaton path for the input private account + let sender_commitment_auth_path = sequencer.get_authentication_path_for(&from_account.commitment()); + + // Fetch public account to deshield to let to_account = sequencer.get_account(&to_address).unwrap(); + + // Set input visibilities + // First entry is the private sender. Second entry is the public receiver let visibilities = vec![ InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), InputVisibiility::Public, ]; + + // Execute privately (off-chain) and submit it to the sequencer let private_outputs = Self::prove_and_send_to_sequencer::( &[from_account, to_account], balance_to_move, @@ -32,6 +39,9 @@ impl MockedClient { commitment_tree_root, sequencer, )?; + + // There's only one private output account corresponding to the new private account of + // the sender, with the remaining balance. let [sender_private_account] = private_outputs.try_into().unwrap(); Ok(sender_private_account) } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index 7e8a6f8..27aca69 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -1,6 +1,6 @@ use core::account::Account; -use core::visibility::InputVisibiility; use core::types::{Address, Commitment, Key, Nullifier}; +use core::visibility::InputVisibiility; use nssa::program::TransferProgram; @@ -8,6 +8,7 @@ use super::{MockedClient, MockedSequencer}; impl MockedClient { /// A private execution of the Transfer program + // All of this is executed locally by the sender pub fn transfer_private( &self, owned_private_account: Account, @@ -15,17 +16,21 @@ impl MockedClient { balance_to_move: u128, sequencer: &mut MockedSequencer, ) -> Result<[Account; 2], ()> { - // All of this is executed locally by the sender + // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let sender_commitment_auth_path = - sequencer.get_authentication_path_for(&owned_private_account.commitment()); - let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); + // Compute authenticaton path for the input private account + let sender_commitment_auth_path = sequencer.get_authentication_path_for(&owned_private_account.commitment()); + + // Create a new default private account for the recipient + let mut receiver_account = Self::fresh_account_for_mint(*to_address); + + // Set visibilities. Both private accounts. let visibilities = vec![ InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), InputVisibiility::Private(None), ]; + // Execute privately (off-chain) and submit it to the sequencer let private_outputs = Self::prove_and_send_to_sequencer::( &[owned_private_account, receiver_account], balance_to_move, @@ -34,6 +39,10 @@ impl MockedClient { sequencer, )?; - Ok(private_outputs.try_into().unwrap()) + // There are two output private accounts of this execution. + // The first corresponds to the sender, with the remaining balance. + // The second corresponds to the newly minted private account for the recipient. + let [sender_private_account, receiver_private_account] = private_outputs.try_into().unwrap(); + Ok([sender_private_account, receiver_private_account]) } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs index a7d9374..d2bf365 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs @@ -7,13 +7,11 @@ use crate::mocked_components::{client::MockedClient, sequencer::MockedSequencer} impl MockedClient { pub fn transfer_public( &self, - receiver_address: &Address, + to_address: &Address, amount_to_transfer: u128, sequencer: &mut MockedSequencer, ) -> Result<(), ()> { - sequencer.process_public_execution::( - &[self.user_address(), *receiver_address], - amount_to_transfer, - ) + // Submit a public (on-chain) execution of the Transfer program to the sequencer + sequencer.process_public_execution::(&[self.user_address(), *to_address], amount_to_transfer) } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index 1aecf3b..9e56bba 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -8,26 +8,38 @@ use super::{MockedClient, MockedSequencer}; impl MockedClient { /// A shielded execution of the Transfer program + // All of this is executed locally by the sender pub fn transfer_shielded( &self, to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, ) -> Result { - // All of this is executed locally by the sender - let sender_account = sequencer.get_account(&self.user_address()).ok_or(())?; + // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); - let receiver_addr = to_address; - let mut receiver_account = Self::fresh_account_for_mint(*receiver_addr); + + // Fetch sender account from the sequencer + let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; + + // Create a new default private account for the receiver + let mut to_account = Self::fresh_account_for_mint(*to_address); + + // Set input visibilities + // First is the public account of the sender. Second is the private account minted in this + // execution let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; + // Execute privately (off-chain) and submit it to the sequencer let private_outputs = Self::prove_and_send_to_sequencer::( - &[sender_account, receiver_account], + &[from_account, to_account], balance_to_move, &visibilities, commitment_tree_root, sequencer, )?; + + // There is only one private account as the output of this execution. It corresponds to the + // receiver. let [receiver_private_account] = private_outputs.try_into().unwrap(); Ok(receiver_private_account) } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 0880c75..938942f 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -20,8 +20,11 @@ pub struct MockedSequencer { deployed_program_ids: HashSet, } +/// List of deployed programs const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; +/// The initial balance of the genesis accounts const INITIAL_BALANCE: u128 = 150; +/// The address of the piñata program account const PINATA_ADDRESS: Address = [0xcafe; 8]; impl MockedSequencer { @@ -46,14 +49,17 @@ impl MockedSequencer { } } + /// Returns the current state of the account for the given address pub fn get_account(&self, address: &Address) -> Option { self.accounts.get(address).cloned() } + /// Returns the root of the commitment tree pub fn get_commitment_tree_root(&self) -> [u32; 8] { bytes_to_words(&self.commitment_tree.root()) } + /// Computes the authentication path for the given commitment pub fn get_authentication_path_for(&self, commitment: &Commitment) -> AuthenticationPath { self.commitment_tree .get_authentication_path_for_value(*commitment) @@ -64,11 +70,13 @@ impl MockedSequencer { .unwrap() } + /// Returns the list of all registered addresses pub fn addresses(&self) -> Vec

{ self.accounts.keys().cloned().collect() } } +/// Pretty prints the chain's state pub fn print_accounts(sequencer: &MockedSequencer, private_accounts: &[&Account]) { println!("\n====================== ACCOUNT SNAPSHOT ======================\n"); @@ -107,10 +115,7 @@ pub fn print_accounts(sequencer: &MockedSequencer, private_accounts: &[&Account] println!("{:-<20}\n", ""); println!(">> Private Accounts:"); - println!( - "{:<20} | {:>10} | {:>10}", - "Address (first u32)", "Nonce", "Balance" - ); + println!("{:<20} | {:>10} | {:>10}", "Address (first u32)", "Nonce", "Balance"); println!("{:-<20}-+-{:-<10}-+-{:-<10}", "", "", ""); for account in private_accounts.iter() { diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index d620d37..3a28015 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -8,19 +8,28 @@ use risc0_zkvm::Receipt; use super::MockedSequencer; impl MockedSequencer { + /// Processes a privacy execution request. + /// Verifies the proof of the privacy execution and updates the state of the chain. pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), ()> { - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); + // Parse the output of the proof. + // This is the output of the "outer" program + let output: (Vec, Vec, Vec, [u32; 8]) = receipt.journal.decode().unwrap(); let (public_inputs_outputs, nullifiers, commitments, commitment_tree_root) = output; + // Reject in case the root used in the privacy execution is not the current root. if commitment_tree_root != self.get_commitment_tree_root() { return Err(()); } + // Reject in case the number of accounts in the public_inputs_outputs is not even. + // This is because it is expected to contain the pre and post-states of public the accounts + // of the inner execution. if public_inputs_outputs.len() % 2 != 0 { return Err(()); } + // Reject if the states of the public input accounts used in the inner execution do not + // coincide with the on-chain state. let num_input_public = public_inputs_outputs.len() >> 1; for account in public_inputs_outputs.iter().take(num_input_public) { let current_account = self.get_account(&account.address).ok_or(())?; @@ -29,7 +38,7 @@ impl MockedSequencer { } } - // Check that nullifiers have not been added before + // Reject if the nullifiers of this privacy execution have already been published. if nullifiers .iter() .any(|nullifier| self.nullifier_set.contains(nullifier)) @@ -37,7 +46,7 @@ impl MockedSequencer { return Err(()); } - // Check that commitments are new too + // Reject if the commitments have already been seen. if commitments .iter() .any(|commitment| self.commitment_tree.values().contains(commitment)) @@ -45,7 +54,13 @@ impl MockedSequencer { return Err(()); } - // Verify consistency between public accounts, nullifiers and commitments + // Verify the proof of the privacy execution. + // This includes a proof of the following statements + // - Public inputs, public outputs, commitments and nullifiers are consistent with the + // execution of some program. + // - The given nullifiers correctly correspond to commitments that currently belong to + // the commitment tree. + // - The given commitments are correctly computed from valid accounts. nssa::verify_privacy_execution( receipt, &public_inputs_outputs, @@ -54,20 +69,21 @@ impl MockedSequencer { &commitment_tree_root, )?; - // Update accounts + // At this point the privacy execution is considered valid. + // + // Update the state of the public accounts with the post-state of this privacy execution public_inputs_outputs .iter() .cloned() .skip(num_input_public) .for_each(|account_post_state| { - self.accounts - .insert(account_post_state.address, account_post_state); + self.accounts.insert(account_post_state.address, account_post_state); }); - // Add nullifiers + // Add all nullifiers to the nullifier set. self.nullifier_set.extend(nullifiers); - // Add commitments + // Add commitments to the commitment tree. for commitment in commitments.iter() { self.commitment_tree.add_value(*commitment); } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index b05e35d..2ab7f55 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -3,18 +3,19 @@ use core::{account::Account, types::Address}; use super::MockedSequencer; impl MockedSequencer { + /// Processes a public execution request of the program `P`. pub fn process_public_execution( &mut self, input_account_addresses: &[Address], instruction_data: P::InstructionData, ) -> Result<(), ()> { - // Fetch accounts + // Fetch the current state of the input accounts. let input_accounts: Vec = input_account_addresses .iter() .map(|address| self.get_account(address).ok_or(())) .collect::>()?; - // Execute + // Execute the program let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; // Perform consistency checks @@ -22,51 +23,58 @@ impl MockedSequencer { return Err(()); } - // Update accounts + // Update the accounts states inputs_outputs .into_iter() .skip(input_accounts.len()) .for_each(|account_post_state| { - self.accounts - .insert(account_post_state.address, account_post_state); + self.accounts.insert(account_post_state.address, account_post_state); }); Ok(()) } - fn inputs_outputs_are_consistent( - &self, - input_accounts: &[Account], - inputs_outputs: &[Account], - ) -> bool { + /// Verifies that a program public execution didn't break the chain's rules. + /// `input_accounts` are the accounts provided as inputs to the program. + /// `inputs_outputs` is the program output, which should consist of the accounts pre and + /// post-states. + fn inputs_outputs_are_consistent(&self, input_accounts: &[Account], inputs_outputs: &[Account]) -> bool { let num_inputs = input_accounts.len(); + + // Fail if the number of accounts pre and post-states is inconsistent with the number of + // inputs. if inputs_outputs.len() != num_inputs * 2 { return false; } + // Fail if the accounts pre-states do not coincide with the input accounts. let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); if claimed_accounts_pre != input_accounts { return false; } for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { + // Fail if the program modified the addresses of the input accounts if account_pre.address != account_post.address { return false; } + // Fail if the program modified the nonces of the input accounts if account_pre.nonce != account_post.nonce { return false; } - // Redundant with previous checks, but better make it explicit. + // Fail if any of the output accounts is not yet registered. + // (redundant with previous checks, but better make it explicit) if !self.accounts.contains_key(&account_post.address) { return false; } } - let accounts_pre_total_balance: u128 = - input_accounts.iter().map(|account| account.balance).sum(); - let accounts_post_total_balance: u128 = - accounts_post.iter().map(|account| account.balance).sum(); - if accounts_pre_total_balance != accounts_post_total_balance { + + let total_balance_pre: u128 = input_accounts.iter().map(|account| account.balance).sum(); + let total_balance_post: u128 = accounts_post.iter().map(|account| account.balance).sum(); + // Fail if the execution didn't preserve the total supply. + if total_balance_pre != total_balance_post { return false; } + return true; } } From f67c4beeb63a5ad25a6f09cbe2435b8bbe7a593f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 17:10:33 -0300 Subject: [PATCH 51/83] add docs --- .../examples/mocked_components/mod.rs | 1 + .../examples/private_execution.rs | 55 +++++++++---------- .../examples/public_execution.rs | 22 ++------ 3 files changed, 32 insertions(+), 46 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs index b0dcc4f..0f9ba62 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs @@ -5,6 +5,7 @@ use crate::mocked_components::client::MockedClient; pub mod client; pub mod sequencer; +/// Default users for examples pub const USER_CLIENTS: [MockedClient; 3] = [ MockedClient::new([1; 8]), MockedClient::new([2; 8]), diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index b32fc9c..8ea15fe 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,33 +1,29 @@ use core::{ account::Account, bytes_to_words, - visibility::InputVisibiility, types::{Address, AuthenticationPath, Commitment, Nullifier}, + visibility::InputVisibiility, }; use nssa::program::TransferMultipleProgram; use program_methods::OUTER_ID; use sparse_merkle_tree::SparseMerkleTree; -fn mint_fresh_account(address: Address) -> Account { - let nonce = [0; 8]; - Account::new(address, nonce) -} - -/// A private execution of the transfer function. +/// A private execution of the TransferMultiple function. /// This actually "burns" a sender private account and "mints" two new private accounts: /// one for the recipient with the transferred balance, and another owned by the sender with the remaining balance. fn main() { - // This is supposed to be an existing private account (UTXO) with balance equal to 150. - // And it is supposed to be a private account of the user running this private execution (hence the access to the private key) + // Setup commitment tree, simulating a current chain state that has a private account already + // committed to the commitment tree. let sender_private_key = [1, 2, 3, 4, 4, 3, 2, 1]; let sender = { - // Creating it now but it's supposed to be already created by other previous transactions. + // Creating this here but it is supposed to be already in the private possesion of the user let mut account = Account::new_from_private_key(sender_private_key); account.balance = 150; account }; - let commitment_tree = SparseMerkleTree::new([sender.commitment()].into_iter().collect()); + + // Get the root of the commitment tree and the authentication path of the commitment of the private account. let root = bytes_to_words(&commitment_tree.root()); let auth_path: Vec<[u32; 8]> = commitment_tree .get_authentication_path_for_value(sender.commitment()) @@ -36,37 +32,40 @@ fn main() { .collect(); let auth_path: AuthenticationPath = auth_path.try_into().unwrap(); - let balance_to_move: u128 = 3; - - // This is the new private account (UTXO) being minted by this private execution. (The `receiver_address` would be in UTXO's terminology) + // These are the new private account being minted by this private execution. + // (the `receiver_address` would be in UTXO's terminology) let receiver_address_1 = [99; 8]; - let receiver_1 = mint_fresh_account(receiver_address_1); - + let receiver_1 = new_default_account(receiver_address_1); let receiver_address_2 = [100; 8]; - let receiver_2 = mint_fresh_account(receiver_address_2); + let receiver_2 = new_default_account(receiver_address_2); + // Setup input visibilites. All accounts are private for this execution. let visibilities = vec![ InputVisibiility::Private(Some((sender_private_key, auth_path))), InputVisibiility::Private(None), InputVisibiility::Private(None), ]; + // Set the balances to be sent to the two receiver addresses. + // This means, the execution will remove 70 tokens from the sender + // and send 30 to the first receiver and 40 to the second. + let balance_to_move = vec![30, 40]; + + // Execute and prove the outer program for the TransferMultipleProgram. let (receipt, _) = nssa::invoke_privacy_execution::( &[sender, receiver_1, receiver_2], - vec![30, 40], + balance_to_move, &visibilities, root, ) .unwrap(); - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); - println!("public_outputs: {:?}", output.0); - println!("nullifiers: {:?}", output.1); - println!("commitments: {:?}", output.2); - println!("commitment_tree_root: {:?}", output.3); - - assert!( - nssa::verify_privacy_execution(receipt, &output.0, &output.1, &output.2, &output.3).is_ok() - ); + // Verify the proof + let output: (Vec, Vec, Vec, [u32; 8]) = receipt.journal.decode().unwrap(); + assert!(nssa::verify_privacy_execution(receipt, &output.0, &output.1, &output.2, &output.3).is_ok()); + println!("OK!"); +} + +fn new_default_account(address: Address) -> Account { + Account::new(address, 0) } diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 0b22a3a..97dc972 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -5,7 +5,7 @@ use nssa; use nssa::program::TransferMultipleProgram; -/// A public execution. +/// A public execution of the TransferMultipleProgram. /// This would be executed by the runtime after checking that /// the initiating transaction includes the sender's signature. pub fn main() { @@ -20,22 +20,8 @@ pub fn main() { let balance_to_move = vec![10, 20]; - let inputs_outputs = nssa::execute::( - &[sender, receiver_1, receiver_2], - balance_to_move, - ) - .unwrap(); + let inputs_outputs = + nssa::execute::(&[sender, receiver_1, receiver_2], balance_to_move).unwrap(); - println!( - "sender_before: {:?}, sender_after: {:?}", - inputs_outputs[0], inputs_outputs[3] - ); - println!( - "receiver_1_before: {:?}, receiver_1_after: {:?}", - inputs_outputs[1], inputs_outputs[4], - ); - println!( - "receiver_2_before: {:?}, receiver_2_after: {:?}", - inputs_outputs[2], inputs_outputs[5], - ); + println!("OK!"); } From f4f01b3d996c6ec6530141b8a0fc2a0e14b45a3a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 17:30:38 -0300 Subject: [PATCH 52/83] add docs --- .../examples/happy_path.rs | 23 +++------ .../program_methods/guest/src/bin/outer.rs | 47 ++++++++++++------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index d25abd3..8fdc947 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -38,20 +38,14 @@ fn main() { .transfer_private(private_account_user_1, &addresses[2], 8, &mut sequencer) .unwrap(); println!("📝 Balances after shielded execution"); - print_accounts( - &sequencer, - &[&private_account_user_1, &private_account_user_2], - ); + print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]); // A deshielded execution of the Transfer Program let private_acount_user_2 = USER_CLIENTS[2] .transfer_deshielded(private_account_user_2, &addresses[0], 1, &mut sequencer) .unwrap(); println!("📝 Balances after deshielded execution"); - print_accounts( - &sequencer, - &[&private_account_user_1, &private_acount_user_2], - ); + print_accounts(&sequencer, &[&private_account_user_1, &private_acount_user_2]); // A public execution of the Piñata program let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); @@ -59,10 +53,7 @@ fn main() { .process_public_execution::(&[[0xcafe; 8], addresses[2]], preimage) .unwrap(); println!("📝 Balances after public piñata execution"); - print_accounts( - &sequencer, - &[&private_account_user_1, &private_acount_user_2], - ); + print_accounts(&sequencer, &[&private_account_user_1, &private_acount_user_2]); // A deshielded execution of the Piñata program let private_account_user_0 = { @@ -87,10 +78,8 @@ fn main() { println!("📝 Balances after private piñata execution"); print_accounts( &sequencer, - &[ - &private_account_user_1, - &private_acount_user_2, - &private_account_user_0, - ], + &[&private_account_user_1, &private_acount_user_2, &private_account_user_0], ); + + println!("Ok!"); } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 72f8831..4bbf5f8 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -6,21 +6,28 @@ use core::{ }; use risc0_zkvm::{guest::env, serde::to_vec}; -/// Private execution logic. -/// Circuit for proving correct execution of some program with program id -/// equal to `program_id` (last input). +/// Privacy execution logic. +/// This is the circuit for proving correct off-chain executions of programs. +/// It also verifies that the chain's invariants are not violated. /// -/// Currently only supports private execution of a program with two input accounts, one -/// of which must be a fresh new account (`account_2`) (for example a private transfer function). +/// Inputs: +/// - Vec: The output of the inner program. This is assumed to include the accounts pre and +/// post-states of the execution of the inner program. /// -/// This circuit checks: -/// - That accounts pre states and post states are consistent with the execution of the given `program_id`. -/// - That `account_2` is fresh (meaning, for this toy example, that it has 0 balance). -/// - That `program_id` execution didn't change addresses of the accounts. +/// - Vec: A vector indicating which accounts are private and which are public. /// -/// Outputs: -/// - The nullifier for the only existing input account (account_1) -/// - The commitments for the private accounts post states. +/// - Vec: The vector of nonces to be used for the output accounts. This is assumed to be +/// sampled at random by the host program. +/// +/// - [u32; 8]: The root of the commitment tree. Commitments of used private accounts will be +/// checked against this to prove that they belong to the tree. +/// - ProgamId: The ID of the inner program. +/// +/// Public outputs: +/// - The vector of accounts' pre and post states for the public accounts. +/// - The nullifiers of the used private accounts. +/// - The commitments for the ouput private accounts. +/// - The commitment tree root used for the authentication path verifications. fn main() { let num_inputs: u32 = env::read(); // Read inputs and outputs @@ -35,11 +42,12 @@ fn main() { let output_nonces: Vec = env::read(); assert_eq!(output_nonces.len() as u32, num_inputs); + // Read root and program id. let commitment_tree_root: [u32; 8] = env::read(); let program_id: ProgramId = env::read(); // Verify pre states and post states of accounts are consistent - // with the execution of the `program_id`` program + // with the execution of the `program_id` program env::verify(program_id, &to_vec(&inputs_outputs).unwrap()).unwrap(); // Split inputs_outputs into two separate vectors @@ -57,12 +65,13 @@ fn main() { // Check the input account was created by a previous transaction by checking it belongs to the commitments tree. let commitment = input_account.commitment(); assert!(is_in_tree(commitment, auth_path, commitment_tree_root)); - // Compute nullifier to nullify this private input account. + // Compute the nullifier to nullify this private input account. let nullifier = compute_nullifier(&commitment, private_key); nullifiers.push(nullifier); } InputVisibiility::Private(None) => { // Private accounts without a companion private key are enforced to have default values + // Used for executions that need to create a new private account. assert_eq!(input_account.balance, 0); assert_eq!(input_account.nonce, [0; 8]); } @@ -77,6 +86,11 @@ fn main() { assert_eq!(account_pre.nonce, account_post.nonce); } + // Check that the program preserved the total supply + let total_balance_pre: u128 = inputs.iter().map(|account| account.balance).sum(); + let total_balance_post: u128 = outputs.iter().map(|account| account.balance).sum(); + assert_eq!(total_balance_pre, total_balance_post); + // Insert new nonces in outputs (including public ones (?!)) outputs .iter_mut() @@ -108,10 +122,7 @@ fn main() { } // Compute commitments for every private output - let private_output_commitments: Vec<_> = private_outputs - .iter() - .map(|account| account.commitment()) - .collect(); + let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect(); // Output nullifier of consumed input accounts and commitments of new output private accounts env::commit(&( From fd665f303ef2b2359dcdc403f0e8f0740f6e222e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 17:35:03 -0300 Subject: [PATCH 53/83] add rustfmt --- .../program_methods/guest/src/bin/outer.rs | 15 +++++++-------- risc0-selective-privacy-poc/rustfmt.toml | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 risc0-selective-privacy-poc/rustfmt.toml diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 4bbf5f8..ae2e0ad 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -80,7 +80,7 @@ fn main() { } } - // Assert `program_id` program didn't modify address fields or nonces + // Assert that the inner program didn't modify address fields or nonces for (account_pre, account_post) in inputs.iter().zip(outputs.iter()) { assert_eq!(account_pre.address, account_post.address); assert_eq!(account_pre.nonce, account_post.nonce); @@ -91,13 +91,15 @@ fn main() { let total_balance_post: u128 = outputs.iter().map(|account| account.balance).sum(); assert_eq!(total_balance_pre, total_balance_post); - // Insert new nonces in outputs (including public ones (?!)) + // From this point on the execution is considered valid + // + // Insert new nonces in outputs (including public ones) outputs .iter_mut() .zip(output_nonces) .for_each(|(account, new_nonce)| account.nonce = new_nonce); - // Compute private outputs commitments + // Compute commitments for every private output let mut private_outputs = Vec::new(); for (output, visibility) in outputs.iter().zip(input_visibilities.iter()) { match visibility { @@ -105,8 +107,9 @@ fn main() { InputVisibiility::Private(_) => private_outputs.push(output), } } + let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect(); - // Get the list of public inputs pre states and their post states + // Get the list of public accounts pre and post states let mut public_inputs_outputs = Vec::new(); for (account, visibility) in inputs .iter() @@ -121,10 +124,6 @@ fn main() { } } - // Compute commitments for every private output - let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect(); - - // Output nullifier of consumed input accounts and commitments of new output private accounts env::commit(&( public_inputs_outputs, nullifiers, diff --git a/risc0-selective-privacy-poc/rustfmt.toml b/risc0-selective-privacy-poc/rustfmt.toml new file mode 100644 index 0000000..7530651 --- /dev/null +++ b/risc0-selective-privacy-poc/rustfmt.toml @@ -0,0 +1 @@ +max_width = 120 From e4d4402bde06151e804ae9e2c0bac33ffecde4c0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 17:41:00 -0300 Subject: [PATCH 54/83] add docs to pinata program --- .../program_methods/guest/src/bin/pinata.rs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs index ddcc45a..17b73f6 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs @@ -1,31 +1,40 @@ use core::{account::Account, hash}; use risc0_zkvm::guest::env; -// preimage is b"NSSA Selective privacy is great!" const TARGET_HASH: [u32; 8] = [ 1363824975, 720119575, 717909014, 2043925380, 717793160, 1495780600, 1253022833, 116132328, ]; const PINATA_ACCOUNT_ADDR: [u32; 8] = [0xcafe; 8]; -const PINATA_PRICE: u128 = 100; +const PINATA_PRIZE: u128 = 100; /// A Piñata program -/// To be used both in public and private contexts. +/// To be used both in public and privacy contexts. fn main() { + // Read input accounts. It is expected to receive only two accounts: [pinata_account, winner_account] let mut input_accounts: Vec = env::read(); + + // Read claimed preimage let preimage: Vec = env::read(); + // Unpack accounts. assert_eq!(input_accounts.len(), 2); let [winner_account] = input_accounts.split_off(1).try_into().unwrap(); let [pinata_account] = input_accounts.try_into().unwrap(); + // Check that the given `pinata_account` is correct assert_eq!(pinata_account.address, PINATA_ACCOUNT_ADDR); - assert!(pinata_account.balance >= PINATA_PRICE); + + // Check that the piñata account has enough balance to pay the prize + assert!(pinata_account.balance >= PINATA_PRIZE); + + // Check that the preimage is correct. assert_eq!(hash(&preimage), TARGET_HASH); + // Pay the prize let mut winner_account_post = winner_account.clone(); let mut pinata_account_post = pinata_account.clone(); - pinata_account_post.balance -= PINATA_PRICE; - winner_account_post.balance += PINATA_PRICE; + pinata_account_post.balance -= PINATA_PRIZE; + winner_account_post.balance += PINATA_PRIZE; env::commit(&vec![ pinata_account, From 272e2dfc83c9698d75883744c76d0171fe9f9c51 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 17:49:28 -0300 Subject: [PATCH 55/83] add docs to transfer multiple program --- .../program_methods/guest/src/bin/pinata.rs | 6 +++--- .../program_methods/guest/src/bin/transfer.rs | 2 ++ .../guest/src/bin/transfer_multiple.rs | 21 +++++++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs index 17b73f6..d864f81 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs @@ -10,7 +10,8 @@ const PINATA_PRIZE: u128 = 100; /// A Piñata program /// To be used both in public and privacy contexts. fn main() { - // Read input accounts. It is expected to receive only two accounts: [pinata_account, winner_account] + // Read input accounts. + // It is expected to receive only two accounts: [pinata_account, winner_account] let mut input_accounts: Vec = env::read(); // Read claimed preimage @@ -18,8 +19,7 @@ fn main() { // Unpack accounts. assert_eq!(input_accounts.len(), 2); - let [winner_account] = input_accounts.split_off(1).try_into().unwrap(); - let [pinata_account] = input_accounts.try_into().unwrap(); + let [pinata_account, winner_account] = input_accounts.try_into().unwrap(); // Check that the given `pinata_account` is correct assert_eq!(pinata_account.address, PINATA_ACCOUNT_ADDR); diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs index 26b3d1e..5364837 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs @@ -4,6 +4,8 @@ use risc0_zkvm::guest::env; /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { + // Read input accounts. + // It is expected to receive only two accounts: [sender_account, receiver_account] let input_accounts: Vec = env::read(); let balance_to_move: u128 = env::read(); diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs index 60b5c34..dc495f1 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs @@ -1,26 +1,35 @@ use core::account::Account; use risc0_zkvm::guest::env; -/// A transfer of balance program with multiple recipients. +/// A transfer of balance program with one sender and multiple recipients. /// To be used both in public and private contexts. fn main() { + // Read input accounts. + // First account is the sender. let mut input_accounts: Vec = env::read(); + + // Read the balances to be send to each recipient let target_balances: Vec = env::read(); + // Check that there is at least one recipient assert!(input_accounts.len() > 1); + + // Check that there's one target balance for each recipient. assert_eq!(target_balances.len() + 1, input_accounts.len()); - let receivers = input_accounts.split_off(1); + // Unpack sender and recipients + let recipients = input_accounts.split_off(1); let sender = input_accounts.pop().unwrap(); - let total_balance_to_move = target_balances.iter().sum(); - // Check sender has enough balance + // Check that the sender has enough balance to pay to all recipients + let total_balance_to_move = target_balances.iter().sum(); assert!(sender.balance >= total_balance_to_move); // Create accounts post states, with updated balances let mut sender_post = sender.clone(); - let mut receivers_post = receivers.clone(); + let mut receivers_post = recipients.clone(); + // Transfer balances sender_post.balance -= total_balance_to_move; for (receiver, balance_for_receiver) in receivers_post.iter_mut().zip(target_balances) { receiver.balance += balance_for_receiver; @@ -29,7 +38,7 @@ fn main() { // Flatten pre and post states for output let inputs_outputs: Vec = vec![sender] .into_iter() - .chain(receivers) + .chain(recipients) .chain(vec![sender_post]) .chain(receivers_post) .collect(); From e3969d76a38c57abaa01c29b054871f461dc781d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 17:55:53 -0300 Subject: [PATCH 56/83] add comments --- .../program_methods/guest/src/bin/transfer.rs | 1 + risc0-selective-privacy-poc/src/lib.rs | 16 ++++++++-------- risc0-selective-privacy-poc/src/program/mod.rs | 4 +--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs index 5364837..1e1a961 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs @@ -9,6 +9,7 @@ fn main() { let input_accounts: Vec = env::read(); let balance_to_move: u128 = env::read(); + // Unpack sender and receiver assert_eq!(input_accounts.len(), 2); let [sender, receiver] = input_accounts.try_into().unwrap(); diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index ab0cabd..35107f0 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -16,6 +16,7 @@ pub fn new_random_nonce() -> Nonce { std::array::from_fn(|_| rng.gen()) } +/// Writes inputs to `env_builder` in the order expected by the programs fn write_inputs( input_accounts: &[Account], instruction_data: P::InstructionData, @@ -27,6 +28,8 @@ fn write_inputs( Ok(()) } +/// Executes and proves the program `P`. +/// Returns the proof and the list of accounts pre and post states fn execute_and_prove_inner( input_accounts: &[Account], instruction_data: P::InstructionData, @@ -47,6 +50,8 @@ fn execute_and_prove_inner( Ok((receipt, inputs_outputs)) } +/// Executes the program `P` without generating a proof. +/// Returns the list of accounts pre and post states. pub fn execute( input_accounts: &[Account], instruction_data: P::InstructionData, @@ -113,12 +118,8 @@ pub fn invoke_privacy_execution( let prover = default_prover(); let prove_info = prover.prove(env, OUTER_ELF).unwrap(); - let private_outputs = extract_private_outputs_from_inner_results( - &inputs_outputs, - num_inputs, - &visibilities, - &output_nonces, - ); + let private_outputs = + extract_private_outputs_from_inner_results(&inputs_outputs, num_inputs, &visibilities, &output_nonces); Ok((prove_info.receipt, private_outputs)) } @@ -129,8 +130,7 @@ pub fn verify_privacy_execution( private_output_commitments: &[Commitment], commitment_tree_root: &[u32; 8], ) -> Result<(), ()> { - let output: (Vec, Vec, Vec, [u32; 8]) = - receipt.journal.decode().unwrap(); + let output: (Vec, Vec, Vec, [u32; 8]) = receipt.journal.decode().unwrap(); let expected_output = ( public_accounts_inputs_outputs.to_vec(), nullifiers.to_vec(), diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index 8fe64b6..8ccec6d 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -1,8 +1,6 @@ use core::types::ProgramId; -use program_methods::{ - PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID, -}; +use program_methods::{PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}; use serde::{Deserialize, Serialize}; pub trait Program { From 952b6469c2aadd7709bc4886505cc5feafb2b9d0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 18:02:43 -0300 Subject: [PATCH 57/83] add comments --- risc0-selective-privacy-poc/src/lib.rs | 14 ++++++++++---- risc0-selective-privacy-poc/src/program/mod.rs | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 35107f0..ea1d9b9 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -71,7 +71,9 @@ pub fn execute( Ok(inputs_outputs) } -pub fn extract_private_outputs_from_inner_results( +/// Builds the private outputs from the results of the execution of an inner program. +/// Populates the nonces with the ones provided. +pub fn build_private_outputs_from_inner_results( inputs_outputs: &[Account], num_inputs: usize, visibilities: &[InputVisibiility], @@ -91,6 +93,9 @@ pub fn extract_private_outputs_from_inner_results( .collect() } +/// Executes and proves the inner program `P` and executes and proves the outer program on top of it. +/// Returns the proof of execution of the outer program and the list of new private accounts +/// resulted from this execution. pub fn invoke_privacy_execution( inputs: &[Account], instruction_data: P::InstructionData, @@ -105,7 +110,6 @@ pub fn invoke_privacy_execution( let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); // Prove outer program. - // This computes the nullifiers for the input accounts and commitments for the output accounts. let mut env_builder = ExecutorEnv::builder(); env_builder.add_assumption(inner_receipt); env_builder.write(&(num_inputs as u32)).unwrap(); @@ -115,14 +119,16 @@ pub fn invoke_privacy_execution( env_builder.write(&commitment_tree_root).unwrap(); env_builder.write(&P::PROGRAM_ID).unwrap(); let env = env_builder.build().unwrap(); - let prover = default_prover(); let prove_info = prover.prove(env, OUTER_ELF).unwrap(); + + // Build private accounts. let private_outputs = - extract_private_outputs_from_inner_results(&inputs_outputs, num_inputs, &visibilities, &output_nonces); + build_private_outputs_from_inner_results(&inputs_outputs, num_inputs, &visibilities, &output_nonces); Ok((prove_info.receipt, private_outputs)) } +/// Verifies a proof of the outer program for the given parameters. pub fn verify_privacy_execution( receipt: Receipt, public_accounts_inputs_outputs: &[Account], diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index 8ccec6d..9ee7dec 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -3,6 +3,7 @@ use core::types::ProgramId; use program_methods::{PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID}; use serde::{Deserialize, Serialize}; +/// A trait to be implemented by inner programs. pub trait Program { const PROGRAM_ID: ProgramId; const PROGRAM_ELF: &[u8]; From b128e81b19150a9305c617195e7ec71e3be1d664 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 18:04:33 -0300 Subject: [PATCH 58/83] clippy --- risc0-selective-privacy-poc/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index ea1d9b9..8c09e0f 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,6 +1,6 @@ use core::{ account::Account, - types::{AuthenticationPath, Commitment, Key, Nonce, Nullifier}, + types::{Commitment, Nonce, Nullifier}, visibility::InputVisibiility, }; use program_methods::{OUTER_ELF, OUTER_ID}; @@ -124,7 +124,7 @@ pub fn invoke_privacy_execution( // Build private accounts. let private_outputs = - build_private_outputs_from_inner_results(&inputs_outputs, num_inputs, &visibilities, &output_nonces); + build_private_outputs_from_inner_results(&inputs_outputs, num_inputs, visibilities, &output_nonces); Ok((prove_info.receipt, private_outputs)) } @@ -144,7 +144,7 @@ pub fn verify_privacy_execution( commitment_tree_root.to_owned(), ); if output != expected_output { - return Err(()); + Err(()) } else { receipt.verify(OUTER_ID).map_err(|_| ()) } From f696c5a452b08674fd35cca746d4cff88c84bbbb Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 18:09:55 -0300 Subject: [PATCH 59/83] unused imports --- risc0-selective-privacy-poc/core/src/account.rs | 2 +- risc0-selective-privacy-poc/core/src/lib.rs | 4 +--- .../program_methods/guest/src/bin/pinata.rs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index fd0549a..edcbc44 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -2,7 +2,7 @@ use crate::{ hash, types::{Address, Commitment, Key, Nonce}, }; -use risc0_zkvm::{serde::to_vec, sha::Impl}; +use risc0_zkvm::serde::to_vec; use serde::{Deserialize, Serialize}; /// Account to be used both in public and private contexts diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 57f40bc..7650de1 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -4,7 +4,6 @@ pub mod visibility; use crate::types::{AuthenticationPath, Commitment, Key, Nullifier}; use risc0_zkvm::sha::{Impl, Sha256}; -use serde::{Deserialize, Serialize}; pub fn hash(bytes: &[u32]) -> [u32; 8] { Impl::hash_words(bytes).as_words().try_into().unwrap() @@ -12,8 +11,7 @@ pub fn hash(bytes: &[u32]) -> [u32; 8] { pub fn is_in_tree(commitment: Commitment, path: &AuthenticationPath, root: [u32; 8]) -> bool { const HASH_ONE: [u32; 8] = [ - 789771595, 3310634292, 3140410939, 3820475020, 3591004369, 2777006897, 1021496535, - 2588247415, + 789771595, 3310634292, 3140410939, 3820475020, 3591004369, 2777006897, 1021496535, 2588247415, ]; let mut hash = HASH_ONE; diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs index d864f81..48c5a50 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs @@ -12,7 +12,7 @@ const PINATA_PRIZE: u128 = 100; fn main() { // Read input accounts. // It is expected to receive only two accounts: [pinata_account, winner_account] - let mut input_accounts: Vec = env::read(); + let input_accounts: Vec = env::read(); // Read claimed preimage let preimage: Vec = env::read(); From fe7727a93cbef043acadb8ab11564b6a986610db Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 18:10:33 -0300 Subject: [PATCH 60/83] fix --- risc0-selective-privacy-poc/examples/happy_path.rs | 10 ++++------ .../examples/mocked_components/client/mod.rs | 3 +-- .../mocked_components/client/transfer_deshielded.rs | 2 +- .../mocked_components/client/transfer_private.rs | 4 ++-- .../mocked_components/client/transfer_shielded.rs | 4 ++-- .../examples/mocked_components/mod.rs | 1 - .../examples/mocked_components/sequencer/mod.rs | 2 +- .../examples/private_execution.rs | 1 - .../examples/public_execution.rs | 1 - 9 files changed, 11 insertions(+), 17 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 8fdc947..b8bc0fd 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -1,12 +1,10 @@ use core::{ - account::Account, - bytes_to_words, hash, - types::{Address, Commitment, Key, Nullifier}, + bytes_to_words, + types::Address, visibility::InputVisibiility, }; -use nssa::program::{PinataProgram, TransferProgram}; -use risc0_zkvm::Receipt; +use nssa::program::PinataProgram; use crate::mocked_components::sequencer::{print_accounts, MockedSequencer}; use crate::mocked_components::{client::MockedClient, USER_CLIENTS}; @@ -60,7 +58,7 @@ fn main() { // All of this is executed locally by the sender let receiver_addr = USER_CLIENTS[1].user_address(); let pinata_account = sequencer.get_account(&[0xcafe; 8]).unwrap(); - let mut receiver_account = MockedClient::fresh_account_for_mint(receiver_addr); + let receiver_account = MockedClient::fresh_account_for_mint(receiver_addr); let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index 9bbd1f9..eb43c96 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -1,10 +1,9 @@ use crate::mocked_components::sequencer::MockedSequencer; use core::{ account::Account, - types::{Address, Commitment, Key, Nullifier}, + types::{Address, Key}, visibility::InputVisibiility, }; -use nssa::program::TransferProgram; pub mod transfer_deshielded; pub mod transfer_private; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index 0d0bb2c..cbfce18 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -1,5 +1,5 @@ use core::account::Account; -use core::types::{Address, Commitment, Key, Nullifier}; +use core::types::Address; use core::visibility::InputVisibiility; use nssa::program::TransferProgram; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index 27aca69..3c62242 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -1,5 +1,5 @@ use core::account::Account; -use core::types::{Address, Commitment, Key, Nullifier}; +use core::types::Address; use core::visibility::InputVisibiility; use nssa::program::TransferProgram; @@ -22,7 +22,7 @@ impl MockedClient { let sender_commitment_auth_path = sequencer.get_authentication_path_for(&owned_private_account.commitment()); // Create a new default private account for the recipient - let mut receiver_account = Self::fresh_account_for_mint(*to_address); + let receiver_account = Self::fresh_account_for_mint(*to_address); // Set visibilities. Both private accounts. let visibilities = vec![ diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index 9e56bba..fb2e3ea 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -1,5 +1,5 @@ use core::account::Account; -use core::types::{Address, Commitment, Key, Nullifier}; +use core::types::Address; use core::visibility::InputVisibiility; use nssa::program::TransferProgram; @@ -22,7 +22,7 @@ impl MockedClient { let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; // Create a new default private account for the receiver - let mut to_account = Self::fresh_account_for_mint(*to_address); + let to_account = Self::fresh_account_for_mint(*to_address); // Set input visibilities // First is the public account of the sender. Second is the private account minted in this diff --git a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs index 0f9ba62..20f37c0 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs @@ -1,4 +1,3 @@ -use core::types::Key; use crate::mocked_components::client::MockedClient; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 938942f..b6ff557 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -1,7 +1,7 @@ use core::{ account::Account, bytes_to_words, - types::{Address, AuthenticationPath, Commitment, Key, Nullifier, ProgramId}, + types::{Address, AuthenticationPath, Commitment, Nullifier, ProgramId}, }; use std::collections::{BTreeMap, HashSet}; diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 8ea15fe..a7d4d7f 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -5,7 +5,6 @@ use core::{ visibility::InputVisibiility, }; use nssa::program::TransferMultipleProgram; -use program_methods::OUTER_ID; use sparse_merkle_tree::SparseMerkleTree; /// A private execution of the TransferMultiple function. diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 97dc972..9ce0f76 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,5 +1,4 @@ use core::account::Account; -use risc0_zkvm::{default_executor, ExecutorEnv}; use nssa; From af7d3675a168e7c7cbd6830db77f20f45a9c4c68 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 18:23:13 -0300 Subject: [PATCH 61/83] improve comments in happy path example --- .../examples/happy_path.rs | 33 ++++++++++--------- .../mocked_components/sequencer/mod.rs | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index b8bc0fd..d0827a8 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -1,12 +1,8 @@ -use core::{ - bytes_to_words, - types::Address, - visibility::InputVisibiility, -}; +use core::{bytes_to_words, types::Address, visibility::InputVisibiility}; use nssa::program::PinataProgram; -use crate::mocked_components::sequencer::{print_accounts, MockedSequencer}; +use crate::mocked_components::sequencer::{print_accounts, MockedSequencer, PINATA_ADDRESS}; use crate::mocked_components::{client::MockedClient, USER_CLIENTS}; mod mocked_components; @@ -18,13 +14,15 @@ fn main() { print_accounts(&sequencer, &[]); // A public execution of the Transfer Program + // User1 sends 51 tokens to the piñata account USER_CLIENTS[1] - .transfer_public(&[0xcafe; 8], 51, &mut sequencer) + .transfer_public(&PINATA_ADDRESS, 51, &mut sequencer) .unwrap(); println!("📝 Balances after transfer"); print_accounts(&sequencer, &[]); // A shielded execution of the Transfer Program + // User0 shields 15 tokens to a new private account of User1 let private_account_user_1 = USER_CLIENTS[0] .transfer_shielded(&addresses[1], 15, &mut sequencer) .unwrap(); @@ -32,6 +30,7 @@ fn main() { print_accounts(&sequencer, &[&private_account_user_1]); // A private execution of the Transfer Program + // User1 uses it's private account to send 8 tokens to a new private account of User2 let [private_account_user_1, private_account_user_2] = USER_CLIENTS[1] .transfer_private(private_account_user_1, &addresses[2], 8, &mut sequencer) .unwrap(); @@ -39,26 +38,28 @@ fn main() { print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]); // A deshielded execution of the Transfer Program - let private_acount_user_2 = USER_CLIENTS[2] + // User2 deshields 1 token to the public account of User0 + let private_account_user_2 = USER_CLIENTS[2] .transfer_deshielded(private_account_user_2, &addresses[0], 1, &mut sequencer) .unwrap(); println!("📝 Balances after deshielded execution"); - print_accounts(&sequencer, &[&private_account_user_1, &private_acount_user_2]); + print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]); // A public execution of the Piñata program + // User2 claims the prize of the Piñata program to its public account let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); sequencer - .process_public_execution::(&[[0xcafe; 8], addresses[2]], preimage) + .process_public_execution::(&[PINATA_ADDRESS, addresses[2]], preimage) .unwrap(); println!("📝 Balances after public piñata execution"); - print_accounts(&sequencer, &[&private_account_user_1, &private_acount_user_2]); + print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]); // A deshielded execution of the Piñata program + // User1 claims the prize of the Piñata program to a new self-owned private account let private_account_user_0 = { - // All of this is executed locally by the sender - let receiver_addr = USER_CLIENTS[1].user_address(); - let pinata_account = sequencer.get_account(&[0xcafe; 8]).unwrap(); - let receiver_account = MockedClient::fresh_account_for_mint(receiver_addr); + // All of this is executed locally by the User1 + let pinata_account = sequencer.get_account(&PINATA_ADDRESS).unwrap(); + let receiver_account = MockedClient::fresh_account_for_mint(USER_CLIENTS[1].user_address()); let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); @@ -76,7 +77,7 @@ fn main() { println!("📝 Balances after private piñata execution"); print_accounts( &sequencer, - &[&private_account_user_1, &private_acount_user_2, &private_account_user_0], + &[&private_account_user_1, &private_account_user_2, &private_account_user_0], ); println!("Ok!"); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index b6ff557..2bacc97 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -25,7 +25,7 @@ const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, /// The initial balance of the genesis accounts const INITIAL_BALANCE: u128 = 150; /// The address of the piñata program account -const PINATA_ADDRESS: Address = [0xcafe; 8]; +pub const PINATA_ADDRESS: Address = [0xcafe; 8]; impl MockedSequencer { pub fn new() -> Self { From 36834f192c65d98b27b505866fa4a40578f7fd9b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 20:34:07 -0300 Subject: [PATCH 62/83] minor changes --- .../examples/happy_path.rs | 8 +++- .../examples/mocked_components/client/mod.rs | 9 +--- .../mocked_components/sequencer/mod.rs | 4 -- .../sequencer/process_public_execution.rs | 2 +- .../examples/private_execution.rs | 3 +- .../examples/public_execution.rs | 2 +- risc0-selective-privacy-poc/src/lib.rs | 46 +++++++++---------- 7 files changed, 35 insertions(+), 39 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index d0827a8..d4425ff 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -34,7 +34,7 @@ fn main() { let [private_account_user_1, private_account_user_2] = USER_CLIENTS[1] .transfer_private(private_account_user_1, &addresses[2], 8, &mut sequencer) .unwrap(); - println!("📝 Balances after shielded execution"); + println!("📝 Balances after private execution"); print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]); // A deshielded execution of the Transfer Program @@ -77,7 +77,11 @@ fn main() { println!("📝 Balances after private piñata execution"); print_accounts( &sequencer, - &[&private_account_user_1, &private_account_user_2, &private_account_user_0], + &[ + &private_account_user_1, + &private_account_user_2, + &private_account_user_0, + ], ); println!("Ok!"); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index eb43c96..a3861ad 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -13,15 +13,11 @@ pub mod transfer_shielded; /// A client that creates and submits transfer transactions pub struct MockedClient { user_private_key: Key, - private_accounts: Vec, } impl MockedClient { pub const fn new(user_private_key: Key) -> Self { - Self { - user_private_key, - private_accounts: Vec::new(), - } + Self { user_private_key } } pub fn user_address(&self) -> Address { @@ -39,8 +35,7 @@ impl MockedClient { ) -> Result, ()> { // Execute and generate proof of the outer program let (receipt, private_outputs) = - nssa::invoke_privacy_execution::

(input_accounts, instruction_data, visibilities, commitment_tree_root) - .unwrap(); + nssa::execute_offchain::

(input_accounts, instruction_data, visibilities, commitment_tree_root).unwrap(); // Send proof to the sequencer sequencer.process_privacy_execution(receipt)?; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 2bacc97..aab2710 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -17,11 +17,8 @@ pub struct MockedSequencer { accounts: BTreeMap, commitment_tree: SparseMerkleTree, nullifier_set: HashSet, - deployed_program_ids: HashSet, } -/// List of deployed programs -const DEPLOYED_PROGRAM_IDS: [ProgramId; 3] = [TRANSFER_ID, TRANSFER_MULTIPLE_ID, PINATA_ID]; /// The initial balance of the genesis accounts const INITIAL_BALANCE: u128 = 150; /// The address of the piñata program account @@ -45,7 +42,6 @@ impl MockedSequencer { accounts, commitment_tree, nullifier_set, - deployed_program_ids: DEPLOYED_PROGRAM_IDS.iter().cloned().collect(), } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 2ab7f55..c6c1366 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -16,7 +16,7 @@ impl MockedSequencer { .collect::>()?; // Execute the program - let inputs_outputs = nssa::execute::

(&input_accounts, instruction_data)?; + let inputs_outputs = nssa::execute_onchain::

(&input_accounts, instruction_data)?; // Perform consistency checks if !self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs) { diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index a7d4d7f..1e55b70 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -51,7 +51,8 @@ fn main() { let balance_to_move = vec![30, 40]; // Execute and prove the outer program for the TransferMultipleProgram. - let (receipt, _) = nssa::invoke_privacy_execution::( + // This is executed off-chain by the sender. + let (receipt, _) = nssa::execute_offchain::( &[sender, receiver_1, receiver_2], balance_to_move, &visibilities, diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 9ce0f76..68dabf3 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -20,7 +20,7 @@ pub fn main() { let balance_to_move = vec![10, 20]; let inputs_outputs = - nssa::execute::(&[sender, receiver_1, receiver_2], balance_to_move).unwrap(); + nssa::execute_onchain::(&[sender, receiver_1, receiver_2], balance_to_move).unwrap(); println!("OK!"); } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 8c09e0f..a3e29d9 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -50,30 +50,9 @@ fn execute_and_prove_inner( Ok((receipt, inputs_outputs)) } -/// Executes the program `P` without generating a proof. -/// Returns the list of accounts pre and post states. -pub fn execute( - input_accounts: &[Account], - instruction_data: P::InstructionData, -) -> Result, ()> { - // Write inputs to the program - let mut env_builder = ExecutorEnv::builder(); - write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; - let env = env_builder.build().unwrap(); - - // Execute the program (without proving) - let executor = default_executor(); - let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; - - // Get (inputs and) outputs - let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; - - Ok(inputs_outputs) -} - /// Builds the private outputs from the results of the execution of an inner program. /// Populates the nonces with the ones provided. -pub fn build_private_outputs_from_inner_results( +fn build_private_outputs_from_inner_results( inputs_outputs: &[Account], num_inputs: usize, visibilities: &[InputVisibiility], @@ -93,10 +72,31 @@ pub fn build_private_outputs_from_inner_results( .collect() } +/// Executes the program `P` without generating a proof. +/// Returns the list of accounts pre and post states. +pub fn execute_onchain( + input_accounts: &[Account], + instruction_data: P::InstructionData, +) -> Result, ()> { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Execute the program (without proving) + let executor = default_executor(); + let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; + + // Get (inputs and) outputs + let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; + + Ok(inputs_outputs) +} + /// Executes and proves the inner program `P` and executes and proves the outer program on top of it. /// Returns the proof of execution of the outer program and the list of new private accounts /// resulted from this execution. -pub fn invoke_privacy_execution( +pub fn execute_offchain( inputs: &[Account], instruction_data: P::InstructionData, visibilities: &[InputVisibiility], From 54927ddd70b3dc8bc509a3491f57f32dbd7b23af Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 18 Jul 2025 21:47:29 -0300 Subject: [PATCH 63/83] nit --- risc0-selective-privacy-poc/examples/happy_path.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index d4425ff..53e8475 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -54,9 +54,9 @@ fn main() { println!("📝 Balances after public piñata execution"); print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]); - // A deshielded execution of the Piñata program + // A shielded execution of the Piñata program // User1 claims the prize of the Piñata program to a new self-owned private account - let private_account_user_0 = { + let another_private_account_user_1 = { // All of this is executed locally by the User1 let pinata_account = sequencer.get_account(&PINATA_ADDRESS).unwrap(); let receiver_account = MockedClient::fresh_account_for_mint(USER_CLIENTS[1].user_address()); @@ -71,8 +71,8 @@ fn main() { &mut sequencer, ) .unwrap(); - let [private_account_user_0] = private_outputs.try_into().unwrap(); - private_account_user_0 + let [private_account_user_1] = private_outputs.try_into().unwrap(); + private_account_user_1 }; println!("📝 Balances after private piñata execution"); print_accounts( @@ -80,7 +80,7 @@ fn main() { &[ &private_account_user_1, &private_account_user_2, - &private_account_user_0, + &another_private_account_user_1, ], ); From 8482a1b18290c20765d41fbf5749a86cd59ecbb9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 17:02:26 -0300 Subject: [PATCH 64/83] rename input visibility to account visibility --- .../core/src/visibility.rs | 2 +- .../examples/happy_path.rs | 4 +-- .../examples/mocked_components/client/mod.rs | 4 +-- .../client/transfer_deshielded.rs | 8 +++--- .../client/transfer_private.rs | 6 ++-- .../client/transfer_shielded.rs | 6 ++-- .../examples/private_execution.rs | 10 +++---- .../program_methods/guest/src/bin/outer.rs | 28 +++++++++---------- risc0-selective-privacy-poc/src/lib.rs | 8 +++--- 9 files changed, 38 insertions(+), 38 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/visibility.rs b/risc0-selective-privacy-poc/core/src/visibility.rs index dc04fd0..79c83e3 100644 --- a/risc0-selective-privacy-poc/core/src/visibility.rs +++ b/risc0-selective-privacy-poc/core/src/visibility.rs @@ -2,7 +2,7 @@ use crate::types::{AuthenticationPath, Key}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -pub enum InputVisibiility { +pub enum AccountVisibility { // A public account Public, // A private account diff --git a/risc0-selective-privacy-poc/examples/happy_path.rs b/risc0-selective-privacy-poc/examples/happy_path.rs index 53e8475..f6b3fbc 100644 --- a/risc0-selective-privacy-poc/examples/happy_path.rs +++ b/risc0-selective-privacy-poc/examples/happy_path.rs @@ -1,4 +1,4 @@ -use core::{bytes_to_words, types::Address, visibility::InputVisibiility}; +use core::{bytes_to_words, types::Address, visibility::AccountVisibility}; use nssa::program::PinataProgram; @@ -60,7 +60,7 @@ fn main() { // All of this is executed locally by the User1 let pinata_account = sequencer.get_account(&PINATA_ADDRESS).unwrap(); let receiver_account = MockedClient::fresh_account_for_mint(USER_CLIENTS[1].user_address()); - let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; + let visibilities = [AccountVisibility::Public, AccountVisibility::Private(None)]; let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec(); let private_outputs = MockedClient::prove_and_send_to_sequencer::( diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index a3861ad..5c2762b 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -2,7 +2,7 @@ use crate::mocked_components::sequencer::MockedSequencer; use core::{ account::Account, types::{Address, Key}, - visibility::InputVisibiility, + visibility::AccountVisibility, }; pub mod transfer_deshielded; @@ -29,7 +29,7 @@ impl MockedClient { pub fn prove_and_send_to_sequencer( input_accounts: &[Account], instruction_data: P::InstructionData, - visibilities: &[InputVisibiility], + visibilities: &[AccountVisibility], commitment_tree_root: [u32; 8], sequencer: &mut MockedSequencer, ) -> Result, ()> { diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index cbfce18..185a407 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -1,6 +1,6 @@ use core::account::Account; use core::types::Address; -use core::visibility::InputVisibiility; +use core::visibility::AccountVisibility; use nssa::program::TransferProgram; @@ -24,11 +24,11 @@ impl MockedClient { // Fetch public account to deshield to let to_account = sequencer.get_account(&to_address).unwrap(); - // Set input visibilities + // Set account visibilities // First entry is the private sender. Second entry is the public receiver let visibilities = vec![ - InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), - InputVisibiility::Public, + AccountVisibility::Private(Some((self.user_private_key, sender_commitment_auth_path))), + AccountVisibility::Public, ]; // Execute privately (off-chain) and submit it to the sequencer diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index 3c62242..c6f2c6c 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -1,6 +1,6 @@ use core::account::Account; use core::types::Address; -use core::visibility::InputVisibiility; +use core::visibility::AccountVisibility; use nssa::program::TransferProgram; @@ -26,8 +26,8 @@ impl MockedClient { // Set visibilities. Both private accounts. let visibilities = vec![ - InputVisibiility::Private(Some((self.user_private_key, sender_commitment_auth_path))), - InputVisibiility::Private(None), + AccountVisibility::Private(Some((self.user_private_key, sender_commitment_auth_path))), + AccountVisibility::Private(None), ]; // Execute privately (off-chain) and submit it to the sequencer diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index fb2e3ea..7a4bec0 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -1,6 +1,6 @@ use core::account::Account; use core::types::Address; -use core::visibility::InputVisibiility; +use core::visibility::AccountVisibility; use nssa::program::TransferProgram; @@ -24,10 +24,10 @@ impl MockedClient { // Create a new default private account for the receiver let to_account = Self::fresh_account_for_mint(*to_address); - // Set input visibilities + // Set account visibilities // First is the public account of the sender. Second is the private account minted in this // execution - let visibilities = [InputVisibiility::Public, InputVisibiility::Private(None)]; + let visibilities = [AccountVisibility::Public, AccountVisibility::Private(None)]; // Execute privately (off-chain) and submit it to the sequencer let private_outputs = Self::prove_and_send_to_sequencer::( diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 1e55b70..316ab04 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -2,7 +2,7 @@ use core::{ account::Account, bytes_to_words, types::{Address, AuthenticationPath, Commitment, Nullifier}, - visibility::InputVisibiility, + visibility::AccountVisibility, }; use nssa::program::TransferMultipleProgram; use sparse_merkle_tree::SparseMerkleTree; @@ -38,11 +38,11 @@ fn main() { let receiver_address_2 = [100; 8]; let receiver_2 = new_default_account(receiver_address_2); - // Setup input visibilites. All accounts are private for this execution. + // Setup input account visibilites. All accounts are private for this execution. let visibilities = vec![ - InputVisibiility::Private(Some((sender_private_key, auth_path))), - InputVisibiility::Private(None), - InputVisibiility::Private(None), + AccountVisibility::Private(Some((sender_private_key, auth_path))), + AccountVisibility::Private(None), + AccountVisibility::Private(None), ]; // Set the balances to be sent to the two receiver addresses. diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index ae2e0ad..1499a21 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -2,7 +2,7 @@ use core::{ account::Account, compute_nullifier, hash, is_in_tree, types::{Nonce, ProgramId}, - visibility::InputVisibiility, + visibility::AccountVisibility, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -14,7 +14,7 @@ use risc0_zkvm::{guest::env, serde::to_vec}; /// - Vec: The output of the inner program. This is assumed to include the accounts pre and /// post-states of the execution of the inner program. /// -/// - Vec: A vector indicating which accounts are private and which are public. +/// - Vec: A vector indicating which accounts are private and which are public. /// /// - Vec: The vector of nonces to be used for the output accounts. This is assumed to be /// sampled at random by the host program. @@ -35,8 +35,8 @@ fn main() { assert_eq!(inputs_outputs.len() as u32, num_inputs * 2); // Read visibilities - let input_visibilities: Vec = env::read(); - assert_eq!(input_visibilities.len() as u32, num_inputs); + let account_visibilities: Vec = env::read(); + assert_eq!(account_visibilities.len() as u32, num_inputs); // Read nonces for outputs let output_nonces: Vec = env::read(); @@ -57,9 +57,9 @@ fn main() { }; let mut nullifiers = Vec::new(); - for (visibility, input_account) in input_visibilities.iter().zip(inputs.iter()) { + for (visibility, input_account) in account_visibilities.iter().zip(inputs.iter()) { match visibility { - InputVisibiility::Private(Some((private_key, auth_path))) => { + AccountVisibility::Private(Some((private_key, auth_path))) => { // Prove ownership of input accounts by proving knowledge of the pre-image of their addresses. assert_eq!(hash(private_key), input_account.address); // Check the input account was created by a previous transaction by checking it belongs to the commitments tree. @@ -69,14 +69,14 @@ fn main() { let nullifier = compute_nullifier(&commitment, private_key); nullifiers.push(nullifier); } - InputVisibiility::Private(None) => { + AccountVisibility::Private(None) => { // Private accounts without a companion private key are enforced to have default values // Used for executions that need to create a new private account. assert_eq!(input_account.balance, 0); assert_eq!(input_account.nonce, [0; 8]); } // No checks on public accounts - InputVisibiility::Public => continue, + AccountVisibility::Public => continue, } } @@ -101,10 +101,10 @@ fn main() { // Compute commitments for every private output let mut private_outputs = Vec::new(); - for (output, visibility) in outputs.iter().zip(input_visibilities.iter()) { + for (output, visibility) in outputs.iter().zip(account_visibilities.iter()) { match visibility { - InputVisibiility::Public => continue, - InputVisibiility::Private(_) => private_outputs.push(output), + AccountVisibility::Public => continue, + AccountVisibility::Private(_) => private_outputs.push(output), } } let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect(); @@ -114,13 +114,13 @@ fn main() { for (account, visibility) in inputs .iter() .chain(outputs.iter()) - .zip(input_visibilities.iter().chain(input_visibilities.iter())) + .zip(account_visibilities.iter().chain(account_visibilities.iter())) { match visibility { - InputVisibiility::Public => { + AccountVisibility::Public => { public_inputs_outputs.push(account); } - InputVisibiility::Private(_) => continue, + AccountVisibility::Private(_) => continue, } } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index a3e29d9..d8deed0 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,7 +1,7 @@ use core::{ account::Account, types::{Commitment, Nonce, Nullifier}, - visibility::InputVisibiility, + visibility::AccountVisibility, }; use program_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; @@ -55,7 +55,7 @@ fn execute_and_prove_inner( fn build_private_outputs_from_inner_results( inputs_outputs: &[Account], num_inputs: usize, - visibilities: &[InputVisibiility], + visibilities: &[AccountVisibility], nonces: &[Nonce], ) -> Vec { inputs_outputs @@ -63,7 +63,7 @@ fn build_private_outputs_from_inner_results( .skip(num_inputs) .zip(visibilities) .zip(nonces) - .filter(|((_, visibility), _)| matches!(visibility, InputVisibiility::Private(_))) + .filter(|((_, visibility), _)| matches!(visibility, AccountVisibility::Private(_))) .map(|((account, _), nonce)| { let mut this = account.clone(); this.nonce = *nonce; @@ -99,7 +99,7 @@ pub fn execute_onchain( pub fn execute_offchain( inputs: &[Account], instruction_data: P::InstructionData, - visibilities: &[InputVisibiility], + visibilities: &[AccountVisibility], commitment_tree_root: [u32; 8], ) -> Result<(Receipt, Vec), ()> { // Prove inner program and get post state of the accounts From 763495a17f1693755da668f46e75629851d5bd83 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 18:08:57 -0300 Subject: [PATCH 65/83] refactor --- risc0-selective-privacy-poc/core/src/lib.rs | 5 +- risc0-selective-privacy-poc/core/src/types.rs | 13 +++++ .../sequencer/process_privacy_execution.rs | 48 +++++++------------ .../examples/private_execution.rs | 3 +- .../program_methods/guest/src/bin/outer.rs | 25 ++++++---- risc0-selective-privacy-poc/src/lib.rs | 21 +------- 6 files changed, 53 insertions(+), 62 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 7650de1..990071f 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -2,7 +2,10 @@ pub mod account; pub mod types; pub mod visibility; -use crate::types::{AuthenticationPath, Commitment, Key, Nullifier}; +use crate::{ + account::Account, + types::{AuthenticationPath, Commitment, Key, Nullifier}, +}; use risc0_zkvm::sha::{Impl, Sha256}; pub fn hash(bytes: &[u32]) -> [u32; 8] { diff --git a/risc0-selective-privacy-poc/core/src/types.rs b/risc0-selective-privacy-poc/core/src/types.rs index ff05508..f742c30 100644 --- a/risc0-selective-privacy-poc/core/src/types.rs +++ b/risc0-selective-privacy-poc/core/src/types.rs @@ -1,3 +1,7 @@ +use serde::{Deserialize, Serialize}; + +use crate::account::Account; + /// For this POC we consider 32-bit commitments pub type Commitment = u32; pub type Nullifier = [u32; 8]; @@ -6,3 +10,12 @@ pub type Nonce = [u32; 8]; pub type Key = [u32; 8]; pub type AuthenticationPath = [[u32; 8]; 32]; pub type ProgramId = [u32; 8]; + +#[derive(Serialize, Deserialize)] +pub struct PrivacyExecutionOutput { + pub public_accounts_pre: Vec, + pub public_accounts_post: Vec, + pub private_output_commitments: Vec, + pub nullifiers: Vec, + pub commitment_tree_root: [u32; 8], +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index 3a28015..ce1715a 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -1,6 +1,6 @@ use core::{ account::Account, - types::{Commitment, Nullifier}, + types::{Commitment, Nullifier, PrivacyExecutionOutput}, }; use risc0_zkvm::Receipt; @@ -11,27 +11,22 @@ impl MockedSequencer { /// Processes a privacy execution request. /// Verifies the proof of the privacy execution and updates the state of the chain. pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), ()> { - // Parse the output of the proof. - // This is the output of the "outer" program - let output: (Vec, Vec, Vec, [u32; 8]) = receipt.journal.decode().unwrap(); - let (public_inputs_outputs, nullifiers, commitments, commitment_tree_root) = output; + // Parse the output of the "outer" program + let output: PrivacyExecutionOutput = receipt.journal.decode().unwrap(); // Reject in case the root used in the privacy execution is not the current root. - if commitment_tree_root != self.get_commitment_tree_root() { + if output.commitment_tree_root != self.get_commitment_tree_root() { return Err(()); } - // Reject in case the number of accounts in the public_inputs_outputs is not even. - // This is because it is expected to contain the pre and post-states of public the accounts - // of the inner execution. - if public_inputs_outputs.len() % 2 != 0 { + // Reject in case the number of accounts pre states is different from the post states + if output.public_accounts_pre.len() != output.public_accounts_post.len() { return Err(()); } // Reject if the states of the public input accounts used in the inner execution do not // coincide with the on-chain state. - let num_input_public = public_inputs_outputs.len() >> 1; - for account in public_inputs_outputs.iter().take(num_input_public) { + for account in output.public_accounts_pre.iter() { let current_account = self.get_account(&account.address).ok_or(())?; if ¤t_account != account { return Err(()); @@ -39,7 +34,8 @@ impl MockedSequencer { } // Reject if the nullifiers of this privacy execution have already been published. - if nullifiers + if output + .nullifiers .iter() .any(|nullifier| self.nullifier_set.contains(nullifier)) { @@ -47,7 +43,8 @@ impl MockedSequencer { } // Reject if the commitments have already been seen. - if commitments + if output + .private_output_commitments .iter() .any(|commitment| self.commitment_tree.values().contains(commitment)) { @@ -61,30 +58,21 @@ impl MockedSequencer { // - The given nullifiers correctly correspond to commitments that currently belong to // the commitment tree. // - The given commitments are correctly computed from valid accounts. - nssa::verify_privacy_execution( - receipt, - &public_inputs_outputs, - &nullifiers, - &commitments, - &commitment_tree_root, - )?; + nssa::verify_privacy_execution(receipt)?; // At this point the privacy execution is considered valid. // // Update the state of the public accounts with the post-state of this privacy execution - public_inputs_outputs - .iter() - .cloned() - .skip(num_input_public) - .for_each(|account_post_state| { - self.accounts.insert(account_post_state.address, account_post_state); - }); + + output.public_accounts_post.into_iter().for_each(|account_post_state| { + self.accounts.insert(account_post_state.address, account_post_state); + }); // Add all nullifiers to the nullifier set. - self.nullifier_set.extend(nullifiers); + self.nullifier_set.extend(output.nullifiers); // Add commitments to the commitment tree. - for commitment in commitments.iter() { + for commitment in output.private_output_commitments.iter() { self.commitment_tree.add_value(*commitment); } diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 316ab04..6963598 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -61,8 +61,7 @@ fn main() { .unwrap(); // Verify the proof - let output: (Vec, Vec, Vec, [u32; 8]) = receipt.journal.decode().unwrap(); - assert!(nssa::verify_privacy_execution(receipt, &output.0, &output.1, &output.2, &output.3).is_ok()); + assert!(nssa::verify_privacy_execution(receipt).is_ok()); println!("OK!"); } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 1499a21..bf75dbb 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,7 +1,7 @@ use core::{ account::Account, compute_nullifier, hash, is_in_tree, - types::{Nonce, ProgramId}, + types::{Nonce, PrivacyExecutionOutput, ProgramId}, visibility::AccountVisibility, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -110,24 +110,29 @@ fn main() { let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect(); // Get the list of public accounts pre and post states - let mut public_inputs_outputs = Vec::new(); - for (account, visibility) in inputs - .iter() - .chain(outputs.iter()) + let mut public_accounts_pre = Vec::new(); + let mut public_accounts_post = Vec::new(); + for ((account_pre, account_post), visibility) in inputs + .into_iter() + .zip(outputs.into_iter()) .zip(account_visibilities.iter().chain(account_visibilities.iter())) { match visibility { AccountVisibility::Public => { - public_inputs_outputs.push(account); + public_accounts_pre.push(account_pre); + public_accounts_post.push(account_post); } AccountVisibility::Private(_) => continue, } } - env::commit(&( - public_inputs_outputs, - nullifiers, + let output = PrivacyExecutionOutput { + public_accounts_pre, + public_accounts_post, private_output_commitments, + nullifiers, commitment_tree_root, - )); + }; + + env::commit(&output); } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index d8deed0..08c17df 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -129,23 +129,6 @@ pub fn execute_offchain( } /// Verifies a proof of the outer program for the given parameters. -pub fn verify_privacy_execution( - receipt: Receipt, - public_accounts_inputs_outputs: &[Account], - nullifiers: &[Nullifier], - private_output_commitments: &[Commitment], - commitment_tree_root: &[u32; 8], -) -> Result<(), ()> { - let output: (Vec, Vec, Vec, [u32; 8]) = receipt.journal.decode().unwrap(); - let expected_output = ( - public_accounts_inputs_outputs.to_vec(), - nullifiers.to_vec(), - private_output_commitments.to_vec(), - commitment_tree_root.to_owned(), - ); - if output != expected_output { - Err(()) - } else { - receipt.verify(OUTER_ID).map_err(|_| ()) - } +pub fn verify_privacy_execution(receipt: Receipt) -> Result<(), ()> { + receipt.verify(OUTER_ID).map_err(|_| ()) } From 159fd52d254e5d3d42fdf5911176c2653694648f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 18:37:21 -0300 Subject: [PATCH 66/83] add program output struct --- risc0-selective-privacy-poc/core/src/types.rs | 6 ++++ .../examples/mocked_components/mod.rs | 1 - .../sequencer/process_public_execution.rs | 36 ++++++++++--------- .../program_methods/guest/src/bin/outer.rs | 22 +++++------- .../program_methods/guest/src/bin/pinata.rs | 14 ++++---- .../program_methods/guest/src/bin/transfer.rs | 9 +++-- .../guest/src/bin/transfer_multiple.rs | 19 +++++----- risc0-selective-privacy-poc/src/lib.rs | 31 +++++++--------- 8 files changed, 68 insertions(+), 70 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/types.rs b/risc0-selective-privacy-poc/core/src/types.rs index f742c30..a2bda29 100644 --- a/risc0-selective-privacy-poc/core/src/types.rs +++ b/risc0-selective-privacy-poc/core/src/types.rs @@ -11,6 +11,12 @@ pub type Key = [u32; 8]; pub type AuthenticationPath = [[u32; 8]; 32]; pub type ProgramId = [u32; 8]; +#[derive(Serialize, Deserialize)] +pub struct ProgramOutput { + pub accounts_pre: Vec, + pub accounts_post: Vec, +} + #[derive(Serialize, Deserialize)] pub struct PrivacyExecutionOutput { pub public_accounts_pre: Vec, diff --git a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs index 20f37c0..7ba4703 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/mod.rs @@ -1,4 +1,3 @@ - use crate::mocked_components::client::MockedClient; pub mod client; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index c6c1366..70c14b4 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -1,4 +1,7 @@ -use core::{account::Account, types::Address}; +use core::{ + account::Account, + types::{Address, ProgramOutput}, +}; use super::MockedSequencer; @@ -16,20 +19,17 @@ impl MockedSequencer { .collect::>()?; // Execute the program - let inputs_outputs = nssa::execute_onchain::

(&input_accounts, instruction_data)?; + let program_output = nssa::execute_onchain::

(&input_accounts, instruction_data)?; // Perform consistency checks - if !self.inputs_outputs_are_consistent(&input_accounts, &inputs_outputs) { + if !self.program_output_is_valid(&input_accounts, &program_output) { return Err(()); } // Update the accounts states - inputs_outputs - .into_iter() - .skip(input_accounts.len()) - .for_each(|account_post_state| { - self.accounts.insert(account_post_state.address, account_post_state); - }); + program_output.accounts_post.into_iter().for_each(|account_post_state| { + self.accounts.insert(account_post_state.address, account_post_state); + }); Ok(()) } @@ -37,22 +37,24 @@ impl MockedSequencer { /// `input_accounts` are the accounts provided as inputs to the program. /// `inputs_outputs` is the program output, which should consist of the accounts pre and /// post-states. - fn inputs_outputs_are_consistent(&self, input_accounts: &[Account], inputs_outputs: &[Account]) -> bool { + fn program_output_is_valid(&self, input_accounts: &[Account], program_output: &ProgramOutput) -> bool { let num_inputs = input_accounts.len(); - // Fail if the number of accounts pre and post-states is inconsistent with the number of - // inputs. - if inputs_outputs.len() != num_inputs * 2 { + // Fail if the number of accounts pre and post-states is differ + if program_output.accounts_pre.len() != program_output.accounts_post.len() { return false; } // Fail if the accounts pre-states do not coincide with the input accounts. - let (claimed_accounts_pre, accounts_post) = inputs_outputs.split_at(num_inputs); - if claimed_accounts_pre != input_accounts { + if program_output.accounts_pre != input_accounts { return false; } - for (account_pre, account_post) in input_accounts.iter().zip(accounts_post) { + for (account_pre, account_post) in program_output + .accounts_pre + .iter() + .zip(program_output.accounts_post.iter()) + { // Fail if the program modified the addresses of the input accounts if account_pre.address != account_post.address { return false; @@ -69,7 +71,7 @@ impl MockedSequencer { } let total_balance_pre: u128 = input_accounts.iter().map(|account| account.balance).sum(); - let total_balance_post: u128 = accounts_post.iter().map(|account| account.balance).sum(); + let total_balance_post: u128 = program_output.accounts_post.iter().map(|account| account.balance).sum(); // Fail if the execution didn't preserve the total supply. if total_balance_pre != total_balance_post { return false; diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index bf75dbb..5871895 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,7 +1,7 @@ use core::{ account::Account, compute_nullifier, hash, is_in_tree, - types::{Nonce, PrivacyExecutionOutput, ProgramId}, + types::{Nonce, PrivacyExecutionOutput, ProgramId, ProgramOutput}, visibility::AccountVisibility, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -29,18 +29,18 @@ use risc0_zkvm::{guest::env, serde::to_vec}; /// - The commitments for the ouput private accounts. /// - The commitment tree root used for the authentication path verifications. fn main() { - let num_inputs: u32 = env::read(); // Read inputs and outputs - let mut inputs_outputs: Vec = env::read(); - assert_eq!(inputs_outputs.len() as u32, num_inputs * 2); + let mut inner_program_output: ProgramOutput = env::read(); + let num_inputs = inner_program_output.accounts_pre.len(); + assert_eq!(inner_program_output.accounts_post.len(), num_inputs); // Read visibilities let account_visibilities: Vec = env::read(); - assert_eq!(account_visibilities.len() as u32, num_inputs); + assert_eq!(account_visibilities.len(), num_inputs); // Read nonces for outputs let output_nonces: Vec = env::read(); - assert_eq!(output_nonces.len() as u32, num_inputs); + assert_eq!(output_nonces.len(), num_inputs); // Read root and program id. let commitment_tree_root: [u32; 8] = env::read(); @@ -48,14 +48,10 @@ fn main() { // Verify pre states and post states of accounts are consistent // with the execution of the `program_id` program - env::verify(program_id, &to_vec(&inputs_outputs).unwrap()).unwrap(); - - // Split inputs_outputs into two separate vectors - let (inputs, mut outputs) = { - let outputs = inputs_outputs.split_off(num_inputs as usize); - (inputs_outputs, outputs) - }; + env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap(); + let inputs = inner_program_output.accounts_pre; + let mut outputs = inner_program_output.accounts_post; let mut nullifiers = Vec::new(); for (visibility, input_account) in account_visibilities.iter().zip(inputs.iter()) { match visibility { diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs index 48c5a50..49c1e84 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/pinata.rs @@ -1,4 +1,4 @@ -use core::{account::Account, hash}; +use core::{account::Account, hash, types::ProgramOutput}; use risc0_zkvm::guest::env; const TARGET_HASH: [u32; 8] = [ @@ -36,10 +36,10 @@ fn main() { pinata_account_post.balance -= PINATA_PRIZE; winner_account_post.balance += PINATA_PRIZE; - env::commit(&vec![ - pinata_account, - winner_account, - pinata_account_post, - winner_account_post, - ]); + let output = ProgramOutput { + accounts_pre: vec![pinata_account, winner_account], + accounts_post: vec![pinata_account_post, winner_account_post], + }; + + env::commit(&output); } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs index 1e1a961..d600d67 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer.rs @@ -1,4 +1,4 @@ -use core::account::Account; +use core::{account::Account, types::ProgramOutput}; use risc0_zkvm::guest::env; /// A transfer of balance program. @@ -22,5 +22,10 @@ fn main() { sender_post.balance -= balance_to_move; receiver_post.balance += balance_to_move; - env::commit(&vec![sender, receiver, sender_post, receiver_post]); + let output = ProgramOutput { + accounts_pre: vec![sender, receiver], + accounts_post: vec![sender_post, receiver_post], + }; + + env::commit(&output); } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs index dc495f1..bdf1dda 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/transfer_multiple.rs @@ -1,4 +1,4 @@ -use core::account::Account; +use core::{account::Account, types::ProgramOutput}; use risc0_zkvm::guest::env; /// A transfer of balance program with one sender and multiple recipients. @@ -27,21 +27,18 @@ fn main() { // Create accounts post states, with updated balances let mut sender_post = sender.clone(); - let mut receivers_post = recipients.clone(); + let mut recipients_post = recipients.clone(); // Transfer balances sender_post.balance -= total_balance_to_move; - for (receiver, balance_for_receiver) in receivers_post.iter_mut().zip(target_balances) { + for (receiver, balance_for_receiver) in recipients_post.iter_mut().zip(target_balances) { receiver.balance += balance_for_receiver; } - // Flatten pre and post states for output - let inputs_outputs: Vec = vec![sender] - .into_iter() - .chain(recipients) - .chain(vec![sender_post]) - .chain(receivers_post) - .collect(); + let output = ProgramOutput { + accounts_pre: vec![sender].into_iter().chain(recipients).collect(), + accounts_post: vec![sender_post].into_iter().chain(recipients_post).collect(), + }; - env::commit(&inputs_outputs); + env::commit(&output); } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 08c17df..b28d4d8 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,6 +1,6 @@ use core::{ account::Account, - types::{Commitment, Nonce, Nullifier}, + types::{Commitment, Nonce, Nullifier, ProgramOutput}, visibility::AccountVisibility, }; use program_methods::{OUTER_ELF, OUTER_ID}; @@ -33,7 +33,7 @@ fn write_inputs( fn execute_and_prove_inner( input_accounts: &[Account], instruction_data: P::InstructionData, -) -> Result<(Receipt, Vec), ()> { +) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; @@ -42,25 +42,20 @@ fn execute_and_prove_inner( // Prove the program let prover = default_prover(); let prove_info = prover.prove(env, P::PROGRAM_ELF).map_err(|_| ())?; - let receipt = prove_info.receipt; - - // Get proof and (inputs and) outputs - let inputs_outputs: Vec = receipt.journal.decode().map_err(|_| ())?; - - Ok((receipt, inputs_outputs)) + Ok(prove_info.receipt) } /// Builds the private outputs from the results of the execution of an inner program. /// Populates the nonces with the ones provided. fn build_private_outputs_from_inner_results( - inputs_outputs: &[Account], + inner_program_output: &ProgramOutput, num_inputs: usize, visibilities: &[AccountVisibility], nonces: &[Nonce], ) -> Vec { - inputs_outputs + inner_program_output + .accounts_post .iter() - .skip(num_inputs) .zip(visibilities) .zip(nonces) .filter(|((_, visibility), _)| matches!(visibility, AccountVisibility::Private(_))) @@ -77,7 +72,7 @@ fn build_private_outputs_from_inner_results( pub fn execute_onchain( input_accounts: &[Account], instruction_data: P::InstructionData, -) -> Result, ()> { +) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; @@ -88,9 +83,7 @@ pub fn execute_onchain( let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; // Get (inputs and) outputs - let inputs_outputs: Vec = session_info.journal.decode().map_err(|_| ())?; - - Ok(inputs_outputs) + session_info.journal.decode().map_err(|_| ()) } /// Executes and proves the inner program `P` and executes and proves the outer program on top of it. @@ -104,7 +97,8 @@ pub fn execute_offchain( ) -> Result<(Receipt, Vec), ()> { // Prove inner program and get post state of the accounts let num_inputs = inputs.len(); - let (inner_receipt, inputs_outputs) = execute_and_prove_inner::

(inputs, instruction_data)?; + let inner_receipt = execute_and_prove_inner::

(inputs, instruction_data)?; + let inner_program_output: ProgramOutput = inner_receipt.journal.decode().map_err(|_| ())?; // Sample fresh random nonces for the outputs of this execution let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); @@ -112,8 +106,7 @@ pub fn execute_offchain( // Prove outer program. let mut env_builder = ExecutorEnv::builder(); env_builder.add_assumption(inner_receipt); - env_builder.write(&(num_inputs as u32)).unwrap(); - env_builder.write(&inputs_outputs).unwrap(); + env_builder.write(&inner_program_output).unwrap(); env_builder.write(&visibilities).unwrap(); env_builder.write(&output_nonces).unwrap(); env_builder.write(&commitment_tree_root).unwrap(); @@ -124,7 +117,7 @@ pub fn execute_offchain( // Build private accounts. let private_outputs = - build_private_outputs_from_inner_results(&inputs_outputs, num_inputs, visibilities, &output_nonces); + build_private_outputs_from_inner_results(&inner_program_output, num_inputs, visibilities, &output_nonces); Ok((prove_info.receipt, private_outputs)) } From 118f835436dce1e31632977b7155eddff1ec92b3 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 18:51:04 -0300 Subject: [PATCH 67/83] remove unnecessary chain --- .../program_methods/guest/src/bin/outer.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 5871895..d9c5e87 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -108,10 +108,8 @@ fn main() { // Get the list of public accounts pre and post states let mut public_accounts_pre = Vec::new(); let mut public_accounts_post = Vec::new(); - for ((account_pre, account_post), visibility) in inputs - .into_iter() - .zip(outputs.into_iter()) - .zip(account_visibilities.iter().chain(account_visibilities.iter())) + for ((account_pre, account_post), visibility) in + inputs.into_iter().zip(outputs.into_iter()).zip(account_visibilities) { match visibility { AccountVisibility::Public => { From e6d1c67c5fc18215e9c2459b616607aea8bf20c0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 19:01:35 -0300 Subject: [PATCH 68/83] remove unnecessary parameter --- risc0-selective-privacy-poc/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index b28d4d8..514b2a6 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -46,10 +46,9 @@ fn execute_and_prove_inner( } /// Builds the private outputs from the results of the execution of an inner program. -/// Populates the nonces with the ones provided. +/// Filters private outputs and populates the nonces with the ones provided. fn build_private_outputs_from_inner_results( inner_program_output: &ProgramOutput, - num_inputs: usize, visibilities: &[AccountVisibility], nonces: &[Nonce], ) -> Vec { @@ -116,8 +115,8 @@ pub fn execute_offchain( let prove_info = prover.prove(env, OUTER_ELF).unwrap(); // Build private accounts. - let private_outputs = - build_private_outputs_from_inner_results(&inner_program_output, num_inputs, visibilities, &output_nonces); + let private_outputs = build_private_outputs_from_inner_results(&inner_program_output, visibilities, &output_nonces); + Ok((prove_info.receipt, private_outputs)) } From 8d09f2ff56d1563c4a43c37db0cbfabd412ea905 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 19:12:56 -0300 Subject: [PATCH 69/83] clippy --- risc0-selective-privacy-poc/core/src/lib.rs | 5 +---- .../mocked_components/client/transfer_deshielded.rs | 2 +- .../examples/mocked_components/sequencer/mod.rs | 7 +++---- .../sequencer/process_privacy_execution.rs | 5 +---- .../sequencer/process_public_execution.rs | 2 +- risc0-selective-privacy-poc/examples/private_execution.rs | 2 +- risc0-selective-privacy-poc/examples/public_execution.rs | 1 - risc0-selective-privacy-poc/src/lib.rs | 5 ++--- 8 files changed, 10 insertions(+), 19 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 990071f..7650de1 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -2,10 +2,7 @@ pub mod account; pub mod types; pub mod visibility; -use crate::{ - account::Account, - types::{AuthenticationPath, Commitment, Key, Nullifier}, -}; +use crate::types::{AuthenticationPath, Commitment, Key, Nullifier}; use risc0_zkvm::sha::{Impl, Sha256}; pub fn hash(bytes: &[u32]) -> [u32; 8] { diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index 185a407..89d581f 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -22,7 +22,7 @@ impl MockedClient { let sender_commitment_auth_path = sequencer.get_authentication_path_for(&from_account.commitment()); // Fetch public account to deshield to - let to_account = sequencer.get_account(&to_address).unwrap(); + let to_account = sequencer.get_account(to_address).unwrap(); // Set account visibilities // First entry is the private sender. Second entry is the public receiver diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index aab2710..09501f9 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -1,11 +1,10 @@ use core::{ account::Account, bytes_to_words, - types::{Address, AuthenticationPath, Commitment, Nullifier, ProgramId}, + types::{Address, AuthenticationPath, Commitment, Nullifier}, }; use std::collections::{BTreeMap, HashSet}; -use program_methods::{PINATA_ID, TRANSFER_ID, TRANSFER_MULTIPLE_ID}; use sparse_merkle_tree::SparseMerkleTree; use crate::mocked_components::USER_CLIENTS; @@ -90,7 +89,7 @@ pub fn print_accounts(sequencer: &MockedSequencer, private_accounts: &[&Account] println!("{:-<20}", ""); for commitment in sequencer.commitment_tree.values().iter() { - println!("{:<20x}", commitment); + println!("{commitment:<20x}"); } println!("{:-<20}\n", ""); @@ -105,7 +104,7 @@ pub fn print_accounts(sequencer: &MockedSequencer, private_accounts: &[&Account] println!("{:-<20}", ""); for entry in formatted { - println!("{:<20}", entry); + println!("{entry:<20}"); } println!("{:-<20}\n", ""); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index ce1715a..af240fc 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -1,7 +1,4 @@ -use core::{ - account::Account, - types::{Commitment, Nullifier, PrivacyExecutionOutput}, -}; +use core::types::PrivacyExecutionOutput; use risc0_zkvm::Receipt; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 70c14b4..87df57a 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -77,6 +77,6 @@ impl MockedSequencer { return false; } - return true; + true } } diff --git a/risc0-selective-privacy-poc/examples/private_execution.rs b/risc0-selective-privacy-poc/examples/private_execution.rs index 6963598..af80da9 100644 --- a/risc0-selective-privacy-poc/examples/private_execution.rs +++ b/risc0-selective-privacy-poc/examples/private_execution.rs @@ -1,7 +1,7 @@ use core::{ account::Account, bytes_to_words, - types::{Address, AuthenticationPath, Commitment, Nullifier}, + types::{Address, AuthenticationPath}, visibility::AccountVisibility, }; use nssa::program::TransferMultipleProgram; diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 68dabf3..1bedb07 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,6 +1,5 @@ use core::account::Account; -use nssa; use nssa::program::TransferMultipleProgram; diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 514b2a6..03d8869 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -1,6 +1,6 @@ use core::{ account::Account, - types::{Commitment, Nonce, Nullifier, ProgramOutput}, + types::{Nonce, ProgramOutput}, visibility::AccountVisibility, }; use program_methods::{OUTER_ELF, OUTER_ID}; @@ -95,12 +95,11 @@ pub fn execute_offchain( commitment_tree_root: [u32; 8], ) -> Result<(Receipt, Vec), ()> { // Prove inner program and get post state of the accounts - let num_inputs = inputs.len(); let inner_receipt = execute_and_prove_inner::

(inputs, instruction_data)?; let inner_program_output: ProgramOutput = inner_receipt.journal.decode().map_err(|_| ())?; // Sample fresh random nonces for the outputs of this execution - let output_nonces: Vec<_> = (0..num_inputs).map(|_| new_random_nonce()).collect(); + let output_nonces: Vec<_> = (0..inputs.len()).map(|_| new_random_nonce()).collect(); // Prove outer program. let mut env_builder = ExecutorEnv::builder(); From c3932839f4825e2cab1e00a5a59326794cc9a85a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 20:44:51 -0300 Subject: [PATCH 70/83] nit --- .../mocked_components/sequencer/process_public_execution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 87df57a..770be37 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -40,7 +40,7 @@ impl MockedSequencer { fn program_output_is_valid(&self, input_accounts: &[Account], program_output: &ProgramOutput) -> bool { let num_inputs = input_accounts.len(); - // Fail if the number of accounts pre and post-states is differ + // Fail if the number of accounts pre and post-states differ if program_output.accounts_pre.len() != program_output.accounts_post.len() { return false; } From 452154576f13dd4bbce34a30ca27478b84138ebb Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 20:49:45 -0300 Subject: [PATCH 71/83] nit --- .../sequencer/process_privacy_execution.rs | 1 - .../sequencer/process_public_execution.rs | 28 ++++++------------- .../examples/public_execution.rs | 1 - 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index af240fc..d5931be 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -60,7 +60,6 @@ impl MockedSequencer { // At this point the privacy execution is considered valid. // // Update the state of the public accounts with the post-state of this privacy execution - output.public_accounts_post.into_iter().for_each(|account_post_state| { self.accounts.insert(account_post_state.address, account_post_state); }); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 770be37..dea7fc3 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -22,7 +22,7 @@ impl MockedSequencer { let program_output = nssa::execute_onchain::

(&input_accounts, instruction_data)?; // Perform consistency checks - if !self.program_output_is_valid(&input_accounts, &program_output) { + if !self.program_output_is_valid(&input_accounts, &program_output.accounts_post) { return Err(()); } @@ -35,26 +35,14 @@ impl MockedSequencer { /// Verifies that a program public execution didn't break the chain's rules. /// `input_accounts` are the accounts provided as inputs to the program. - /// `inputs_outputs` is the program output, which should consist of the accounts pre and - /// post-states. - fn program_output_is_valid(&self, input_accounts: &[Account], program_output: &ProgramOutput) -> bool { - let num_inputs = input_accounts.len(); - - // Fail if the number of accounts pre and post-states differ - if program_output.accounts_pre.len() != program_output.accounts_post.len() { + /// `output_accounts` are the accounts post states after execution of the program + fn program_output_is_valid(&self, input_accounts: &[Account], output_accounts: &[Account]) -> bool { + // Fail if the number of input and output accounts differ + if input_accounts.len() != output_accounts.len() { return false; } - // Fail if the accounts pre-states do not coincide with the input accounts. - if program_output.accounts_pre != input_accounts { - return false; - } - - for (account_pre, account_post) in program_output - .accounts_pre - .iter() - .zip(program_output.accounts_post.iter()) - { + for (account_pre, account_post) in input_accounts.iter().zip(output_accounts) { // Fail if the program modified the addresses of the input accounts if account_pre.address != account_post.address { return false; @@ -70,9 +58,9 @@ impl MockedSequencer { } } - let total_balance_pre: u128 = input_accounts.iter().map(|account| account.balance).sum(); - let total_balance_post: u128 = program_output.accounts_post.iter().map(|account| account.balance).sum(); // Fail if the execution didn't preserve the total supply. + let total_balance_pre: u128 = input_accounts.iter().map(|account| account.balance).sum(); + let total_balance_post: u128 = output_accounts.iter().map(|account| account.balance).sum(); if total_balance_pre != total_balance_post { return false; } diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index 1bedb07..c9325ae 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -1,6 +1,5 @@ use core::account::Account; - use nssa::program::TransferMultipleProgram; /// A public execution of the TransferMultipleProgram. From 39b8085a3b46f10c6dbe31752b0d56360cd9f8b9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 22:05:03 -0300 Subject: [PATCH 72/83] remove all warning --- .../examples/mocked_components/client/mod.rs | 4 +- .../client/transfer_deshielded.rs | 2 +- .../client/transfer_private.rs | 2 +- .../client/transfer_public.rs | 2 +- .../client/transfer_shielded.rs | 6 ++- .../mocked_components/sequencer/mod.rs | 5 --- .../sequencer/process_privacy_execution.rs | 14 +++--- .../sequencer/process_public_execution.rs | 11 ++--- .../examples/public_execution.rs | 2 +- .../program_methods/guest/src/bin/outer.rs | 5 +-- .../sparse_merkle_tree/src/lib.rs | 43 ++++++++----------- risc0-selective-privacy-poc/src/error.rs | 15 +++++++ risc0-selective-privacy-poc/src/lib.rs | 26 +++++------ 13 files changed, 71 insertions(+), 66 deletions(-) create mode 100644 risc0-selective-privacy-poc/src/error.rs diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index 5c2762b..194cab8 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -32,10 +32,10 @@ impl MockedClient { visibilities: &[AccountVisibility], commitment_tree_root: [u32; 8], sequencer: &mut MockedSequencer, - ) -> Result, ()> { + ) -> Result, nssa::Error> { // Execute and generate proof of the outer program let (receipt, private_outputs) = - nssa::execute_offchain::

(input_accounts, instruction_data, visibilities, commitment_tree_root).unwrap(); + nssa::execute_offchain::

(input_accounts, instruction_data, visibilities, commitment_tree_root)?; // Send proof to the sequencer sequencer.process_privacy_execution(receipt)?; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index 89d581f..e22f239 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -15,7 +15,7 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Result { + ) -> Result { // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); // Compute authenticaton path for the input private account diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index c6f2c6c..d96c29f 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -15,7 +15,7 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Result<[Account; 2], ()> { + ) -> Result<[Account; 2], nssa::Error> { // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); // Compute authenticaton path for the input private account diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs index d2bf365..697ec1c 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs @@ -10,7 +10,7 @@ impl MockedClient { to_address: &Address, amount_to_transfer: u128, sequencer: &mut MockedSequencer, - ) -> Result<(), ()> { + ) -> Result<(), nssa::Error> { // Submit a public (on-chain) execution of the Transfer program to the sequencer sequencer.process_public_execution::(&[self.user_address(), *to_address], amount_to_transfer) } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index 7a4bec0..ad869a5 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -14,12 +14,14 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Result { + ) -> Result { // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); // Fetch sender account from the sequencer - let from_account = sequencer.get_account(&self.user_address()).ok_or(())?; + let from_account = sequencer + .get_account(&self.user_address()) + .ok_or(nssa::Error::Generic)?; // Create a new default private account for the receiver let to_account = Self::fresh_account_for_mint(*to_address); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 09501f9..9931d7e 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -64,11 +64,6 @@ impl MockedSequencer { .try_into() .unwrap() } - - /// Returns the list of all registered addresses - pub fn addresses(&self) -> Vec

{ - self.accounts.keys().cloned().collect() - } } /// Pretty prints the chain's state diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index d5931be..c4dcdff 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -7,26 +7,26 @@ use super::MockedSequencer; impl MockedSequencer { /// Processes a privacy execution request. /// Verifies the proof of the privacy execution and updates the state of the chain. - pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), ()> { + pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), nssa::Error> { // Parse the output of the "outer" program let output: PrivacyExecutionOutput = receipt.journal.decode().unwrap(); // Reject in case the root used in the privacy execution is not the current root. if output.commitment_tree_root != self.get_commitment_tree_root() { - return Err(()); + return Err(nssa::Error::Generic); } // Reject in case the number of accounts pre states is different from the post states if output.public_accounts_pre.len() != output.public_accounts_post.len() { - return Err(()); + return Err(nssa::Error::Generic); } // Reject if the states of the public input accounts used in the inner execution do not // coincide with the on-chain state. for account in output.public_accounts_pre.iter() { - let current_account = self.get_account(&account.address).ok_or(())?; + let current_account = self.get_account(&account.address).ok_or(nssa::Error::Generic)?; if ¤t_account != account { - return Err(()); + return Err(nssa::Error::Generic); } } @@ -36,7 +36,7 @@ impl MockedSequencer { .iter() .any(|nullifier| self.nullifier_set.contains(nullifier)) { - return Err(()); + return Err(nssa::Error::Generic); } // Reject if the commitments have already been seen. @@ -45,7 +45,7 @@ impl MockedSequencer { .iter() .any(|commitment| self.commitment_tree.values().contains(commitment)) { - return Err(()); + return Err(nssa::Error::Generic); } // Verify the proof of the privacy execution. diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index dea7fc3..54e615e 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -1,7 +1,4 @@ -use core::{ - account::Account, - types::{Address, ProgramOutput}, -}; +use core::{account::Account, types::Address}; use super::MockedSequencer; @@ -11,11 +8,11 @@ impl MockedSequencer { &mut self, input_account_addresses: &[Address], instruction_data: P::InstructionData, - ) -> Result<(), ()> { + ) -> Result<(), nssa::Error> { // Fetch the current state of the input accounts. let input_accounts: Vec = input_account_addresses .iter() - .map(|address| self.get_account(address).ok_or(())) + .map(|address| self.get_account(address).ok_or(nssa::Error::Generic)) .collect::>()?; // Execute the program @@ -23,7 +20,7 @@ impl MockedSequencer { // Perform consistency checks if !self.program_output_is_valid(&input_accounts, &program_output.accounts_post) { - return Err(()); + return Err(nssa::Error::Generic); } // Update the accounts states diff --git a/risc0-selective-privacy-poc/examples/public_execution.rs b/risc0-selective-privacy-poc/examples/public_execution.rs index c9325ae..f1f59e6 100644 --- a/risc0-selective-privacy-poc/examples/public_execution.rs +++ b/risc0-selective-privacy-poc/examples/public_execution.rs @@ -17,7 +17,7 @@ pub fn main() { let balance_to_move = vec![10, 20]; - let inputs_outputs = + let _inputs_outputs = nssa::execute_onchain::(&[sender, receiver_1, receiver_2], balance_to_move).unwrap(); println!("OK!"); diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index d9c5e87..b415f29 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,5 +1,4 @@ use core::{ - account::Account, compute_nullifier, hash, is_in_tree, types::{Nonce, PrivacyExecutionOutput, ProgramId, ProgramOutput}, visibility::AccountVisibility, @@ -29,8 +28,8 @@ use risc0_zkvm::{guest::env, serde::to_vec}; /// - The commitments for the ouput private accounts. /// - The commitment tree root used for the authentication path verifications. fn main() { - // Read inputs and outputs - let mut inner_program_output: ProgramOutput = env::read(); + // Read inner program output + let inner_program_output: ProgramOutput = env::read(); let num_inputs = inner_program_output.accounts_pre.len(); assert_eq!(inner_program_output.accounts_post.len(), num_inputs); diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs index 3822116..a4f5528 100644 --- a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs @@ -5,13 +5,9 @@ use sha2::{Digest, Sha256}; use std::collections::{HashMap, HashSet}; const TREE_DEPTH: usize = 32; -const ZERO_HASH: [u8; 32] = [ - 110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, 63, - 179, 55, 56, 118, 133, 17, 163, 6, 23, 175, 160, 29, -]; const ONE_HASH: [u8; 32] = [ - 75, 245, 18, 47, 52, 69, 84, 197, 59, 222, 46, 187, 140, 210, 183, 227, 209, 96, 10, 214, 49, - 195, 133, 165, 215, 204, 226, 60, 119, 133, 69, 154, + 75, 245, 18, 47, 52, 69, 84, 197, 59, 222, 46, 187, 140, 210, 183, 227, 209, 96, 10, 214, 49, 195, 133, 165, 215, + 204, 226, 60, 119, 133, 69, 154, ]; /// Compute parent as the hash of two child nodes @@ -65,12 +61,8 @@ impl SparseMerkleTree { let left_index = parent_index << 1; let right_index = left_index | 1; - let left = nodes - .get(&(depth + 1, left_index)) - .unwrap_or(&DEFAULT_HASHES[depth]); - let right = nodes - .get(&(depth + 1, right_index)) - .unwrap_or(&DEFAULT_HASHES[depth]); + let left = nodes.get(&(depth + 1, left_index)).unwrap_or(&DEFAULT_HASHES[depth]); + let right = nodes.get(&(depth + 1, right_index)).unwrap_or(&DEFAULT_HASHES[depth]); if left != &DEFAULT_HASHES[depth] || right != &DEFAULT_HASHES[depth] { let h = hash_node(left, right); @@ -84,10 +76,7 @@ impl SparseMerkleTree { } pub fn root(&self) -> [u8; 32] { - self.node_map - .get(&(0, 0)) - .cloned() - .unwrap_or(DEFAULT_HASHES[0]) + self.node_map.get(&(0, 0)).cloned().unwrap_or(DEFAULT_HASHES[0]) } pub fn get_authentication_path_for_value(&self, value: u32) -> [[u8; 32]; 32] { @@ -133,14 +122,20 @@ impl SparseMerkleTree { #[cfg(test)] mod tests { use super::*; + + const ZERO_HASH: [u8; 32] = [ + 110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, 63, 179, 55, 56, 118, + 133, 17, 163, 6, 23, 175, 160, 29, + ]; + #[test] fn test_default_hashes() { assert_eq!(DEFAULT_HASHES[TREE_DEPTH - 1], ZERO_HASH); assert_eq!( DEFAULT_HASHES[0], [ - 157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, - 64, 171, 100, 192, 44, 121, 46, 78, 53, 190, 198, 191, 82, 85, 16 + 157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, 64, 171, 100, 192, 44, + 121, 46, 78, 53, 190, 198, 191, 82, 85, 16 ] ); } @@ -158,8 +153,8 @@ mod tests { assert_eq!( tree.root(), [ - 109, 94, 224, 93, 195, 77, 137, 36, 108, 105, 177, 22, 212, 17, 160, 255, 224, 61, - 191, 17, 129, 10, 26, 76, 197, 42, 230, 160, 80, 44, 101, 184 + 109, 94, 224, 93, 195, 77, 137, 36, 108, 105, 177, 22, 212, 17, 160, 255, 224, 61, 191, 17, 129, 10, + 26, 76, 197, 42, 230, 160, 80, 44, 101, 184 ] ); } @@ -174,8 +169,8 @@ mod tests { assert_eq!( tree.root(), [ - 36, 178, 159, 245, 165, 76, 242, 85, 25, 218, 149, 135, 194, 127, 130, 201, 219, - 187, 167, 216, 1, 222, 234, 197, 152, 156, 243, 174, 68, 27, 114, 8 + 36, 178, 159, 245, 165, 76, 242, 85, 25, 218, 149, 135, 194, 127, 130, 201, 219, 187, 167, 216, 1, 222, + 234, 197, 152, 156, 243, 174, 68, 27, 114, 8 ] ); } @@ -188,8 +183,8 @@ mod tests { assert_eq!( tree.root(), [ - 148, 76, 190, 191, 248, 243, 89, 40, 197, 157, 206, 23, 58, 197, 86, 169, 225, 217, - 110, 166, 54, 10, 245, 175, 168, 4, 145, 220, 30, 210, 67, 113 + 148, 76, 190, 191, 248, 243, 89, 40, 197, 157, 206, 23, 58, 197, 86, 169, 225, 217, 110, 166, 54, 10, + 245, 175, 168, 4, 145, 220, 30, 210, 67, 113 ] ); } diff --git a/risc0-selective-privacy-poc/src/error.rs b/risc0-selective-privacy-poc/src/error.rs new file mode 100644 index 0000000..f1d7fa5 --- /dev/null +++ b/risc0-selective-privacy-poc/src/error.rs @@ -0,0 +1,15 @@ +#[derive(Debug)] +pub enum Error { + /// For simplicity, this POC uses a generic error + Generic, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Generic => write!(f, "An unexpected error occurred"), + } + } +} + +impl std::error::Error for Error {} diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index 03d8869..f635798 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -7,7 +7,9 @@ use program_methods::{OUTER_ELF, OUTER_ID}; use rand::{rngs::OsRng, Rng}; use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt}; +pub mod error; pub mod program; +pub use error::Error; pub use program::Program; @@ -21,10 +23,10 @@ fn write_inputs( input_accounts: &[Account], instruction_data: P::InstructionData, env_builder: &mut ExecutorEnvBuilder, -) -> Result<(), ()> { +) -> Result<(), Error> { let input_accounts = input_accounts.to_vec(); - env_builder.write(&input_accounts).map_err(|_| ())?; - env_builder.write(&instruction_data).map_err(|_| ())?; + env_builder.write(&input_accounts).map_err(|_| Error::Generic)?; + env_builder.write(&instruction_data).map_err(|_| Error::Generic)?; Ok(()) } @@ -33,7 +35,7 @@ fn write_inputs( fn execute_and_prove_inner( input_accounts: &[Account], instruction_data: P::InstructionData, -) -> Result { +) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; @@ -41,7 +43,7 @@ fn execute_and_prove_inner( // Prove the program let prover = default_prover(); - let prove_info = prover.prove(env, P::PROGRAM_ELF).map_err(|_| ())?; + let prove_info = prover.prove(env, P::PROGRAM_ELF).map_err(|_| Error::Generic)?; Ok(prove_info.receipt) } @@ -71,7 +73,7 @@ fn build_private_outputs_from_inner_results( pub fn execute_onchain( input_accounts: &[Account], instruction_data: P::InstructionData, -) -> Result { +) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); write_inputs::

(input_accounts, instruction_data, &mut env_builder)?; @@ -79,10 +81,10 @@ pub fn execute_onchain( // Execute the program (without proving) let executor = default_executor(); - let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?; + let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| Error::Generic)?; // Get (inputs and) outputs - session_info.journal.decode().map_err(|_| ()) + session_info.journal.decode().map_err(|_| Error::Generic) } /// Executes and proves the inner program `P` and executes and proves the outer program on top of it. @@ -93,10 +95,10 @@ pub fn execute_offchain( instruction_data: P::InstructionData, visibilities: &[AccountVisibility], commitment_tree_root: [u32; 8], -) -> Result<(Receipt, Vec), ()> { +) -> Result<(Receipt, Vec), Error> { // Prove inner program and get post state of the accounts let inner_receipt = execute_and_prove_inner::

(inputs, instruction_data)?; - let inner_program_output: ProgramOutput = inner_receipt.journal.decode().map_err(|_| ())?; + let inner_program_output: ProgramOutput = inner_receipt.journal.decode().map_err(|_| Error::Generic)?; // Sample fresh random nonces for the outputs of this execution let output_nonces: Vec<_> = (0..inputs.len()).map(|_| new_random_nonce()).collect(); @@ -120,6 +122,6 @@ pub fn execute_offchain( } /// Verifies a proof of the outer program for the given parameters. -pub fn verify_privacy_execution(receipt: Receipt) -> Result<(), ()> { - receipt.verify(OUTER_ID).map_err(|_| ()) +pub fn verify_privacy_execution(receipt: Receipt) -> Result<(), Error> { + receipt.verify(OUTER_ID).map_err(|_| Error::Generic) } From 7f43adfa1c122128be9ef64ef2e2453f21157b01 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 22:31:25 -0300 Subject: [PATCH 73/83] add sequencer error --- .../examples/mocked_components/client/mod.rs | 7 +++++-- .../client/transfer_deshielded.rs | 7 +++---- .../client/transfer_private.rs | 7 +++---- .../client/transfer_shielded.rs | 11 ++++------- .../mocked_components/sequencer/error.rs | 15 +++++++++++++++ .../examples/mocked_components/sequencer/mod.rs | 1 + .../sequencer/process_privacy_execution.rs | 17 +++++++++-------- 7 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index 194cab8..f80de89 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -5,6 +5,8 @@ use core::{ visibility::AccountVisibility, }; +use super::sequencer::error::Error; + pub mod transfer_deshielded; pub mod transfer_private; pub mod transfer_public; @@ -32,10 +34,11 @@ impl MockedClient { visibilities: &[AccountVisibility], commitment_tree_root: [u32; 8], sequencer: &mut MockedSequencer, - ) -> Result, nssa::Error> { + ) -> Result, Error> { // Execute and generate proof of the outer program let (receipt, private_outputs) = - nssa::execute_offchain::

(input_accounts, instruction_data, visibilities, commitment_tree_root)?; + nssa::execute_offchain::

(input_accounts, instruction_data, visibilities, commitment_tree_root) + .map_err(|_| Error::Generic)?; // Send proof to the sequencer sequencer.process_privacy_execution(receipt)?; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs index e22f239..b86ba9d 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_deshielded.rs @@ -1,11 +1,10 @@ +use super::{MockedClient, MockedSequencer}; +use crate::mocked_components::sequencer::error::Error; use core::account::Account; use core::types::Address; use core::visibility::AccountVisibility; - use nssa::program::TransferProgram; -use super::{MockedClient, MockedSequencer}; - impl MockedClient { /// A deshielded transaction of the Transfer program. /// All of this is executed locally by the sender @@ -15,7 +14,7 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Result { + ) -> Result { // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); // Compute authenticaton path for the input private account diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs index d96c29f..a1a8d31 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_private.rs @@ -1,11 +1,10 @@ +use super::{MockedClient, MockedSequencer}; +use crate::mocked_components::sequencer::error::Error; use core::account::Account; use core::types::Address; use core::visibility::AccountVisibility; - use nssa::program::TransferProgram; -use super::{MockedClient, MockedSequencer}; - impl MockedClient { /// A private execution of the Transfer program // All of this is executed locally by the sender @@ -15,7 +14,7 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Result<[Account; 2], nssa::Error> { + ) -> Result<[Account; 2], Error> { // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); // Compute authenticaton path for the input private account diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index ad869a5..b3ef569 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -1,11 +1,10 @@ +use super::{MockedClient, MockedSequencer}; +use crate::mocked_components::sequencer::error::Error; use core::account::Account; use core::types::Address; use core::visibility::AccountVisibility; - use nssa::program::TransferProgram; -use super::{MockedClient, MockedSequencer}; - impl MockedClient { /// A shielded execution of the Transfer program // All of this is executed locally by the sender @@ -14,14 +13,12 @@ impl MockedClient { to_address: &Address, balance_to_move: u128, sequencer: &mut MockedSequencer, - ) -> Result { + ) -> Result { // Fetch commitment tree root from the sequencer let commitment_tree_root = sequencer.get_commitment_tree_root(); // Fetch sender account from the sequencer - let from_account = sequencer - .get_account(&self.user_address()) - .ok_or(nssa::Error::Generic)?; + let from_account = sequencer.get_account(&self.user_address()).ok_or(Error::Generic)?; // Create a new default private account for the receiver let to_account = Self::fresh_account_for_mint(*to_address); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs new file mode 100644 index 0000000..f1d7fa5 --- /dev/null +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs @@ -0,0 +1,15 @@ +#[derive(Debug)] +pub enum Error { + /// For simplicity, this POC uses a generic error + Generic, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Generic => write!(f, "An unexpected error occurred"), + } + } +} + +impl std::error::Error for Error {} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index 9931d7e..ce950f0 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -9,6 +9,7 @@ use sparse_merkle_tree::SparseMerkleTree; use crate::mocked_components::USER_CLIENTS; +pub mod error; pub mod process_privacy_execution; pub mod process_public_execution; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index c4dcdff..cf90cba 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -2,31 +2,32 @@ use core::types::PrivacyExecutionOutput; use risc0_zkvm::Receipt; +use super::error::Error; use super::MockedSequencer; impl MockedSequencer { /// Processes a privacy execution request. /// Verifies the proof of the privacy execution and updates the state of the chain. - pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), nssa::Error> { + pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), Error> { // Parse the output of the "outer" program let output: PrivacyExecutionOutput = receipt.journal.decode().unwrap(); // Reject in case the root used in the privacy execution is not the current root. if output.commitment_tree_root != self.get_commitment_tree_root() { - return Err(nssa::Error::Generic); + return Err(Error::Generic); } // Reject in case the number of accounts pre states is different from the post states if output.public_accounts_pre.len() != output.public_accounts_post.len() { - return Err(nssa::Error::Generic); + return Err(Error::Generic); } // Reject if the states of the public input accounts used in the inner execution do not // coincide with the on-chain state. for account in output.public_accounts_pre.iter() { - let current_account = self.get_account(&account.address).ok_or(nssa::Error::Generic)?; + let current_account = self.get_account(&account.address).ok_or(Error::Generic)?; if ¤t_account != account { - return Err(nssa::Error::Generic); + return Err(Error::Generic); } } @@ -36,7 +37,7 @@ impl MockedSequencer { .iter() .any(|nullifier| self.nullifier_set.contains(nullifier)) { - return Err(nssa::Error::Generic); + return Err(Error::Generic); } // Reject if the commitments have already been seen. @@ -45,7 +46,7 @@ impl MockedSequencer { .iter() .any(|commitment| self.commitment_tree.values().contains(commitment)) { - return Err(nssa::Error::Generic); + return Err(Error::Generic); } // Verify the proof of the privacy execution. @@ -55,7 +56,7 @@ impl MockedSequencer { // - The given nullifiers correctly correspond to commitments that currently belong to // the commitment tree. // - The given commitments are correctly computed from valid accounts. - nssa::verify_privacy_execution(receipt)?; + nssa::verify_privacy_execution(receipt).map_err(|_| Error::Generic)?; // At this point the privacy execution is considered valid. // From 9e3c9b3ae83d2284f19956940f0072daece0e4dd Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 19 Jul 2025 23:40:02 -0300 Subject: [PATCH 74/83] add errors --- .../examples/mocked_components/client/mod.rs | 2 +- .../client/transfer_public.rs | 11 +++++++--- .../client/transfer_shielded.rs | 2 +- .../mocked_components/sequencer/error.rs | 7 ++++--- .../sequencer/process_privacy_execution.rs | 14 ++++++------- .../sequencer/process_public_execution.rs | 11 ++++++---- risc0-selective-privacy-poc/src/error.rs | 7 ++++--- risc0-selective-privacy-poc/src/lib.rs | 21 ++++++++++++------- .../src/program/mod.rs | 2 +- 9 files changed, 47 insertions(+), 30 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs index f80de89..563fc21 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/mod.rs @@ -38,7 +38,7 @@ impl MockedClient { // Execute and generate proof of the outer program let (receipt, private_outputs) = nssa::execute_offchain::

(input_accounts, instruction_data, visibilities, commitment_tree_root) - .map_err(|_| Error::Generic)?; + .map_err(|_| Error::BadInput)?; // Send proof to the sequencer sequencer.process_privacy_execution(receipt)?; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs index 697ec1c..cdbb775 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_public.rs @@ -2,7 +2,10 @@ use core::types::Address; use nssa::program::TransferProgram; -use crate::mocked_components::{client::MockedClient, sequencer::MockedSequencer}; +use crate::mocked_components::{ + client::MockedClient, + sequencer::{error::Error, MockedSequencer}, +}; impl MockedClient { pub fn transfer_public( @@ -10,8 +13,10 @@ impl MockedClient { to_address: &Address, amount_to_transfer: u128, sequencer: &mut MockedSequencer, - ) -> Result<(), nssa::Error> { + ) -> Result<(), Error> { // Submit a public (on-chain) execution of the Transfer program to the sequencer - sequencer.process_public_execution::(&[self.user_address(), *to_address], amount_to_transfer) + sequencer + .process_public_execution::(&[self.user_address(), *to_address], amount_to_transfer) + .map_err(|_| Error::BadInput) } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs index b3ef569..58a4c06 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/client/transfer_shielded.rs @@ -18,7 +18,7 @@ impl MockedClient { let commitment_tree_root = sequencer.get_commitment_tree_root(); // Fetch sender account from the sequencer - let from_account = sequencer.get_account(&self.user_address()).ok_or(Error::Generic)?; + let from_account = sequencer.get_account(&self.user_address()).ok_or(Error::NotFound)?; // Create a new default private account for the receiver let to_account = Self::fresh_account_for_mint(*to_address); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs index f1d7fa5..f3b3fbc 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/error.rs @@ -1,13 +1,14 @@ #[derive(Debug)] pub enum Error { - /// For simplicity, this POC uses a generic error - Generic, + NotFound, + BadInput, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Error::Generic => write!(f, "An unexpected error occurred"), + Error::NotFound => write!(f, "Not found"), + Error::BadInput => write!(f, "Bad input"), } } } diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index cf90cba..b78dc59 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -14,20 +14,20 @@ impl MockedSequencer { // Reject in case the root used in the privacy execution is not the current root. if output.commitment_tree_root != self.get_commitment_tree_root() { - return Err(Error::Generic); + return Err(Error::BadInput); } // Reject in case the number of accounts pre states is different from the post states if output.public_accounts_pre.len() != output.public_accounts_post.len() { - return Err(Error::Generic); + return Err(Error::BadInput); } // Reject if the states of the public input accounts used in the inner execution do not // coincide with the on-chain state. for account in output.public_accounts_pre.iter() { - let current_account = self.get_account(&account.address).ok_or(Error::Generic)?; + let current_account = self.get_account(&account.address).ok_or(Error::NotFound)?; if ¤t_account != account { - return Err(Error::Generic); + return Err(Error::BadInput); } } @@ -37,7 +37,7 @@ impl MockedSequencer { .iter() .any(|nullifier| self.nullifier_set.contains(nullifier)) { - return Err(Error::Generic); + return Err(Error::BadInput); } // Reject if the commitments have already been seen. @@ -46,7 +46,7 @@ impl MockedSequencer { .iter() .any(|commitment| self.commitment_tree.values().contains(commitment)) { - return Err(Error::Generic); + return Err(Error::BadInput); } // Verify the proof of the privacy execution. @@ -56,7 +56,7 @@ impl MockedSequencer { // - The given nullifiers correctly correspond to commitments that currently belong to // the commitment tree. // - The given commitments are correctly computed from valid accounts. - nssa::verify_privacy_execution(receipt).map_err(|_| Error::Generic)?; + nssa::verify_privacy_execution(receipt).map_err(|_| Error::BadInput)?; // At this point the privacy execution is considered valid. // diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 54e615e..808e29a 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -1,5 +1,7 @@ use core::{account::Account, types::Address}; +use crate::mocked_components::sequencer::error::Error; + use super::MockedSequencer; impl MockedSequencer { @@ -8,19 +10,20 @@ impl MockedSequencer { &mut self, input_account_addresses: &[Address], instruction_data: P::InstructionData, - ) -> Result<(), nssa::Error> { + ) -> Result<(), Error> { // Fetch the current state of the input accounts. let input_accounts: Vec = input_account_addresses .iter() - .map(|address| self.get_account(address).ok_or(nssa::Error::Generic)) + .map(|address| self.get_account(address).ok_or(Error::NotFound)) .collect::>()?; // Execute the program - let program_output = nssa::execute_onchain::

(&input_accounts, instruction_data)?; + let program_output = + nssa::execute_onchain::

(&input_accounts, instruction_data).map_err(|_| Error::BadInput)?; // Perform consistency checks if !self.program_output_is_valid(&input_accounts, &program_output.accounts_post) { - return Err(nssa::Error::Generic); + return Err(Error::BadInput); } // Update the accounts states diff --git a/risc0-selective-privacy-poc/src/error.rs b/risc0-selective-privacy-poc/src/error.rs index f1d7fa5..69cbd67 100644 --- a/risc0-selective-privacy-poc/src/error.rs +++ b/risc0-selective-privacy-poc/src/error.rs @@ -1,13 +1,14 @@ #[derive(Debug)] pub enum Error { - /// For simplicity, this POC uses a generic error - Generic, + BadInput, + Risc0(String), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Error::Generic => write!(f, "An unexpected error occurred"), + Error::BadInput => write!(f, "Bad input"), + Error::Risc0(_) => write!(f, "Risc0 error"), } } } diff --git a/risc0-selective-privacy-poc/src/lib.rs b/risc0-selective-privacy-poc/src/lib.rs index f635798..4de1700 100644 --- a/risc0-selective-privacy-poc/src/lib.rs +++ b/risc0-selective-privacy-poc/src/lib.rs @@ -25,8 +25,8 @@ fn write_inputs( env_builder: &mut ExecutorEnvBuilder, ) -> Result<(), Error> { let input_accounts = input_accounts.to_vec(); - env_builder.write(&input_accounts).map_err(|_| Error::Generic)?; - env_builder.write(&instruction_data).map_err(|_| Error::Generic)?; + env_builder.write(&input_accounts).map_err(|_| Error::BadInput)?; + env_builder.write(&instruction_data).map_err(|_| Error::BadInput)?; Ok(()) } @@ -43,7 +43,9 @@ fn execute_and_prove_inner( // Prove the program let prover = default_prover(); - let prove_info = prover.prove(env, P::PROGRAM_ELF).map_err(|_| Error::Generic)?; + let prove_info = prover + .prove(env, P::PROGRAM_ELF) + .map_err(|e| Error::Risc0(e.to_string()))?; Ok(prove_info.receipt) } @@ -81,10 +83,12 @@ pub fn execute_onchain( // Execute the program (without proving) let executor = default_executor(); - let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| Error::Generic)?; + let session_info = executor + .execute(env, P::PROGRAM_ELF) + .map_err(|e| Error::Risc0(e.to_string()))?; // Get (inputs and) outputs - session_info.journal.decode().map_err(|_| Error::Generic) + session_info.journal.decode().map_err(|e| Error::Risc0(e.to_string())) } /// Executes and proves the inner program `P` and executes and proves the outer program on top of it. @@ -98,7 +102,10 @@ pub fn execute_offchain( ) -> Result<(Receipt, Vec), Error> { // Prove inner program and get post state of the accounts let inner_receipt = execute_and_prove_inner::

(inputs, instruction_data)?; - let inner_program_output: ProgramOutput = inner_receipt.journal.decode().map_err(|_| Error::Generic)?; + let inner_program_output: ProgramOutput = inner_receipt + .journal + .decode() + .map_err(|e| Error::Risc0(e.to_string()))?; // Sample fresh random nonces for the outputs of this execution let output_nonces: Vec<_> = (0..inputs.len()).map(|_| new_random_nonce()).collect(); @@ -123,5 +130,5 @@ pub fn execute_offchain( /// Verifies a proof of the outer program for the given parameters. pub fn verify_privacy_execution(receipt: Receipt) -> Result<(), Error> { - receipt.verify(OUTER_ID).map_err(|_| Error::Generic) + receipt.verify(OUTER_ID).map_err(|e| Error::Risc0(e.to_string())) } diff --git a/risc0-selective-privacy-poc/src/program/mod.rs b/risc0-selective-privacy-poc/src/program/mod.rs index 9ee7dec..964e3db 100644 --- a/risc0-selective-privacy-poc/src/program/mod.rs +++ b/risc0-selective-privacy-poc/src/program/mod.rs @@ -30,6 +30,6 @@ pub struct PinataProgram; impl Program for PinataProgram { const PROGRAM_ID: ProgramId = PINATA_ID; const PROGRAM_ELF: &[u8] = PINATA_ELF; - /// Preimage of target hash to win price + /// Preimage of target hash to win prize type InstructionData = Vec; } From 7b5db4323882ce8350fd3ddb32239d1ffd685516 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 20 Jul 2025 19:44:16 -0300 Subject: [PATCH 75/83] refactor so that both sequencer and outer program use the same consistency checks --- risc0-selective-privacy-poc/core/src/lib.rs | 32 +++++++++++- .../sequencer/process_public_execution.rs | 51 +++++-------------- .../program_methods/guest/src/bin/outer.rs | 46 +++++++++-------- 3 files changed, 68 insertions(+), 61 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 7650de1..0b8252d 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -2,7 +2,7 @@ pub mod account; pub mod types; pub mod visibility; -use crate::types::{AuthenticationPath, Commitment, Key, Nullifier}; +use crate::{account::Account, types::{AuthenticationPath, Commitment, Key, Nullifier}}; use risc0_zkvm::sha::{Impl, Sha256}; pub fn hash(bytes: &[u32]) -> [u32; 8] { @@ -49,3 +49,33 @@ pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { } words } + +/// Verifies that a program public execution didn't break the chain's rules. +/// `input_accounts` are the accounts provided as inputs to the program. +/// `output_accounts` are the accounts post states after execution of the program +pub fn inputs_outputs_preserve_invariants(input_accounts: &[Account], output_accounts: &[Account]) -> bool { + // Fail if the number of input and output accounts differ + if input_accounts.len() != output_accounts.len() { + return false; + } + + for (account_pre, account_post) in input_accounts.iter().zip(output_accounts) { + // Fail if the program modified the addresses of the input accounts + if account_pre.address != account_post.address { + return false; + } + // Fail if the program modified the nonces of the input accounts + if account_pre.nonce != account_post.nonce { + return false; + } + } + + // Fail if the execution didn't preserve the total supply. + let total_balance_pre: u128 = input_accounts.iter().map(|account| account.balance).sum(); + let total_balance_post: u128 = output_accounts.iter().map(|account| account.balance).sum(); + if total_balance_pre != total_balance_post { + return false; + } + + true +} diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 808e29a..521c9d1 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -1,4 +1,4 @@ -use core::{account::Account, types::Address}; +use core::{account::Account, inputs_outputs_preserve_invariants, types::Address}; use crate::mocked_components::sequencer::error::Error; @@ -21,8 +21,18 @@ impl MockedSequencer { let program_output = nssa::execute_onchain::

(&input_accounts, instruction_data).map_err(|_| Error::BadInput)?; - // Perform consistency checks - if !self.program_output_is_valid(&input_accounts, &program_output.accounts_post) { + // Assert accounts pre- and post-states preserve chains invariants + if !inputs_outputs_preserve_invariants(&input_accounts, &program_output.accounts_post) { + return Err(Error::BadInput); + } + + // Fail if any of the output accounts is not yet registered. + // This is redundant with previous checks, but better make it explicit. + if !program_output + .accounts_post + .iter() + .all(|account| self.accounts.contains_key(&account.address)) + { return Err(Error::BadInput); } @@ -32,39 +42,4 @@ impl MockedSequencer { }); Ok(()) } - - /// Verifies that a program public execution didn't break the chain's rules. - /// `input_accounts` are the accounts provided as inputs to the program. - /// `output_accounts` are the accounts post states after execution of the program - fn program_output_is_valid(&self, input_accounts: &[Account], output_accounts: &[Account]) -> bool { - // Fail if the number of input and output accounts differ - if input_accounts.len() != output_accounts.len() { - return false; - } - - for (account_pre, account_post) in input_accounts.iter().zip(output_accounts) { - // Fail if the program modified the addresses of the input accounts - if account_pre.address != account_post.address { - return false; - } - // Fail if the program modified the nonces of the input accounts - if account_pre.nonce != account_post.nonce { - return false; - } - // Fail if any of the output accounts is not yet registered. - // (redundant with previous checks, but better make it explicit) - if !self.accounts.contains_key(&account_post.address) { - return false; - } - } - - // Fail if the execution didn't preserve the total supply. - let total_balance_pre: u128 = input_accounts.iter().map(|account| account.balance).sum(); - let total_balance_post: u128 = output_accounts.iter().map(|account| account.balance).sum(); - if total_balance_pre != total_balance_post { - return false; - } - - true - } } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index b415f29..0c5c747 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,5 +1,5 @@ use core::{ - compute_nullifier, hash, is_in_tree, + compute_nullifier, hash, inputs_outputs_preserve_invariants, is_in_tree, types::{Nonce, PrivacyExecutionOutput, ProgramId, ProgramOutput}, visibility::AccountVisibility, }; @@ -31,7 +31,6 @@ fn main() { // Read inner program output let inner_program_output: ProgramOutput = env::read(); let num_inputs = inner_program_output.accounts_pre.len(); - assert_eq!(inner_program_output.accounts_post.len(), num_inputs); // Read visibilities let account_visibilities: Vec = env::read(); @@ -45,14 +44,14 @@ fn main() { let commitment_tree_root: [u32; 8] = env::read(); let program_id: ProgramId = env::read(); - // Verify pre states and post states of accounts are consistent - // with the execution of the `program_id` program - env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap(); - - let inputs = inner_program_output.accounts_pre; - let mut outputs = inner_program_output.accounts_post; + // Authentication step: + // Check private accounts are owned by caller and that they are consistent + // with the commitments tree. let mut nullifiers = Vec::new(); - for (visibility, input_account) in account_visibilities.iter().zip(inputs.iter()) { + for (visibility, input_account) in account_visibilities + .iter() + .zip(inner_program_output.accounts_pre.iter()) + { match visibility { AccountVisibility::Private(Some((private_key, auth_path))) => { // Prove ownership of input accounts by proving knowledge of the pre-image of their addresses. @@ -75,28 +74,29 @@ fn main() { } } - // Assert that the inner program didn't modify address fields or nonces - for (account_pre, account_post) in inputs.iter().zip(outputs.iter()) { - assert_eq!(account_pre.address, account_post.address); - assert_eq!(account_pre.nonce, account_post.nonce); - } + // Verify pre states and post states of accounts are consistent + // with the execution of the `program_id` program + env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap(); - // Check that the program preserved the total supply - let total_balance_pre: u128 = inputs.iter().map(|account| account.balance).sum(); - let total_balance_post: u128 = outputs.iter().map(|account| account.balance).sum(); - assert_eq!(total_balance_pre, total_balance_post); + // Assert accounts pre- and post-states preserve chains invariants + assert!(inputs_outputs_preserve_invariants( + &inner_program_output.accounts_pre, + &inner_program_output.accounts_post + )); // From this point on the execution is considered valid // // Insert new nonces in outputs (including public ones) - outputs + let accounts_pre = inner_program_output.accounts_pre; + let mut accounts_post = inner_program_output.accounts_post; + accounts_post .iter_mut() .zip(output_nonces) .for_each(|(account, new_nonce)| account.nonce = new_nonce); // Compute commitments for every private output let mut private_outputs = Vec::new(); - for (output, visibility) in outputs.iter().zip(account_visibilities.iter()) { + for (output, visibility) in accounts_post.iter().zip(account_visibilities.iter()) { match visibility { AccountVisibility::Public => continue, AccountVisibility::Private(_) => private_outputs.push(output), @@ -107,8 +107,10 @@ fn main() { // Get the list of public accounts pre and post states let mut public_accounts_pre = Vec::new(); let mut public_accounts_post = Vec::new(); - for ((account_pre, account_post), visibility) in - inputs.into_iter().zip(outputs.into_iter()).zip(account_visibilities) + for ((account_pre, account_post), visibility) in accounts_pre + .into_iter() + .zip(accounts_post.into_iter()) + .zip(account_visibilities) { match visibility { AccountVisibility::Public => { From 1e955346109a6d5edb6a1dae3116669185680ddd Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 20 Jul 2025 19:58:02 -0300 Subject: [PATCH 76/83] refactor outer program for readability --- .../program_methods/guest/src/bin/outer.rs | 147 ++++++++++-------- 1 file changed, 86 insertions(+), 61 deletions(-) diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 0c5c747..97c35b6 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -33,8 +33,8 @@ fn main() { let num_inputs = inner_program_output.accounts_pre.len(); // Read visibilities - let account_visibilities: Vec = env::read(); - assert_eq!(account_visibilities.len(), num_inputs); + let visibilities: Vec = env::read(); + assert_eq!(visibilities.len(), num_inputs); // Read nonces for outputs let output_nonces: Vec = env::read(); @@ -45,8 +45,89 @@ fn main() { let program_id: ProgramId = env::read(); // Authentication step: - // Check private accounts are owned by caller and that they are consistent - // with the commitments tree. + let nullifiers = verify_and_nullify_private_inputs(&inner_program_output, &visibilities, commitment_tree_root); + + // Verify pre states and post states of accounts are consistent + // with the execution of the `program_id` program + env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap(); + + // Assert accounts pre- and post-states preserve chains invariants + assert!(inputs_outputs_preserve_invariants( + &inner_program_output.accounts_pre, + &inner_program_output.accounts_post + )); + + // From this point on the execution is considered valid + + let output = assemble_privacy_execution_output( + inner_program_output, + visibilities, + output_nonces, + commitment_tree_root, + nullifiers, + ); + + env::commit(&output); +} + +fn assemble_privacy_execution_output( + inner_program_output: ProgramOutput, + visibilities: Vec, + output_nonces: Vec<[u32; 8]>, + commitment_tree_root: [u32; 8], + nullifiers: Vec<[u32; 8]>, +) -> PrivacyExecutionOutput { + // Insert new nonces in outputs (including public ones) + let accounts_pre = inner_program_output.accounts_pre; + let mut accounts_post = inner_program_output.accounts_post; + accounts_post + .iter_mut() + .zip(output_nonces) + .for_each(|(account, new_nonce)| account.nonce = new_nonce); + + // Compute commitments for every private output + let mut private_outputs = Vec::new(); + for (output, visibility) in accounts_post.iter().zip(visibilities.iter()) { + match visibility { + AccountVisibility::Public => continue, + AccountVisibility::Private(_) => private_outputs.push(output), + } + } + let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect(); + + // Get the list of public accounts pre and post states + let mut public_accounts_pre = Vec::new(); + let mut public_accounts_post = Vec::new(); + for ((account_pre, account_post), visibility) in accounts_pre + .into_iter() + .zip(accounts_post.into_iter()) + .zip(visibilities) + { + match visibility { + AccountVisibility::Public => { + public_accounts_pre.push(account_pre); + public_accounts_post.push(account_post); + } + AccountVisibility::Private(_) => continue, + } + } + + let output = PrivacyExecutionOutput { + public_accounts_pre, + public_accounts_post, + private_output_commitments, + nullifiers, + commitment_tree_root, + }; + output +} + +/// Compute nullifiers of private accounts pre states and check that their commitments belong to the commitments tree +fn verify_and_nullify_private_inputs( + inner_program_output: &ProgramOutput, + account_visibilities: &Vec, + commitment_tree_root: [u32; 8], +) -> Vec<[u32; 8]> { let mut nullifiers = Vec::new(); for (visibility, input_account) in account_visibilities .iter() @@ -73,61 +154,5 @@ fn main() { AccountVisibility::Public => continue, } } - - // Verify pre states and post states of accounts are consistent - // with the execution of the `program_id` program - env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap(); - - // Assert accounts pre- and post-states preserve chains invariants - assert!(inputs_outputs_preserve_invariants( - &inner_program_output.accounts_pre, - &inner_program_output.accounts_post - )); - - // From this point on the execution is considered valid - // - // Insert new nonces in outputs (including public ones) - let accounts_pre = inner_program_output.accounts_pre; - let mut accounts_post = inner_program_output.accounts_post; - accounts_post - .iter_mut() - .zip(output_nonces) - .for_each(|(account, new_nonce)| account.nonce = new_nonce); - - // Compute commitments for every private output - let mut private_outputs = Vec::new(); - for (output, visibility) in accounts_post.iter().zip(account_visibilities.iter()) { - match visibility { - AccountVisibility::Public => continue, - AccountVisibility::Private(_) => private_outputs.push(output), - } - } - let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect(); - - // Get the list of public accounts pre and post states - let mut public_accounts_pre = Vec::new(); - let mut public_accounts_post = Vec::new(); - for ((account_pre, account_post), visibility) in accounts_pre - .into_iter() - .zip(accounts_post.into_iter()) - .zip(account_visibilities) - { - match visibility { - AccountVisibility::Public => { - public_accounts_pre.push(account_pre); - public_accounts_post.push(account_post); - } - AccountVisibility::Private(_) => continue, - } - } - - let output = PrivacyExecutionOutput { - public_accounts_pre, - public_accounts_post, - private_output_commitments, - nullifiers, - commitment_tree_root, - }; - - env::commit(&output); + nullifiers } From e93ab78f2e738e9f5157815de30973b4377d9626 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 20 Jul 2025 20:02:03 -0300 Subject: [PATCH 77/83] minor refactor --- .../sequencer/process_privacy_execution.rs | 16 ++++++---------- .../sequencer/process_public_execution.rs | 1 + 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs index b78dc59..0b278cd 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_privacy_execution.rs @@ -12,16 +12,6 @@ impl MockedSequencer { // Parse the output of the "outer" program let output: PrivacyExecutionOutput = receipt.journal.decode().unwrap(); - // Reject in case the root used in the privacy execution is not the current root. - if output.commitment_tree_root != self.get_commitment_tree_root() { - return Err(Error::BadInput); - } - - // Reject in case the number of accounts pre states is different from the post states - if output.public_accounts_pre.len() != output.public_accounts_post.len() { - return Err(Error::BadInput); - } - // Reject if the states of the public input accounts used in the inner execution do not // coincide with the on-chain state. for account in output.public_accounts_pre.iter() { @@ -31,6 +21,11 @@ impl MockedSequencer { } } + // Reject in case the root used in the privacy execution is not the current root. + if output.commitment_tree_root != self.get_commitment_tree_root() { + return Err(Error::BadInput); + } + // Reject if the nullifiers of this privacy execution have already been published. if output .nullifiers @@ -56,6 +51,7 @@ impl MockedSequencer { // - The given nullifiers correctly correspond to commitments that currently belong to // the commitment tree. // - The given commitments are correctly computed from valid accounts. + // - The chain invariants are preserved nssa::verify_privacy_execution(receipt).map_err(|_| Error::BadInput)?; // At this point the privacy execution is considered valid. diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 521c9d1..68f695d 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -40,6 +40,7 @@ impl MockedSequencer { program_output.accounts_post.into_iter().for_each(|account_post_state| { self.accounts.insert(account_post_state.address, account_post_state); }); + Ok(()) } } From c969ee0890b0af5fd64f202548b058a14e0db946 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 20 Jul 2025 20:18:36 -0300 Subject: [PATCH 78/83] remove unnecessary check --- .../sequencer/process_public_execution.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 68f695d..7fc0df9 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -26,16 +26,6 @@ impl MockedSequencer { return Err(Error::BadInput); } - // Fail if any of the output accounts is not yet registered. - // This is redundant with previous checks, but better make it explicit. - if !program_output - .accounts_post - .iter() - .all(|account| self.accounts.contains_key(&account.address)) - { - return Err(Error::BadInput); - } - // Update the accounts states program_output.accounts_post.into_iter().for_each(|account_post_state| { self.accounts.insert(account_post_state.address, account_post_state); From 87dc9d9d3e1d51fdc9314026a4563e4902874024 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 20 Jul 2025 21:55:21 -0300 Subject: [PATCH 79/83] add program owners --- .../core/src/account.rs | 4 +++- risc0-selective-privacy-poc/core/src/lib.rs | 22 +++++++++++++++++-- .../mocked_components/sequencer/mod.rs | 10 ++++++++- .../sequencer/process_public_execution.rs | 2 +- .../program_methods/guest/src/bin/outer.rs | 3 ++- 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/account.rs b/risc0-selective-privacy-poc/core/src/account.rs index edcbc44..088b7ee 100644 --- a/risc0-selective-privacy-poc/core/src/account.rs +++ b/risc0-selective-privacy-poc/core/src/account.rs @@ -1,6 +1,6 @@ use crate::{ hash, - types::{Address, Commitment, Key, Nonce}, + types::{Address, Commitment, Key, Nonce, ProgramId}, }; use risc0_zkvm::serde::to_vec; use serde::{Deserialize, Serialize}; @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; /// Account to be used both in public and private contexts #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Account { + pub program_owner: Option, pub address: Address, pub balance: u128, pub nonce: Nonce, @@ -16,6 +17,7 @@ pub struct Account { impl Account { pub fn new(address: Address, balance: u128) -> Self { Self { + program_owner: None, address, balance, nonce: [0; 8], diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 0b8252d..85b6b1f 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -2,7 +2,10 @@ pub mod account; pub mod types; pub mod visibility; -use crate::{account::Account, types::{AuthenticationPath, Commitment, Key, Nullifier}}; +use crate::{ + account::Account, + types::{AuthenticationPath, Commitment, Key, Nullifier, ProgramId}, +}; use risc0_zkvm::sha::{Impl, Sha256}; pub fn hash(bytes: &[u32]) -> [u32; 8] { @@ -53,7 +56,11 @@ pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { /// Verifies that a program public execution didn't break the chain's rules. /// `input_accounts` are the accounts provided as inputs to the program. /// `output_accounts` are the accounts post states after execution of the program -pub fn inputs_outputs_preserve_invariants(input_accounts: &[Account], output_accounts: &[Account]) -> bool { +pub fn inputs_outputs_preserve_invariants( + input_accounts: &[Account], + output_accounts: &[Account], + program_id: ProgramId, +) -> bool { // Fail if the number of input and output accounts differ if input_accounts.len() != output_accounts.len() { return false; @@ -68,6 +75,17 @@ pub fn inputs_outputs_preserve_invariants(input_accounts: &[Account], output_acc if account_pre.nonce != account_post.nonce { return false; } + + // Fail if the program modified the program owner + if account_pre.program_owner != account_post.program_owner { + return false; + } + + // Fail if the program subtracted balance from an account it doesn't own. + // (This check always passes if `program_owner` is `None`) + if account_pre.balance > account_post.balance && account_pre.program_owner.unwrap_or(program_id) != program_id { + return false; + } } // Fail if the execution didn't preserve the total supply. diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs index ce950f0..e66bf83 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/mod.rs @@ -5,6 +5,7 @@ use core::{ }; use std::collections::{BTreeMap, HashSet}; +use nssa::{program::PinataProgram, Program}; use sparse_merkle_tree::SparseMerkleTree; use crate::mocked_components::USER_CLIENTS; @@ -33,7 +34,14 @@ impl MockedSequencer { .map(|account| (account.address, account)) .collect(); - let pinata_account = Account::new(PINATA_ADDRESS, INITIAL_BALANCE); + let pinata_account = { + let mut this = Account::new(PINATA_ADDRESS, INITIAL_BALANCE); + // Set the owner of the Pinata account so that only the Pinata program + // can reduce its balance. + this.program_owner = Some(PinataProgram::PROGRAM_ID); + this + }; + accounts.insert(pinata_account.address, pinata_account); let commitment_tree = SparseMerkleTree::new_empty(); diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 7fc0df9..7a67911 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -22,7 +22,7 @@ impl MockedSequencer { nssa::execute_onchain::

(&input_accounts, instruction_data).map_err(|_| Error::BadInput)?; // Assert accounts pre- and post-states preserve chains invariants - if !inputs_outputs_preserve_invariants(&input_accounts, &program_output.accounts_post) { + if !inputs_outputs_preserve_invariants(&input_accounts, &program_output.accounts_post, P::PROGRAM_ID) { return Err(Error::BadInput); } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 97c35b6..7c877f7 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -54,7 +54,8 @@ fn main() { // Assert accounts pre- and post-states preserve chains invariants assert!(inputs_outputs_preserve_invariants( &inner_program_output.accounts_pre, - &inner_program_output.accounts_post + &inner_program_output.accounts_post, + program_id )); // From this point on the execution is considered valid From 734206bf73dccf6003ba31c95dc73de855667006 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 20 Jul 2025 22:02:16 -0300 Subject: [PATCH 80/83] change function name --- risc0-selective-privacy-poc/core/src/lib.rs | 2 +- .../mocked_components/sequencer/process_public_execution.rs | 4 ++-- .../program_methods/guest/src/bin/outer.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 85b6b1f..9342094 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -56,7 +56,7 @@ pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { /// Verifies that a program public execution didn't break the chain's rules. /// `input_accounts` are the accounts provided as inputs to the program. /// `output_accounts` are the accounts post states after execution of the program -pub fn inputs_outputs_preserve_invariants( +pub fn post_execution_consistency_checks( input_accounts: &[Account], output_accounts: &[Account], program_id: ProgramId, diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index 7a67911..b41b1b2 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -1,4 +1,4 @@ -use core::{account::Account, inputs_outputs_preserve_invariants, types::Address}; +use core::{account::Account, post_execution_consistency_checks, types::Address}; use crate::mocked_components::sequencer::error::Error; @@ -22,7 +22,7 @@ impl MockedSequencer { nssa::execute_onchain::

(&input_accounts, instruction_data).map_err(|_| Error::BadInput)?; // Assert accounts pre- and post-states preserve chains invariants - if !inputs_outputs_preserve_invariants(&input_accounts, &program_output.accounts_post, P::PROGRAM_ID) { + if !post_execution_consistency_checks(&input_accounts, &program_output.accounts_post, P::PROGRAM_ID) { return Err(Error::BadInput); } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 7c877f7..c95726c 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,5 +1,5 @@ use core::{ - compute_nullifier, hash, inputs_outputs_preserve_invariants, is_in_tree, + compute_nullifier, hash, is_in_tree, post_execution_consistency_checks, types::{Nonce, PrivacyExecutionOutput, ProgramId, ProgramOutput}, visibility::AccountVisibility, }; @@ -52,7 +52,7 @@ fn main() { env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap(); // Assert accounts pre- and post-states preserve chains invariants - assert!(inputs_outputs_preserve_invariants( + assert!(post_execution_consistency_checks( &inner_program_output.accounts_pre, &inner_program_output.accounts_post, program_id From 29b56e3a32e1e0b6186562681c9b8bedca529468 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 20 Jul 2025 22:30:31 -0300 Subject: [PATCH 81/83] improve docstrings --- risc0-selective-privacy-poc/core/src/lib.rs | 18 ++++++++++++++---- .../sequencer/process_public_execution.rs | 4 ++-- .../program_methods/guest/src/bin/outer.rs | 13 +++++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 9342094..dbd4b5b 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -53,10 +53,19 @@ pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { words } -/// Verifies that a program public execution didn't break the chain's rules. -/// `input_accounts` are the accounts provided as inputs to the program. -/// `output_accounts` are the accounts post states after execution of the program -pub fn post_execution_consistency_checks( +/// Ensures that account transitions follow the rules of a well-behaved program. +/// +/// A well-behaved program is one that: +/// - does not change account addresses +/// - does not change account nonces +/// - does not change the `program_owner` field +/// - only reduces the balance of accounts it owns +/// - preserves the total token supply across all accounts +/// +/// This function does **not** check that the output accounts are the result of correctly +/// executing the program. That must be checked separately, either by re-executing +/// the program with the inputs or by verifying a proof of correct execution. +pub fn check_well_behaved_account_transition( input_accounts: &[Account], output_accounts: &[Account], program_id: ProgramId, @@ -71,6 +80,7 @@ pub fn post_execution_consistency_checks( if account_pre.address != account_post.address { return false; } + // Fail if the program modified the nonces of the input accounts if account_pre.nonce != account_post.nonce { return false; diff --git a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs index b41b1b2..fcf7562 100644 --- a/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs +++ b/risc0-selective-privacy-poc/examples/mocked_components/sequencer/process_public_execution.rs @@ -1,4 +1,4 @@ -use core::{account::Account, post_execution_consistency_checks, types::Address}; +use core::{account::Account, check_well_behaved_account_transition, types::Address}; use crate::mocked_components::sequencer::error::Error; @@ -22,7 +22,7 @@ impl MockedSequencer { nssa::execute_onchain::

(&input_accounts, instruction_data).map_err(|_| Error::BadInput)?; // Assert accounts pre- and post-states preserve chains invariants - if !post_execution_consistency_checks(&input_accounts, &program_output.accounts_post, P::PROGRAM_ID) { + if !check_well_behaved_account_transition(&input_accounts, &program_output.accounts_post, P::PROGRAM_ID) { return Err(Error::BadInput); } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index c95726c..2f4fcd7 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -1,5 +1,5 @@ use core::{ - compute_nullifier, hash, is_in_tree, post_execution_consistency_checks, + check_well_behaved_account_transition, compute_nullifier, hash, is_in_tree, types::{Nonce, PrivacyExecutionOutput, ProgramId, ProgramOutput}, visibility::AccountVisibility, }; @@ -7,18 +7,15 @@ use risc0_zkvm::{guest::env, serde::to_vec}; /// Privacy execution logic. /// This is the circuit for proving correct off-chain executions of programs. -/// It also verifies that the chain's invariants are not violated. +/// It also verifies that the chain's invariants are not violated and the program is well-behaved. /// /// Inputs: -/// - Vec: The output of the inner program. This is assumed to include the accounts pre and +/// - ProgramOuptut: The output of the inner program. This is includes the accounts pre and /// post-states of the execution of the inner program. -/// /// - Vec: A vector indicating which accounts are private and which are public. -/// /// - Vec: The vector of nonces to be used for the output accounts. This is assumed to be /// sampled at random by the host program. -/// -/// - [u32; 8]: The root of the commitment tree. Commitments of used private accounts will be +/// - [u32; 8]: The root of the commitment tree. Commitments of input private accounts will be /// checked against this to prove that they belong to the tree. /// - ProgamId: The ID of the inner program. /// @@ -52,7 +49,7 @@ fn main() { env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap(); // Assert accounts pre- and post-states preserve chains invariants - assert!(post_execution_consistency_checks( + assert!(check_well_behaved_account_transition( &inner_program_output.accounts_pre, &inner_program_output.accounts_post, program_id From 68127f03916a3e280573aa47b9e7d436f16a09e0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 21 Jul 2025 15:14:38 -0300 Subject: [PATCH 82/83] fix repeated accounts bug --- risc0-selective-privacy-poc/core/src/lib.rs | 18 ++++++++++++- .../program_methods/guest/src/bin/outer.rs | 25 ++++++++++--------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index dbd4b5b..853ab40 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -2,6 +2,8 @@ pub mod account; pub mod types; pub mod visibility; +use std::collections::HashSet; + use crate::{ account::Account, types::{AuthenticationPath, Commitment, Key, Nullifier, ProgramId}, @@ -59,7 +61,7 @@ pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { /// - does not change account addresses /// - does not change account nonces /// - does not change the `program_owner` field -/// - only reduces the balance of accounts it owns +/// - only reduces the balance of accounts it owns /// - preserves the total token supply across all accounts /// /// This function does **not** check that the output accounts are the result of correctly @@ -104,6 +106,20 @@ pub fn check_well_behaved_account_transition( if total_balance_pre != total_balance_post { return false; } + // The previous check is only meaningful if the accounts involved in the sum are all different + // otherwise the same balance is counted more than once in the total balance check. Therefore, we need to check + // that the set of input accounts doesn't have repeated pairs of (address, nonce). + // Note: Checking only that addresses are unique would be too restrictive, since different private accounts can have + // the same address (but different nonces). + if input_accounts + .iter() + .map(|account| (account.address, account.nonce)) + .collect::>() + .len() + != input_accounts.len() + { + return false; + } true } diff --git a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs index 2f4fcd7..bc65957 100644 --- a/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs +++ b/risc0-selective-privacy-poc/program_methods/guest/src/bin/outer.rs @@ -10,13 +10,10 @@ use risc0_zkvm::{guest::env, serde::to_vec}; /// It also verifies that the chain's invariants are not violated and the program is well-behaved. /// /// Inputs: -/// - ProgramOuptut: The output of the inner program. This is includes the accounts pre and -/// post-states of the execution of the inner program. +/// - ProgramOuptut: The output of the inner program. This is includes the accounts pre and post-states of the execution of the inner program. /// - Vec: A vector indicating which accounts are private and which are public. -/// - Vec: The vector of nonces to be used for the output accounts. This is assumed to be -/// sampled at random by the host program. -/// - [u32; 8]: The root of the commitment tree. Commitments of input private accounts will be -/// checked against this to prove that they belong to the tree. +/// - Vec: The vector of nonces to be used for the output accounts. This is assumed to be sampled at random by the host program. +/// - [u32; 8]: The root of the commitment tree. Commitments of input private accounts will be checked against this to prove that they belong to the tree. /// - ProgamId: The ID of the inner program. /// /// Public outputs: @@ -75,13 +72,18 @@ fn assemble_privacy_execution_output( commitment_tree_root: [u32; 8], nullifiers: Vec<[u32; 8]>, ) -> PrivacyExecutionOutput { - // Insert new nonces in outputs (including public ones) + // Insert new nonces in private outputs let accounts_pre = inner_program_output.accounts_pre; let mut accounts_post = inner_program_output.accounts_post; accounts_post .iter_mut() .zip(output_nonces) - .for_each(|(account, new_nonce)| account.nonce = new_nonce); + .zip(visibilities.iter()) + .for_each(|((account, new_nonce), visibility)| { + if matches!(visibility, AccountVisibility::Private(_)) { + account.nonce = new_nonce; + } + }); // Compute commitments for every private output let mut private_outputs = Vec::new(); @@ -110,20 +112,19 @@ fn assemble_privacy_execution_output( } } - let output = PrivacyExecutionOutput { + PrivacyExecutionOutput { public_accounts_pre, public_accounts_post, private_output_commitments, nullifiers, commitment_tree_root, - }; - output + } } /// Compute nullifiers of private accounts pre states and check that their commitments belong to the commitments tree fn verify_and_nullify_private_inputs( inner_program_output: &ProgramOutput, - account_visibilities: &Vec, + account_visibilities: &[AccountVisibility], commitment_tree_root: [u32; 8], ) -> Vec<[u32; 8]> { let mut nullifiers = Vec::new(); From 816959a8b12097bebe7a7943cff04523858f5ced Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 21 Jul 2025 15:21:20 -0300 Subject: [PATCH 83/83] clarify authentication check not included in the docstrings --- risc0-selective-privacy-poc/core/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/risc0-selective-privacy-poc/core/src/lib.rs b/risc0-selective-privacy-poc/core/src/lib.rs index 853ab40..bde57f9 100644 --- a/risc0-selective-privacy-poc/core/src/lib.rs +++ b/risc0-selective-privacy-poc/core/src/lib.rs @@ -61,7 +61,8 @@ pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] { /// - does not change account addresses /// - does not change account nonces /// - does not change the `program_owner` field -/// - only reduces the balance of accounts it owns +/// - only reduces the balance of accounts it owns and for which authentication was provided +/// (**NOTE**: this authentication check is **not** included in this proof of concept) /// - preserves the total token supply across all accounts /// /// This function does **not** check that the output accounts are the result of correctly