Merge pull request #151 from logos-blockchain/schouhy/implement-privacy-preserving-tail-calls

Implement privacy preserving tail calls
This commit is contained in:
Sergio Chouhy 2025-12-11 08:08:23 -03:00 committed by GitHub
commit abb1fc5e45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1258 additions and 177 deletions

Binary file not shown.

View File

@ -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(

View File

@ -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,

View File

@ -10,7 +10,7 @@ use crate::{
#[derive(Serialize, Deserialize)]
pub struct PrivacyPreservingCircuitInput {
pub program_output: ProgramOutput,
pub program_outputs: Vec<ProgramOutput>,
pub visibility_mask: Vec<u8>,
pub private_account_nonces: Vec<Nonce>,
pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>,

View File

@ -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<u32>;
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
pub struct ProgramInput<T> {
pub pre_states: Vec<AccountWithMetadata>,
@ -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<AccountWithMetadata>,
pub pda_seeds: Vec<PdaSeed>,
@ -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<AccountWithMetadata>,
pub post_states: Vec<AccountPostState>,
pub chained_calls: Vec<ChainedCall>,
}
pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<T> {
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionData) {
let pre_states: Vec<AccountWithMetadata> = 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<AccountWithMetadata>,
post_states: Vec<AccountPostState>,
) {
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<AccountWithMetadata>,
post_states: Vec<AccountPostState>,
chained_calls: Vec<ChainedCall>,
) {
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::<HashSet<_>>()
.len();
number_of_accounts == number_of_account_ids
}
/// Representation of a number as `lo + hi * 2^128`.
#[derive(PartialEq, Eq)]
struct WrappedBalanceSum {

View File

@ -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<AccountPostState> {
// 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);
}

View File

@ -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::<Instruction>();
let (
ProgramInput {
pre_states,
instruction: solution,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
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),

View File

@ -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::<Instruction>();
let (
ProgramInput {
pre_states,
instruction: solution,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
let [
pinata_definition,
@ -98,6 +101,7 @@ fn main() {
}];
write_nssa_outputs_with_chained_call(
instruction_words,
vec![
pinata_definition,
pinata_token_holding,

View File

@ -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<AccountWithMetadata> = Vec::new();
let mut state_diff: HashMap<AccountId, Account> = 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::<HashSet<_>>()
.len();
number_of_accounts == number_of_account_ids
}

View File

@ -249,10 +249,13 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<AccountPostStat
type Instruction = [u8; 23];
fn main() {
let ProgramInput {
pre_states,
instruction,
} = read_nssa_inputs::<Instruction>();
let (
ProgramInput {
pre_states,
instruction,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
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)]

View File

@ -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<u8>);
#[derive(Clone)]
pub struct ProgramWithDependencies {
pub program: Program,
// TODO: avoid having a copy of the bytecode of each dependency.
pub dependencies: HashMap<ProgramId, Program>,
}
impl ProgramWithDependencies {
pub fn new(program: Program, dependencies: HashMap<ProgramId, Program>) -> Self {
Self {
program,
dependencies,
}
}
}
impl From<Program> 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();

View File

@ -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<u8>,

View File

@ -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<PdaSeed>) = (
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();

View File

@ -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::<Instruction>();
let (
ProgramInput {
pre_states,
instruction: balance_to_burn,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
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)]);
}

View File

@ -10,10 +10,13 @@ type Instruction = (u128, ProgramId, u32, Option<PdaSeed>);
/// 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>();
},
instruction_words
) = read_nssa_inputs::<Instruction>();
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),

View File

@ -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::<Instruction>();
let (
ProgramInput {
pre_states,
instruction: _,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
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]);
}

View File

@ -4,7 +4,7 @@ type Instruction = Vec<u8>;
/// A program that modifies the account data by setting bytes sent in instruction.
fn main() {
let ProgramInput { pre_states, instruction: data } = read_nssa_inputs::<Instruction>();
let (ProgramInput { pre_states, instruction: data }, instruction_words) = read_nssa_inputs::<Instruction>();
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)],
);
}

View File

@ -6,7 +6,7 @@ use nssa_core::{
type Instruction = ();
fn main() {
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
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),

View File

@ -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::<Instruction>();
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
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)]);
}

View File

@ -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::<Instruction>();
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
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)],
);
}

View File

@ -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<AccountPostState> {
// 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);
}

View File

@ -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::<Instruction>();
let (ProgramInput { pre_states, .. } , instruction_words) = read_nssa_inputs::<Instruction>();
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)]);
}

View File

@ -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::<Instruction>();
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
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)]);
}

View File

@ -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::<Instruction>();
let (
ProgramInput {
pre_states,
instruction: balance,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
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),

View File

@ -283,7 +283,7 @@ impl WalletCore {
.map(|keys| (keys.npk.clone(), keys.ssk.clone()))
.collect::<Vec<_>>(),
&acc_manager.private_account_auth(),
program,
&program.to_owned().into(),
)
.unwrap();

View File

@ -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<SendTxResponse, ExecutionFailureKind> {
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],
))
}
}

View File

@ -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<NullifierSecretKey>,
pub npk: NullifierPublicKey,
pub ipk: IncomingViewingPublicKey,
pub auth_acc: AccountWithMetadata,
pub proof: Option<MembershipProof>,
}
impl WalletCore {
pub(crate) async fn private_acc_preparation(
&self,
account_id: AccountId,
is_authorized: bool,
needs_proof: bool,
) -> Result<AccountPreparedData, ExecutionFailureKind> {
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<SendTxResponse, ExecutionFailureKind> {
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],
))
}
}