From b6bec23ac2138ec0de8ffd7f4aa54694a0705b2f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 16:45:07 -0300 Subject: [PATCH] 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: {:?}",