use cdk_common::nuts::{Conditions, SigFlag, SpendingConditions};
use cdk_common::Amount;
use crate::test_helpers::nut10::{
create_test_hash_and_preimage, create_test_keypair, unzip3, TestMintHelper,
};
#[tokio::test]
async fn test_htlc_requiring_preimage_and_one_signature() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (hash, preimage) = create_test_hash_and_preimage();
println!("Alice pubkey: {}", alice_pubkey);
println!("Hash: {}", hash);
println!("Preimage: {}", preimage);
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_htlc_hash(
&hash,
Some(Conditions {
locktime: None,
pubkeys: Some(vec![alice_pubkey]),
refund_keys: None,
num_sigs: None, sig_flag: SigFlag::default(),
num_sigs_refund: None,
}),
)
.unwrap();
println!("Created HTLC spending conditions");
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 (htlc_outputs, blinding_factors, secrets) = unzip3(
split_amounts
.iter()
.map(|&amt| test_mint.create_blinded_message(amt, &spending_conditions))
.collect(),
);
println!(
"Created {} HTLC outputs locked to alice with hash",
htlc_outputs.len()
);
let swap_request =
cdk_common::nuts::SwapRequest::new(input_proofs.clone(), htlc_outputs.clone());
let swap_response = mint
.process_swap_request(swap_request)
.await
.expect("Failed to swap for HTLC proofs");
println!("Swap successful! Got BlindSignatures for our HTLC outputs");
use cdk_common::dhke::construct_proofs;
let htlc_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> = htlc_proofs.iter().map(|p| p.amount.to_string()).collect();
println!(
"Constructed {} HTLC proof(s) [{}]",
htlc_proofs.len(),
proof_amounts.join("+")
);
use crate::test_helpers::mint::create_test_blinded_messages;
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_preimage_only =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
for proof in swap_request_preimage_only.inputs_mut() {
proof.add_preimage(preimage.clone());
}
let result = mint.process_swap_request(swap_request_preimage_only).await;
assert!(
result.is_err(),
"Should fail with only preimage (no signature)"
);
println!("✓ Spending with ONLY preimage failed as expected");
let mut swap_request_signature_only =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
for proof in swap_request_signature_only.inputs_mut() {
proof.sign_p2pk(alice_secret.clone()).unwrap();
}
let result = mint.process_swap_request(swap_request_signature_only).await;
assert!(
result.is_err(),
"Should fail with only signature (no preimage)"
);
println!("✓ Spending with ONLY signature failed as expected");
let mut swap_request_both =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
for proof in swap_request_both.inputs_mut() {
proof.add_preimage(preimage.clone());
proof.sign_p2pk(alice_secret.clone()).unwrap();
}
let result = mint.process_swap_request(swap_request_both).await;
assert!(
result.is_ok(),
"Should succeed with correct preimage and signature"
);
println!("✓ HTLC spent successfully with correct preimage AND signature");
}
#[tokio::test]
async fn test_htlc_wrong_preimage() {
let test_mint = TestMintHelper::new().await.unwrap();
let mint = test_mint.mint();
let (alice_secret, alice_pubkey) = create_test_keypair();
let (hash, _correct_preimage) = create_test_hash_and_preimage();
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_htlc_hash(
&hash,
Some(Conditions {
locktime: None,
pubkeys: Some(vec![alice_pubkey]),
refund_keys: None,
num_sigs: None,
sig_flag: SigFlag::default(),
num_sigs_refund: None,
}),
)
.unwrap();
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (htlc_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(), htlc_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
use cdk_common::dhke::construct_proofs;
let htlc_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
use crate::test_helpers::mint::create_test_blinded_messages;
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
let wrong_preimage = "this_is_the_wrong_preimage";
for proof in swap_request.inputs_mut() {
proof.add_preimage(wrong_preimage.to_string());
proof.sign_p2pk(alice_secret.clone()).unwrap();
}
let result = mint.process_swap_request(swap_request).await;
assert!(result.is_err(), "Should fail with wrong preimage");
println!("✓ HTLC with wrong preimage failed as expected");
}
#[tokio::test]
async fn test_htlc_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 (hash, _preimage) = create_test_hash_and_preimage();
let past_locktime = cdk_common::util::unix_time() - 1000;
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_htlc_hash(
&hash,
Some(Conditions {
locktime: Some(past_locktime),
pubkeys: Some(vec![alice_pubkey]),
refund_keys: Some(vec![bob_pubkey]),
num_sigs: None,
sig_flag: SigFlag::default(),
num_sigs_refund: None,
}),
)
.unwrap();
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (htlc_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(), htlc_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
use cdk_common::dhke::construct_proofs;
let htlc_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
use crate::test_helpers::mint::create_test_blinded_messages;
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
for proof in swap_request.inputs_mut() {
proof.add_preimage(String::new()); proof.sign_p2pk(bob_secret.clone()).unwrap();
}
let result = mint.process_swap_request(swap_request).await;
assert!(
result.is_ok(),
"Bob should be able to spend after locktime without preimage"
);
println!("✓ HTLC spent by refund key after locktime (no preimage needed)");
}
#[tokio::test]
async fn test_htlc_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 (_charlie_secret, charlie_pubkey) = create_test_keypair();
let (hash, preimage) = create_test_hash_and_preimage();
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_htlc_hash(
&hash,
Some(Conditions {
locktime: None,
pubkeys: Some(vec![alice_pubkey, bob_pubkey, charlie_pubkey]),
refund_keys: None,
num_sigs: Some(2), sig_flag: SigFlag::default(),
num_sigs_refund: None,
}),
)
.unwrap();
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (htlc_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(), htlc_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
use cdk_common::dhke::construct_proofs;
let htlc_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
use crate::test_helpers::mint::create_test_blinded_messages;
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request_one_sig =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
for proof in swap_request_one_sig.inputs_mut() {
proof.add_preimage(preimage.clone());
proof.sign_p2pk(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!("✓ HTLC with 1-of-3 signatures failed as expected");
let mut swap_request_two_sigs =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
for proof in swap_request_two_sigs.inputs_mut() {
proof.add_preimage(preimage.clone());
proof.sign_p2pk(alice_secret.clone()).unwrap();
proof.sign_p2pk(bob_secret.clone()).unwrap();
}
let result = mint.process_swap_request(swap_request_two_sigs).await;
assert!(
result.is_ok(),
"Should succeed with preimage + 2-of-3 signatures"
);
println!("✓ HTLC spent with preimage + 2-of-3 signatures");
}
#[tokio::test]
async fn test_htlc_receiver_path_after_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 (hash, preimage) = create_test_hash_and_preimage();
let past_locktime = cdk_common::util::unix_time() - 1000;
let input_amount = Amount::from(10);
let input_proofs = test_mint.mint_proofs(input_amount).await.unwrap();
let spending_conditions = SpendingConditions::new_htlc_hash(
&hash,
Some(Conditions {
locktime: Some(past_locktime),
pubkeys: Some(vec![alice_pubkey]),
refund_keys: Some(vec![bob_pubkey]),
num_sigs: None,
sig_flag: SigFlag::default(),
num_sigs_refund: None,
}),
)
.unwrap();
let split_amounts = test_mint.split_amount(input_amount).unwrap();
let (htlc_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(), htlc_outputs.clone());
let swap_response = mint.process_swap_request(swap_request).await.unwrap();
use cdk_common::dhke::construct_proofs;
let htlc_proofs = construct_proofs(
swap_response.signatures.clone(),
blinding_factors.clone(),
secrets.clone(),
&test_mint.public_keys_of_the_active_sat_keyset,
)
.unwrap();
use crate::test_helpers::mint::create_test_blinded_messages;
let (new_outputs, _) = create_test_blinded_messages(mint, input_amount)
.await
.unwrap();
let mut swap_request =
cdk_common::nuts::SwapRequest::new(htlc_proofs.clone(), new_outputs.clone());
for proof in swap_request.inputs_mut() {
proof.add_preimage(preimage.clone());
proof.sign_p2pk(alice_secret.clone()).unwrap();
}
let result = mint.process_swap_request(swap_request).await;
assert!(
result.is_ok(),
"Receiver should be able to spend with preimage even after locktime"
);
println!("✓ HTLC receiver path works after locktime (NUT-14 compliant)");
}