mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-06-14 21:19:27 +00:00
feat(state): forbid private bridge withdrawals
This commit is contained in:
parent
9e3ec3ad0e
commit
63e4403cc5
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -9035,6 +9035,7 @@ dependencies = [
|
||||
"futures",
|
||||
"hex",
|
||||
"humantime-serde",
|
||||
"key_protocol",
|
||||
"lee",
|
||||
"lee_core",
|
||||
"log",
|
||||
|
||||
@ -52,6 +52,7 @@ impl std::fmt::Debug for Commitment {
|
||||
impl Commitment {
|
||||
/// Generates the commitment to a private account owned by user for `account_id`:
|
||||
/// SHA256( `Comm_DS` || `account_id` || `program_owner` || balance || nonce || SHA256(data)).
|
||||
// TODO: Accept account_id by value as it's Copy
|
||||
#[must_use]
|
||||
pub fn new(account_id: &AccountId, account: &Account) -> Self {
|
||||
const COMMITMENT_PREFIX: &[u8; 32] =
|
||||
|
||||
@ -97,6 +97,7 @@ impl Nullifier {
|
||||
}
|
||||
|
||||
/// Computes a nullifier for an account initialization.
|
||||
// TODO: Accept account_id by value as it's Copy
|
||||
#[must_use]
|
||||
pub fn for_account_initialization(account_id: &AccountId) -> Self {
|
||||
const INIT_PREFIX: &[u8; 32] = b"/LEE/v0.3/Nullifier/Initialize/\x00";
|
||||
|
||||
@ -4725,4 +4725,90 @@ pub mod tests {
|
||||
|
||||
assert_eq!(state.get_account_by_id(recipient_id).balance, amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_bridge_withdraw_invocation_is_dropped() {
|
||||
let sender_keys = test_private_account_keys_1();
|
||||
let sender_private_account = Account {
|
||||
// Keep sender private account owned by a non-authenticated-transfer program
|
||||
// so bridge::Withdraw is rejected.
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: Nonce(0xdead_beef),
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0)
|
||||
.with_private_account(&sender_keys, &sender_private_account);
|
||||
|
||||
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0);
|
||||
let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account);
|
||||
let bridge_account_id = system_bridge_account_id();
|
||||
|
||||
let sender_pre =
|
||||
AccountWithMetadata::new(sender_private_account, true, (&sender_keys.npk(), 0));
|
||||
let bridge_pre = AccountWithMetadata::new(
|
||||
state.get_account_by_id(bridge_account_id),
|
||||
false,
|
||||
bridge_account_id,
|
||||
);
|
||||
|
||||
let esk = [3; 32];
|
||||
let shared_secret = SharedSecretKey::new(esk, &sender_keys.vpk());
|
||||
|
||||
let instruction = Program::serialize_instruction(bridge_core::Instruction::Withdraw {
|
||||
amount: 1,
|
||||
bedrock_account_pk: [0; 32],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let program_with_deps = ProgramWithDependencies::new(
|
||||
Program::bridge(),
|
||||
[(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
Program::authenticated_transfer_program(),
|
||||
)]
|
||||
.into(),
|
||||
);
|
||||
|
||||
let bridge_nonce = bridge_pre.account.nonce;
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender_pre, bridge_pre],
|
||||
instruction,
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
ssk: shared_secret,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&sender_commitment)
|
||||
.expect("sender commitment must be in state"),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::Public,
|
||||
],
|
||||
&program_with_deps,
|
||||
)
|
||||
.expect("Execution should succeed");
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![bridge_account_id],
|
||||
vec![bridge_nonce],
|
||||
vec![(
|
||||
sender_keys.npk(),
|
||||
sender_keys.vpk(),
|
||||
EphemeralPublicKey::from_scalar(esk),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.expect("Message construction should succeed");
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
let res = state.transition_from_privacy_preserving_transaction(&tx, 1, 0);
|
||||
|
||||
assert!(
|
||||
res.is_err(),
|
||||
"Bridge withdraw invocation should be rejected in private execution"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,3 +47,4 @@ mock = []
|
||||
futures.workspace = true
|
||||
test_program_methods.workspace = true
|
||||
lee = { workspace = true, features = ["test-utils"] }
|
||||
key_protocol.workspace = true
|
||||
|
||||
@ -757,6 +757,20 @@ mod tests {
|
||||
test_utils::sequencer_sign_key_for_testing,
|
||||
transaction::{LeeTransaction, clock_invocation},
|
||||
};
|
||||
use key_protocol::key_management::KeyChain;
|
||||
use lee::{
|
||||
Account, AccountId, Data, EphemeralPublicKey, PrivacyPreservingTransaction,
|
||||
SharedSecretKey, V03State,
|
||||
error::LeeError,
|
||||
execute_and_prove,
|
||||
privacy_preserving_transaction::{Message, circuit::ProgramWithDependencies},
|
||||
program::Program,
|
||||
system_bridge_account_id,
|
||||
};
|
||||
use lee_core::{
|
||||
Commitment, InputAccountIdentity, Nullifier,
|
||||
account::{AccountWithMetadata, Nonce},
|
||||
};
|
||||
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
||||
use mempool::MemPoolHandle;
|
||||
use storage::sequencer::sequencer_cells::PendingDepositEventRecord;
|
||||
@ -1531,4 +1545,99 @@ mod tests {
|
||||
"Block production should abort when clock account data is corrupted"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_bridge_withdraw_invocation_is_dropped() {
|
||||
let sender_keys = KeyChain::new_os_random();
|
||||
let sender_account_id =
|
||||
AccountId::for_regular_private_account(&sender_keys.nullifier_public_key, 0);
|
||||
let sender_private_account = Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: Nonce(0xdead_beef),
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
let mut state = V03State::new_with_genesis_accounts(
|
||||
&[],
|
||||
vec![(
|
||||
Commitment::new(&sender_account_id, &sender_private_account),
|
||||
Nullifier::for_account_initialization(&sender_account_id),
|
||||
)],
|
||||
0,
|
||||
);
|
||||
|
||||
let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account);
|
||||
let bridge_account_id = system_bridge_account_id();
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
sender_private_account,
|
||||
true,
|
||||
(&sender_keys.nullifier_public_key, 0),
|
||||
);
|
||||
let bridge_pre = AccountWithMetadata::new(
|
||||
state.get_account_by_id(bridge_account_id),
|
||||
false,
|
||||
bridge_account_id,
|
||||
);
|
||||
|
||||
let shared_secret = SharedSecretKey::encapsulate(&sender_keys.viewing_public_key).0;
|
||||
|
||||
let instruction = Program::serialize_instruction(bridge_core::Instruction::Withdraw {
|
||||
amount: 1,
|
||||
bedrock_account_pk: [0; 32],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let program_with_deps = ProgramWithDependencies::new(
|
||||
Program::bridge(),
|
||||
[(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
Program::authenticated_transfer_program(),
|
||||
)]
|
||||
.into(),
|
||||
);
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender_pre, bridge_pre],
|
||||
instruction,
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
ssk: shared_secret,
|
||||
nsk: sender_keys.private_key_holder.nullifier_secret_key,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&sender_commitment)
|
||||
.expect("sender commitment must be in state"),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::Public,
|
||||
],
|
||||
&program_with_deps,
|
||||
)
|
||||
.expect("Execution should succeed");
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![bridge_account_id],
|
||||
vec![],
|
||||
vec![(
|
||||
sender_keys.nullifier_public_key,
|
||||
sender_keys.viewing_public_key,
|
||||
EphemeralPublicKey(vec![12_u8; 1088]),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.expect("Message construction should succeed");
|
||||
let witness_set =
|
||||
lee::privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = LeeTransaction::PrivacyPreserving(PrivacyPreservingTransaction::new(
|
||||
message,
|
||||
witness_set,
|
||||
));
|
||||
let res = tx.execute_check_on_state(&mut state, 1, 0);
|
||||
|
||||
assert!(
|
||||
matches!(res, Err(LeeError::InvalidInput(_))),
|
||||
"Bridge withdraw invocation should be rejected in private execution"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user