use cdk_common::dhke::construct_proofs;
use cdk_common::nuts::{Conditions, SigFlag, SpendingConditions};
use cdk_common::Amount;
use crate::test_helpers::mint::create_test_blinded_messages;
use crate::test_helpers::nut10::{create_test_keypair, unzip3, TestMintHelper};
use crate::util::unix_time;
#[tokio::test]
async fn test_p2pk_sig_all_requires_transaction_signature() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
println!("Alice pubkey: {}", alice_pubkey);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(
Conditions::new(
None, None, None, None, Some(SigFlag::SigAll), None, )
.unwrap(),
),
);
println!("Created P2PK spending conditions with SIG_ALL flag");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let split_display: Vec<String> = split_amounts.iter().map(|a| a.to_string()).collect();
println!("Split {} into [{}]", input_amount, split_display.join("+"));
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
println!(
"Created {} P2PK outputs locked to alice",
p2pk_outputs.len()
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint
.process_swap_request(swap_request)
.await
.expect("Failed to swap for P2PK proofs");
println!("Swap successful! Got BlindSignatures for our P2PK outputs");
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let proof_amounts: Vec<String> = p2pk_proofs.iter().map(|p| p.amount.to_string()).collect();
println!(
"Constructed {} P2PK proof(s) [{}]",
p2pk_proofs.len(),
proof_amounts.join("+")
);
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let swap_request_no_sig =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
let result = mint.process_swap_request(swap_request_no_sig).await;
assert!(result.is_err(), "Should fail without signature");
println!("✓ Spending WITHOUT signature failed as expected");
let mut swap_request_sig_inputs =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
for proof in swap_request_sig_inputs.inputs_mut() {
proof.sign_p2pk(alice_secret.clone()).unwrap();
}
let result = mint.process_swap_request(swap_request_sig_inputs).await;
assert!(
result.is_err(),
"Should fail - SIG_INPUTS signatures not valid for SIG_ALL"
);
println!("✓ Spending with SIG_INPUTS signatures failed as expected");
let mut swap_request_with_sig =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_with_sig
.sign_sig_all(alice_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_with_sig).await;
assert!(result.is_ok(), "Should succeed with valid signature");
println!("✓ Spending WITH ALL signatures (SIG_ALL) succeeded");
}
#[tokio::test]
async fn test_p2pk_sig_all_multisig_2of3() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (bob_secret, bob_pubkey) = create_test_keypair();
let (_carol_secret, carol_pubkey) = create_test_keypair();
let (dave_secret, _dave_pubkey) = create_test_keypair();
let (eve_secret, _eve_pubkey) = create_test_keypair();
println!("Alice: {}", alice_pubkey);
println!("Bob: {}", bob_pubkey);
println!("Carol: {}", carol_pubkey);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(
Conditions::new(
None, Some(vec![bob_pubkey, carol_pubkey]), None, Some(2), Some(SigFlag::SigAll), None, )
.unwrap(),
),
);
println!("Created 2-of-3 multisig spending conditions with SIG_ALL (Alice, Bob, Carol)");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
println!("Created P2PK multisig proofs (2-of-3) with SIG_ALL");
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_one_sig =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_one_sig
.sign_sig_all(alice_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_one_sig).await;
assert!(
result.is_err(),
"Should fail with only 1 signature (need 2)"
);
println!("✓ Spending with only 1 signature (Alice) failed as expected");
let mut swap_request_invalid_sigs =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_invalid_sigs
.sign_sig_all(dave_secret.clone())
.unwrap();
swap_request_invalid_sigs
.sign_sig_all(eve_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_invalid_sigs).await;
assert!(result.is_err(), "Should fail with 2 invalid signatures");
println!("✓ Spending with 2 INVALID signatures (Dave + Eve) failed as expected");
let mut swap_request_valid_sigs =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_valid_sigs
.sign_sig_all(alice_secret.clone())
.unwrap();
swap_request_valid_sigs
.sign_sig_all(bob_secret.clone())
.unwrap();
println!(
"{}",
serde_json::to_string_pretty(&swap_request_valid_sigs.clone()).unwrap()
);
let result = mint.process_swap_request(swap_request_valid_sigs).await;
assert!(result.is_ok(), "Should succeed with 2 valid signatures");
println!("✓ Spending with 2 VALID signatures (Alice + Bob) succeeded");
}
#[tokio::test]
async fn test_p2pk_sig_all_signed_by_wrong_person() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (_alice_secret, alice_pubkey) = create_test_keypair();
let (bob_secret, _bob_pubkey) = create_test_keypair();
println!("Alice pubkey: {}", alice_pubkey);
println!("Bob will try to spend Alice's proofs");
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(
Conditions::new(
None, None, None, None, Some(SigFlag::SigAll), None, )
.unwrap(),
),
);
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
println!("Created P2PK proofs locked to Alice with SIG_ALL");
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_wrong_sig =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_wrong_sig
.sign_sig_all(bob_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_wrong_sig).await;
assert!(result.is_err(), "Should fail when signed with wrong key");
println!("✓ Spending signed by wrong person failed as expected");
}
#[tokio::test]
async fn test_p2pk_sig_all_duplicate_signatures() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (_bob_secret, bob_pubkey) = create_test_keypair();
println!("Alice: {}", alice_pubkey);
println!("Bob: {}", bob_pubkey);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(
Conditions::new(
None, Some(vec![bob_pubkey]), None, Some(2), Some(SigFlag::SigAll), None, )
.unwrap(),
),
);
println!("Created 2-of-2 multisig (Alice, Bob) with SIG_ALL");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_duplicate =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_duplicate
.sign_sig_all(alice_secret.clone())
.unwrap();
swap_request_duplicate
.sign_sig_all(alice_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_duplicate).await;
assert!(
result.is_err(),
"Should fail - duplicate signatures not allowed"
);
println!("✓ Spending with duplicate signatures (Alice + Alice) failed as expected");
}
#[tokio::test]
async fn test_p2pk_sig_all_locktime_before_expiry() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (bob_secret, bob_pubkey) = create_test_keypair();
let locktime = unix_time() + 3600;
println!("Alice (primary): {}", alice_pubkey);
println!("Bob (refund): {}", bob_pubkey);
println!("Current time: {}", unix_time());
println!("Locktime: {} (expires in 1 hour)", locktime);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(
Conditions::new(
Some(locktime), None, Some(vec![bob_pubkey]), None, Some(SigFlag::SigAll), None, )
.unwrap(),
),
);
println!("Created P2PK with locktime and refund key with SIG_ALL");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_refund =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_refund
.sign_sig_all(bob_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_refund).await;
assert!(
result.is_err(),
"Should fail - refund key cannot spend before locktime"
);
println!("✓ Spending with refund key (Bob) BEFORE locktime failed as expected");
let mut swap_request_primary =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_primary
.sign_sig_all(alice_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_primary).await;
assert!(
result.is_ok(),
"Should succeed - primary key can spend before locktime"
);
println!("✓ Spending with primary key (Alice) BEFORE locktime succeeded");
}
#[tokio::test]
async fn test_p2pk_sig_all_locktime_after_expiry() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (_bob_secret, bob_pubkey) = create_test_keypair();
let locktime = unix_time() - 3600;
println!("Alice (primary): {}", alice_pubkey);
println!("Bob (refund): {}", bob_pubkey);
println!("Current time: {}", unix_time());
println!("Locktime: {} (expired 1 hour ago)", locktime);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: Some(locktime), pubkeys: None, refund_keys: Some(vec![bob_pubkey]), num_sigs: None, sig_flag: SigFlag::SigAll, num_sigs_refund: None, }),
);
println!("Created P2PK with expired locktime and refund key with SIG_ALL");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_primary =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_primary
.sign_sig_all(alice_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_primary).await;
assert!(
result.is_ok(),
"Should succeed - primary key can STILL spend after locktime (NUT-11 compliant): {:?}",
result.err()
);
println!("✓ Spending with primary key (Alice) AFTER locktime succeeded (NUT-11 compliant)");
}
#[tokio::test]
async fn test_p2pk_sig_all_locktime_after_expiry_no_refund_anyone_can_spend() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (_alice_secret, alice_pubkey) = create_test_keypair();
let locktime = unix_time() - 3600;
println!("Alice (primary): {}", alice_pubkey);
println!("Current time: {}", unix_time());
println!("Locktime: {} (expired 1 hour ago)", locktime);
println!("No refund keys configured - anyone can spend after locktime");
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: Some(locktime), pubkeys: None, refund_keys: None, num_sigs: None, sig_flag: SigFlag::SigAll, num_sigs_refund: None, }),
);
println!("Created P2PK with expired locktime, NO refund keys, and SIG_ALL");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let swap_request_no_sig =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
let result = mint.process_swap_request(swap_request_no_sig).await;
assert!(
result.is_ok(),
"Should succeed - anyone can spend after locktime with no refund keys: {:?}",
result.err()
);
println!("✓ Spending WITHOUT any signatures succeeded (anyone can spend)");
}
#[tokio::test]
async fn test_p2pk_sig_all_multisig_locktime() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (bob_secret, bob_pubkey) = create_test_keypair();
let (_carol_secret, carol_pubkey) = create_test_keypair();
let (_dave_secret, dave_pubkey) = create_test_keypair();
let (_eve_secret, eve_pubkey) = create_test_keypair();
let locktime = unix_time() - 100;
println!("Primary multisig: Alice, Bob, Carol (need 2-of-3)");
println!("Refund multisig: Dave, Eve (need 1-of-2)");
println!("Current time: {}", unix_time());
println!("Locktime: {} (expired)", locktime);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: Some(locktime), pubkeys: Some(vec![bob_pubkey, carol_pubkey]), refund_keys: Some(vec![dave_pubkey, eve_pubkey]), num_sigs: Some(2), sig_flag: SigFlag::SigAll, num_sigs_refund: Some(1), }),
);
println!("Created complex P2PK with SIG_ALL: 2-of-3 before locktime, 1-of-2 after locktime");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_primary =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_primary
.sign_sig_all(alice_secret.clone())
.unwrap();
swap_request_primary
.sign_sig_all(bob_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_primary).await;
assert!(
result.is_ok(),
"Should succeed - primary keys (2-of-3) can STILL spend after locktime (NUT-11): {:?}",
result.err()
);
println!(
"✓ Spending with primary keys (Alice + Bob, 2-of-3) AFTER locktime succeeded (NUT-11)"
);
}
#[tokio::test]
async fn test_p2pk_sig_all_mixed_proofs_different_data() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (bob_secret, bob_pubkey) = create_test_keypair();
println!("Alice pubkey: {}", alice_pubkey);
println!("Bob pubkey: {}", bob_pubkey);
let alice_input_amount = Amount::from(10);
let alice_input_proofs = test_mint.mint_proofs(alice_input_amount).await.unwrap();
let alice_spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: None,
pubkeys: None,
refund_keys: None,
num_sigs: None,
sig_flag: SigFlag::SigAll,
num_sigs_refund: None,
}),
);
let alice_split_amounts = test_mint.split_amount(alice_input_amount).unwrap();
let (alice_outputs, alice_blinding_factors, alice_secrets) = unzip3(
alice_split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &alice_spending_conditions))
.collect(),
);
let swap_request_alice =
cdk_common::nuts::SwapRequest::new(alice_input_proofs, alice_outputs.clone());
let swap_response_alice = mint.process_swap_request(swap_request_alice).await.unwrap();
let alice_proofs = construct_proofs(
swap_response_alice.signatures.clone(),
alice_blinding_factors.clone(),
alice_secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
println!(
"Created {} Alice proofs (locked to Alice with SIG_ALL)",
alice_proofs.len()
);
let bob_input_amount = Amount::from(10);
let bob_input_proofs = test_mint.mint_proofs(bob_input_amount).await.unwrap();
let bob_spending_conditions = SpendingConditions::new_p2pk(
bob_pubkey,
Some(Conditions {
locktime: None,
pubkeys: None,
refund_keys: None,
num_sigs: None,
sig_flag: SigFlag::SigAll,
num_sigs_refund: None,
}),
);
let bob_split_amounts = test_mint.split_amount(bob_input_amount).unwrap();
let (bob_outputs, bob_blinding_factors, bob_secrets) = unzip3(
bob_split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &bob_spending_conditions))
.collect(),
);
let swap_request_bob =
cdk_common::nuts::SwapRequest::new(bob_input_proofs, bob_outputs.clone());
let swap_response_bob = mint.process_swap_request(swap_request_bob).await.unwrap();
let bob_proofs = construct_proofs(
swap_response_bob.signatures.clone(),
bob_blinding_factors.clone(),
bob_secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
println!(
"Created {} Bob proofs (locked to Bob with SIG_ALL)",
bob_proofs.len()
);
let total_amount = alice_input_amount + bob_input_amount;
let (new_outputs, _) = create_test_blinded_messages(mint, total_amount)
.await
.unwrap();
let mut mixed_proofs = alice_proofs.clone();
mixed_proofs.extend(bob_proofs.clone());
let mut swap_request_mixed =
cdk_common::nuts::SwapRequest::new(mixed_proofs, new_outputs.clone());
swap_request_mixed
.sign_sig_all(alice_secret.clone())
.unwrap();
swap_request_mixed.sign_sig_all(bob_secret.clone()).unwrap();
let result = mint.process_swap_request(swap_request_mixed).await;
assert!(result.is_err(), "Should fail - cannot mix proofs with different data in SIG_ALL transaction, even with both signatures");
let error_msg = format!("{:?}", result.err().unwrap());
println!(
"✓ Mixing Alice and Bob proofs in SIG_ALL transaction failed at mint verification: {}",
error_msg
);
let (alice_new_outputs, _) = create_test_blinded_messages(mint, alice_input_amount)
.await
.unwrap();
let mut swap_request_alice_only =
cdk_common::nuts::SwapRequest::new(alice_proofs.clone(), alice_new_outputs.clone());
swap_request_alice_only
.sign_sig_all(alice_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_alice_only).await;
assert!(
result.is_ok(),
"Should succeed - Alice spending her own proofs: {:?}",
result.err()
);
println!("✓ Alice successfully spent her own proofs separately");
let (bob_new_outputs, _) = create_test_blinded_messages(mint, bob_input_amount)
.await
.unwrap();
let mut swap_request_bob_only =
cdk_common::nuts::SwapRequest::new(bob_proofs.clone(), bob_new_outputs.clone());
swap_request_bob_only
.sign_sig_all(bob_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_bob_only).await;
assert!(
result.is_ok(),
"Should succeed - Bob spending his own proofs: {:?}",
result.err()
);
println!("✓ Bob successfully spent his own proofs separately");
}
#[tokio::test]
async fn test_p2pk_sig_all_multisig_before_locktime() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (bob_secret, bob_pubkey) = create_test_keypair();
let (_carol_secret, carol_pubkey) = create_test_keypair();
let (_dave_secret, dave_pubkey) = create_test_keypair();
let (_eve_secret, eve_pubkey) = create_test_keypair();
let locktime = unix_time() + 3600;
println!("Primary multisig: Alice, Bob, Carol (need 2-of-3)");
println!("Refund multisig: Dave, Eve (need 1-of-2)");
println!("Current time: {}", unix_time());
println!("Locktime: {} (expires in 1 hour)", locktime);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: Some(locktime), pubkeys: Some(vec![bob_pubkey, carol_pubkey]), refund_keys: Some(vec![dave_pubkey, eve_pubkey]), num_sigs: Some(2), sig_flag: SigFlag::SigAll, num_sigs_refund: Some(1), }),
);
println!("Created complex P2PK with SIG_ALL: 2-of-3 before locktime, 1-of-2 after locktime");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_one_sig =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_one_sig
.sign_sig_all(alice_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_one_sig).await;
assert!(
result.is_err(),
"Should fail - need 2-of-3 signatures before locktime"
);
println!("✓ Spending with only 1 signature (Alice) BEFORE locktime failed as expected");
let mut swap_request_two_sigs =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_two_sigs
.sign_sig_all(alice_secret.clone())
.unwrap();
swap_request_two_sigs
.sign_sig_all(bob_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_two_sigs).await;
assert!(
result.is_ok(),
"Should succeed - 2-of-3 signatures before locktime"
);
println!("✓ Spending with 2 signatures (Alice + Bob, 2-of-3) BEFORE locktime succeeded");
}
#[tokio::test]
async fn test_p2pk_sig_all_more_signatures_than_required() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (bob_secret, bob_pubkey) = create_test_keypair();
let (carol_secret, carol_pubkey) = create_test_keypair();
println!("Multisig: Alice, Bob, Carol (need 2-of-3)");
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: None,
pubkeys: Some(vec![bob_pubkey, carol_pubkey]), refund_keys: None,
num_sigs: Some(2), sig_flag: SigFlag::SigAll,
num_sigs_refund: None,
}),
);
println!("Created 2-of-3 multisig with SIG_ALL");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_all_sigs =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_all_sigs
.sign_sig_all(alice_secret.clone())
.unwrap();
swap_request_all_sigs
.sign_sig_all(bob_secret.clone())
.unwrap();
swap_request_all_sigs
.sign_sig_all(carol_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_all_sigs).await;
assert!(
result.is_ok(),
"Should succeed - 3 valid signatures when only 2-of-3 required"
);
println!("✓ Spending with 3 signatures (all of Alice, Bob, Carol) when only 2-of-3 required succeeded");
}
#[tokio::test]
async fn test_p2pk_sig_all_refund_multisig_2of2() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (_alice_secret, alice_pubkey) = create_test_keypair();
let (dave_secret, dave_pubkey) = create_test_keypair();
let (eve_secret, eve_pubkey) = create_test_keypair();
let locktime = unix_time() - 3600;
println!("Alice (primary)");
println!("Dave, Eve (refund, need 2-of-2)");
println!("Current time: {}", unix_time());
println!("Locktime: {} (expired 1 hour ago)", locktime);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: Some(locktime), pubkeys: None,
refund_keys: Some(vec![dave_pubkey, eve_pubkey]), num_sigs: None, sig_flag: SigFlag::SigAll,
num_sigs_refund: Some(2), }),
);
println!("Created P2PK with SIG_ALL: 2-of-2 refund multisig after locktime");
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), p2pk_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_one_refund =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_one_refund
.sign_sig_all(dave_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_one_refund).await;
assert!(
result.is_err(),
"Should fail - need 2-of-2 refund signatures"
);
println!("✓ Spending with only 1 refund signature (Dave) AFTER locktime failed as expected");
let mut swap_request_both_refunds =
cdk_common::nuts::SwapRequest::new(p2pk_proofs.clone(), new_outputs.clone());
swap_request_both_refunds
.sign_sig_all(dave_secret.clone())
.unwrap();
swap_request_both_refunds
.sign_sig_all(eve_secret.clone())
.unwrap();
let result = mint.process_swap_request(swap_request_both_refunds).await;
assert!(
result.is_ok(),
"Should succeed - 2-of-2 refund signatures after locktime"
);
println!("✓ Spending with 2-of-2 refund signatures (Dave + Eve) AFTER locktime succeeded");
}
#[tokio::test]
async fn test_sig_all_should_reject_if_the_output_amounts_are_swapped() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
println!("Alice pubkey: {}", alice_pubkey);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
println!("Minted {} sats", input_amount);
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(
Conditions::new(
None, None, None, None, Some(SigFlag::SigAll), None, )
.unwrap(),
),
);
let split_amounts = [Amount::from(8), Amount::from(2)];
let (p2pk_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
let swap_request = cdk_common::nuts::SwapRequest::new(input_proofs, p2pk_outputs);
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
let p2pk_proofs = construct_proofs(
swap_response.signatures,
blinding_factors,
secrets,
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
println!("Created {} P2PK proofs with SIG_ALL", p2pk_proofs.len());
assert_eq!(p2pk_proofs.len(), 2, "Should have 2 proofs (8+2)");
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request = cdk_common::nuts::SwapRequest::new(p2pk_proofs, new_outputs);
println!("Outputs in swap request:");
for (i, output) in swap_request.outputs().iter().enumerate() {
println!(
" Output {}: amount={}, blinded_secret={}",
i,
output.amount,
output.blinded_secret.to_hex()
);
}
swap_request.sign_sig_all(alice_secret).unwrap();
let outputs = swap_request.outputs_mut();
let temp_amount = outputs[0].amount;
outputs[0].amount = outputs[1].amount;
outputs[1].amount = temp_amount;
println!("Outputs after swapping amounts:");
for (i, output) in swap_request.outputs().iter().enumerate() {
println!(
" Output {}: amount={}, blinded_secret={}",
i,
output.amount,
output.blinded_secret.to_hex()
);
}
let result = mint.process_swap_request(swap_request.clone()).await;
assert!(
result.is_err(),
"Swap should fail - amounts were tampered with after signing"
);
println!("✓ Swap correctly rejected after output amounts were swapped!");
println!(" Error: {}", result.err().unwrap());
let outputs = swap_request.outputs_mut();
let temp_amount = outputs[0].amount;
outputs[0].amount = outputs[1].amount;
outputs[1].amount = temp_amount;
println!("Outputs after swapping back to original:");
for (i, output) in swap_request.outputs().iter().enumerate() {
println!(
" Output {}: amount={}, blinded_secret={}",
i,
output.amount,
output.blinded_secret.to_hex()
);
}
let result = mint.process_swap_request(swap_request).await;
assert!(
result.is_ok(),
"Swap should succeed with original amounts: {:?}",
result.err()
);
println!("✓ Swap succeeded after restoring original amounts!");
}