lssa/lee/state_machine/src/state/tests/privacy_preserving.rs

541 lines
18 KiB
Rust

use super::*;
#[test]
fn transition_from_privacy_preserving_transaction_shielded() {
let sender_keys = test_public_account_keys_1();
let recipient_keys = test_private_account_keys_1();
let mut state = V03State::new().with_public_accounts([(
sender_keys.account_id(),
Account {
program_owner: crate::test_methods::simple_balance_transfer().id(),
balance: 200,
..Account::default()
},
)]);
let balance_to_move = 37;
let tx =
shielded_balance_transfer_for_tests(&sender_keys, &recipient_keys, balance_to_move, &state);
let expected_sender_post = {
let mut this = state.get_account_by_id(sender_keys.account_id());
this.balance -= balance_to_move;
this.nonce.public_account_nonce_increment();
this
};
let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap();
assert!(!state.private_state.0.contains(&expected_new_commitment));
state
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap();
let sender_post = state.get_account_by_id(sender_keys.account_id());
assert_eq!(sender_post, expected_sender_post);
assert!(state.private_state.0.contains(&expected_new_commitment));
assert_eq!(
state.get_account_by_id(sender_keys.account_id()).balance,
200 - balance_to_move
);
}
#[test]
fn transition_from_privacy_preserving_transaction_private() {
let sender_keys = test_private_account_keys_1();
let sender_nonce = Nonce(0xdead_beef);
let sender_private_account = Account {
program_owner: crate::test_methods::simple_balance_transfer().id(),
balance: 100,
nonce: sender_nonce,
data: Data::default(),
};
let recipient_keys = test_private_account_keys_2();
let mut state = V03State::new().with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let tx = private_balance_transfer_for_tests(
&sender_keys,
&sender_private_account,
&recipient_keys,
balance_to_move,
&state,
);
let sender_account_id =
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let expected_new_commitment_1 = Commitment::new(
&sender_account_id,
&Account {
program_owner: crate::test_methods::simple_balance_transfer().id(),
nonce: sender_nonce.private_account_nonce_increment(&sender_keys.nsk),
balance: sender_private_account.balance - balance_to_move,
data: Data::default(),
},
);
let sender_pre_commitment = Commitment::new(&sender_account_id, &sender_private_account);
let expected_new_nullifier =
Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk);
let expected_new_commitment_2 = Commitment::new(
&recipient_account_id,
&Account {
program_owner: crate::test_methods::simple_balance_transfer().id(),
nonce: Nonce::private_account_nonce_init(&recipient_account_id),
balance: balance_to_move,
..Account::default()
},
);
let previous_public_state = state.public_state.clone();
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment_1));
assert!(!state.private_state.0.contains(&expected_new_commitment_2));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap();
assert_eq!(state.public_state, previous_public_state);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment_1));
assert!(state.private_state.0.contains(&expected_new_commitment_2));
assert!(state.private_state.1.contains(&expected_new_nullifier));
}
/// After a valid fully-private tx is proven, tampering with a note's epk should
/// make the shielding proof invalid.
#[test]
fn privacy_tampered_epk_is_rejected() {
use crate::validated_state_diff::ValidatedStateDiff;
let (state, mut tx) = valid_private_transfer_tx_and_state();
// Baseline: the untampered tx verifies
assert!(
ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0).is_ok(),
"the unmodified private transfer must verify"
);
// Flip a byte of the first note's epk
tx.message.encrypted_private_post_states[0].epk.0[0] ^= 0xFF;
assert!(
matches!(
ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0),
Err(LeeError::InvalidPrivacyPreservingProof)
),
"a tampered epk must be rejected by proof verification"
);
}
/// After a valid fully-private tx is proven, tampering with a note's view tag should
/// make the shielding proof invalid.
#[test]
fn privacy_tampered_view_tag_is_rejected() {
use crate::validated_state_diff::ValidatedStateDiff;
let (state, mut tx) = valid_private_transfer_tx_and_state();
// Baseline: the untampered tx verifies.
assert!(
ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0).is_ok(),
"the unmodified private transfer must verify"
);
// Flip the first note's view_tag
tx.message.encrypted_private_post_states[0].view_tag ^= 0xFF;
assert!(
matches!(
ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0),
Err(LeeError::InvalidPrivacyPreservingProof)
),
"a tampered view_tag must be rejected by proof verification"
);
}
#[test]
fn transition_from_privacy_preserving_transaction_deshielded() {
let sender_keys = test_private_account_keys_1();
let sender_nonce = Nonce(0xdead_beef);
let sender_private_account = Account {
program_owner: crate::test_methods::simple_balance_transfer().id(),
balance: 100,
nonce: sender_nonce,
data: Data::default(),
};
let recipient_keys = test_public_account_keys_1();
let recipient_initial_balance = 400;
let mut state = V03State::new()
.with_public_accounts([(
recipient_keys.account_id(),
Account {
program_owner: crate::test_methods::simple_balance_transfer().id(),
balance: recipient_initial_balance,
..Account::default()
},
)])
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let expected_recipient_post = {
let mut this = state.get_account_by_id(recipient_keys.account_id());
this.balance += balance_to_move;
this
};
let tx = deshielded_balance_transfer_for_tests(
&sender_keys,
&sender_private_account,
&recipient_keys.account_id(),
balance_to_move,
&state,
);
let sender_account_id =
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let expected_new_commitment = Commitment::new(
&sender_account_id,
&Account {
program_owner: crate::test_methods::simple_balance_transfer().id(),
nonce: sender_nonce.private_account_nonce_increment(&sender_keys.nsk),
balance: sender_private_account.balance - balance_to_move,
data: Data::default(),
},
);
let sender_pre_commitment = Commitment::new(&sender_account_id, &sender_private_account);
let expected_new_nullifier =
Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap();
let recipient_post = state.get_account_by_id(recipient_keys.account_id());
assert_eq!(recipient_post, expected_recipient_post);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment));
assert!(state.private_state.1.contains(&expected_new_nullifier));
assert_eq!(
state.get_account_by_id(recipient_keys.account_id()).balance,
recipient_initial_balance + balance_to_move
);
}
#[test]
fn burner_program_should_fail_in_privacy_preserving_circuit() {
let program = crate::test_methods::burner();
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(10_u128).unwrap(),
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn minter_program_should_fail_in_privacy_preserving_circuit() {
let program = crate::test_methods::minter();
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(10_u128).unwrap(),
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn nonce_changer_program_should_fail_in_privacy_preserving_circuit() {
let program = crate::test_methods::nonce_changer();
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() {
let program = crate::test_methods::data_changer();
let public_account = AccountWithMetadata::new(
Account {
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(vec![0]).unwrap(),
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn data_changer_program_should_fail_for_too_large_data_in_privacy_preserving_circuit() {
let program = crate::test_methods::data_changer();
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let large_data: Vec<u8> =
vec![
0;
usize::try_from(lee_core::account::data::DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize")
+ 1
];
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(large_data).unwrap(),
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::ProgramProveFailed(_))));
}
#[test]
fn extra_output_program_should_fail_in_privacy_preserving_circuit() {
let program = crate::test_methods::extra_output();
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn missing_output_program_should_fail_in_privacy_preserving_circuit() {
let program = crate::test_methods::missing_output();
let public_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
vec![public_account_1, public_account_2],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Public, InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn program_owner_changer_should_fail_in_privacy_preserving_circuit() {
let program = crate::test_methods::program_owner_changer();
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() {
let program = crate::test_methods::simple_balance_transfer();
let public_account_1 = AccountWithMetadata::new(
Account {
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 100,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
vec![public_account_1, public_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![InputAccountIdentity::Public, InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
// Arrange
let malicious_program = crate::test_methods::malicious_authorization_changer();
let simple_transfers = crate::test_methods::simple_balance_transfer();
let sender_keys = test_public_account_keys_1();
let recipient_keys = test_private_account_keys_1();
let sender_account = AccountWithMetadata::new(
Account {
program_owner: simple_transfers.id(),
balance: 100,
..Default::default()
},
false,
sender_keys.account_id(),
);
let recipient_account = AccountWithMetadata::new(
Account::default(),
true,
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
);
let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let recipient_commitment = Commitment::new(&recipient_account_id, &recipient_account.account);
let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id);
let state = V03State::new()
.with_public_accounts(public_state_from_balances(&[(
sender_account.account_id,
sender_account.account.balance,
)]))
.with_private_accounts([(recipient_commitment.clone(), recipient_init_nullifier)])
.with_test_programs();
let balance_to_transfer = 10_u128;
let instruction = (balance_to_transfer, simple_transfers.id());
let mut dependencies = HashMap::new();
dependencies.insert(simple_transfers.id(), simple_transfers);
let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies);
// Act - execute the malicious program - this should fail during proving
let result = execute_and_prove(
vec![sender_account, recipient_account],
Program::serialize_instruction(instruction).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: recipient_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&recipient_commitment)
.expect("recipient's commitment must be in state"),
identifier: 0,
},
],
&program_with_deps,
);
// Assert - should fail because the malicious program tries to manipulate is_authorized
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}