From 3e237b292f488ed153fa46865e6c465433c69d06 Mon Sep 17 00:00:00 2001 From: Joey Yandle Date: Wed, 25 Mar 2026 17:38:39 +0100 Subject: [PATCH 1/2] add state machine tests for bitcoin --- Cargo.toml | 5 +- src/btc.rs | 2 +- src/lib.rs | 2 +- src/state_machine/coordinator/fire.rs | 13 ++- src/state_machine/coordinator/frost.rs | 13 ++- src/state_machine/coordinator/mod.rs | 107 +++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33fb15ad..d3adbddb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,12 @@ categories = ["cryptography"] default = ["with_p256k1_bindgen"] with_p256k1_bindgen = ["p256k1/with_bindgen"] with_v1 = [] -testing = [] +testing = ["dep:bitcoin", "dep:bitcoinconsensus"] [dependencies] +bitcoin = { version = "0.32", default-features = false, features = ["serde", "rand-std"], optional = true } +bitcoinconsensus = { version = "0.106.0", default-features = false, optional = true } + aes-gcm = "0.10" bs58 = "0.5" elliptic-curve = { version = "0.13.8", features = ["hash2curve"] } diff --git a/src/btc.rs b/src/btc.rs index 20ae1a94..c6f5b3f0 100644 --- a/src/btc.rs +++ b/src/btc.rs @@ -1,7 +1,6 @@ use bitcoin::{ absolute::LockTime, consensus::Encodable, - key::TapTweak, secp256k1::{self, Secp256k1, Verification, XOnlyPublicKey}, sighash::{Prevouts, SighashCache}, taproot::{LeafVersion, Signature}, @@ -227,6 +226,7 @@ impl UnsignedTx { #[cfg(test)] mod test { use super::*; + use bitcoin::key::TapTweak; use crate::{ compute, taproot::{test_helpers, SchnorrProof}, diff --git a/src/lib.rs b/src/lib.rs index c6b91ceb..15994270 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![doc = include_str!("../README.md")] /// test bitcoin/taproot integration using libbitcoin_consensus -#[cfg(test)] +#[cfg(any(test, feature = "testing"))] pub mod btc; /// Types which are common to both v1 and v2 #[allow(clippy::op_ref)] diff --git a/src/state_machine/coordinator/fire.rs b/src/state_machine/coordinator/fire.rs index 28a163e0..922b5857 100644 --- a/src/state_machine/coordinator/fire.rs +++ b/src/state_machine/coordinator/fire.rs @@ -1596,7 +1596,7 @@ pub mod test { empty_private_shares, empty_public_shares, equal_after_save_load, feedback_messages, feedback_mutated_messages, gen_nonces, invalid_nonce, new_coordinator, run_dkg_sign, setup, setup_with_timeouts, start_dkg_round, - start_signing_round, verify_packet_sigs, + start_signing_round, verify_packet_sigs, btc_sign_verify, }, Config, Coordinator as CoordinatorTrait, State, }, @@ -3732,4 +3732,15 @@ pub mod test { fn verify_packet_sigs_v2() { verify_packet_sigs::, v2::Signer>(); } + + #[test] + #[cfg(feature = "with_v1")] + fn btc_sign_verify_v1() { + btc_sign_verify::, v1::Signer>(5, 2); + } + + #[test] + fn btc_sign_verify_v2() { + btc_sign_verify::, v2::Signer>(5, 2); + } } diff --git a/src/state_machine/coordinator/frost.rs b/src/state_machine/coordinator/frost.rs index 05cbdf5a..fd91daf4 100644 --- a/src/state_machine/coordinator/frost.rs +++ b/src/state_machine/coordinator/frost.rs @@ -1014,7 +1014,7 @@ pub mod test { bad_signature_share_request, check_signature_shares, coordinator_state_machine, empty_private_shares, empty_public_shares, equal_after_save_load, invalid_nonce, new_coordinator, run_dkg_sign, setup, start_dkg_round, start_signing_round, - verify_packet_sigs, + verify_packet_sigs, btc_sign_verify, }, Config, Coordinator as CoordinatorTrait, State, }, @@ -1614,4 +1614,15 @@ pub mod test { fn verify_packet_sigs_v2() { verify_packet_sigs::, v2::Signer>(); } + + #[test] + #[cfg(feature = "with_v1")] + fn btc_sign_verify_v1() { + btc_sign_verify::, v1::Signer>(5, 2); + } + + #[test] + fn btc_sign_verify_v2() { + btc_sign_verify::, v2::Signer>(5, 2); + } } diff --git a/src/state_machine/coordinator/mod.rs b/src/state_machine/coordinator/mod.rs index cf37777a..9db0cad0 100644 --- a/src/state_machine/coordinator/mod.rs +++ b/src/state_machine/coordinator/mod.rs @@ -1885,4 +1885,111 @@ pub mod test { ); } } + + use crate::btc::UnsignedTx; + + use bitcoin::{ + blockdata::script::Builder, + opcodes::all::*, + secp256k1::{Secp256k1, XOnlyPublicKey}, + taproot::{self, LeafVersion, TaprootBuilder}, + TapSighashType, Witness, + }; + + /// Create a taproot transaction with key and script spends, then sign/verify each spend path + pub fn btc_sign_verify( + num_signers: u32, + keys_per_signer: u32, + ) { + let (mut coordinators, mut signers) = + run_dkg::(num_signers, keys_per_signer); + + let aggregate_public_key = coordinators[0] + .get_aggregate_public_key() + .expect("public key"); + let aggregate_xonly_key = XOnlyPublicKey::from_slice(&aggregate_public_key.x().to_bytes()) + .expect("failed to create XOnlyPublicKey"); + + let secp = Secp256k1::new(); + let script = Builder::new() + .push_x_only_key(&aggregate_xonly_key) + .push_opcode(OP_CHECKSIG) + .into_script(); + let spend_info = TaprootBuilder::new() + .add_leaf(0, script.clone()) + .unwrap() + .finalize(&secp, aggregate_xonly_key) + .expect("failed to finalize taproot_spend_info"); + let merkle_root = spend_info.merkle_root(); + let internal_key = spend_info.internal_key(); + let unsigned = UnsignedTx::new(internal_key); + + // test the key spend + let sighash = unsigned + .compute_sighash(&secp, merkle_root) + .expect("failed to compute taproot sighash"); + let msg: &[u8] = sighash.as_ref(); + + let raw_merkle_root = merkle_root.map(|root| { + let bytes: [u8; 32] = *root.to_raw_hash().as_ref(); + bytes + }); + + let OperationResult::SignTaproot(proof) = run_sign::( + &mut coordinators, + &mut signers, + msg, + SignatureType::Taproot(raw_merkle_root), + ) else { + panic!("taproot signature failed"); + }; + + let schnorr_sig = bitcoin::secp256k1::schnorr::Signature::from_slice(&proof.to_bytes()) + .expect("Failed to parse Signature from slice"); + let taproot_sig = taproot::Signature { + signature: schnorr_sig, + sighash_type: TapSighashType::All, + }; + + unsigned + .verify_signature(&secp, &taproot_sig, merkle_root) + .expect("signature verification failed"); + + // test the script spend + let sighash = unsigned + .compute_script_sighash(&secp, merkle_root, &script) + .expect("failed to compute taproot sighash"); + let msg: &[u8] = sighash.as_ref(); + + let OperationResult::SignSchnorr(proof) = run_sign::( + &mut coordinators, + &mut signers, + msg, + SignatureType::Schnorr, + ) else { + panic!("schnorr signature failed"); + }; + + let schnorr_sig = bitcoin::secp256k1::schnorr::Signature::from_slice(&proof.to_bytes()) + .expect("Failed to parse Signature from slice"); + let taproot_sig = taproot::Signature { + signature: schnorr_sig, + sighash_type: TapSighashType::All, + }; + + let control_block = spend_info + .control_block(&(script.clone(), LeafVersion::TapScript)) + .expect("insert the accept script into the control block"); + + println!("ControlBlock {control_block:?}"); + + let mut witness = Witness::new(); + witness.push(taproot_sig.to_vec()); + witness.push(script.as_bytes()); + witness.push(control_block.serialize()); + + unsigned + .verify_witness(&secp, witness, merkle_root) + .expect("signature verification failed"); + } } From 87eced328619380bb2c91537d5e75753930396af Mon Sep 17 00:00:00 2001 From: Joey Yandle Date: Wed, 25 Mar 2026 17:40:59 +0100 Subject: [PATCH 2/2] fmt fixes --- src/btc.rs | 2 +- src/state_machine/coordinator/fire.rs | 10 +++++----- src/state_machine/coordinator/frost.rs | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/btc.rs b/src/btc.rs index c6f5b3f0..1dd9bd64 100644 --- a/src/btc.rs +++ b/src/btc.rs @@ -226,13 +226,13 @@ impl UnsignedTx { #[cfg(test)] mod test { use super::*; - use bitcoin::key::TapTweak; use crate::{ compute, taproot::{test_helpers, SchnorrProof}, traits::{Aggregator, Signer}, v2, }; + use bitcoin::key::TapTweak; use bitcoin::{ blockdata::{ diff --git a/src/state_machine/coordinator/fire.rs b/src/state_machine/coordinator/fire.rs index 922b5857..b9ff95a8 100644 --- a/src/state_machine/coordinator/fire.rs +++ b/src/state_machine/coordinator/fire.rs @@ -1592,11 +1592,11 @@ pub mod test { coordinator::{ fire::Coordinator as FireCoordinator, test::{ - bad_signature_share_request, check_signature_shares, coordinator_state_machine, - empty_private_shares, empty_public_shares, equal_after_save_load, - feedback_messages, feedback_mutated_messages, gen_nonces, invalid_nonce, - new_coordinator, run_dkg_sign, setup, setup_with_timeouts, start_dkg_round, - start_signing_round, verify_packet_sigs, btc_sign_verify, + bad_signature_share_request, btc_sign_verify, check_signature_shares, + coordinator_state_machine, empty_private_shares, empty_public_shares, + equal_after_save_load, feedback_messages, feedback_mutated_messages, + gen_nonces, invalid_nonce, new_coordinator, run_dkg_sign, setup, + setup_with_timeouts, start_dkg_round, start_signing_round, verify_packet_sigs, }, Config, Coordinator as CoordinatorTrait, State, }, diff --git a/src/state_machine/coordinator/frost.rs b/src/state_machine/coordinator/frost.rs index fd91daf4..ff5e20a4 100644 --- a/src/state_machine/coordinator/frost.rs +++ b/src/state_machine/coordinator/frost.rs @@ -1011,10 +1011,10 @@ pub mod test { state_machine::coordinator::{ frost::Coordinator as FrostCoordinator, test::{ - bad_signature_share_request, check_signature_shares, coordinator_state_machine, - empty_private_shares, empty_public_shares, equal_after_save_load, invalid_nonce, - new_coordinator, run_dkg_sign, setup, start_dkg_round, start_signing_round, - verify_packet_sigs, btc_sign_verify, + bad_signature_share_request, btc_sign_verify, check_signature_shares, + coordinator_state_machine, empty_private_shares, empty_public_shares, + equal_after_save_load, invalid_nonce, new_coordinator, run_dkg_sign, setup, + start_dkg_round, start_signing_round, verify_packet_sigs, }, Config, Coordinator as CoordinatorTrait, State, },