mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-12 19:19:30 +00:00
add tests
This commit is contained in:
parent
11949e9fa1
commit
1599fc655c
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/test_program_methods/auth_transfer_proxy.bin
Normal file
BIN
artifacts/test_program_methods/auth_transfer_proxy.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -26,6 +26,7 @@ pub mod test_context_ffi;
|
||||
pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12;
|
||||
pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin";
|
||||
pub const NSSA_PROGRAM_FOR_TEST_NOOP: &str = "noop.bin";
|
||||
pub const NSSA_PROGRAM_FOR_TEST_AUTH_TRANSFER_PROXY: &str = "auth_transfer_proxy.bin";
|
||||
|
||||
const BEDROCK_SERVICE_WITH_OPEN_PORT: &str = "logos-blockchain-node-0";
|
||||
const BEDROCK_SERVICE_PORT: u16 = 18080;
|
||||
|
||||
291
integration_tests/tests/private_pda.rs
Normal file
291
integration_tests/tests/private_pda.rs
Normal file
@ -0,0 +1,291 @@
|
||||
#![expect(
|
||||
clippy::tests_outside_test_module,
|
||||
reason = "We don't care about these in tests"
|
||||
)]
|
||||
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use integration_tests::{
|
||||
NSSA_PROGRAM_FOR_TEST_AUTH_TRANSFER_PROXY, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
|
||||
verify_commitment_is_in_state,
|
||||
};
|
||||
use log::info;
|
||||
use nssa::{
|
||||
AccountId,
|
||||
privacy_preserving_transaction::circuit::ProgramWithDependencies,
|
||||
program::Program,
|
||||
};
|
||||
use nssa_core::{NullifierPublicKey, encryption::ViewingPublicKey, program::PdaSeed};
|
||||
use tokio::test;
|
||||
use wallet::{PrivacyPreservingAccount, WalletCore};
|
||||
use wallet::cli::{Command, account::AccountSubcommand};
|
||||
|
||||
/// Funds a private PDA via auth_transfer directly (no proxy).
|
||||
///
|
||||
/// The PDA is foreign: the wallet knows its account_id/npk/vpk but not the nsk.
|
||||
/// auth_transfer claims the uninitialized PDA with Claim::Authorized on the first receive.
|
||||
async fn fund_private_pda(
|
||||
wallet: &WalletCore,
|
||||
sender: AccountId,
|
||||
pda_account_id: AccountId,
|
||||
npk: NullifierPublicKey,
|
||||
vpk: ViewingPublicKey,
|
||||
identifier: u128,
|
||||
amount: u128,
|
||||
auth_transfer: &ProgramWithDependencies,
|
||||
) -> Result<()> {
|
||||
wallet
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::Public(sender),
|
||||
PrivacyPreservingAccount::PrivatePdaForeign {
|
||||
account_id: pda_account_id,
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
},
|
||||
],
|
||||
Program::serialize_instruction(amount)
|
||||
.context("failed to serialize auth_transfer instruction")?,
|
||||
auth_transfer,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Spends from an owned private PDA to a fresh private-foreign recipient.
|
||||
///
|
||||
/// Alice must own the PDA in the wallet (i.e. it must have been synced after a receive).
|
||||
async fn spend_private_pda(
|
||||
wallet: &WalletCore,
|
||||
pda_account_id: AccountId,
|
||||
recipient_npk: NullifierPublicKey,
|
||||
recipient_vpk: ViewingPublicKey,
|
||||
seed: PdaSeed,
|
||||
amount: u128,
|
||||
spend_program: &ProgramWithDependencies,
|
||||
auth_transfer_id: nssa::ProgramId,
|
||||
) -> Result<()> {
|
||||
wallet
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::PrivatePdaOwned(pda_account_id),
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: recipient_npk,
|
||||
vpk: recipient_vpk,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
Program::serialize_instruction((seed, amount, auth_transfer_id))
|
||||
.context("failed to serialize auth_transfer_proxy instruction")?,
|
||||
spend_program,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Two private transfers go to distinct members of the same PDA family (same seed and npk,
|
||||
/// but identifier=0 and identifier=1). Alice then spends from both PDAs.
|
||||
///
|
||||
/// This exercises the full identifier-diversified private PDA lifecycle:
|
||||
/// receive(id=0), receive(id=1) → sync → spend(id=0), spend(id=1) → sync → assert.
|
||||
#[test]
|
||||
async fn private_pda_family_members_receive_and_spend() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
// ── Build alice's key chain ──────────────────────────────────────────────────────────────────
|
||||
let alice_chain_index = ctx.wallet_mut().create_private_accounts_key(None);
|
||||
let (alice_npk, alice_vpk) = {
|
||||
let node = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&alice_chain_index)
|
||||
.context("key node was just inserted")?;
|
||||
let kc = &node.value.0;
|
||||
(kc.nullifier_public_key, kc.viewing_public_key.clone())
|
||||
};
|
||||
|
||||
let proxy = {
|
||||
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../artifacts/test_program_methods")
|
||||
.join(NSSA_PROGRAM_FOR_TEST_AUTH_TRANSFER_PROXY);
|
||||
Program::new(std::fs::read(&path).with_context(|| format!("reading {path:?}"))?)
|
||||
.context("invalid auth_transfer_proxy binary")?
|
||||
};
|
||||
let auth_transfer = Program::authenticated_transfer_program();
|
||||
let proxy_id = proxy.id();
|
||||
let auth_transfer_id = auth_transfer.id();
|
||||
let seed = PdaSeed::new([42; 32]);
|
||||
let amount: u128 = 100;
|
||||
|
||||
let auth_transfer_program = ProgramWithDependencies::new(auth_transfer.clone(), [].into());
|
||||
let spend_program =
|
||||
ProgramWithDependencies::new(proxy, [(auth_transfer_id, auth_transfer)].into());
|
||||
|
||||
let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0);
|
||||
let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1);
|
||||
|
||||
// Use two different public senders to avoid nonce conflicts between the back-to-back txs.
|
||||
let senders = ctx.existing_public_accounts();
|
||||
let sender_0 = senders[0];
|
||||
let sender_1 = senders[1];
|
||||
|
||||
// ── Receive ──────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
info!("Sending to alice_pda_0 (identifier=0)");
|
||||
fund_private_pda(
|
||||
ctx.wallet(),
|
||||
sender_0,
|
||||
alice_pda_0_id,
|
||||
alice_npk,
|
||||
alice_vpk.clone(),
|
||||
0,
|
||||
amount,
|
||||
&auth_transfer_program,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Sending to alice_pda_1 (identifier=1)");
|
||||
fund_private_pda(
|
||||
ctx.wallet(),
|
||||
sender_1,
|
||||
alice_pda_1_id,
|
||||
alice_npk,
|
||||
alice_vpk.clone(),
|
||||
1,
|
||||
amount,
|
||||
&auth_transfer_program,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Waiting for block");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
// Sync so alice's wallet discovers and stores both PDAs.
|
||||
wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::Account(AccountSubcommand::SyncPrivate {}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Both PDAs must be discoverable and have the correct balance.
|
||||
let pda_0_account = ctx
|
||||
.wallet()
|
||||
.get_account_private(alice_pda_0_id)
|
||||
.context("alice_pda_0 not found after sync")?;
|
||||
assert_eq!(pda_0_account.balance, amount);
|
||||
|
||||
let pda_1_account = ctx
|
||||
.wallet()
|
||||
.get_account_private(alice_pda_1_id)
|
||||
.context("alice_pda_1 not found after sync")?;
|
||||
assert_eq!(pda_1_account.balance, amount);
|
||||
|
||||
// Commitments for both PDAs must be in the sequencer's state.
|
||||
let commitment_0 = ctx
|
||||
.wallet()
|
||||
.get_private_account_commitment(alice_pda_0_id)
|
||||
.context("commitment for alice_pda_0 missing")?;
|
||||
assert!(
|
||||
verify_commitment_is_in_state(commitment_0.clone(), ctx.sequencer_client()).await,
|
||||
"alice_pda_0 commitment not in state after receive"
|
||||
);
|
||||
|
||||
let commitment_1 = ctx
|
||||
.wallet()
|
||||
.get_private_account_commitment(alice_pda_1_id)
|
||||
.context("commitment for alice_pda_1 missing")?;
|
||||
assert!(
|
||||
verify_commitment_is_in_state(commitment_1.clone(), ctx.sequencer_client()).await,
|
||||
"alice_pda_1 commitment not in state after receive"
|
||||
);
|
||||
assert_ne!(commitment_0, commitment_1, "distinct identifiers must yield distinct commitments");
|
||||
|
||||
// ── Spend ─────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Fresh recipients — hardcoded npks not in any wallet.
|
||||
let recipient_npk_0 = NullifierPublicKey([0xAA; 32]);
|
||||
let recipient_vpk_0 = ViewingPublicKey::from_scalar(recipient_npk_0.0);
|
||||
|
||||
let recipient_npk_1 = NullifierPublicKey([0xBB; 32]);
|
||||
let recipient_vpk_1 = ViewingPublicKey::from_scalar(recipient_npk_1.0);
|
||||
|
||||
let amount_spend_0: u128 = 13;
|
||||
let amount_spend_1: u128 = 37;
|
||||
|
||||
info!("Alice spending from alice_pda_0");
|
||||
spend_private_pda(
|
||||
ctx.wallet(),
|
||||
alice_pda_0_id,
|
||||
recipient_npk_0,
|
||||
recipient_vpk_0,
|
||||
seed,
|
||||
amount_spend_0,
|
||||
&spend_program,
|
||||
auth_transfer_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Alice spending from alice_pda_1");
|
||||
spend_private_pda(
|
||||
ctx.wallet(),
|
||||
alice_pda_1_id,
|
||||
recipient_npk_1,
|
||||
recipient_vpk_1,
|
||||
seed,
|
||||
amount_spend_1,
|
||||
&spend_program,
|
||||
auth_transfer_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Waiting for block");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::Account(AccountSubcommand::SyncPrivate {}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// After spending, PDAs should have the remaining balance.
|
||||
let pda_0_spent = ctx
|
||||
.wallet()
|
||||
.get_account_private(alice_pda_0_id)
|
||||
.context("alice_pda_0 not found after spend sync")?;
|
||||
assert_eq!(pda_0_spent.balance, amount - amount_spend_0);
|
||||
|
||||
let pda_1_spent = ctx
|
||||
.wallet()
|
||||
.get_account_private(alice_pda_1_id)
|
||||
.context("alice_pda_1 not found after spend sync")?;
|
||||
assert_eq!(pda_1_spent.balance, amount - amount_spend_1);
|
||||
|
||||
// Post-spend commitments must be in state.
|
||||
let post_spend_commitment_0 = ctx
|
||||
.wallet()
|
||||
.get_private_account_commitment(alice_pda_0_id)
|
||||
.context("post-spend commitment for alice_pda_0 missing")?;
|
||||
assert!(
|
||||
verify_commitment_is_in_state(post_spend_commitment_0, ctx.sequencer_client()).await,
|
||||
"alice_pda_0 post-spend commitment not in state"
|
||||
);
|
||||
|
||||
let post_spend_commitment_1 = ctx
|
||||
.wallet()
|
||||
.get_private_account_commitment(alice_pda_1_id)
|
||||
.context("post-spend commitment for alice_pda_1 missing")?;
|
||||
assert!(
|
||||
verify_commitment_is_in_state(post_spend_commitment_1, ctx.sequencer_client()).await,
|
||||
"alice_pda_1 post-spend commitment not in state"
|
||||
);
|
||||
|
||||
info!("Private PDA family member receive-and-spend test passed");
|
||||
Ok(())
|
||||
}
|
||||
@ -923,6 +923,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Different identifiers produce different addresses for the same (program_id, seed, npk),
|
||||
/// confirming that each (program_id, seed, npk) controls a family of 2^128 addresses.
|
||||
#[test]
|
||||
fn for_private_pda_differs_for_different_identifier() {
|
||||
let program_id: ProgramId = [1; 8];
|
||||
let seed = PdaSeed::new([2; 32]);
|
||||
let npk = NullifierPublicKey([3; 32]);
|
||||
assert_ne!(
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, 0),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, 1),
|
||||
);
|
||||
assert_ne!(
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, 0),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX),
|
||||
);
|
||||
}
|
||||
|
||||
/// A private PDA at the same (program, seed) has a different address than a public PDA,
|
||||
/// because the private formula uses a different prefix and includes npk.
|
||||
#[test]
|
||||
|
||||
@ -186,6 +186,8 @@ mod tests {
|
||||
use nssa_core::{
|
||||
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, SharedSecretKey,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||
encryption::PrivateAccountKind,
|
||||
program::PdaSeed,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -411,4 +413,192 @@ mod tests {
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
}
|
||||
|
||||
/// A private PDA claimed with a non-default identifier produces a ciphertext that decrypts
|
||||
/// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`.
|
||||
#[test]
|
||||
fn private_pda_claim_with_custom_identifier_encrypts_correct_kind() {
|
||||
let program = Program::pda_claimer();
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
let seed = PdaSeed::new([42; 32]);
|
||||
let identifier: u128 = 99;
|
||||
let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk());
|
||||
|
||||
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier);
|
||||
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
|
||||
|
||||
let (output, _proof) = execute_and_prove(
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![3],
|
||||
vec![(npk, identifier, shared_secret.clone())],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program.clone().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let commitment = output.new_commitments[0].clone();
|
||||
let (kind, _account) =
|
||||
EncryptionScheme::decrypt(&output.ciphertexts[0], &shared_secret, &commitment, 0)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
kind,
|
||||
PrivateAccountKind::Pda { program_id: program.id(), seed, identifier },
|
||||
);
|
||||
}
|
||||
|
||||
/// A private PDA family has two members (identifier=0 and identifier=1, same seed/npk).
|
||||
/// Each is funded in a separate transaction; commitments must be distinct and ciphertexts
|
||||
/// must carry the correct `PrivateAccountKind::Pda { identifier }`. Alice then spends both.
|
||||
#[test]
|
||||
fn two_private_pda_family_members_receive_and_spend() {
|
||||
let alice_keys = test_private_account_keys_1();
|
||||
let alice_npk = alice_keys.npk();
|
||||
let recipient_keys = test_private_account_keys_2();
|
||||
|
||||
let proxy = Program::auth_transfer_proxy();
|
||||
let auth_transfer = Program::authenticated_transfer_program();
|
||||
let proxy_id = proxy.id();
|
||||
let auth_transfer_id = auth_transfer.id();
|
||||
let seed = PdaSeed::new([42; 32]);
|
||||
let amount: u128 = 100;
|
||||
|
||||
let program_with_deps = ProgramWithDependencies::new(
|
||||
proxy,
|
||||
[(auth_transfer_id, auth_transfer)].into(),
|
||||
);
|
||||
|
||||
let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0);
|
||||
let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1);
|
||||
|
||||
// Public funder account: already owned by auth_transfer so its balance can be debited.
|
||||
let funder = AccountWithMetadata::new(
|
||||
Account { program_owner: auth_transfer_id, balance: 500, ..Account::default() },
|
||||
true,
|
||||
AccountId::new([0xAB; 32]),
|
||||
);
|
||||
|
||||
let alice_shared_0 = SharedSecretKey::new(&[10; 32], &alice_keys.vpk());
|
||||
let alice_shared_1 = SharedSecretKey::new(&[11; 32], &alice_keys.vpk());
|
||||
|
||||
// ── Receive 0: fund alice_pda_0 (identifier = 0) ────────────────────────────────────────
|
||||
let (output_recv_0, _) = execute_and_prove(
|
||||
vec![
|
||||
AccountWithMetadata::new(Account::default(), false, alice_pda_0_id),
|
||||
funder.clone(),
|
||||
],
|
||||
Program::serialize_instruction((seed, amount, auth_transfer_id)).unwrap(),
|
||||
vec![3, 0],
|
||||
vec![(alice_npk, 0, alice_shared_0.clone())],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program_with_deps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output_recv_0.new_commitments.len(), 1);
|
||||
let commitment_pda_0 = output_recv_0.new_commitments[0].clone();
|
||||
|
||||
let (kind_0, alice_pda_0_account) = EncryptionScheme::decrypt(
|
||||
&output_recv_0.ciphertexts[0],
|
||||
&alice_shared_0,
|
||||
&commitment_pda_0,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
kind_0,
|
||||
PrivateAccountKind::Pda { program_id: proxy_id, seed, identifier: 0 },
|
||||
);
|
||||
assert_eq!(alice_pda_0_account.balance, amount);
|
||||
|
||||
// ── Receive 1: fund alice_pda_1 (identifier = 1, same seed) ─────────────────────────────
|
||||
let (output_recv_1, _) = execute_and_prove(
|
||||
vec![
|
||||
AccountWithMetadata::new(Account::default(), false, alice_pda_1_id),
|
||||
funder.clone(),
|
||||
],
|
||||
Program::serialize_instruction((seed, amount, auth_transfer_id)).unwrap(),
|
||||
vec![3, 0],
|
||||
vec![(alice_npk, 1, alice_shared_1.clone())],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program_with_deps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output_recv_1.new_commitments.len(), 1);
|
||||
let commitment_pda_1 = output_recv_1.new_commitments[0].clone();
|
||||
|
||||
let (kind_1, alice_pda_1_account) = EncryptionScheme::decrypt(
|
||||
&output_recv_1.ciphertexts[0],
|
||||
&alice_shared_1,
|
||||
&commitment_pda_1,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
kind_1,
|
||||
PrivateAccountKind::Pda { program_id: proxy_id, seed, identifier: 1 },
|
||||
);
|
||||
assert_eq!(alice_pda_1_account.balance, amount);
|
||||
|
||||
// Different identifiers produce distinct commitments.
|
||||
assert_ne!(commitment_pda_0, commitment_pda_1);
|
||||
|
||||
// ── Spend 0: alice spends alice_pda_0 ───────────────────────────────────────────────────
|
||||
let mut cs_0 = CommitmentSet::with_capacity(1);
|
||||
cs_0.extend(std::slice::from_ref(&commitment_pda_0));
|
||||
let proof_pda_0 = cs_0.get_proof_for(&commitment_pda_0);
|
||||
|
||||
let recipient_0_id = AccountId::from((&recipient_keys.npk(), 0u128));
|
||||
let (output_spend_0, _) = execute_and_prove(
|
||||
vec![
|
||||
AccountWithMetadata::new(alice_pda_0_account, true, alice_pda_0_id),
|
||||
AccountWithMetadata::new(Account::default(), false, recipient_0_id),
|
||||
],
|
||||
Program::serialize_instruction((seed, amount, auth_transfer_id)).unwrap(),
|
||||
vec![3, 2],
|
||||
vec![
|
||||
(alice_npk, 0, alice_shared_0.clone()),
|
||||
(recipient_keys.npk(), 0, SharedSecretKey::new(&[20; 32], &recipient_keys.vpk())),
|
||||
],
|
||||
vec![alice_keys.nsk],
|
||||
vec![proof_pda_0, None],
|
||||
&program_with_deps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output_spend_0.new_commitments.len(), 2);
|
||||
assert_eq!(output_spend_0.new_nullifiers.len(), 2);
|
||||
|
||||
// ── Spend 1: alice spends alice_pda_1 ───────────────────────────────────────────────────
|
||||
let mut cs_1 = CommitmentSet::with_capacity(1);
|
||||
cs_1.extend(std::slice::from_ref(&commitment_pda_1));
|
||||
let proof_pda_1 = cs_1.get_proof_for(&commitment_pda_1);
|
||||
|
||||
let recipient_1_id = AccountId::from((&recipient_keys.npk(), 1u128));
|
||||
let (output_spend_1, _) = execute_and_prove(
|
||||
vec![
|
||||
AccountWithMetadata::new(alice_pda_1_account, true, alice_pda_1_id),
|
||||
AccountWithMetadata::new(Account::default(), false, recipient_1_id),
|
||||
],
|
||||
Program::serialize_instruction((seed, amount, auth_transfer_id)).unwrap(),
|
||||
vec![3, 2],
|
||||
vec![
|
||||
(alice_npk, 1, alice_shared_1.clone()),
|
||||
(recipient_keys.npk(), 1, SharedSecretKey::new(&[21; 32], &recipient_keys.vpk())),
|
||||
],
|
||||
vec![alice_keys.nsk],
|
||||
vec![proof_pda_1, None],
|
||||
&program_with_deps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output_spend_1.new_commitments.len(), 2);
|
||||
assert_eq!(output_spend_1.new_nullifiers.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,6 +168,7 @@ impl Program {
|
||||
elf: PINATA_TOKEN_ELF.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -322,6 +323,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn auth_transfer_proxy() -> Self {
|
||||
use test_program_methods::{AUTH_TRANSFER_PROXY_ELF, AUTH_TRANSFER_PROXY_ID};
|
||||
|
||||
Self {
|
||||
id: AUTH_TRANSFER_PROXY_ID,
|
||||
elf: AUTH_TRANSFER_PROXY_ELF.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[must_use]
|
||||
pub fn changer_claimer() -> Self {
|
||||
use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID};
|
||||
|
||||
@ -356,12 +356,7 @@ impl ExecutionState {
|
||||
},
|
||||
3 => {
|
||||
match claim {
|
||||
Claim::Authorized => {
|
||||
assert!(
|
||||
pre_is_authorized,
|
||||
"Cannot claim unauthorized private PDA {pre_account_id}"
|
||||
);
|
||||
}
|
||||
Claim::Authorized => {}
|
||||
Claim::Pda(seed) => {
|
||||
let (npk, identifier) = self
|
||||
.private_pda_npk_by_position
|
||||
|
||||
52
test_program_methods/guest/src/bin/auth_transfer_proxy.rs
Normal file
52
test_program_methods/guest/src/bin/auth_transfer_proxy.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput,
|
||||
read_nssa_inputs,
|
||||
};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
|
||||
/// Spends from a private PDA by proxying the debit through auth_transfer.
|
||||
///
|
||||
/// pre_states[0] = the private PDA (must be authorized)
|
||||
/// pre_states[1] = the recipient
|
||||
///
|
||||
/// The PDA-to-npk binding is established via `pda_seeds` in the chained call to auth_transfer.
|
||||
/// Funding a PDA is done by calling auth_transfer directly (no proxy needed).
|
||||
type Instruction = (PdaSeed, u128, ProgramId);
|
||||
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (seed, amount, auth_transfer_id),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let Ok([pda, recipient]) = <[_; 2]>::try_from(pre_states) else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert!(pda.is_authorized, "PDA must be authorized");
|
||||
|
||||
let pda_post = AccountPostState::new(pda.account.clone());
|
||||
let recipient_post = AccountPostState::new(recipient.account.clone());
|
||||
|
||||
let chained_call = ChainedCall {
|
||||
program_id: auth_transfer_id,
|
||||
instruction_data: to_vec(&amount).unwrap(),
|
||||
pre_states: vec![pda.clone(), recipient.clone()],
|
||||
pda_seeds: vec![seed],
|
||||
};
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
vec![pda, recipient],
|
||||
vec![pda_post, recipient_post],
|
||||
)
|
||||
.with_chained_calls(vec![chained_call])
|
||||
.write();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user