2025-11-20 01:40:05 -03:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
2025-11-19 09:00:32 +02:00
|
|
|
use borsh::{BorshDeserialize, BorshSerialize};
|
2025-08-22 08:32:05 -03:00
|
|
|
use nssa_core::{
|
2025-08-27 16:24:20 -03:00
|
|
|
MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
|
|
|
|
|
PrivacyPreservingCircuitOutput, SharedSecretKey,
|
2025-09-11 15:49:54 -03:00
|
|
|
account::AccountWithMetadata,
|
2025-11-20 01:40:05 -03:00
|
|
|
program::{InstructionData, ProgramId, ProgramOutput},
|
2025-08-22 08:32:05 -03:00
|
|
|
};
|
|
|
|
|
use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover};
|
|
|
|
|
|
2025-11-26 00:27:20 +03:00
|
|
|
use crate::{
|
|
|
|
|
error::NssaError,
|
|
|
|
|
program::Program,
|
|
|
|
|
program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID},
|
2025-11-26 16:37:04 -03:00
|
|
|
state::MAX_NUMBER_CHAINED_CALLS,
|
2025-11-26 00:27:20 +03:00
|
|
|
};
|
2025-08-22 08:32:05 -03:00
|
|
|
|
2025-08-27 18:23:56 -03:00
|
|
|
/// Proof of the privacy preserving execution circuit
|
2025-11-19 09:00:32 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
2025-09-12 15:06:49 +03:00
|
|
|
pub struct Proof(pub(crate) Vec<u8>);
|
2025-08-22 08:32:05 -03:00
|
|
|
|
2025-12-09 22:27:38 -03:00
|
|
|
#[derive(Clone)]
|
2025-11-22 16:39:34 -03:00
|
|
|
pub struct ProgramWithDependencies {
|
|
|
|
|
pub program: Program,
|
2025-11-22 18:39:02 -03:00
|
|
|
// TODO: avoid having a copy of the bytecode of each dependency.
|
2025-11-22 16:39:34 -03:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 18:23:56 -03:00
|
|
|
/// Generates a proof of the execution of a NSSA program inside the privacy preserving execution
|
|
|
|
|
/// circuit
|
2025-11-14 01:28:34 -03:00
|
|
|
#[expect(clippy::too_many_arguments, reason = "TODO: fix later")]
|
2025-08-22 08:32:05 -03:00
|
|
|
pub fn execute_and_prove(
|
|
|
|
|
pre_states: &[AccountWithMetadata],
|
|
|
|
|
instruction_data: &InstructionData,
|
|
|
|
|
visibility_mask: &[u8],
|
|
|
|
|
private_account_nonces: &[u128],
|
2025-08-27 18:23:56 -03:00
|
|
|
private_account_keys: &[(NullifierPublicKey, SharedSecretKey)],
|
2025-11-14 01:28:34 -03:00
|
|
|
private_account_nsks: &[NullifierSecretKey],
|
2025-12-12 16:53:30 +03:00
|
|
|
private_account_membership_proofs: &[Option<MembershipProof>],
|
2025-11-20 19:25:56 -03:00
|
|
|
program_with_dependencies: &ProgramWithDependencies,
|
2025-08-22 08:32:05 -03:00
|
|
|
) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> {
|
2025-11-20 19:25:56 -03:00
|
|
|
let mut program = &program_with_dependencies.program;
|
|
|
|
|
let dependencies = &program_with_dependencies.dependencies;
|
2025-11-20 01:40:05 -03:00
|
|
|
let mut instruction_data = instruction_data.clone();
|
|
|
|
|
let mut pre_states = pre_states.to_vec();
|
2025-11-06 19:35:47 -03:00
|
|
|
let mut env_builder = ExecutorEnv::builder();
|
|
|
|
|
let mut program_outputs = Vec::new();
|
2025-11-18 01:38:47 -03:00
|
|
|
|
2025-11-06 19:35:47 -03:00
|
|
|
for _i in 0..MAX_NUMBER_CHAINED_CALLS {
|
2025-11-22 16:39:56 -03:00
|
|
|
let inner_receipt = execute_and_prove_program(program, &pre_states, &instruction_data)?;
|
2025-08-22 08:32:05 -03:00
|
|
|
|
2025-11-06 19:35:47 -03:00
|
|
|
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);
|
|
|
|
|
|
2025-12-09 22:27:38 -03:00
|
|
|
// 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() {
|
2025-11-20 19:25:56 -03:00
|
|
|
program = dependencies
|
|
|
|
|
.get(&next_call.program_id)
|
|
|
|
|
.ok_or(NssaError::InvalidProgramBehavior)?;
|
2025-11-20 01:40:05 -03:00
|
|
|
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();
|
2025-12-09 22:27:38 -03:00
|
|
|
post_with_metadata.account = post.account().clone();
|
2025-11-20 01:40:05 -03:00
|
|
|
post_states_with_metadata.push(post_with_metadata);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-09 22:27:38 -03:00
|
|
|
pre_states = next_call.pre_states.clone();
|
2025-11-20 01:40:05 -03:00
|
|
|
} else {
|
2025-11-06 19:35:47 -03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-22 08:32:05 -03:00
|
|
|
|
|
|
|
|
let circuit_input = PrivacyPreservingCircuitInput {
|
2025-11-06 19:35:47 -03:00
|
|
|
program_outputs,
|
2025-08-22 08:32:05 -03:00
|
|
|
visibility_mask: visibility_mask.to_vec(),
|
|
|
|
|
private_account_nonces: private_account_nonces.to_vec(),
|
|
|
|
|
private_account_keys: private_account_keys.to_vec(),
|
2025-11-14 01:28:34 -03:00
|
|
|
private_account_nsks: private_account_nsks.to_vec(),
|
|
|
|
|
private_account_membership_proofs: private_account_membership_proofs.to_vec(),
|
2025-11-20 19:25:56 -03:00
|
|
|
program_id: program_with_dependencies.program.id(),
|
2025-08-22 08:32:05 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
env_builder.write(&circuit_input).unwrap();
|
|
|
|
|
let env = env_builder.build().unwrap();
|
|
|
|
|
let prover = default_prover();
|
2025-09-02 12:38:31 -03:00
|
|
|
let prove_info = prover
|
|
|
|
|
.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF)
|
|
|
|
|
.map_err(|e| NssaError::CircuitProvingError(e.to_string()))?;
|
2025-08-22 08:32:05 -03:00
|
|
|
|
|
|
|
|
let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?);
|
|
|
|
|
|
|
|
|
|
let circuit_output: PrivacyPreservingCircuitOutput = prove_info
|
|
|
|
|
.receipt
|
|
|
|
|
.journal
|
|
|
|
|
.decode()
|
|
|
|
|
.map_err(|e| NssaError::CircuitOutputDeserializationError(e.to_string()))?;
|
|
|
|
|
|
|
|
|
|
Ok((circuit_output, proof))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn execute_and_prove_program(
|
|
|
|
|
program: &Program,
|
|
|
|
|
pre_states: &[AccountWithMetadata],
|
|
|
|
|
instruction_data: &InstructionData,
|
|
|
|
|
) -> Result<Receipt, NssaError> {
|
|
|
|
|
// Write inputs to the program
|
|
|
|
|
let mut env_builder = ExecutorEnv::builder();
|
2025-09-10 18:56:34 -03:00
|
|
|
Program::write_inputs(pre_states, instruction_data, &mut env_builder)?;
|
2025-08-22 08:32:05 -03:00
|
|
|
let env = env_builder.build().unwrap();
|
|
|
|
|
|
|
|
|
|
// Prove the program
|
|
|
|
|
let prover = default_prover();
|
|
|
|
|
Ok(prover
|
|
|
|
|
.prove(env, program.elf())
|
|
|
|
|
.map_err(|e| NssaError::ProgramProveFailed(e.to_string()))?
|
|
|
|
|
.receipt)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-15 10:39:57 +03:00
|
|
|
impl Proof {
|
|
|
|
|
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
|
|
|
|
|
let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap();
|
|
|
|
|
let receipt = Receipt::new(inner, circuit_output.to_bytes());
|
|
|
|
|
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 08:32:05 -03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use nssa_core::{
|
2025-10-03 20:44:29 -03:00
|
|
|
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier,
|
2025-12-05 02:17:09 +03:00
|
|
|
account::{Account, AccountId, AccountWithMetadata, data::Data},
|
2025-08-22 08:32:05 -03:00
|
|
|
};
|
|
|
|
|
|
2025-11-26 00:27:20 +03:00
|
|
|
use super::*;
|
2025-08-22 08:32:05 -03:00
|
|
|
use crate::{
|
2025-08-27 16:24:20 -03:00
|
|
|
privacy_preserving_transaction::circuit::execute_and_prove,
|
2025-08-22 08:32:05 -03:00
|
|
|
program::Program,
|
2025-08-22 13:42:37 -03:00
|
|
|
state::{
|
|
|
|
|
CommitmentSet,
|
|
|
|
|
tests::{test_private_account_keys_1, test_private_account_keys_2},
|
|
|
|
|
},
|
2025-08-22 08:32:05 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() {
|
2025-09-10 18:56:34 -03:00
|
|
|
let recipient_keys = test_private_account_keys_1();
|
2025-08-22 14:59:57 -03:00
|
|
|
let program = Program::authenticated_transfer_program();
|
2025-09-11 16:37:28 -03:00
|
|
|
let sender = AccountWithMetadata::new(
|
|
|
|
|
Account {
|
2025-09-02 12:56:01 -03:00
|
|
|
program_owner: program.id(),
|
2025-08-22 08:32:05 -03:00
|
|
|
balance: 100,
|
|
|
|
|
..Account::default()
|
|
|
|
|
},
|
2025-09-11 16:37:28 -03:00
|
|
|
true,
|
2025-09-12 09:18:40 -03:00
|
|
|
AccountId::new([0; 32]),
|
2025-09-11 16:37:28 -03:00
|
|
|
);
|
2025-08-22 08:32:05 -03:00
|
|
|
|
2025-09-11 16:37:28 -03:00
|
|
|
let recipient = AccountWithMetadata::new(
|
|
|
|
|
Account::default(),
|
|
|
|
|
false,
|
2025-09-12 09:18:40 -03:00
|
|
|
AccountId::from(&recipient_keys.npk()),
|
2025-09-11 16:37:28 -03:00
|
|
|
);
|
2025-08-22 08:32:05 -03:00
|
|
|
|
|
|
|
|
let balance_to_move: u128 = 37;
|
|
|
|
|
|
|
|
|
|
let expected_sender_post = Account {
|
2025-08-22 14:59:57 -03:00
|
|
|
program_owner: program.id(),
|
2025-08-22 08:32:05 -03:00
|
|
|
balance: 100 - balance_to_move,
|
2025-12-11 08:59:28 -05:00
|
|
|
nonce: 0,
|
2025-12-05 02:17:09 +03:00
|
|
|
data: Data::default(),
|
2025-08-22 14:59:57 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let expected_recipient_post = Account {
|
|
|
|
|
program_owner: program.id(),
|
|
|
|
|
balance: balance_to_move,
|
|
|
|
|
nonce: 0xdeadbeef,
|
2025-12-05 02:17:09 +03:00
|
|
|
data: Data::default(),
|
2025-08-22 08:32:05 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let expected_sender_pre = sender.clone();
|
2025-08-25 14:51:46 -03:00
|
|
|
|
|
|
|
|
let esk = [3; 32];
|
2025-08-26 14:53:02 -03:00
|
|
|
let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk());
|
2025-08-25 14:51:46 -03:00
|
|
|
|
2025-08-22 08:32:05 -03:00
|
|
|
let (output, proof) = execute_and_prove(
|
|
|
|
|
&[sender, recipient],
|
|
|
|
|
&Program::serialize_instruction(balance_to_move).unwrap(),
|
|
|
|
|
&[0, 2],
|
|
|
|
|
&[0xdeadbeef],
|
2025-08-26 14:53:02 -03:00
|
|
|
&[(recipient_keys.npk(), shared_secret.clone())],
|
2025-08-22 08:32:05 -03:00
|
|
|
&[],
|
2025-12-12 16:53:30 +03:00
|
|
|
&[None],
|
2025-11-20 19:25:56 -03:00
|
|
|
&Program::authenticated_transfer_program().into(),
|
2025-08-22 08:32:05 -03:00
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(proof.is_valid_for(&output));
|
|
|
|
|
|
|
|
|
|
let [sender_pre] = output.public_pre_states.try_into().unwrap();
|
|
|
|
|
let [sender_post] = output.public_post_states.try_into().unwrap();
|
|
|
|
|
assert_eq!(sender_pre, expected_sender_pre);
|
|
|
|
|
assert_eq!(sender_post, expected_sender_post);
|
|
|
|
|
assert_eq!(output.new_commitments.len(), 1);
|
2025-10-03 20:44:29 -03:00
|
|
|
assert_eq!(output.new_nullifiers.len(), 1);
|
2025-08-25 14:51:46 -03:00
|
|
|
assert_eq!(output.ciphertexts.len(), 1);
|
2025-08-22 14:59:57 -03:00
|
|
|
|
2025-08-26 14:53:02 -03:00
|
|
|
let recipient_post = EncryptionScheme::decrypt(
|
|
|
|
|
&output.ciphertexts[0],
|
|
|
|
|
&shared_secret,
|
|
|
|
|
&output.new_commitments[0],
|
|
|
|
|
0,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-08-22 14:59:57 -03:00
|
|
|
assert_eq!(recipient_post, expected_recipient_post);
|
2025-08-22 08:32:05 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn prove_privacy_preserving_execution_circuit_fully_private() {
|
2025-09-02 12:56:01 -03:00
|
|
|
let program = Program::authenticated_transfer_program();
|
2025-09-10 18:56:34 -03:00
|
|
|
let sender_keys = test_private_account_keys_1();
|
|
|
|
|
let recipient_keys = test_private_account_keys_2();
|
|
|
|
|
|
2025-09-11 16:37:28 -03:00
|
|
|
let sender_pre = AccountWithMetadata::new(
|
|
|
|
|
Account {
|
2025-08-22 08:32:05 -03:00
|
|
|
balance: 100,
|
|
|
|
|
nonce: 0xdeadbeef,
|
2025-09-02 12:56:01 -03:00
|
|
|
program_owner: program.id(),
|
2025-12-05 02:17:09 +03:00
|
|
|
data: Data::default(),
|
2025-08-22 08:32:05 -03:00
|
|
|
},
|
2025-09-11 16:37:28 -03:00
|
|
|
true,
|
2025-09-12 09:18:40 -03:00
|
|
|
AccountId::from(&sender_keys.npk()),
|
2025-09-11 16:37:28 -03:00
|
|
|
);
|
2025-08-22 13:42:37 -03:00
|
|
|
let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account);
|
2025-08-25 07:44:56 -03:00
|
|
|
|
2025-09-11 16:37:28 -03:00
|
|
|
let recipient = AccountWithMetadata::new(
|
|
|
|
|
Account::default(),
|
|
|
|
|
false,
|
2025-09-12 09:18:40 -03:00
|
|
|
AccountId::from(&recipient_keys.npk()),
|
2025-09-11 16:37:28 -03:00
|
|
|
);
|
2025-08-22 08:32:05 -03:00
|
|
|
let balance_to_move: u128 = 37;
|
|
|
|
|
|
2025-08-25 09:22:59 -03:00
|
|
|
let mut commitment_set = CommitmentSet::with_capacity(2);
|
2025-08-27 16:24:20 -03:00
|
|
|
commitment_set.extend(std::slice::from_ref(&commitment_sender));
|
2025-08-25 09:22:59 -03:00
|
|
|
|
2025-10-03 20:44:29 -03:00
|
|
|
let expected_new_nullifiers = vec![
|
|
|
|
|
(
|
|
|
|
|
Nullifier::for_account_update(&commitment_sender, &sender_keys.nsk),
|
|
|
|
|
commitment_set.digest(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
Nullifier::for_account_initialization(&recipient_keys.npk()),
|
|
|
|
|
DUMMY_COMMITMENT_HASH,
|
|
|
|
|
),
|
|
|
|
|
];
|
2025-08-25 07:44:56 -03:00
|
|
|
|
2025-08-22 08:32:05 -03:00
|
|
|
let program = Program::authenticated_transfer_program();
|
|
|
|
|
|
|
|
|
|
let expected_private_account_1 = Account {
|
|
|
|
|
program_owner: program.id(),
|
|
|
|
|
balance: 100 - balance_to_move,
|
|
|
|
|
nonce: 0xdeadbeef1,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
let expected_private_account_2 = Account {
|
|
|
|
|
program_owner: program.id(),
|
|
|
|
|
balance: balance_to_move,
|
|
|
|
|
nonce: 0xdeadbeef2,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
let expected_new_commitments = vec![
|
2025-08-22 13:42:37 -03:00
|
|
|
Commitment::new(&sender_keys.npk(), &expected_private_account_1),
|
|
|
|
|
Commitment::new(&recipient_keys.npk(), &expected_private_account_2),
|
2025-08-22 08:32:05 -03:00
|
|
|
];
|
2025-08-25 07:44:56 -03:00
|
|
|
|
2025-08-25 14:51:46 -03:00
|
|
|
let esk_1 = [3; 32];
|
2025-08-26 14:53:02 -03:00
|
|
|
let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk());
|
2025-08-25 14:51:46 -03:00
|
|
|
|
|
|
|
|
let esk_2 = [5; 32];
|
2025-08-26 14:53:02 -03:00
|
|
|
let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk());
|
2025-08-25 14:51:46 -03:00
|
|
|
|
2025-08-22 08:32:05 -03:00
|
|
|
let (output, proof) = execute_and_prove(
|
2025-08-22 13:42:37 -03:00
|
|
|
&[sender_pre.clone(), recipient],
|
2025-08-22 08:32:05 -03:00
|
|
|
&Program::serialize_instruction(balance_to_move).unwrap(),
|
|
|
|
|
&[1, 2],
|
|
|
|
|
&[0xdeadbeef1, 0xdeadbeef2],
|
|
|
|
|
&[
|
2025-08-26 14:53:02 -03:00
|
|
|
(sender_keys.npk(), shared_secret_1.clone()),
|
|
|
|
|
(recipient_keys.npk(), shared_secret_2.clone()),
|
2025-08-22 08:32:05 -03:00
|
|
|
],
|
2025-11-14 01:28:34 -03:00
|
|
|
&[sender_keys.nsk],
|
2025-12-12 16:53:30 +03:00
|
|
|
&[commitment_set.get_proof_for(&commitment_sender), None],
|
2025-11-20 19:25:56 -03:00
|
|
|
&program.into(),
|
2025-08-22 08:32:05 -03:00
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(proof.is_valid_for(&output));
|
|
|
|
|
assert!(output.public_pre_states.is_empty());
|
|
|
|
|
assert!(output.public_post_states.is_empty());
|
|
|
|
|
assert_eq!(output.new_commitments, expected_new_commitments);
|
|
|
|
|
assert_eq!(output.new_nullifiers, expected_new_nullifiers);
|
2025-08-25 14:51:46 -03:00
|
|
|
assert_eq!(output.ciphertexts.len(), 2);
|
2025-08-22 14:59:57 -03:00
|
|
|
|
2025-08-26 14:53:02 -03:00
|
|
|
let sender_post = EncryptionScheme::decrypt(
|
|
|
|
|
&output.ciphertexts[0],
|
|
|
|
|
&shared_secret_1,
|
|
|
|
|
&expected_new_commitments[0],
|
|
|
|
|
0,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-08-26 13:50:52 -03:00
|
|
|
assert_eq!(sender_post, expected_private_account_1);
|
2025-08-22 14:59:57 -03:00
|
|
|
|
2025-08-26 14:53:02 -03:00
|
|
|
let recipient_post = EncryptionScheme::decrypt(
|
|
|
|
|
&output.ciphertexts[1],
|
|
|
|
|
&shared_secret_2,
|
|
|
|
|
&expected_new_commitments[1],
|
|
|
|
|
1,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-08-26 13:50:52 -03:00
|
|
|
assert_eq!(recipient_post, expected_private_account_2);
|
2025-08-22 08:32:05 -03:00
|
|
|
}
|
|
|
|
|
}
|