diff --git a/integration_tests/data_changer.bin b/integration_tests/data_changer.bin index 3d062c30..eb28a627 100644 Binary files a/integration_tests/data_changer.bin and b/integration_tests/data_changer.bin differ diff --git a/integration_tests/src/tps_test_utils.rs b/integration_tests/src/tps_test_utils.rs index 29462f66..6f597e21 100644 --- a/integration_tests/src/tps_test_utils.rs +++ b/integration_tests/src/tps_test_utils.rs @@ -168,7 +168,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { (recipient_npk.clone(), recipient_ss), ], &[(sender_nsk, proof)], - &program, + &program.into(), ) .unwrap(); let message = pptx::message::Message::try_from_circuit_output( diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 89bec37a..c152581d 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -25,8 +25,8 @@ pub struct Account { pub nonce: Nonce, } -#[derive(Serialize, Deserialize, Clone)] -#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 5bf620e4..848fe3e6 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -10,7 +10,7 @@ use crate::{ #[derive(Serialize, Deserialize)] pub struct PrivacyPreservingCircuitInput { - pub program_output: ProgramOutput, + pub program_outputs: Vec, pub visibility_mask: Vec, pub private_account_nonces: Vec, pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 8f497249..26ee8deb 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; @@ -8,6 +10,7 @@ use crate::account::{Account, AccountWithMetadata}; pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; +pub const MAX_NUMBER_CHAINED_CALLS: usize = 10; pub struct ProgramInput { pub pre_states: Vec, @@ -54,7 +57,9 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId { #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ChainedCall { + /// The program ID of the program to execute pub program_id: ProgramId, + /// The instruction data to pass pub instruction_data: InstructionData, pub pre_states: Vec, pub pda_seeds: Vec, @@ -111,26 +116,34 @@ impl AccountPostState { #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ProgramOutput { + /// The instruction data the program received to produce this output + pub instruction_data: InstructionData, + /// The account pre states the program received to produce this output pub pre_states: Vec, pub post_states: Vec, pub chained_calls: Vec, } -pub fn read_nssa_inputs() -> ProgramInput { +pub fn read_nssa_inputs() -> (ProgramInput, InstructionData) { let pre_states: Vec = env::read(); let instruction_words: InstructionData = env::read(); let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap(); - ProgramInput { - pre_states, - instruction, - } + ( + ProgramInput { + pre_states, + instruction, + }, + instruction_words, + ) } pub fn write_nssa_outputs( + instruction_data: InstructionData, pre_states: Vec, post_states: Vec, ) { let output = ProgramOutput { + instruction_data, pre_states, post_states, chained_calls: Vec::new(), @@ -139,11 +152,13 @@ pub fn write_nssa_outputs( } pub fn write_nssa_outputs_with_chained_call( + instruction_data: InstructionData, pre_states: Vec, post_states: Vec, chained_calls: Vec, ) { let output = ProgramOutput { + instruction_data, pre_states, post_states, chained_calls, @@ -162,32 +177,37 @@ pub fn validate_execution( post_states: &[AccountPostState], executing_program_id: ProgramId, ) -> bool { - // 1. Lengths must match + // 1. Check account ids are all different + if !validate_uniqueness_of_account_ids(pre_states) { + return false; + } + + // 2. Lengths must match if pre_states.len() != post_states.len() { return false; } for (pre, post) in pre_states.iter().zip(post_states) { - // 2. Nonce must remain unchanged + // 3. Nonce must remain unchanged if pre.account.nonce != post.account.nonce { return false; } - // 3. Program ownership changes are not allowed + // 4. Program ownership changes are not allowed if pre.account.program_owner != post.account.program_owner { return false; } let account_program_owner = pre.account.program_owner; - // 4. Decreasing balance only allowed if owned by executing program + // 5. Decreasing balance only allowed if owned by executing program if post.account.balance < pre.account.balance && account_program_owner != executing_program_id { return false; } - // 5. Data changes only allowed if owned by executing program or if account pre state has + // 6. Data changes only allowed if owned by executing program or if account pre state has // default values if pre.account.data != post.account.data && pre.account != Account::default() @@ -196,14 +216,14 @@ pub fn validate_execution( return false; } - // 6. If a post state has default program owner, the pre state must have been a default + // 7. If a post state has default program owner, the pre state must have been a default // account if post.account.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() { return false; } } - // 7. Total balance is preserved + // 8. Total balance is preserved let Some(total_balance_pre_states) = WrappedBalanceSum::from_balances(pre_states.iter().map(|pre| pre.account.balance)) @@ -224,6 +244,17 @@ pub fn validate_execution( true } +fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> bool { + let number_of_accounts = pre_states.len(); + let number_of_account_ids = pre_states + .iter() + .map(|account| &account.account_id) + .collect::>() + .len(); + + number_of_accounts == number_of_account_ids +} + /// Representation of a number as `lo + hi * 2^128`. #[derive(PartialEq, Eq)] struct WrappedBalanceSum { diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 50afa507..fe02d065 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -6,34 +6,37 @@ use nssa_core::{ }; /// Initializes a default account under the ownership of this program. -fn initialize_account(pre_state: AccountWithMetadata) { +fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState { let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone()); let is_authorized = pre_state.is_authorized; // Continue only if the account to claim has default values if account_to_claim.account() != &Account::default() { - return; + panic!("Account must be uninitialized"); } // Continue only if the owner authorized this operation if !is_authorized { - return; + panic!("Invalid input"); } - // Noop will result in account being claimed for this program - write_nssa_outputs(vec![pre_state], vec![account_to_claim]); + account_to_claim } /// Transfers `balance_to_move` native balance from `sender` to `recipient`. -fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) { +fn transfer( + sender: AccountWithMetadata, + recipient: AccountWithMetadata, + balance_to_move: u128, +) -> Vec { // Continue only if the sender has authorized this operation if !sender.is_authorized { - return; + panic!("Invalid input"); } // Continue only if the sender has enough balance if sender.account.balance < balance_to_move { - return; + panic!("Invalid input"); } // Create accounts post states, with updated balances @@ -57,23 +60,31 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance } }; - write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]); + vec![sender_post, recipient_post] } /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { // Read input accounts. - let ProgramInput { - pre_states, - instruction: balance_to_move, - } = read_nssa_inputs(); + let ( + ProgramInput { + pre_states, + instruction: balance_to_move, + }, + instruction_words, + ) = read_nssa_inputs(); - match (pre_states.as_slice(), balance_to_move) { - ([account_to_claim], 0) => initialize_account(account_to_claim.clone()), + let post_states = match (pre_states.as_slice(), balance_to_move) { + ([account_to_claim], 0) => { + let post = initialize_account(account_to_claim.clone()); + vec![post] + } ([sender, recipient], balance_to_move) => { transfer(sender.clone(), recipient.clone(), balance_to_move) } _ => panic!("invalid params"), - } + }; + + write_nssa_outputs(instruction_words, pre_states, post_states); } diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs index 1c880e26..a0c46a1a 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -44,10 +44,13 @@ impl Challenge { fn main() { // Read input accounts. // It is expected to receive only two accounts: [pinata_account, winner_account] - let ProgramInput { - pre_states, - instruction: solution, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: solution, + }, + instruction_words, + ) = read_nssa_inputs::(); let [pinata, winner] = match pre_states.try_into() { Ok(array) => array, @@ -71,6 +74,7 @@ fn main() { winner_post.balance += PRIZE; write_nssa_outputs( + instruction_words, vec![pinata, winner], vec![ AccountPostState::new(pinata_post), diff --git a/nssa/program_methods/guest/src/bin/pinata_token.rs b/nssa/program_methods/guest/src/bin/pinata_token.rs index 38104855..f988be9e 100644 --- a/nssa/program_methods/guest/src/bin/pinata_token.rs +++ b/nssa/program_methods/guest/src/bin/pinata_token.rs @@ -54,10 +54,13 @@ fn main() { // Read input accounts. // It is expected to receive three accounts: [pinata_definition, pinata_token_holding, // winner_token_holding] - let ProgramInput { - pre_states, - instruction: solution, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: solution, + }, + instruction_words, + ) = read_nssa_inputs::(); let [ pinata_definition, @@ -98,6 +101,7 @@ fn main() { }]; write_nssa_outputs_with_chained_call( + instruction_words, vec![ pinata_definition, pinata_token_holding, diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index ac4e2127..4cbc42cc 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::HashMap; use nssa_core::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, @@ -6,43 +6,114 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, compute_digest_for_path, encryption::Ciphertext, - program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution}, + program::{DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, validate_execution}, }; use risc0_zkvm::{guest::env, serde::to_vec}; fn main() { let PrivacyPreservingCircuitInput { - program_output, + program_outputs, visibility_mask, private_account_nonces, private_account_keys, private_account_auth, - program_id, + mut program_id, } = env::read(); - // Check that `program_output` is consistent with the execution of the corresponding program. - env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap(); + let mut pre_states: Vec = Vec::new(); + let mut state_diff: HashMap = HashMap::new(); - let ProgramOutput { - pre_states, - post_states, - chained_calls, - } = program_output; - - // TODO: implement chained calls for privacy preserving transactions - if !chained_calls.is_empty() { - panic!("Privacy preserving transactions do not support yet chained calls.") + let num_calls = program_outputs.len(); + if num_calls > MAX_NUMBER_CHAINED_CALLS { + panic!("Max chained calls depth is exceeded"); } - // Check that there are no repeated account ids - if !validate_uniqueness_of_account_ids(&pre_states) { - panic!("Repeated account ids found") + let Some(last_program_call) = program_outputs.last() else { + panic!("Program outputs is empty") + }; + + if !last_program_call.chained_calls.is_empty() { + panic!("Call stack is incomplete"); } - // Check that the program is well behaved. - // See the # Programs section for the definition of the `validate_execution` method. - if !validate_execution(&pre_states, &post_states, program_id) { - panic!("Bad behaved program"); + for window in program_outputs.windows(2) { + let caller = &window[0]; + let callee = &window[1]; + + if caller.chained_calls.len() > 1 { + panic!("Privacy Multi-chained calls are not supported yet"); + } + + // TODO: Modify when multi-chain calls are supported in the circuit + let Some(caller_chained_call) = &caller.chained_calls.first() else { + panic!("Expected chained call"); + }; + + // Check that instruction data in caller is the instruction data in callee + if caller_chained_call.instruction_data != callee.instruction_data { + panic!("Invalid instruction data"); + } + + // Check that account pre_states in caller are the ones in calle + if caller_chained_call.pre_states != callee.pre_states { + panic!("Invalid pre states"); + } + } + + for (i, program_output) in program_outputs.iter().enumerate() { + let mut program_output = program_output.clone(); + + // Check that `program_output` is consistent with the execution of the corresponding program. + let program_output_words = + &to_vec(&program_output).expect("program_output must be serializable"); + env::verify(program_id, program_output_words) + .expect("program output must match the program's execution"); + + // Check that the program is well behaved. + // See the # Programs section for the definition of the `validate_execution` method. + if !validate_execution( + &program_output.pre_states, + &program_output.post_states, + program_id, + ) { + panic!("Bad behaved program"); + } + + // The invoked program claims the accounts with default program id. + for post in program_output + .post_states + .iter_mut() + .filter(|post| post.requires_claim()) + { + // The invoked program can only claim accounts with default program id. + if post.account().program_owner == DEFAULT_PROGRAM_ID { + post.account_mut().program_owner = program_id; + } else { + panic!("Cannot claim an initialized account") + } + } + + for (pre, post) in program_output + .pre_states + .iter() + .zip(&program_output.post_states) + { + if let Some(account_pre) = state_diff.get(&pre.account_id) { + if account_pre != &pre.account { + panic!("Invalid input"); + } + } else { + pre_states.push(pre.clone()); + } + state_diff.insert(pre.account_id.clone(), post.account().clone()); + } + + // TODO: Modify when multi-chain calls are supported in the circuit + if let Some(next_chained_call) = &program_output.chained_calls.first() { + program_id = next_chained_call.program_id; + } else if i != program_outputs.len() - 1 { + panic!("Inner call without a chained call found") + }; } let n_accounts = pre_states.len(); @@ -69,7 +140,7 @@ fn main() { // Public account public_pre_states.push(pre_states[i].clone()); - let mut post = post_states[i].account().clone(); + let mut post = state_diff.get(&pre_states[i].account_id).unwrap().clone(); if pre_states[i].is_authorized { post.nonce += 1; } @@ -125,7 +196,8 @@ fn main() { } // Update post-state with new nonce - let mut post_with_updated_values = post_states[i].account().clone(); + let mut post_with_updated_values = + state_diff.get(&pre_states[i].account_id).unwrap().clone(); post_with_updated_values.nonce = *new_nonce; if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { @@ -174,14 +246,3 @@ fn main() { env::commit(&output); } - -fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> bool { - let number_of_accounts = pre_states.len(); - let number_of_account_ids = pre_states - .iter() - .map(|account| account.account_id.clone()) - .collect::>() - .len(); - - number_of_accounts == number_of_account_ids -} diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 614b5d94..ffe8eb70 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -249,10 +249,13 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec(); + let ( + ProgramInput { + pre_states, + instruction, + }, + instruction_words, + ) = read_nssa_inputs::(); let post_states = match instruction[0] { 0 => { @@ -295,7 +298,7 @@ fn main() { _ => panic!("Invalid instruction"), }; - write_nssa_outputs(pre_states, post_states); + write_nssa_outputs(instruction_words, pre_states, post_states); } #[cfg(test)] diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 4ef02b3c..d6fc2c90 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,9 +1,11 @@ +use std::collections::HashMap; + use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::AccountWithMetadata, - program::{InstructionData, ProgramOutput}, + program::{InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -11,12 +13,35 @@ use crate::{ error::NssaError, program::Program, program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}, + state::MAX_NUMBER_CHAINED_CALLS, }; /// Proof of the privacy preserving execution circuit #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Proof(pub(crate) Vec); +#[derive(Clone)] +pub struct ProgramWithDependencies { + pub program: Program, + // TODO: avoid having a copy of the bytecode of each dependency. + pub dependencies: HashMap, +} + +impl ProgramWithDependencies { + pub fn new(program: Program, dependencies: HashMap) -> Self { + Self { + program, + dependencies, + } + } +} + +impl From for ProgramWithDependencies { + fn from(program: Program) -> Self { + ProgramWithDependencies::new(program, HashMap::new()) + } +} + /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// circuit pub fn execute_and_prove( @@ -26,27 +51,64 @@ pub fn execute_and_prove( private_account_nonces: &[u128], private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], private_account_auth: &[(NullifierSecretKey, MembershipProof)], - program: &Program, + program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { - let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; + let mut program = &program_with_dependencies.program; + let dependencies = &program_with_dependencies.dependencies; + let mut instruction_data = instruction_data.clone(); + let mut pre_states = pre_states.to_vec(); + let mut env_builder = ExecutorEnv::builder(); + let mut program_outputs = Vec::new(); - let program_output: ProgramOutput = inner_receipt - .journal - .decode() - .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; + for _i in 0..MAX_NUMBER_CHAINED_CALLS { + let inner_receipt = execute_and_prove_program(program, &pre_states, &instruction_data)?; + + let program_output: ProgramOutput = inner_receipt + .journal + .decode() + .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; + + // TODO: remove clone + program_outputs.push(program_output.clone()); + + // Prove circuit. + env_builder.add_assumption(inner_receipt); + + // TODO: Remove when multi-chain calls are supported in the circuit + assert!(program_output.chained_calls.len() <= 1); + // TODO: Modify when multi-chain calls are supported in the circuit + if let Some(next_call) = program_output.chained_calls.first() { + program = dependencies + .get(&next_call.program_id) + .ok_or(NssaError::InvalidProgramBehavior)?; + instruction_data = next_call.instruction_data.clone(); + // Build post states with metadata for next call + let mut post_states_with_metadata = Vec::new(); + for (pre, post) in program_output + .pre_states + .iter() + .zip(program_output.post_states) + { + let mut post_with_metadata = pre.clone(); + post_with_metadata.account = post.account().clone(); + post_states_with_metadata.push(post_with_metadata); + } + + pre_states = next_call.pre_states.clone(); + } else { + break; + } + } let circuit_input = PrivacyPreservingCircuitInput { - program_output, + program_outputs, visibility_mask: visibility_mask.to_vec(), private_account_nonces: private_account_nonces.to_vec(), private_account_keys: private_account_keys.to_vec(), private_account_auth: private_account_auth.to_vec(), - program_id: program.id(), + program_id: program_with_dependencies.program.id(), }; - // Prove circuit. - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); env_builder.write(&circuit_input).unwrap(); let env = env_builder.build().unwrap(); let prover = default_prover(); @@ -156,7 +218,7 @@ mod tests { &[0xdeadbeef], &[(recipient_keys.npk(), shared_secret.clone())], &[], - &Program::authenticated_transfer_program(), + &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -257,7 +319,7 @@ mod tests { sender_keys.nsk, commitment_set.get_proof_for(&commitment_sender).unwrap(), )], - &program, + &program.into(), ) .unwrap(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 89c3ed3d..1865248a 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -14,7 +14,7 @@ use crate::{ /// TODO: Make this variable when fees are implemented const MAX_NUM_CYCLES_PUBLIC_EXECUTION: u64 = 1024 * 1024 * 32; // 32M cycles -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Program { id: ProgramId, elf: Vec, diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 72efbd24..c3bbc3df 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -272,7 +272,10 @@ pub mod tests { error::NssaError, execute_and_prove, privacy_preserving_transaction::{ - PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet, + PrivacyPreservingTransaction, + circuit::{self, ProgramWithDependencies}, + message::Message, + witness_set::WitnessSet, }, program::Program, public_transaction, @@ -859,7 +862,7 @@ pub mod tests { &[0xdeadbeef], &[(recipient_keys.npk(), shared_secret)], &[], - &Program::authenticated_transfer_program(), + &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -911,7 +914,7 @@ pub mod tests { sender_keys.nsk, state.get_proof_for_commitment(&sender_commitment).unwrap(), )], - &program, + &program.into(), ) .unwrap(); @@ -963,7 +966,7 @@ pub mod tests { sender_keys.nsk, state.get_proof_for_commitment(&sender_commitment).unwrap(), )], - &program, + &program.into(), ) .unwrap(); @@ -1176,7 +1179,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1202,7 +1205,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1228,7 +1231,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1254,7 +1257,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1282,7 +1285,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.to_owned().into(), ); assert!(matches!(result, Err(NssaError::ProgramProveFailed(_)))); @@ -1308,7 +1311,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1343,7 +1346,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1369,7 +1372,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1404,7 +1407,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1441,7 +1444,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1482,7 +1485,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1516,7 +1519,7 @@ pub mod tests { &[0xdeadbeef1, 0xdeadbeef2], &private_account_keys, &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1557,7 +1560,7 @@ pub mod tests { ), ], &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1605,7 +1608,7 @@ pub mod tests { &[0xdeadbeef1, 0xdeadbeef2], &private_account_keys, &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1651,7 +1654,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1698,7 +1701,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1744,7 +1747,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1790,7 +1793,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1834,7 +1837,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1863,7 +1866,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1905,7 +1908,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1951,7 +1954,7 @@ pub mod tests { &[0xdeadbeef1, 0xdeadbeef2], &private_account_keys, &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1997,7 +2000,7 @@ pub mod tests { ), ], &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -2088,7 +2091,7 @@ pub mod tests { (sender_keys.npk(), shared_secret), ], &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -2131,7 +2134,7 @@ pub mod tests { } #[test] - fn test_chained_call_succeeds() { + fn test_public_chained_call() { let program = Program::chain_caller(); let key = PrivateKey::try_new([1; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&key)); @@ -2310,6 +2313,128 @@ pub mod tests { assert_eq!(to_post, expected_to_post); } + #[test] + fn test_private_chained_call() { + // Arrange + let chain_caller = Program::chain_caller(); + let auth_transfers = Program::authenticated_transfer_program(); + let from_keys = test_private_account_keys_1(); + let to_keys = test_private_account_keys_2(); + let initial_balance = 100; + let from_account = AccountWithMetadata::new( + Account { + program_owner: auth_transfers.id(), + balance: initial_balance, + ..Account::default() + }, + true, + &from_keys.npk(), + ); + let to_account = AccountWithMetadata::new( + Account { + program_owner: auth_transfers.id(), + ..Account::default() + }, + true, + &to_keys.npk(), + ); + + let from_commitment = Commitment::new(&from_keys.npk(), &from_account.account); + let to_commitment = Commitment::new(&to_keys.npk(), &to_account.account); + let mut state = V02State::new_with_genesis_accounts( + &[], + &[from_commitment.clone(), to_commitment.clone()], + ) + .with_test_programs(); + let amount: u128 = 37; + let instruction: (u128, ProgramId, u32, Option) = ( + amount, + Program::authenticated_transfer_program().id(), + 1, + None, + ); + + let from_esk = [3; 32]; + let from_ss = SharedSecretKey::new(&from_esk, &from_keys.ivk()); + let from_epk = EphemeralPublicKey::from_scalar(from_esk); + + let to_esk = [3; 32]; + let to_ss = SharedSecretKey::new(&to_esk, &to_keys.ivk()); + let to_epk = EphemeralPublicKey::from_scalar(to_esk); + + let mut dependencies = HashMap::new(); + + dependencies.insert(auth_transfers.id(), auth_transfers); + let program_with_deps = ProgramWithDependencies::new(chain_caller, dependencies); + + let from_new_nonce = 0xdeadbeef1; + let to_new_nonce = 0xdeadbeef2; + + let from_expected_post = Account { + balance: initial_balance - amount, + nonce: from_new_nonce, + ..from_account.account.clone() + }; + let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post); + + let to_expected_post = Account { + balance: amount, + nonce: to_new_nonce, + ..to_account.account.clone() + }; + let to_expected_commitment = Commitment::new(&to_keys.npk(), &to_expected_post); + + // Act + let (output, proof) = execute_and_prove( + &[to_account, from_account], + &Program::serialize_instruction(instruction).unwrap(), + &[1, 1], + &[from_new_nonce, to_new_nonce], + &[(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], + &[ + ( + from_keys.nsk, + state.get_proof_for_commitment(&from_commitment).unwrap(), + ), + ( + to_keys.nsk, + state.get_proof_for_commitment(&to_commitment).unwrap(), + ), + ], + &program_with_deps, + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + (to_keys.npk(), to_keys.ivk(), to_epk), + (from_keys.npk(), from_keys.ivk(), from_epk), + ], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let transaction = PrivacyPreservingTransaction::new(message, witness_set); + + state + .transition_from_privacy_preserving_transaction(&transaction) + .unwrap(); + + // Assert + assert!( + state + .get_proof_for_commitment(&from_expected_commitment) + .is_some() + ); + assert!( + state + .get_proof_for_commitment(&to_expected_commitment) + .is_some() + ); + } + #[test] fn test_pda_mechanism_with_pinata_token_program() { let pinata_token = Program::pinata_token(); diff --git a/nssa/test_program_methods/guest/src/bin/burner.rs b/nssa/test_program_methods/guest/src/bin/burner.rs index 01b46b25..5deef7cc 100644 --- a/nssa/test_program_methods/guest/src/bin/burner.rs +++ b/nssa/test_program_methods/guest/src/bin/burner.rs @@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = u128; fn main() { - let ProgramInput { - pre_states, - instruction: balance_to_burn, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: balance_to_burn, + }, + instruction_words, + ) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -17,5 +20,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance -= balance_to_burn; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index ee01ffa5..f2d3cb6f 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -10,10 +10,13 @@ type Instruction = (u128, ProgramId, u32, Option); /// It permutes the order of the input accounts on the subsequent call /// The `ProgramId` in the instruction must be the program_id of the authenticated transfers program fn main() { - let ProgramInput { - pre_states, + let ( + ProgramInput { + pre_states, instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed), - } = read_nssa_inputs::(); + }, + instruction_words + ) = read_nssa_inputs::(); let [recipient_pre, sender_pre] = match pre_states.try_into() { Ok(array) => array, @@ -44,6 +47,7 @@ fn main() { } write_nssa_outputs_with_chained_call( + instruction_words, vec![sender_pre.clone(), recipient_pre.clone()], vec![ AccountPostState::new(sender_pre.account), diff --git a/nssa/test_program_methods/guest/src/bin/claimer.rs b/nssa/test_program_methods/guest/src/bin/claimer.rs index 7687e5af..8687704b 100644 --- a/nssa/test_program_methods/guest/src/bin/claimer.rs +++ b/nssa/test_program_methods/guest/src/bin/claimer.rs @@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = (); fn main() { - let ProgramInput { - pre_states, - instruction: _, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: _, + }, + instruction_words, + ) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -15,5 +18,5 @@ fn main() { let account_post = AccountPostState::new_claimed(pre.account.clone()); - write_nssa_outputs(vec![pre], vec![account_post]); + write_nssa_outputs(instruction_words, vec![pre], vec![account_post]); } diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index b5908864..91544404 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -4,7 +4,7 @@ type Instruction = Vec; /// A program that modifies the account data by setting bytes sent in instruction. fn main() { - let ProgramInput { pre_states, instruction: data } = read_nssa_inputs::(); + let (ProgramInput { pre_states, instruction: data }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -15,5 +15,9 @@ fn main() { let mut account_post = account_pre.clone(); account_post.data = data.try_into().expect("provided data should fit into data limit"); - write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]); + write_nssa_outputs( + instruction_words, + vec![pre], + vec![AccountPostState::new_claimed(account_post)], + ); } diff --git a/nssa/test_program_methods/guest/src/bin/extra_output.rs b/nssa/test_program_methods/guest/src/bin/extra_output.rs index 71372627..4950f14a 100644 --- a/nssa/test_program_methods/guest/src/bin/extra_output.rs +++ b/nssa/test_program_methods/guest/src/bin/extra_output.rs @@ -6,7 +6,7 @@ use nssa_core::{ type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -16,6 +16,7 @@ fn main() { let account_pre = pre.account.clone(); write_nssa_outputs( + instruction_words, vec![pre], vec![ AccountPostState::new(account_pre), diff --git a/nssa/test_program_methods/guest/src/bin/minter.rs b/nssa/test_program_methods/guest/src/bin/minter.rs index 5f697722..51baa5ec 100644 --- a/nssa/test_program_methods/guest/src/bin/minter.rs +++ b/nssa/test_program_methods/guest/src/bin/minter.rs @@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance += 1; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/nssa/test_program_methods/guest/src/bin/missing_output.rs index f7d78be2..7b910c69 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -3,7 +3,7 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre1, pre2] = match pre_states.try_into() { Ok(array) => array, @@ -12,5 +12,9 @@ fn main() { let account_pre1 = pre1.account.clone(); - write_nssa_outputs(vec![pre1, pre2], vec![AccountPostState::new(account_pre1)]); + write_nssa_outputs( + instruction_words, + vec![pre1, pre2], + vec![AccountPostState::new(account_pre1)], + ); } diff --git a/nssa/test_program_methods/guest/src/bin/modified_transfer.rs b/nssa/test_program_methods/guest/src/bin/modified_transfer.rs index 0f85e539..dd93e836 100644 --- a/nssa/test_program_methods/guest/src/bin/modified_transfer.rs +++ b/nssa/test_program_methods/guest/src/bin/modified_transfer.rs @@ -5,32 +5,32 @@ use nssa_core::{ /// Initializes a default account under the ownership of this program. /// This is achieved by a noop. -fn initialize_account(pre_state: AccountWithMetadata) { +fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState { let account_to_claim = pre_state.account.clone(); let is_authorized = pre_state.is_authorized; // Continue only if the account to claim has default values if account_to_claim != Account::default() { - return; + panic!("Account is already initialized"); } // Continue only if the owner authorized this operation if !is_authorized { - return; + panic!("Missing required authorization"); } - // Noop will result in account being claimed for this program - write_nssa_outputs( - vec![pre_state], - vec![AccountPostState::new(account_to_claim)], - ); + AccountPostState::new(account_to_claim) } /// Transfers `balance_to_move` native balance from `sender` to `recipient`. -fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) { +fn transfer( + sender: AccountWithMetadata, + recipient: AccountWithMetadata, + balance_to_move: u128, +) -> Vec { // Continue only if the sender has authorized this operation if !sender.is_authorized { - return; + panic!("Missing required authorization"); } // This segment is a safe protection from authenticated transfer program @@ -50,29 +50,33 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance sender_post.balance -= balance_to_move + malicious_offset; recipient_post.balance += balance_to_move + malicious_offset; - write_nssa_outputs( - vec![sender, recipient], - vec![ - AccountPostState::new(sender_post), - AccountPostState::new(recipient_post), - ], - ); + vec![ + AccountPostState::new(sender_post), + AccountPostState::new(recipient_post), + ] } /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { // Read input accounts. - let ProgramInput { - pre_states, - instruction: balance_to_move, - } = read_nssa_inputs(); + let ( + ProgramInput { + pre_states, + instruction: balance_to_move, + }, + instruction_data, + ) = read_nssa_inputs(); - match (pre_states.as_slice(), balance_to_move) { - ([account_to_claim], 0) => initialize_account(account_to_claim.clone()), + let post_states = match (pre_states.as_slice(), balance_to_move) { + ([account_to_claim], 0) => { + let post = initialize_account(account_to_claim.clone()); + vec![post] + } ([sender, recipient], balance_to_move) => { transfer(sender.clone(), recipient.clone(), balance_to_move) } _ => panic!("invalid params"), - } + }; + write_nssa_outputs(instruction_data, pre_states, post_states); } diff --git a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs index fc245720..4ca6c734 100644 --- a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs @@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. } , instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.nonce += 1; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words ,vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs index 2fa54002..2b212c13 100644 --- a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7]; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs index be56e165..e1dbc1b7 100644 --- a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs @@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = u128; fn main() { - let ProgramInput { - pre_states, - instruction: balance, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: balance, + }, + instruction_words, + ) = read_nssa_inputs::(); let [sender_pre, receiver_pre] = match pre_states.try_into() { Ok(array) => array, @@ -19,6 +22,7 @@ fn main() { receiver_post.balance += balance; write_nssa_outputs( + instruction_words, vec![sender_pre, receiver_pre], vec![ AccountPostState::new(sender_post), diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 313fdcd4..bc283117 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -283,7 +283,7 @@ impl WalletCore { .map(|keys| (keys.npk.clone(), keys.ssk.clone())) .collect::>(), &acc_manager.private_account_auth(), - program, + &program.to_owned().into(), ) .unwrap(); diff --git a/wallet/src/pinata_interactions.rs b/wallet/src/pinata_interactions.rs new file mode 100644 index 00000000..65a67b78 --- /dev/null +++ b/wallet/src/pinata_interactions.rs @@ -0,0 +1,161 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{AccountId, privacy_preserving_transaction::circuit}; +use nssa_core::{MembershipProof, SharedSecretKey, account::AccountWithMetadata}; + +use crate::{ + WalletCore, helperfunctions::produce_random_nonces, transaction_utils::AccountPreparedData, +}; + +impl WalletCore { + pub async fn claim_pinata( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result { + let account_ids = vec![pinata_account_id, winner_account_id]; + let program_id = nssa::program::Program::pinata().id(); + let message = + nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn claim_pinata_private_owned_account_already_initialized( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + winner_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: winner_nsk, + npk: winner_npk, + ipk: winner_ipk, + auth_acc: winner_pre, + proof: _, + } = self + .private_acc_preparation(winner_account_id, true, false) + .await?; + + let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); + + let program = nssa::program::Program::pinata(); + + let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); + + let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); + let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[pinata_pre, winner_pre], + &nssa::program::Program::serialize_instruction(solution).unwrap(), + &[0, 1], + &produce_random_nonces(1), + &[(winner_npk.clone(), shared_secret_winner.clone())], + &[(winner_nsk.unwrap(), winner_proof)], + &program.into(), + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![pinata_account_id], + vec![], + vec![( + winner_npk.clone(), + winner_ipk.clone(), + eph_holder_winner.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( + message, + witness_set, + ); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_winner], + )) + } + + pub async fn claim_pinata_private_owned_account_not_initialized( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: _, + npk: winner_npk, + ipk: winner_ipk, + auth_acc: winner_pre, + proof: _, + } = self + .private_acc_preparation(winner_account_id, false, false) + .await?; + + let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); + + let program = nssa::program::Program::pinata(); + + let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); + + let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); + let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[pinata_pre, winner_pre], + &nssa::program::Program::serialize_instruction(solution).unwrap(), + &[0, 2], + &produce_random_nonces(1), + &[(winner_npk.clone(), shared_secret_winner.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![pinata_account_id], + vec![], + vec![( + winner_npk.clone(), + winner_ipk.clone(), + eph_holder_winner.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( + message, + witness_set, + ); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_winner], + )) + } +} diff --git a/wallet/src/transaction_utils.rs b/wallet/src/transaction_utils.rs new file mode 100644 index 00000000..123e49d3 --- /dev/null +++ b/wallet/src/transaction_utils.rs @@ -0,0 +1,592 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{ + Account, AccountId, PrivacyPreservingTransaction, + privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet}, + program::Program, +}; +use nssa_core::{ + Commitment, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + account::AccountWithMetadata, encryption::IncomingViewingPublicKey, program::InstructionData, +}; + +use crate::{WalletCore, helperfunctions::produce_random_nonces}; + +pub(crate) struct AccountPreparedData { + pub nsk: Option, + pub npk: NullifierPublicKey, + pub ipk: IncomingViewingPublicKey, + pub auth_acc: AccountWithMetadata, + pub proof: Option, +} + +impl WalletCore { + pub(crate) async fn private_acc_preparation( + &self, + account_id: AccountId, + is_authorized: bool, + needs_proof: bool, + ) -> Result { + let Some((from_keys, from_acc)) = self + .storage + .user_data + .get_private_account(&account_id) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let mut nsk = None; + let mut proof = None; + + let from_npk = from_keys.nullifer_public_key; + let from_ipk = from_keys.incoming_viewing_public_key; + + let sender_commitment = Commitment::new(&from_npk, &from_acc); + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk); + + if is_authorized { + nsk = Some(from_keys.private_key_holder.nullifier_secret_key); + } + + if needs_proof { + proof = self + .sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap(); + } + + Ok(AccountPreparedData { + nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof, + }) + } + + pub(crate) async fn private_tx_two_accs_all_init( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + to_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let AccountPreparedData { + nsk: to_nsk, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, true, false).await?; + + tx_pre_check(&sender_pre.account, &recipient_pre.account)?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); + + let eph_holder_to = EphemeralKeyHolder::new(&to_npk); + let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 1], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[ + (from_nsk.unwrap(), from_proof.unwrap()), + (to_nsk.unwrap(), to_proof), + ], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk.clone(), + from_ipk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder_to.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn private_tx_two_accs_receiver_uninit( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let AccountPreparedData { + nsk: _, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, false, false).await?; + + tx_pre_check(&sender_pre.account, &recipient_pre.account)?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); + + let eph_holder_to = EphemeralKeyHolder::new(&to_npk); + let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk.clone(), + from_ipk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder_to.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn private_tx_two_accs_receiver_outer( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_ipk: IncomingViewingPublicKey, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let to_acc = nssa_core::account::Account::default(); + + tx_pre_check(&sender_pre.account, &to_acc)?; + + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + + let shared_secret_from = eph_holder.calculate_shared_secret_sender(&from_ipk); + let shared_secret_to = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk.clone(), + from_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn deshielded_tx_two_accs( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let Ok(to_acc) = self.get_account_public(to).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + tx_pre_check(&sender_pre.account, &to_acc)?; + + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, to); + + let eph_holder = EphemeralKeyHolder::new(&from_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&from_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 0], + &produce_random_nonces(1), + &[(from_npk.clone(), shared_secret.clone())], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![to], + vec![], + vec![( + from_npk.clone(), + from_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_all_init( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + to_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let AccountPreparedData { + nsk: to_nsk, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, true, false).await?; + + tx_pre_check(&from_acc, &recipient_pre.account)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 1], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[(to_nsk.unwrap(), to_proof)], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_receiver_uninit( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let AccountPreparedData { + nsk: _, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, false, false).await?; + + tx_pre_check(&from_acc, &recipient_pre.account)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 2], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_receiver_outer( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_ipk: IncomingViewingPublicKey, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let to_acc = Account::default(); + + tx_pre_check(&from_acc, &to_acc)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 2], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_private(tx).await?) + } + + pub async fn register_account_under_authenticated_transfers_programs_private( + &self, + from: AccountId, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: _, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: _, + } = self.private_acc_preparation(from, false, false).await?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); + + let instruction: u128 = 0; + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[2], + &produce_random_nonces(1), + &[(from_npk.clone(), shared_secret_from.clone())], + &[], + &Program::authenticated_transfer_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![( + from_npk.clone(), + from_ipk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from], + )) + } +}