use std::str::FromStr;
use cdk_common::dhke::construct_proofs;
use cdk_common::melt::MeltQuoteRequest;
use cdk_common::nuts::{Conditions, SigFlag, SpendingConditions};
use cdk_common::{Amount, SpendingConditionVerification};
use crate::test_helpers::nut10::{create_test_keypair, unzip3, TestMintHelper};
use crate::util::unix_time;
#[tokio::test]
async fn test_p2pk_post_locktime_anyone_can_spend() {
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);
let input_amount = Amount::from(20);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let locktime = unix_time() - 3600;
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(Conditions {
locktime: Some(locktime), pubkeys: None, refund_keys: None, num_sigs: None, sig_flag: SigFlag::SigInputs, num_sigs_refund: None, }),
);
println!(
"Created P2PK spending conditions with expired locktime: {}",
locktime
);
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 with locktime", p2pk_outputs.len());
let swap_request = cdk_common::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");
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 bolt11_str = "lnbc100n1pnvpufspp5djn8hrq49r8cghwye9kqw752qjncwyfnrprhprpqk43mwcy4yfsqdq5g9kxy7fqd9h8vmmfvdjscqzzsxqyz5vqsp5uhpjt36rj75pl7jq2sshaukzfkt7uulj456s4mh7uy7l6vx7lvxs9qxpqysgqedwz08acmqwtk8g4vkwm2w78suwt2qyzz6jkkwcgrjm3r3hs6fskyhvud4fan3keru7emjm8ygqpcrwtlmhfjfmer3afs5hhwamgr4cqtactdq";
let bolt11 = cdk_common::Bolt11Invoice::from_str(bolt11_str).unwrap();
let melt_quote_request = cdk_common::MeltQuoteBolt11Request {
request: bolt11,
unit: cdk_common::CurrencyUnit::Sat,
options: None,
};
let melt_quote = mint
.get_melt_quote(MeltQuoteRequest::Bolt11(melt_quote_request))
.await
.unwrap();
println!("Created melt quote: {}", melt_quote.quote);
let mut proofs_bob_signed = p2pk_proofs.clone();
for proof in proofs_bob_signed.iter_mut() {
proof.sign_p2pk(bob_secret.clone()).unwrap();
}
let melt_request_bob =
cdk_common::MeltRequest::new(melt_quote.quote.clone(), proofs_bob_signed, None);
melt_request_bob.verify_spending_conditions().unwrap();
println!("✓ Post-locktime spending conditions verified successfully (anyone-can-spend)");
let melt_response = mint.melt(&melt_request_bob).await.unwrap().await.unwrap();
println!("✓ Melt operation completed successfully with Bob's key after locktime!");
println!(" Quote state: {}", melt_response.state);
assert_eq!(melt_response.quote, melt_quote.quote);
}
#[tokio::test]
async fn test_p2pk_before_locktime_requires_correct_key() {
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);
let input_amount = Amount::from(20);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let locktime = unix_time() + 365 * 24 * 60 * 60;
let spending_conditions = SpendingConditions::new_p2pk(
alice_pubkey,
Some(
Conditions::new(
Some(locktime), None, None, None, Some(SigFlag::SigInputs), None, )
.unwrap(),
),
);
println!(
"Created P2PK spending conditions with future locktime: {}",
locktime
);
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 with locktime", p2pk_outputs.len());
let swap_request = cdk_common::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");
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 bolt11_str = "lnbc100n1pnvpufspp5djn8hrq49r8cghwye9kqw752qjncwyfnrprhprpqk43mwcy4yfsqdq5g9kxy7fqd9h8vmmfvdjscqzzsxqyz5vqsp5uhpjt36rj75pl7jq2sshaukzfkt7uulj456s4mh7uy7l6vx7lvxs9qxpqysgqedwz08acmqwtk8g4vkwm2w78suwt2qyzz6jkkwcgrjm3r3hs6fskyhvud4fan3keru7emjm8ygqpcrwtlmhfjfmer3afs5hhwamgr4cqtactdq";
let bolt11 = cdk_common::Bolt11Invoice::from_str(bolt11_str).unwrap();
let melt_quote_request = cdk_common::MeltQuoteBolt11Request {
request: bolt11,
unit: cdk_common::CurrencyUnit::Sat,
options: None,
};
let melt_quote = mint
.get_melt_quote(MeltQuoteRequest::Bolt11(melt_quote_request))
.await
.unwrap();
println!("Created melt quote: {}", melt_quote.quote);
let mut proofs_bob_signed = p2pk_proofs.clone();
for proof in proofs_bob_signed.iter_mut() {
proof.sign_p2pk(bob_secret.clone()).unwrap();
}
let melt_request_bob =
cdk_common::MeltRequest::new(melt_quote.quote.clone(), proofs_bob_signed, None);
let result = melt_request_bob.verify_spending_conditions();
assert!(
result.is_err(),
"Should fail with wrong key before locktime"
);
println!("✓ Melting with Bob's key before locktime failed verification as expected");
let melt_result = mint.melt(&melt_request_bob).await;
assert!(
melt_result.is_err(),
"Actual melt should also fail with wrong key"
);
println!("✓ Actual melt with Bob's key before locktime also failed as expected");
let mut proofs_alice_signed = p2pk_proofs.clone();
for proof in proofs_alice_signed.iter_mut() {
proof.sign_p2pk(alice_secret.clone()).unwrap();
}
let melt_request_alice =
cdk_common::MeltRequest::new(melt_quote.quote.clone(), proofs_alice_signed, None);
melt_request_alice.verify_spending_conditions().unwrap();
println!("✓ Pre-locktime spending conditions verified successfully with Alice's key");
let melt_response = mint.melt(&melt_request_alice).await.unwrap().await.unwrap();
println!("✓ Melt operation completed successfully with Alice's key before locktime!");
println!(" Quote state: {}", melt_response.state);
assert_eq!(melt_response.quote, melt_quote.quote);
}