mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-05-21 09:09:28 +00:00
replace unit tests with integration tests
This commit is contained in:
parent
58226fd0f7
commit
0e177f1eba
@ -1,6 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use common::transaction::NSSATransaction;
|
||||
use integration_tests::{
|
||||
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, private_mention,
|
||||
public_mention, verify_commitment_is_in_state,
|
||||
@ -623,3 +624,130 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
|
||||
use nssa::{
|
||||
EphemeralPublicKey, SharedSecretKey, execute_and_prove,
|
||||
privacy_preserving_transaction::{self, circuit::ProgramWithDependencies},
|
||||
};
|
||||
use nssa_core::{InputAccountIdentity, account::AccountWithMetadata};
|
||||
|
||||
let ctx = TestContext::new().await?;
|
||||
|
||||
let binary = std::fs::read(
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../artifacts/test_program_methods/faucet_chain_caller.bin"),
|
||||
)?;
|
||||
let deploy_tx = NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(binary.clone()),
|
||||
));
|
||||
ctx.sequencer_client().send_transaction(deploy_tx).await?;
|
||||
|
||||
info!("Waiting for deploy block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let faucet_account_id = nssa::system_faucet_account_id();
|
||||
let attacker_id = ctx.existing_public_accounts()[0];
|
||||
let faucet_program_id = Program::faucet().id();
|
||||
let vault_program_id = Program::vault().id();
|
||||
let auth_transfer_program_id = Program::authenticated_transfer_program().id();
|
||||
let nsk: nssa_core::NullifierSecretKey = [3; 32];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let vpk = Secp256k1Point::from_scalar([4; 32]);
|
||||
let ssk = SharedSecretKey::new([55; 32], &vpk);
|
||||
let epk = EphemeralPublicKey::from_scalar([55; 32]);
|
||||
let attacker_vault_id = {
|
||||
let seed = vault_core::compute_vault_seed(attacker_id);
|
||||
AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337)
|
||||
};
|
||||
let amount: u128 = 1;
|
||||
|
||||
let faucet_pre = AccountWithMetadata::new(
|
||||
ctx.sequencer_client()
|
||||
.get_account(faucet_account_id)
|
||||
.await?,
|
||||
false,
|
||||
faucet_account_id,
|
||||
);
|
||||
let vault_pda_pre = AccountWithMetadata::new(
|
||||
ctx.sequencer_client()
|
||||
.get_account(attacker_vault_id)
|
||||
.await?,
|
||||
false,
|
||||
attacker_vault_id,
|
||||
);
|
||||
|
||||
let faucet_chain_caller = Program::new(binary)?;
|
||||
let program_with_deps = ProgramWithDependencies::new(
|
||||
faucet_chain_caller,
|
||||
[
|
||||
(faucet_program_id, Program::faucet()),
|
||||
(vault_program_id, Program::vault()),
|
||||
(
|
||||
auth_transfer_program_id,
|
||||
Program::authenticated_transfer_program(),
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
|
||||
let instruction =
|
||||
Program::serialize_instruction((faucet_program_id, vault_program_id, attacker_id, amount))?;
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![faucet_pre, vault_pda_pre],
|
||||
instruction,
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
npk,
|
||||
ssk,
|
||||
identifier: 1337,
|
||||
},
|
||||
],
|
||||
&program_with_deps,
|
||||
)?;
|
||||
|
||||
let message = privacy_preserving_transaction::Message::try_from_circuit_output(
|
||||
vec![faucet_account_id],
|
||||
vec![],
|
||||
vec![(npk, vpk, epk)],
|
||||
output,
|
||||
)?;
|
||||
let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
|
||||
let attack_ppt = NSSATransaction::PrivacyPreserving(nssa::PrivacyPreservingTransaction::new(
|
||||
message,
|
||||
witness_set,
|
||||
));
|
||||
|
||||
let faucet_balance_before = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(faucet_account_id)
|
||||
.await?;
|
||||
let vault_balance_before = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(attacker_vault_id)
|
||||
.await?;
|
||||
|
||||
let tx_hash = ctx.sequencer_client().send_transaction(attack_ppt).await?;
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let faucet_balance_after = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(faucet_account_id)
|
||||
.await?;
|
||||
let vault_balance_after = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(attacker_vault_id)
|
||||
.await?;
|
||||
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
|
||||
|
||||
assert_eq!(faucet_balance_after, faucet_balance_before);
|
||||
assert_eq!(vault_balance_after, vault_balance_before);
|
||||
assert!(tx_on_chain.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::time::Duration;
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use common::transaction::NSSATransaction;
|
||||
@ -492,3 +492,69 @@ async fn cannot_execute_faucet_program() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn user_tx_that_chain_calls_faucet_is_dropped() -> Result<()> {
|
||||
let ctx = TestContext::new().await?;
|
||||
|
||||
let binary = std::fs::read(
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../artifacts/test_program_methods/faucet_chain_caller.bin"),
|
||||
)?;
|
||||
let faucet_chain_caller_id = Program::new(binary.clone())?.id();
|
||||
let deploy_tx = NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(binary),
|
||||
));
|
||||
ctx.sequencer_client().send_transaction(deploy_tx).await?;
|
||||
|
||||
info!("Waiting for deploy block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let faucet_account_id = system_faucet_account_id();
|
||||
let attacker = ctx.existing_public_accounts()[0];
|
||||
let faucet_program_id = Program::faucet().id();
|
||||
let vault_program_id = Program::vault().id();
|
||||
let attacker_vault_id = vault_core::compute_vault_account_id(vault_program_id, attacker);
|
||||
let amount: u128 = 1;
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
faucet_chain_caller_id,
|
||||
vec![faucet_account_id, attacker_vault_id],
|
||||
vec![],
|
||||
(faucet_program_id, vault_program_id, attacker, amount),
|
||||
)?;
|
||||
let attack_tx = NSSATransaction::Public(nssa::PublicTransaction::new(
|
||||
message,
|
||||
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
||||
));
|
||||
|
||||
let faucet_balance_before = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(faucet_account_id)
|
||||
.await?;
|
||||
let vault_balance_before = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(attacker_vault_id)
|
||||
.await?;
|
||||
|
||||
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let faucet_balance_after = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(faucet_account_id)
|
||||
.await?;
|
||||
let vault_balance_after = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(attacker_vault_id)
|
||||
.await?;
|
||||
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
|
||||
|
||||
assert_eq!(faucet_balance_after, faucet_balance_before);
|
||||
assert_eq!(vault_balance_after, vault_balance_before);
|
||||
assert!(tx_on_chain.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -437,8 +437,6 @@ mod tests {
|
||||
};
|
||||
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
||||
use mempool::MemPoolHandle;
|
||||
use nssa::{EphemeralPublicKey, SharedSecretKey};
|
||||
use nssa_core::{NullifierPublicKey, account::AccountId, encryption::ViewingPublicKey};
|
||||
use tempfile::tempdir;
|
||||
use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys};
|
||||
|
||||
@ -1062,186 +1060,4 @@ mod tests {
|
||||
"Block production should abort when clock account data is corrupted"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_tx_that_chain_calls_faucet_is_dropped() {
|
||||
let (mut sequencer, mempool_handle) = common_setup().await;
|
||||
|
||||
// Deploy the faucet_chain_caller test program.
|
||||
let deploy_tx =
|
||||
NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(
|
||||
test_program_methods::FAUCET_CHAIN_CALLER_ELF.to_vec(),
|
||||
),
|
||||
));
|
||||
mempool_handle.push(deploy_tx).await.unwrap();
|
||||
sequencer.produce_new_block().await.unwrap();
|
||||
|
||||
// The attacker chain-calls the faucet through their own program:
|
||||
// faucet_chain_caller → faucet → vault → authenticated_transfer.
|
||||
// Funds from the system faucet would land in the attacker's vault PDA.
|
||||
let faucet_account_id = nssa::system_faucet_account_id();
|
||||
let attacker_id = initial_accounts()[0].account_id;
|
||||
let faucet_program_id = nssa::program::Program::faucet().id();
|
||||
let vault_program_id = nssa::program::Program::vault().id();
|
||||
let attacker_vault_id = vault_core::compute_vault_account_id(vault_program_id, attacker_id);
|
||||
let amount: u128 = 1;
|
||||
|
||||
let faucet_chain_caller_id =
|
||||
nssa::program::Program::new(test_program_methods::FAUCET_CHAIN_CALLER_ELF.to_vec())
|
||||
.unwrap()
|
||||
.id();
|
||||
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
faucet_chain_caller_id,
|
||||
vec![faucet_account_id, attacker_vault_id],
|
||||
vec![], // no signers — faucet PDA authorization is handled internally
|
||||
(faucet_program_id, vault_program_id, attacker_id, amount),
|
||||
)
|
||||
.unwrap();
|
||||
let attack_tx = NSSATransaction::Public(nssa::PublicTransaction::new(
|
||||
message,
|
||||
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
||||
));
|
||||
|
||||
let faucet_balance_before = sequencer.state.get_account_by_id(faucet_account_id).balance;
|
||||
let vault_balance_before = sequencer.state.get_account_by_id(attacker_vault_id).balance;
|
||||
|
||||
mempool_handle.push(attack_tx).await.unwrap();
|
||||
sequencer.produce_new_block().await.unwrap();
|
||||
|
||||
let faucet_balance_after = sequencer.state.get_account_by_id(faucet_account_id).balance;
|
||||
let vault_balance_after = sequencer.state.get_account_by_id(attacker_vault_id).balance;
|
||||
|
||||
assert_eq!(faucet_balance_after, faucet_balance_before);
|
||||
assert_eq!(vault_balance_after, vault_balance_before);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ppt_that_chain_calls_faucet_is_dropped() {
|
||||
use nssa::privacy_preserving_transaction::circuit::ProgramWithDependencies;
|
||||
use nssa_core::{InputAccountIdentity, account::AccountWithMetadata};
|
||||
|
||||
let (mut sequencer, mempool_handle) = common_setup().await;
|
||||
|
||||
// Deploy the faucet_chain_caller test program.
|
||||
let deploy_tx =
|
||||
NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(
|
||||
test_program_methods::FAUCET_CHAIN_CALLER_ELF.to_vec(),
|
||||
),
|
||||
));
|
||||
mempool_handle.push(deploy_tx).await.unwrap();
|
||||
sequencer.produce_new_block().await.unwrap();
|
||||
|
||||
// The attacker runs faucet_chain_caller inside a PPT circuit, producing a valid proof
|
||||
// that the faucet was drained into their vault PDA.
|
||||
let faucet_account_id = nssa::system_faucet_account_id();
|
||||
let attacker_id = initial_accounts()[0].account_id;
|
||||
let faucet_program_id = nssa::program::Program::faucet().id();
|
||||
let vault_program_id = nssa::program::Program::vault().id();
|
||||
let auth_transfer_program_id =
|
||||
nssa::program::Program::authenticated_transfer_program().id();
|
||||
let nsk = [3; 32];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let vsk = [4; 32];
|
||||
let vpk = ViewingPublicKey::from_scalar(vsk);
|
||||
let ssk = SharedSecretKey::new([55; 32], &vpk);
|
||||
let epk = EphemeralPublicKey::from_scalar([55; 32]);
|
||||
let attacker_vault_id = {
|
||||
let seed = vault_core::compute_vault_seed(attacker_id);
|
||||
AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337)
|
||||
};
|
||||
let amount: u128 = 1;
|
||||
|
||||
let faucet_pre = AccountWithMetadata::new(
|
||||
sequencer.state.get_account_by_id(faucet_account_id),
|
||||
false,
|
||||
faucet_account_id,
|
||||
);
|
||||
let vault_pda_pre = AccountWithMetadata::new(
|
||||
sequencer.state.get_account_by_id(attacker_vault_id),
|
||||
false,
|
||||
attacker_vault_id,
|
||||
);
|
||||
|
||||
let faucet_chain_caller =
|
||||
nssa::program::Program::new(test_program_methods::FAUCET_CHAIN_CALLER_ELF.to_vec())
|
||||
.unwrap();
|
||||
let program_with_deps = ProgramWithDependencies::new(
|
||||
faucet_chain_caller,
|
||||
[
|
||||
(faucet_program_id, nssa::program::Program::faucet()),
|
||||
(vault_program_id, nssa::program::Program::vault()),
|
||||
(
|
||||
auth_transfer_program_id,
|
||||
nssa::program::Program::authenticated_transfer_program(),
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
|
||||
let instruction = nssa::program::Program::serialize_instruction((
|
||||
faucet_program_id,
|
||||
vault_program_id,
|
||||
attacker_id,
|
||||
amount,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let (output, proof) = nssa::execute_and_prove(
|
||||
vec![faucet_pre, vault_pda_pre],
|
||||
instruction,
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
npk,
|
||||
ssk,
|
||||
identifier: 1337,
|
||||
},
|
||||
],
|
||||
&program_with_deps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = nssa::privacy_preserving_transaction::Message::try_from_circuit_output(
|
||||
vec![faucet_account_id],
|
||||
vec![], // no public signers
|
||||
vec![(npk, vpk, epk)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = nssa::privacy_preserving_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
proof,
|
||||
&[], // no signatures
|
||||
);
|
||||
let attack_ppt = NSSATransaction::PrivacyPreserving(
|
||||
nssa::PrivacyPreservingTransaction::new(message, witness_set),
|
||||
);
|
||||
|
||||
let faucet_balance_before = sequencer.state.get_account_by_id(faucet_account_id).balance;
|
||||
let vault_balance_before = sequencer.state.get_account_by_id(attacker_vault_id).balance;
|
||||
|
||||
mempool_handle.push(attack_ppt).await.unwrap();
|
||||
sequencer.produce_new_block().await.unwrap();
|
||||
|
||||
let block = sequencer
|
||||
.store
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let faucet_balance_after = sequencer.state.get_account_by_id(faucet_account_id).balance;
|
||||
let vault_balance_after = sequencer.state.get_account_by_id(attacker_vault_id).balance;
|
||||
|
||||
// The attack PPT must be dropped; only the mandatory clock invocation remains.
|
||||
assert_eq!(
|
||||
block.body.transactions,
|
||||
vec![NSSATransaction::Public(clock_invocation(
|
||||
block.header.timestamp
|
||||
))]
|
||||
);
|
||||
assert_eq!(faucet_balance_after, faucet_balance_before);
|
||||
assert_eq!(vault_balance_after, vault_balance_before);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user