From e6985c2e7f4d9286f98dd90ab27512bc12a53c1d Mon Sep 17 00:00:00 2001 From: atacann Date: Thu, 6 Feb 2025 20:56:44 +0300 Subject: [PATCH 01/17] prototype --- core/src/actor.rs | 124 +++++++++++++++++++++- core/src/builder/script.rs | 42 +++++--- core/src/builder/transaction/input.rs | 4 + core/src/builder/transaction/txhandler.rs | 16 +++ core/src/errors.rs | 6 ++ 5 files changed, 177 insertions(+), 15 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 13fe24fb..b8c2c9dd 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -1,10 +1,20 @@ +use std::collections::HashMap; + +use crate::builder::script::{ + CheckSig, DepositScript, OtherSpendable, PreimageRevealScript, SpendPath, TimelockScript, + WinternitzCommit, +}; +use crate::builder::transaction::input::SpentTxIn; use crate::builder::transaction::TxHandler; use crate::errors::BridgeError; +use crate::errors::BridgeError::NonOwnedKeyPath; use crate::operator::PublicHash; +use crate::rpc::clementine::tagged_signature::SignatureId; +use crate::rpc::clementine::TaggedSignature; use crate::utils::{self, SECP}; use bitcoin::hashes::hash160; use bitcoin::secp256k1::PublicKey; -use bitcoin::sighash::SighashCache; +use bitcoin::sighash::{self, SighashCache}; use bitcoin::{ hashes::Hash, secp256k1::{schnorr, Keypair, Message, SecretKey, XOnlyPublicKey}, @@ -14,6 +24,8 @@ use bitcoin::{TapNodeHash, TapSighashType, TxOut, Witness}; use bitvm::signatures::winternitz::{ self, BinarysearchVerifier, StraightforwardConverter, Winternitz, }; +use clap::ValueHint::Other; +use secp256k1::schnorr::Signature; /// Available transaction types for [`WinternitzDerivationPath`]. #[derive(Clone, Copy, Debug)] @@ -288,6 +300,116 @@ impl Actor { let hash = hash160::Hash::hash(&preimage); Ok(hash.to_byte_array()) } + + fn get_signature( + signature_id: SignatureId, + signatures: &Vec, + ) -> Option { + let sig = signatures + .iter() + .find(|sig| sig.signature_id.unwrap() == signature_id); + match sig { + None => None, + Some(sig) => match Signature::from_slice(sig.signature.as_ref()) { + Ok(sig) => Some(sig), + Err(_) => None, + }, + } + } + + pub fn partial_sign( + &self, + txhandler: &mut TxHandler, + signatures: &Vec, + ) -> Result<(), BridgeError> { + let signer = |idx: usize, spt: &SpentTxIn| -> Result, BridgeError> { + match spt.get_spend_path() { + SpendPath::ScriptSpend(script_idx) => { + let script = spt + .get_spendable() + .get_scripts() + .get(script_idx) + .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; + let sig = Self::get_signature(spt.get_signature_id(), &signatures); + + if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + match sig { + None => { + if script.0 == self.xonly_public_key { + return Ok(Some(script.generate_witness( + self.sign_taproot_script_spend_tx(txhandler, idx, script_idx), + ))) + } else { + return Err(BridgeError::SignatureNotFound) + } + } + Some(sig) => return Ok(Some(script.generate_witness(&sig))), + } + } else if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + if let Some(xonly_key) = script.0 { + match sig { + None => { + if xonly_key == self.xonly_public_key { + return Ok(Some(script.generate_witness( + Some(self.sign_taproot_script_spend_tx(txhandler, idx, script_idx)?.as_ref()), + ))) + } else { + return Err(BridgeError::SignatureNotFound) + } + } + Some(sig) => return Ok(Some(script.generate_witness(&sig))), + } + } else { + return Ok(Some(Witness::new())) + } + } else if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + match sig { + None => { + if script.0 == self.xonly_public_key { + return Ok(Some(script.generate_witness( + self.sign_taproot_script_spend_tx(txhandler, idx, script_idx)?.as_ref(), + ))) + } else { + return Err(BridgeError::SignatureNotFound) + } + } + Some(sig) => return Ok(Some(script.generate_witness(&sig))), + } + } + Err(BridgeError::Error("Not handled script type".to_string())) + } + SpendPath::KeySpend => { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or_else(|| BridgeError::MissingSpendInfo)?; + let xonly_public_key = spendinfo.internal_key(); + if xonly_public_key == self.xonly_public_key { + let sighash = txhandler + .calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All))?; + // TODO: get Schnorr sigs, not Vec, pref in HashMap + let sig = Self::get_signature(spt.get_signature_id(), &signatures); + let sig = match sig { + Some(sig) => sig, + None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, + }; + return Ok(Some(Witness::p2tr_key_spend(sig))); + } + Err(NonOwnedKeyPath) + } + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + }; + txhandler.sign_txins(signer)?; + Ok(()) + } } #[cfg(test)] diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 4189dd53..c50cad63 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -14,12 +14,12 @@ use bitcoin::{ ScriptBuf, XOnlyPublicKey, }; use bitcoin::{Amount, Witness}; -use bitvm::signatures::winternitz; +use bitvm::signatures::winternitz::{self, SecretKey}; use bitvm::signatures::winternitz::{Parameters, PublicKey}; use std::any::Any; use std::fmt::Debug; -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub enum SpendPath { ScriptSpend(usize), KeySpend, @@ -74,7 +74,7 @@ impl OtherSpendable { /// Struct for scripts that only includes a CHECKSIG #[derive(Debug, Clone)] -pub struct CheckSig(XOnlyPublicKey); +pub struct CheckSig(pub(crate) XOnlyPublicKey); impl SpendableScript for CheckSig { fn as_any(&self) -> &dyn Any { self @@ -89,7 +89,7 @@ impl SpendableScript for CheckSig { } impl CheckSig { - fn generate_witness(&self, signature: schnorr::Signature) -> Witness { + pub fn generate_witness(&self, signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[signature.serialize()]) } @@ -107,7 +107,7 @@ impl SpendableScript for WinternitzCommit { } fn to_script_buf(&self) -> ScriptBuf { - let pubkey = self.0.clone(); + let winternitz_pubkey = self.0.clone(); let params = self.1.clone(); let xonly_pubkey = self.2; let verifier = winternitz::Winternitz::< @@ -115,7 +115,7 @@ impl SpendableScript for WinternitzCommit { winternitz::TabledConverter, >::new(); verifier - .checksig_verify(¶ms, &pubkey) + .checksig_verify(¶ms, &winternitz_pubkey) .push_x_only_key(&xonly_pubkey) .push_opcode(OP_CHECKSIG) .compile() @@ -123,8 +123,19 @@ impl SpendableScript for WinternitzCommit { } impl WinternitzCommit { - fn generate_witness(&self, commit_data: &[u8], signature: schnorr::Signature) -> Witness { - Witness::from_slice(&[commit_data, &signature.serialize()]) + pub fn generate_witness( + &self, + commit_data: &Vec, + secret_key: &SecretKey, + signature: &schnorr::Signature, + ) -> Witness { + let verifier = winternitz::Winternitz::< + winternitz::ListpickVerifier, + winternitz::TabledConverter, + >::new(); + let mut witness = verifier.sign(&self.1, secret_key, &commit_data); + witness.push(signature.serialize()); + witness } pub fn new(pubkey: PublicKey, params: Parameters, xonly_pubkey: XOnlyPublicKey) -> Self { @@ -144,7 +155,7 @@ impl WinternitzCommit { /// - [BIP-0068](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki) /// - [BIP-0112](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki) #[derive(Debug, Clone)] -pub struct TimelockScript(Option, u16); +pub struct TimelockScript(pub(crate) Option, u16); impl SpendableScript for TimelockScript { fn as_any(&self) -> &dyn Any { @@ -169,8 +180,11 @@ impl SpendableScript for TimelockScript { } impl TimelockScript { - fn generate_witness(&self, signature: schnorr::Signature) -> Witness { - Witness::from_slice(&[signature.serialize()]) + pub fn generate_witness(&self, signature: &Option) -> Witness { + match signature { + Some(sig) => Witness::from_slice(&[sig.serialize()]), + None => Witness::default(), + } } pub fn new(xonly_pk: Option, block_count: u16) -> Self { @@ -198,7 +212,7 @@ impl SpendableScript for PreimageRevealScript { } impl PreimageRevealScript { - fn generate_witness(&self, preimage: &[u8], signature: schnorr::Signature) -> Witness { + pub fn generate_witness(&self, preimage: &[u8], signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[preimage, &signature.serialize()]) } @@ -208,7 +222,7 @@ impl PreimageRevealScript { } /// Struct for deposit script that commits Citrea address to be deposited into onchain. -pub struct DepositScript(XOnlyPublicKey, EVMAddress, Amount); +pub struct DepositScript(pub(crate) XOnlyPublicKey, EVMAddress, Amount); impl SpendableScript for DepositScript { fn as_any(&self) -> &dyn Any { @@ -232,7 +246,7 @@ impl SpendableScript for DepositScript { } impl DepositScript { - fn generate_witness(&self, signature: schnorr::Signature) -> Witness { + pub fn generate_witness(&self, signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[signature.serialize()]) } diff --git a/core/src/builder/transaction/input.rs b/core/src/builder/transaction/input.rs index 795e614d..49d93155 100644 --- a/core/src/builder/transaction/input.rs +++ b/core/src/builder/transaction/input.rs @@ -193,6 +193,10 @@ impl SpentTxIn { &self.spendable } + pub fn get_spend_path(&self) -> SpendPath { + self.spend_path + } + pub fn get_witness(&self) -> &Option { &self.witness } diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 5228d9e5..2acae73b 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -68,6 +68,22 @@ impl TxHandler { &self.cached_txid } + pub fn sign_txins( + &mut self, + mut signer: impl FnMut(usize, &SpentTxIn) -> Result, BridgeError>, + ) -> Result<(), BridgeError> { + for (idx, txin) in self.txins.iter_mut().enumerate() { + if txin.get_witness().is_some() { + continue; + } + + if let Some(witness) = signer(idx, &txin)? { + txin.set_witness(witness); + } + } + Ok(()) + } + pub fn calculate_pubkey_spend_sighash( &self, txin_index: usize, diff --git a/core/src/errors.rs b/core/src/errors.rs index 1149bfd8..3d63638c 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -227,6 +227,12 @@ pub enum BridgeError { NoScriptsForTxIn(usize), #[error("No script in TxHandler for the index {0}")] NoScriptAtIndex(usize), + #[error("Spend Path in SpentTxIn in TxHandler not specified")] + SpendPathNotSpecified, + #[error("Actor does not own the key needed in P2TR keypath")] + NonOwnedKeyPath, + #[error("Couldn't find needed signature from database")] + SignatureNotFound, #[error("BitvmSetupNotFound for operator {0}, sequential_collateral_tx {1}, kickoff {2}")] BitvmSetupNotFound(i32, i32, i32), From 8eefcf3c59e595503b0458c4f7cfdbda852986e9 Mon Sep 17 00:00:00 2001 From: atacann Date: Fri, 7 Feb 2025 10:35:36 +0300 Subject: [PATCH 02/17] prototype2 --- core/src/actor.rs | 161 ++++++++++++++++------ core/src/builder/script.rs | 12 +- core/src/builder/transaction/txhandler.rs | 13 +- 3 files changed, 131 insertions(+), 55 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index b8c2c9dd..d7306b53 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use crate::builder::script::{ CheckSig, DepositScript, OtherSpendable, PreimageRevealScript, SpendPath, TimelockScript, WinternitzCommit, @@ -15,12 +13,9 @@ use crate::utils::{self, SECP}; use bitcoin::hashes::hash160; use bitcoin::secp256k1::PublicKey; use bitcoin::sighash::{self, SighashCache}; -use bitcoin::{ - hashes::Hash, - secp256k1::{schnorr, Keypair, Message, SecretKey, XOnlyPublicKey}, - Address, TapSighash, TapTweakHash, -}; +use bitcoin::{hashes::Hash, secp256k1::{schnorr, Keypair, Message, SecretKey, XOnlyPublicKey}, Address, ScriptBuf, TapSighash, TapTweakHash}; use bitcoin::{TapNodeHash, TapSighashType, TxOut, Witness}; +use bitcoin::taproot::{LeafVersion, TaprootSpendInfo}; use bitvm::signatures::winternitz::{ self, BinarysearchVerifier, StraightforwardConverter, Winternitz, }; @@ -166,7 +161,7 @@ impl Actor { #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] pub fn sign_taproot_script_spend_tx( &self, - tx: &mut TxHandler, + tx: &TxHandler, txin_index: usize, script_index: usize, ) -> Result { @@ -182,7 +177,7 @@ impl Actor { #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] pub fn sign_taproot_pubkey_spend( &self, - tx_handler: &mut TxHandler, + tx_handler: &TxHandler, input_index: usize, sighash_type: Option, ) -> Result { @@ -236,7 +231,7 @@ impl Actor { #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] pub fn sign_taproot_script_spend_tx_new_tweaked( &self, - tx_handler: &mut TxHandler, + tx_handler: &TxHandler, txin_index: usize, script_index: usize, ) -> Result { @@ -301,20 +296,90 @@ impl Actor { Ok(hash.to_byte_array()) } - fn get_signature( + fn get_saved_signature( signature_id: SignatureId, signatures: &Vec, - ) -> Option { - let sig = signatures + ) -> Option { + signatures .iter() - .find(|sig| sig.signature_id.unwrap() == signature_id); - match sig { - None => None, - Some(sig) => match Signature::from_slice(sig.signature.as_ref()) { - Ok(sig) => Some(sig), - Err(_) => None, - }, - } + .find(|sig| sig.signature_id.map(|id| id == signature_id).unwrap_or(false)) + .and_then(|sig| schnorr::Signature::from_slice(sig.signature.as_ref()).ok()) + } + + fn add_script_path_to_witness( + witness: &mut Witness, + script: &ScriptBuf, + spend_info: &TaprootSpendInfo + ) -> Result<(), BridgeError> { + let spend_control_block = spend_info + .control_block(&(script.clone(), LeafVersion::TapScript)) + .ok_or(BridgeError::ControlBlockError)?; + witness.push(script.clone()); + witness.push(spend_control_block.serialize()); + Ok(()) + } + + pub fn partial_sign_winternitz_or_preimagereveal( + &self, + txhandler: &mut TxHandler, + signatures: &Vec, + data: &[u8], + ) -> Result<(), BridgeError> { + let txhandlerclone = txhandler.clone(); + let signer = |idx: usize, spt: &SpentTxIn| -> Result, BridgeError> { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or_else(|| BridgeError::MissingSpendInfo)?; + match spt.get_spend_path() { + SpendPath::ScriptSpend(script_idx) => { + let script = spt + .get_spendable() + .get_scripts() + .get(script_idx) + .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; + let mut witness = Witness::default(); + + if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() { + return Ok(None) + } + else { + return Err(BridgeError::Error("Not handled script type".to_string())) + } + Self::add_script_path_to_witness(&mut witness, &script.to_script_buf(), &spendinfo)?; + return Ok(Some(witness)) + } + SpendPath::KeySpend => { + let xonly_public_key = spendinfo.internal_key(); + if xonly_public_key == self.xonly_public_key { + let sighash = txhandlerclone + .calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All))?; + // TODO: get Schnorr sigs, not Vec, pref in HashMap + let sig = Self::get_saved_signature(spt.get_signature_id(), &signatures); + let sig = match sig { + Some(sig) => sig, + None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, + }; + return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); + } + Err(NonOwnedKeyPath) + } + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + }; + txhandler.sign_txins(signer)?; + Ok(()) } pub fn partial_sign( @@ -322,7 +387,13 @@ impl Actor { txhandler: &mut TxHandler, signatures: &Vec, ) -> Result<(), BridgeError> { + let txhandlerclone = txhandler.clone(); let signer = |idx: usize, spt: &SpentTxIn| -> Result, BridgeError> { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or_else(|| BridgeError::MissingSpendInfo)?; match spt.get_spend_path() { SpendPath::ScriptSpend(script_idx) => { let script = spt @@ -330,7 +401,8 @@ impl Actor { .get_scripts() .get(script_idx) .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; - let sig = Self::get_signature(spt.get_signature_id(), &signatures); + let sig = Self::get_saved_signature(spt.get_signature_id(), &signatures); + let mut witness = Witness::default(); if let Some(script) = script.as_any().downcast_ref::() { return Ok(None) @@ -338,14 +410,14 @@ impl Actor { match sig { None => { if script.0 == self.xonly_public_key { - return Ok(Some(script.generate_witness( - self.sign_taproot_script_spend_tx(txhandler, idx, script_idx), - ))) + witness = script.generate_script_inputs( + &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, + ) } else { return Err(BridgeError::SignatureNotFound) } } - Some(sig) => return Ok(Some(script.generate_witness(&sig))), + Some(sig) => witness = script.generate_script_inputs(&sig), } } else if let Some(script) = script.as_any().downcast_ref::() { return Ok(None) @@ -354,17 +426,17 @@ impl Actor { match sig { None => { if xonly_key == self.xonly_public_key { - return Ok(Some(script.generate_witness( - Some(self.sign_taproot_script_spend_tx(txhandler, idx, script_idx)?.as_ref()), - ))) + witness = script.generate_script_inputs( + &Some(self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?), + ) } else { - return Err(BridgeError::SignatureNotFound) + return Err(BridgeError::SignatureNotFound) } } - Some(sig) => return Ok(Some(script.generate_witness(&sig))), + Some(sig) => witness = script.generate_script_inputs(&Some(sig)), } } else { - return Ok(Some(Witness::new())) + witness = Witness::new() } } else if let Some(script) = script.as_any().downcast_ref::() { return Ok(None) @@ -372,35 +444,34 @@ impl Actor { match sig { None => { if script.0 == self.xonly_public_key { - return Ok(Some(script.generate_witness( - self.sign_taproot_script_spend_tx(txhandler, idx, script_idx)?.as_ref(), - ))) + witness = script.generate_script_inputs( + &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, + ) } else { return Err(BridgeError::SignatureNotFound) } } - Some(sig) => return Ok(Some(script.generate_witness(&sig))), + Some(sig) => witness = script.generate_script_inputs(&sig), } } - Err(BridgeError::Error("Not handled script type".to_string())) + else { + return Err(BridgeError::Error("Not handled script type".to_string())) + } + Self::add_script_path_to_witness(&mut witness, &script.to_script_buf(), &spendinfo)?; + return Ok(Some(witness)) } SpendPath::KeySpend => { - let spendinfo = spt - .get_spendable() - .get_spend_info() - .as_ref() - .ok_or_else(|| BridgeError::MissingSpendInfo)?; let xonly_public_key = spendinfo.internal_key(); if xonly_public_key == self.xonly_public_key { - let sighash = txhandler + let sighash = txhandlerclone .calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All))?; // TODO: get Schnorr sigs, not Vec, pref in HashMap - let sig = Self::get_signature(spt.get_signature_id(), &signatures); + let sig = Self::get_saved_signature(spt.get_signature_id(), &signatures); let sig = match sig { Some(sig) => sig, None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, }; - return Ok(Some(Witness::p2tr_key_spend(sig))); + return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); } Err(NonOwnedKeyPath) } diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index c50cad63..90e61597 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -63,7 +63,7 @@ impl OtherSpendable { &self.0 } - fn generate_witness(&self, witness: Witness) -> Witness { + fn generate_script_inputs(&self, witness: Witness) -> Witness { witness } @@ -89,7 +89,7 @@ impl SpendableScript for CheckSig { } impl CheckSig { - pub fn generate_witness(&self, signature: &schnorr::Signature) -> Witness { + pub fn generate_script_inputs(&self, signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[signature.serialize()]) } @@ -123,7 +123,7 @@ impl SpendableScript for WinternitzCommit { } impl WinternitzCommit { - pub fn generate_witness( + pub fn generate_script_inputs( &self, commit_data: &Vec, secret_key: &SecretKey, @@ -180,7 +180,7 @@ impl SpendableScript for TimelockScript { } impl TimelockScript { - pub fn generate_witness(&self, signature: &Option) -> Witness { + pub fn generate_script_inputs(&self, signature: &Option) -> Witness { match signature { Some(sig) => Witness::from_slice(&[sig.serialize()]), None => Witness::default(), @@ -212,7 +212,7 @@ impl SpendableScript for PreimageRevealScript { } impl PreimageRevealScript { - pub fn generate_witness(&self, preimage: &[u8], signature: &schnorr::Signature) -> Witness { + pub fn generate_script_inputs(&self, preimage: &[u8], signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[preimage, &signature.serialize()]) } @@ -246,7 +246,7 @@ impl SpendableScript for DepositScript { } impl DepositScript { - pub fn generate_witness(&self, signature: &schnorr::Signature) -> Witness { + pub fn generate_script_inputs(&self, signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[signature.serialize()]) } diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 2acae73b..e5cd722d 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -72,14 +72,19 @@ impl TxHandler { &mut self, mut signer: impl FnMut(usize, &SpentTxIn) -> Result, BridgeError>, ) -> Result<(), BridgeError> { - for (idx, txin) in self.txins.iter_mut().enumerate() { - if txin.get_witness().is_some() { + for (idx) in 0..self.txins.len() { + let test_closure = || { + println!("{self:?}"); + }; + if self.txins[idx].get_witness().is_some() { continue; } - if let Some(witness) = signer(idx, &txin)? { - txin.set_witness(witness); + if let Some(witness) = signer(idx, &self.txins[idx])? { + test_closure(); + self.txins[idx].set_witness(witness); } + } Ok(()) } From 61e9b807b36db00be6db1f5d650cd60cf39374c1 Mon Sep 17 00:00:00 2001 From: atacann Date: Fri, 7 Feb 2025 10:52:54 +0300 Subject: [PATCH 03/17] add winternitz and preimagereveal --- core/src/actor.rs | 124 +++++++++++++++------- core/src/builder/script.rs | 14 ++- core/src/builder/transaction/txhandler.rs | 1 - core/src/errors.rs | 4 +- 4 files changed, 98 insertions(+), 45 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index d7306b53..195181b0 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -5,7 +5,7 @@ use crate::builder::script::{ use crate::builder::transaction::input::SpentTxIn; use crate::builder::transaction::TxHandler; use crate::errors::BridgeError; -use crate::errors::BridgeError::NonOwnedKeyPath; +use crate::errors::BridgeError::NotOwnKeyPath; use crate::operator::PublicHash; use crate::rpc::clementine::tagged_signature::SignatureId; use crate::rpc::clementine::TaggedSignature; @@ -13,9 +13,13 @@ use crate::utils::{self, SECP}; use bitcoin::hashes::hash160; use bitcoin::secp256k1::PublicKey; use bitcoin::sighash::{self, SighashCache}; -use bitcoin::{hashes::Hash, secp256k1::{schnorr, Keypair, Message, SecretKey, XOnlyPublicKey}, Address, ScriptBuf, TapSighash, TapTweakHash}; -use bitcoin::{TapNodeHash, TapSighashType, TxOut, Witness}; use bitcoin::taproot::{LeafVersion, TaprootSpendInfo}; +use bitcoin::{ + hashes::Hash, + secp256k1::{schnorr, Keypair, Message, SecretKey, XOnlyPublicKey}, + Address, ScriptBuf, TapSighash, TapTweakHash, +}; +use bitcoin::{TapNodeHash, TapSighashType, TxOut, Witness}; use bitvm::signatures::winternitz::{ self, BinarysearchVerifier, StraightforwardConverter, Winternitz, }; @@ -302,14 +306,18 @@ impl Actor { ) -> Option { signatures .iter() - .find(|sig| sig.signature_id.map(|id| id == signature_id).unwrap_or(false)) + .find(|sig| { + sig.signature_id + .map(|id| id == signature_id) + .unwrap_or(false) + }) .and_then(|sig| schnorr::Signature::from_slice(sig.signature.as_ref()).ok()) } fn add_script_path_to_witness( witness: &mut Witness, script: &ScriptBuf, - spend_info: &TaprootSpendInfo + spend_info: &TaprootSpendInfo, ) -> Result<(), BridgeError> { let spend_control_block = spend_info .control_block(&(script.clone(), LeafVersion::TapScript)) @@ -323,7 +331,7 @@ impl Actor { &self, txhandler: &mut TxHandler, signatures: &Vec, - data: &[u8], + data: &Vec, ) -> Result<(), BridgeError> { let txhandlerclone = txhandler.clone(); let signer = |idx: usize, spt: &SpentTxIn| -> Result, BridgeError> { @@ -342,23 +350,43 @@ impl Actor { let mut witness = Witness::default(); if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) + return Ok(None); } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) - } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) + return Ok(None); + } else if let Some(script) = + script.as_any().downcast_ref::() + { + if (script.0 != self.xonly_public_key) { + return Err(BridgeError::NotOwnedScriptPath); + } + witness = script.generate_script_inputs( + data, + &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, + ); } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) - } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) + return Ok(None); + } else if let Some(script) = script.as_any().downcast_ref::() + { + if (script.2 != self.xonly_public_key) { + return Err(BridgeError::NotOwnedScriptPath); + } + witness = script.generate_script_inputs( + data, + &self.winternitz_secret_key + .ok_or(BridgeError::NoWinternitzSecretKey)?.as_ref().to_vec(), + &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, + ); } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) - } - else { - return Err(BridgeError::Error("Not handled script type".to_string())) + return Ok(None); + } else { + return Err(BridgeError::Error("Not handled script type".to_string())); } - Self::add_script_path_to_witness(&mut witness, &script.to_script_buf(), &spendinfo)?; - return Ok(Some(witness)) + Self::add_script_path_to_witness( + &mut witness, + &script.to_script_buf(), + &spendinfo, + )?; + return Ok(Some(witness)); } SpendPath::KeySpend => { let xonly_public_key = spendinfo.internal_key(); @@ -373,7 +401,7 @@ impl Actor { }; return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); } - Err(NonOwnedKeyPath) + Err(NotOwnKeyPath) } SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), } @@ -405,32 +433,42 @@ impl Actor { let mut witness = Witness::default(); if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) + return Ok(None); } else if let Some(script) = script.as_any().downcast_ref::() { match sig { None => { if script.0 == self.xonly_public_key { witness = script.generate_script_inputs( - &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, + &self.sign_taproot_script_spend_tx( + &txhandlerclone, + idx, + script_idx, + )?, ) } else { - return Err(BridgeError::SignatureNotFound) + return Err(BridgeError::SignatureNotFound); } } Some(sig) => witness = script.generate_script_inputs(&sig), } - } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) + } else if let Some(script) = + script.as_any().downcast_ref::() + { + return Ok(None); } else if let Some(script) = script.as_any().downcast_ref::() { if let Some(xonly_key) = script.0 { match sig { None => { if xonly_key == self.xonly_public_key { - witness = script.generate_script_inputs( - &Some(self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?), - ) + witness = script.generate_script_inputs(&Some( + self.sign_taproot_script_spend_tx( + &txhandlerclone, + idx, + script_idx, + )?, + )) } else { - return Err(BridgeError::SignatureNotFound) + return Err(BridgeError::SignatureNotFound); } } Some(sig) => witness = script.generate_script_inputs(&Some(sig)), @@ -438,27 +476,35 @@ impl Actor { } else { witness = Witness::new() } - } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None) + } else if let Some(script) = script.as_any().downcast_ref::() + { + return Ok(None); } else if let Some(script) = script.as_any().downcast_ref::() { match sig { None => { if script.0 == self.xonly_public_key { witness = script.generate_script_inputs( - &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, + &self.sign_taproot_script_spend_tx( + &txhandlerclone, + idx, + script_idx, + )?, ) } else { - return Err(BridgeError::SignatureNotFound) + return Err(BridgeError::SignatureNotFound); } } Some(sig) => witness = script.generate_script_inputs(&sig), } + } else { + return Err(BridgeError::Error("Not handled script type".to_string())); } - else { - return Err(BridgeError::Error("Not handled script type".to_string())) - } - Self::add_script_path_to_witness(&mut witness, &script.to_script_buf(), &spendinfo)?; - return Ok(Some(witness)) + Self::add_script_path_to_witness( + &mut witness, + &script.to_script_buf(), + &spendinfo, + )?; + return Ok(Some(witness)); } SpendPath::KeySpend => { let xonly_public_key = spendinfo.internal_key(); @@ -473,7 +519,7 @@ impl Actor { }; return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); } - Err(NonOwnedKeyPath) + Err(NotOwnKeyPath) } SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), } diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 90e61597..053b4a8a 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -100,7 +100,7 @@ impl CheckSig { /// Struct for scripts that commit to a message using Winternitz keys #[derive(Clone)] -pub struct WinternitzCommit(PublicKey, Parameters, XOnlyPublicKey); +pub struct WinternitzCommit(PublicKey, Parameters, pub(crate) XOnlyPublicKey); impl SpendableScript for WinternitzCommit { fn as_any(&self) -> &dyn Any { self @@ -193,7 +193,7 @@ impl TimelockScript { } /// Struct for scripts that reveal a preimage and verify it against a hash. -pub struct PreimageRevealScript(XOnlyPublicKey, [u8; 20]); +pub struct PreimageRevealScript(pub(crate) XOnlyPublicKey, [u8; 20]); impl SpendableScript for PreimageRevealScript { fn as_any(&self) -> &dyn Any { @@ -212,8 +212,14 @@ impl SpendableScript for PreimageRevealScript { } impl PreimageRevealScript { - pub fn generate_script_inputs(&self, preimage: &[u8], signature: &schnorr::Signature) -> Witness { - Witness::from_slice(&[preimage, &signature.serialize()]) + pub fn generate_script_inputs( + &self, + preimage: impl AsRef<[u8]>, + signature: &schnorr::Signature, + ) -> Witness { + let mut witness = Witness::from_slice(&[preimage]); + witness.push(signature.serialize()); + witness } pub fn new(xonly_pk: XOnlyPublicKey, hash: [u8; 20]) -> Self { diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index e5cd722d..26b2718b 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -84,7 +84,6 @@ impl TxHandler { test_closure(); self.txins[idx].set_witness(witness); } - } Ok(()) } diff --git a/core/src/errors.rs b/core/src/errors.rs index 3d63638c..fbc1f0d7 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -230,7 +230,9 @@ pub enum BridgeError { #[error("Spend Path in SpentTxIn in TxHandler not specified")] SpendPathNotSpecified, #[error("Actor does not own the key needed in P2TR keypath")] - NonOwnedKeyPath, + NotOwnKeyPath, + #[error("public key of Checksig in script is not owned by Actor")] + NotOwnedScriptPath, #[error("Couldn't find needed signature from database")] SignatureNotFound, From dff0664617127c8f2e53f85f3d0e0a4117b32efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Fri, 7 Feb 2025 10:55:46 +0300 Subject: [PATCH 04/17] refactor with script kind for easy matching --- core/src/actor.rs | 117 ++++++++++++++++--------------------- core/src/builder/script.rs | 31 ++++++++++ 2 files changed, 80 insertions(+), 68 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 195181b0..4c0ab61f 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -372,8 +372,11 @@ impl Actor { } witness = script.generate_script_inputs( data, - &self.winternitz_secret_key - .ok_or(BridgeError::NoWinternitzSecretKey)?.as_ref().to_vec(), + &self + .winternitz_secret_key + .ok_or(BridgeError::NoWinternitzSecretKey)? + .as_ref() + .to_vec(), &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, ); } else if let Some(script) = script.as_any().downcast_ref::() { @@ -422,6 +425,7 @@ impl Actor { .get_spend_info() .as_ref() .ok_or_else(|| BridgeError::MissingSpendInfo)?; + match spt.get_spend_path() { SpendPath::ScriptSpend(script_idx) => { let script = spt @@ -430,75 +434,51 @@ impl Actor { .get(script_idx) .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; let sig = Self::get_saved_signature(spt.get_signature_id(), &signatures); - let mut witness = Witness::default(); - - if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None); - } else if let Some(script) = script.as_any().downcast_ref::() { - match sig { - None => { - if script.0 == self.xonly_public_key { - witness = script.generate_script_inputs( - &self.sign_taproot_script_spend_tx( - &txhandlerclone, - idx, - script_idx, - )?, - ) - } else { - return Err(BridgeError::SignatureNotFound); - } - } - Some(sig) => witness = script.generate_script_inputs(&sig), - } - } else if let Some(script) = - script.as_any().downcast_ref::() - { - return Ok(None); - } else if let Some(script) = script.as_any().downcast_ref::() { - if let Some(xonly_key) = script.0 { - match sig { - None => { - if xonly_key == self.xonly_public_key { - witness = script.generate_script_inputs(&Some( - self.sign_taproot_script_spend_tx( - &txhandlerclone, - idx, - script_idx, - )?, - )) - } else { - return Err(BridgeError::SignatureNotFound); - } - } - Some(sig) => witness = script.generate_script_inputs(&Some(sig)), + use crate::builder::script::ScriptKind as Kind; + + // Set the script inputs of the witness + let mut witness: Witness = match script.into() { + Kind::DepositScript(script) => { + match (sig, script.0 == self.xonly_public_key) { + (Some(sig), _) => script.generate_script_inputs(&sig), + (None, true) => script.generate_script_inputs( + &self.sign_taproot_script_spend_tx( + &txhandlerclone, + idx, + script_idx, + )?, + ), + (None, false) => return Err(BridgeError::SignatureNotFound), } - } else { - witness = Witness::new() } - } else if let Some(script) = script.as_any().downcast_ref::() - { - return Ok(None); - } else if let Some(script) = script.as_any().downcast_ref::() { - match sig { - None => { - if script.0 == self.xonly_public_key { - witness = script.generate_script_inputs( - &self.sign_taproot_script_spend_tx( - &txhandlerclone, - idx, - script_idx, - )?, - ) - } else { - return Err(BridgeError::SignatureNotFound); - } + Kind::TimelockScript(script) => match (sig, script.0) { + (Some(sig), Some(_)) => script.generate_script_inputs(&Some(sig)), + (None, Some(xonly_key)) if xonly_key == self.xonly_public_key => script + .generate_script_inputs(&Some(self.sign_taproot_script_spend_tx( + &txhandlerclone, + idx, + script_idx, + )?)), + (None, Some(_)) => return Err(BridgeError::SignatureNotFound), + (_, None) => Witness::new(), + }, + Kind::CheckSig(script) => match (sig, script.0 == self.xonly_public_key) { + (Some(sig), _) => script.generate_script_inputs(&sig), + (None, true) => { + script.generate_script_inputs(&self.sign_taproot_script_spend_tx( + &txhandlerclone, + idx, + script_idx, + )?) } - Some(sig) => witness = script.generate_script_inputs(&sig), - } - } else { - return Err(BridgeError::Error("Not handled script type".to_string())); - } + (None, false) => return Err(BridgeError::SignatureNotFound), + }, + Kind::WinternitzCommit(_) + | Kind::PreimageRevealScript(_) + | Kind::Other(_) => return Ok(None), + }; + + // Add P2TR elements (control block and script) to the witness Self::add_script_path_to_witness( &mut witness, &script.to_script_buf(), @@ -508,6 +488,7 @@ impl Actor { } SpendPath::KeySpend => { let xonly_public_key = spendinfo.internal_key(); + if xonly_public_key == self.xonly_public_key { let sighash = txhandlerclone .calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All))?; diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 053b4a8a..33701efa 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -18,6 +18,7 @@ use bitvm::signatures::winternitz::{self, SecretKey}; use bitvm::signatures::winternitz::{Parameters, PublicKey}; use std::any::Any; use std::fmt::Debug; +use std::sync::Arc; #[derive(Debug, Copy, Clone)] pub enum SpendPath { @@ -261,6 +262,36 @@ impl DepositScript { } } +#[derive(Clone)] +pub enum ScriptKind<'a> { + CheckSig(&'a CheckSig), + WinternitzCommit(&'a WinternitzCommit), + TimelockScript(&'a TimelockScript), + PreimageRevealScript(&'a PreimageRevealScript), + DepositScript(&'a DepositScript), + Other(&'a OtherSpendable), +} + +impl<'a> From<&'a Arc> for ScriptKind<'a> { + fn from(script: &'a Arc) -> ScriptKind<'a> { + let type_id = script.as_any().type_id(); + + if type_id == std::any::TypeId::of::() { + Self::CheckSig(script.as_any().downcast_ref().expect("just checked")) + } else if type_id == std::any::TypeId::of::() { + Self::WinternitzCommit(script.as_any().downcast_ref().expect("just checked")) + } else if type_id == std::any::TypeId::of::() { + Self::TimelockScript(script.as_any().downcast_ref().expect("just checked")) + } else if type_id == std::any::TypeId::of::() { + Self::PreimageRevealScript(script.as_any().downcast_ref().expect("just checked")) + } else if type_id == std::any::TypeId::of::() { + Self::DepositScript(script.as_any().downcast_ref().expect("just checked")) + } else { + Self::Other(script.as_any().downcast_ref().expect("just checked")) + } + } +} + #[cfg(test)] fn get_script_from_arr( arr: &Vec>, From 45a16fba1978325875999cb9e528e8c560abc645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Fri, 7 Feb 2025 12:15:09 +0300 Subject: [PATCH 05/17] feat(actor): remove txhandler clone by refactoring sighash calc Added a new scriptkind parser for code quality with tests Implemented sighash calculation closure to avoid cloning and ensure immutability of the TxHandler inputs. --- core/src/actor.rs | 210 +++++++++++----------- core/src/builder/script.rs | 167 +++++++++++++++-- core/src/builder/transaction/txhandler.rs | 45 ++++- core/src/errors.rs | 3 + 4 files changed, 291 insertions(+), 134 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 4c0ab61f..ad2be151 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -1,7 +1,4 @@ -use crate::builder::script::{ - CheckSig, DepositScript, OtherSpendable, PreimageRevealScript, SpendPath, TimelockScript, - WinternitzCommit, -}; +use crate::builder::script::SpendPath; use crate::builder::transaction::input::SpentTxIn; use crate::builder::transaction::TxHandler; use crate::errors::BridgeError; @@ -12,7 +9,7 @@ use crate::rpc::clementine::TaggedSignature; use crate::utils::{self, SECP}; use bitcoin::hashes::hash160; use bitcoin::secp256k1::PublicKey; -use bitcoin::sighash::{self, SighashCache}; +use bitcoin::sighash::SighashCache; use bitcoin::taproot::{LeafVersion, TaprootSpendInfo}; use bitcoin::{ hashes::Hash, @@ -23,8 +20,6 @@ use bitcoin::{TapNodeHash, TapSighashType, TxOut, Witness}; use bitvm::signatures::winternitz::{ self, BinarysearchVerifier, StraightforwardConverter, Winternitz, }; -use clap::ValueHint::Other; -use secp256k1::schnorr::Signature; /// Available transaction types for [`WinternitzDerivationPath`]. #[derive(Clone, Copy, Debug)] @@ -302,7 +297,7 @@ impl Actor { fn get_saved_signature( signature_id: SignatureId, - signatures: &Vec, + signatures: &[TaggedSignature], ) -> Option { signatures .iter() @@ -327,88 +322,93 @@ impl Actor { Ok(()) } + // pub fn partial_sign_winternitz_or_preimagereveal( &self, txhandler: &mut TxHandler, - signatures: &Vec, + signatures: &[TaggedSignature], data: &Vec, ) -> Result<(), BridgeError> { - let txhandlerclone = txhandler.clone(); - let signer = |idx: usize, spt: &SpentTxIn| -> Result, BridgeError> { - let spendinfo = spt - .get_spendable() - .get_spend_info() - .as_ref() - .ok_or_else(|| BridgeError::MissingSpendInfo)?; - match spt.get_spend_path() { - SpendPath::ScriptSpend(script_idx) => { - let script = spt - .get_spendable() - .get_scripts() - .get(script_idx) - .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; - let mut witness = Witness::default(); - - if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None); - } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None); - } else if let Some(script) = - script.as_any().downcast_ref::() - { - if (script.0 != self.xonly_public_key) { - return Err(BridgeError::NotOwnedScriptPath); - } - witness = script.generate_script_inputs( - data, - &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, - ); - } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None); - } else if let Some(script) = script.as_any().downcast_ref::() - { - if (script.2 != self.xonly_public_key) { - return Err(BridgeError::NotOwnedScriptPath); + let mut signed_winternitz = false; + + let signer = + move |_: usize, + spt: &SpentTxIn, + calc_sighash: Box Result + '_>| + -> Result, BridgeError> { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or_else(|| BridgeError::MissingSpendInfo)?; + match spt.get_spend_path() { + SpendPath::ScriptSpend(script_idx) => { + let script = spt + .get_spendable() + .get_scripts() + .get(script_idx) + .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; + + use crate::builder::script::ScriptKind as Kind; + + let mut witness = match script.into() { + Kind::PreimageRevealScript(script) => { + if script.0 != self.xonly_public_key { + return Err(BridgeError::NotOwnedScriptPath); + } + script.generate_script_inputs(data, &self.sign(calc_sighash()?)) + } + Kind::WinternitzCommit(script) => { + if script.2 != self.xonly_public_key { + return Err(BridgeError::NotOwnedScriptPath); + } + script.generate_script_inputs( + data, + &self + .winternitz_secret_key + .ok_or(BridgeError::NoWinternitzSecretKey)? + .as_ref() + .to_vec(), + &self.sign(calc_sighash()?), + ) + } + Kind::CheckSig(_) + | Kind::Other(_) + | Kind::DepositScript(_) + | Kind::TimelockScript(_) => return Ok(None), + }; + + if signed_winternitz { + return Err(BridgeError::MultipleWinternitzScripts); } - witness = script.generate_script_inputs( - data, - &self - .winternitz_secret_key - .ok_or(BridgeError::NoWinternitzSecretKey)? - .as_ref() - .to_vec(), - &self.sign_taproot_script_spend_tx(&txhandlerclone, idx, script_idx)?, - ); - } else if let Some(script) = script.as_any().downcast_ref::() { - return Ok(None); - } else { - return Err(BridgeError::Error("Not handled script type".to_string())); + + signed_winternitz = true; + + Self::add_script_path_to_witness( + &mut witness, + &script.to_script_buf(), + spendinfo, + )?; + Ok(Some(witness)) } - Self::add_script_path_to_witness( - &mut witness, - &script.to_script_buf(), - &spendinfo, - )?; - return Ok(Some(witness)); - } - SpendPath::KeySpend => { - let xonly_public_key = spendinfo.internal_key(); - if xonly_public_key == self.xonly_public_key { - let sighash = txhandlerclone - .calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All))?; - // TODO: get Schnorr sigs, not Vec, pref in HashMap - let sig = Self::get_saved_signature(spt.get_signature_id(), &signatures); - let sig = match sig { - Some(sig) => sig, - None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, - }; - return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); + SpendPath::KeySpend => { + let xonly_public_key = spendinfo.internal_key(); + if xonly_public_key == self.xonly_public_key { + let sighash = calc_sighash()?; + // TODO: get Schnorr sigs, not Vec, pref in HashMap + let sig = Self::get_saved_signature(spt.get_signature_id(), signatures); + let sig = match sig { + Some(sig) => sig, + None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, + }; + return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); + } + Err(NotOwnKeyPath) } - Err(NotOwnKeyPath) + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), } - SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), - } - }; + }; + txhandler.sign_txins(signer)?; Ok(()) } @@ -416,10 +416,14 @@ impl Actor { pub fn partial_sign( &self, txhandler: &mut TxHandler, - signatures: &Vec, + signatures: &[TaggedSignature], ) -> Result<(), BridgeError> { - let txhandlerclone = txhandler.clone(); - let signer = |idx: usize, spt: &SpentTxIn| -> Result, BridgeError> { + let signer = move |_, + spt: &SpentTxIn, + calc_sighash: Box< + dyn for<'a> FnOnce() -> Result + '_, + >| + -> Result, BridgeError> { let spendinfo = spt .get_spendable() .get_spend_info() @@ -433,7 +437,7 @@ impl Actor { .get_scripts() .get(script_idx) .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; - let sig = Self::get_saved_signature(spt.get_signature_id(), &signatures); + let sig = Self::get_saved_signature(spt.get_signature_id(), signatures); use crate::builder::script::ScriptKind as Kind; // Set the script inputs of the witness @@ -441,35 +445,24 @@ impl Actor { Kind::DepositScript(script) => { match (sig, script.0 == self.xonly_public_key) { (Some(sig), _) => script.generate_script_inputs(&sig), - (None, true) => script.generate_script_inputs( - &self.sign_taproot_script_spend_tx( - &txhandlerclone, - idx, - script_idx, - )?, - ), + (None, true) => { + script.generate_script_inputs(&self.sign(calc_sighash()?)) + } (None, false) => return Err(BridgeError::SignatureNotFound), } } Kind::TimelockScript(script) => match (sig, script.0) { (Some(sig), Some(_)) => script.generate_script_inputs(&Some(sig)), - (None, Some(xonly_key)) if xonly_key == self.xonly_public_key => script - .generate_script_inputs(&Some(self.sign_taproot_script_spend_tx( - &txhandlerclone, - idx, - script_idx, - )?)), + (None, Some(xonly_key)) if xonly_key == self.xonly_public_key => { + script.generate_script_inputs(&Some(self.sign(calc_sighash()?))) + } (None, Some(_)) => return Err(BridgeError::SignatureNotFound), (_, None) => Witness::new(), }, Kind::CheckSig(script) => match (sig, script.0 == self.xonly_public_key) { (Some(sig), _) => script.generate_script_inputs(&sig), (None, true) => { - script.generate_script_inputs(&self.sign_taproot_script_spend_tx( - &txhandlerclone, - idx, - script_idx, - )?) + script.generate_script_inputs(&self.sign(calc_sighash()?)) } (None, false) => return Err(BridgeError::SignatureNotFound), }, @@ -482,18 +475,17 @@ impl Actor { Self::add_script_path_to_witness( &mut witness, &script.to_script_buf(), - &spendinfo, + spendinfo, )?; - return Ok(Some(witness)); + Ok(Some(witness)) } SpendPath::KeySpend => { let xonly_public_key = spendinfo.internal_key(); if xonly_public_key == self.xonly_public_key { - let sighash = txhandlerclone - .calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All))?; + let sighash = calc_sighash()?; // TODO: get Schnorr sigs, not Vec, pref in HashMap - let sig = Self::get_saved_signature(spt.get_signature_id(), &signatures); + let sig = Self::get_saved_signature(spt.get_signature_id(), signatures); let sig = match sig { Some(sig) => sig, None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, @@ -505,6 +497,7 @@ impl Actor { SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), } }; + txhandler.sign_txins(signer)?; Ok(()) } @@ -515,6 +508,7 @@ mod tests { use super::Actor; use crate::builder::address::create_taproot_address; + use crate::builder::script::ScriptKind as Kind; use crate::builder::transaction::input::SpendableTxIn; use crate::builder::transaction::output::UnspentTxOut; use crate::builder::transaction::TxHandlerBuilder; diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 33701efa..2336a7c5 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -27,6 +27,14 @@ pub enum SpendPath { Unknown, } +/// A trait that marks all script types. Each script has a `generate_script_inputs` (eg. [`WinternitzCommit::generate_script_inputs`]) function that +/// generates the witness for the script using various arguments. A `dyn SpendableScript` is cast into a concrete [`ScriptKind`] to +/// generate a witness, the trait object can be used to generate the script_buf. +/// +/// We store [`Arc`]s inside a [`super::transaction::TxHandler`] input, and we cast them into a [`ScriptKind`] when signing. +/// +/// When creating a new Script, make sure you add it to the [`ScriptKind`] enum and add a test for it below. +/// Otherwise, it will not be spendable. pub trait SpendableScript: Send + Sync + 'static + std::any::Any { fn as_any(&self) -> &dyn Any; @@ -134,7 +142,7 @@ impl WinternitzCommit { winternitz::ListpickVerifier, winternitz::TabledConverter, >::new(); - let mut witness = verifier.sign(&self.1, secret_key, &commit_data); + let mut witness = verifier.sign(&self.1, secret_key, commit_data); witness.push(signature.serialize()); witness } @@ -229,6 +237,7 @@ impl PreimageRevealScript { } /// Struct for deposit script that commits Citrea address to be deposited into onchain. +#[derive(Debug, Clone)] pub struct DepositScript(pub(crate) XOnlyPublicKey, EVMAddress, Amount); impl SpendableScript for DepositScript { @@ -300,23 +309,143 @@ fn get_script_from_arr( .enumerate() .find_map(|(i, x)| x.as_any().downcast_ref::().map(|x| (i, x))) } - -#[test] -fn test_dynamic_casting() { +#[cfg(test)] +mod tests { use crate::utils; - let scripts: Vec> = vec![ - Box::new(OtherSpendable(ScriptBuf::from_hex("51").expect(""))), - Box::new(CheckSig(*utils::UNSPENDABLE_XONLY_PUBKEY)), - ]; - - let otherspendable = scripts - .first() - .expect("") - .as_any() - .downcast_ref::() - .expect(""); - - let checksig = get_script_from_arr::(&scripts).expect(""); - println!("{:?}", otherspendable); - println!("{:?}", checksig); + + use super::*; + + use bitcoin::secp256k1::PublicKey; + // Create some dummy values for testing. + // Note: These values are not cryptographically secure and are only used for tests. + fn dummy_xonly() -> XOnlyPublicKey { + // 32 bytes array filled with 0x03. + *utils::UNSPENDABLE_XONLY_PUBKEY + } + + fn dummy_scriptbuf() -> ScriptBuf { + ScriptBuf::from_hex("51").expect("valid hex") + } + + fn dummy_pubkey() -> PublicKey { + *utils::UNSPENDABLE_PUBKEY + } + + fn dummy_params() -> Parameters { + Parameters::new(32, 4) + } + + fn dummy_evm_address() -> EVMAddress { + // For testing purposes, we use a dummy 20-byte array. + EVMAddress([0u8; 20]) + } + + #[test] + fn test_dynamic_casting_extended() { + // Build a collection of SpendableScript implementations. + let scripts: Vec> = vec![ + Box::new(OtherSpendable::new(dummy_scriptbuf())), + Box::new(CheckSig::new(dummy_xonly())), + Box::new(WinternitzCommit::new( + vec![[0u8; 20]; 32], + dummy_params(), + dummy_xonly(), + )), + Box::new(TimelockScript::new(Some(dummy_xonly()), 10)), + Box::new(PreimageRevealScript::new(dummy_xonly(), [0; 20])), + Box::new(DepositScript::new( + dummy_xonly(), + dummy_evm_address(), + Amount::from_sat(100), + )), + ]; + + // helper closures that return Option<(usize, &T)> using get_script_from_arr. + let checksig = get_script_from_arr::(&scripts); + let winternitz = get_script_from_arr::(&scripts); + let timelock = get_script_from_arr::(&scripts); + let preimage = get_script_from_arr::(&scripts); + let deposit = get_script_from_arr::(&scripts); + let others = get_script_from_arr::(&scripts); + + assert!(checksig.is_some(), "CheckSig not found"); + assert!(winternitz.is_some(), "WinternitzCommit not found"); + assert!(timelock.is_some(), "TimelockScript not found"); + assert!(preimage.is_some(), "PreimageRevealScript not found"); + assert!(deposit.is_some(), "DepositScript not found"); + assert!(others.is_some(), "OtherSpendable not found"); + + // Print found items. + println!("CheckSig: {:?}", checksig.unwrap().1); + // println!("WinternitzCommit: {:?}", winternitz.unwrap().1); + println!("TimelockScript: {:?}", timelock.unwrap().1); + // println!("PreimageRevealScript: {:?}", preimage.unwrap().1); + // println!("DepositScript: {:?}", deposit.unwrap().1); + println!("OtherSpendable: {:?}", others.unwrap().1); + } + + #[test] + fn test_dynamic_casting() { + use crate::utils; + let scripts: Vec> = vec![ + Box::new(OtherSpendable(ScriptBuf::from_hex("51").expect(""))), + Box::new(CheckSig(*utils::UNSPENDABLE_XONLY_PUBKEY)), + ]; + + let otherspendable = scripts + .first() + .expect("") + .as_any() + .downcast_ref::() + .expect(""); + + let checksig = get_script_from_arr::(&scripts).expect(""); + println!("{:?}", otherspendable); + println!("{:?}", checksig); + } + + #[test] + fn test_scriptkind_completeness() { + let script_variants: Vec<(&str, Arc)> = vec![ + ("CheckSig", Arc::new(CheckSig::new(dummy_xonly()))), + ( + "WinternitzCommit", + Arc::new(WinternitzCommit::new( + vec![[0u8; 20]; 32], + dummy_params(), + dummy_xonly(), + )), + ), + ( + "TimelockScript", + Arc::new(TimelockScript::new(Some(dummy_xonly()), 15)), + ), + ( + "PreimageRevealScript", + Arc::new(PreimageRevealScript::new(dummy_xonly(), [1; 20])), + ), + ( + "DepositScript", + Arc::new(DepositScript::new( + dummy_xonly(), + dummy_evm_address(), + Amount::from_sat(50), + )), + ), + ("Other", Arc::new(OtherSpendable::new(dummy_scriptbuf()))), + ]; + + for (expected, script) in script_variants { + let kind = ScriptKind::from(&script); + match (expected, kind) { + ("CheckSig", ScriptKind::CheckSig(_)) => (), + ("WinternitzCommit", ScriptKind::WinternitzCommit(_)) => (), + ("TimelockScript", ScriptKind::TimelockScript(_)) => (), + ("PreimageRevealScript", ScriptKind::PreimageRevealScript(_)) => (), + ("DepositScript", ScriptKind::DepositScript(_)) => (), + ("Other", ScriptKind::Other(_)) => (), + (s, _) => panic!("ScriptKind conversion not comprehensive for variant: {}", s), + } + } + } } diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 26b2718b..063e472f 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -68,20 +68,51 @@ impl TxHandler { &self.cached_txid } + fn get_sighash_calculator( + &self, + idx: usize, + ) -> impl FnOnce() -> Result + '_ { + move || -> Result { + match self.txins[idx].get_spend_path() { + SpendPath::KeySpend => { + self.calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All)) + } + SpendPath::ScriptSpend(script_idx) => self.calculate_script_spend_sighash_indexed( + idx, + script_idx, + TapSighashType::All, + ), + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + } + } + + /// Signs all **unsigned** transaction inputs using the provided signer function. + /// + /// This function will skip all transaction inputs that already have a witness. + /// + /// # Parameters + /// * `signer` - A function that returns an optional witness for transaction inputs or returns an error + /// if the signing fails. The function takes the input idx, input object, and a sighash calculator closure. + /// + /// # Returns + /// * `Ok(())` if signing is successful + /// * `Err(BridgeError)` if signing fails pub fn sign_txins( &mut self, - mut signer: impl FnMut(usize, &SpentTxIn) -> Result, BridgeError>, + mut signer: impl for<'a> FnMut( + usize, + &'a SpentTxIn, + Box Result + 'a>, + ) -> Result, BridgeError>, ) -> Result<(), BridgeError> { - for (idx) in 0..self.txins.len() { - let test_closure = || { - println!("{self:?}"); - }; + for idx in 0..self.txins.len() { + let calc_sighash = Box::new(self.get_sighash_calculator(idx)); if self.txins[idx].get_witness().is_some() { continue; } - if let Some(witness) = signer(idx, &self.txins[idx])? { - test_closure(); + if let Some(witness) = signer(idx, &self.txins[idx], calc_sighash)? { self.txins[idx].set_witness(witness); } } diff --git a/core/src/errors.rs b/core/src/errors.rs index fbc1f0d7..70235255 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -257,6 +257,9 @@ pub enum BridgeError { #[error("Not enough operators")] NotEnoughOperators, + + #[error("Encountered multiple winternitz scripts when attempting to commit to only one.")] + MultipleWinternitzScripts, } impl From for ErrorObject<'static> { From 8f9076f7097cb78623e46735f87a98760f4b56f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Fri, 7 Feb 2025 13:04:09 +0300 Subject: [PATCH 06/17] test: actor correctly signs --- core/src/actor.rs | 324 +++++++++++++--------- core/src/builder/transaction/txhandler.rs | 57 +--- core/tests/taproot.rs | 10 +- 3 files changed, 204 insertions(+), 187 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index ad2be151..037fe787 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -9,14 +9,13 @@ use crate::rpc::clementine::TaggedSignature; use crate::utils::{self, SECP}; use bitcoin::hashes::hash160; use bitcoin::secp256k1::PublicKey; -use bitcoin::sighash::SighashCache; use bitcoin::taproot::{LeafVersion, TaprootSpendInfo}; use bitcoin::{ hashes::Hash, secp256k1::{schnorr, Keypair, Message, SecretKey, XOnlyPublicKey}, Address, ScriptBuf, TapSighash, TapTweakHash, }; -use bitcoin::{TapNodeHash, TapSighashType, TxOut, Witness}; +use bitcoin::{TapNodeHash, Witness}; use bitvm::signatures::winternitz::{ self, BinarysearchVerifier, StraightforwardConverter, Winternitz, }; @@ -157,92 +156,6 @@ impl Actor { ) } - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_script_spend_tx( - &self, - tx: &TxHandler, - txin_index: usize, - script_index: usize, - ) -> Result { - let sighash = tx.calculate_script_spend_sighash_indexed( - txin_index, - script_index, - TapSighashType::Default, - )?; - - Ok(self.sign(sighash)) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_pubkey_spend( - &self, - tx_handler: &TxHandler, - input_index: usize, - sighash_type: Option, - ) -> Result { - let sig_hash = tx_handler.calculate_pubkey_spend_sighash(input_index, sighash_type)?; - - self.sign_with_tweak(sig_hash, None) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_pubkey_spend_tx( - &self, - tx: &mut bitcoin::Transaction, - prevouts: &[TxOut], - input_index: usize, - ) -> Result { - let mut sighash_cache = SighashCache::new(tx); - - let sig_hash = sighash_cache.taproot_key_spend_signature_hash( - input_index, - &bitcoin::sighash::Prevouts::All(prevouts), - bitcoin::sighash::TapSighashType::Default, - )?; - - self.sign_with_tweak(sig_hash, None) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_pubkey_spend_tx_with_sighash( - &self, - tx: &mut bitcoin::Transaction, - prevouts: &[TxOut], - input_index: usize, - sighash_type: Option, - ) -> Result { - let mut sighash_cache = SighashCache::new(tx); - - let sig_hash = sighash_cache.taproot_key_spend_signature_hash( - input_index, - &match sighash_type { - Some(TapSighashType::SinglePlusAnyoneCanPay) => { - bitcoin::sighash::Prevouts::One(input_index, prevouts[input_index].clone()) - } - _ => bitcoin::sighash::Prevouts::All(prevouts), - }, - sighash_type.unwrap_or(TapSighashType::Default), - )?; - - self.sign_with_tweak(sig_hash, None) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_script_spend_tx_new_tweaked( - &self, - tx_handler: &TxHandler, - txin_index: usize, - script_index: usize, - ) -> Result { - let sighash = tx_handler.calculate_script_spend_sighash_indexed( - txin_index, - script_index, - TapSighashType::Default, - )?; - - self.sign_with_tweak(sighash, None) - } - /// Returns derivied Winternitz secret key from given path. fn get_derived_winternitz_sk( &self, @@ -381,7 +294,7 @@ impl Actor { if signed_winternitz { return Err(BridgeError::MultipleWinternitzScripts); } - + signed_winternitz = true; Self::add_script_path_to_witness( @@ -508,20 +421,24 @@ mod tests { use super::Actor; use crate::builder::address::create_taproot_address; - use crate::builder::script::ScriptKind as Kind; + use super::*; + use crate::builder::script::{CheckSig, ScriptKind, SpendPath, SpendableScript}; use crate::builder::transaction::input::SpendableTxIn; use crate::builder::transaction::output::UnspentTxOut; - use crate::builder::transaction::TxHandlerBuilder; + use crate::builder::transaction::{TxHandler, TxHandlerBuilder}; use crate::config::BridgeConfig; use crate::rpc::clementine::NormalSignatureKind; use crate::utils::{initialize_logger, SECP}; use crate::{ - actor::WinternitzDerivationPath, builder::transaction::TxHandler, - create_test_config_with_thread_name, database::Database, initialize_database, + actor::WinternitzDerivationPath, create_test_config_with_thread_name, database::Database, + initialize_database, }; - use bitcoin::secp256k1::SecretKey; + use bitcoin::secp256k1::{schnorr, Message, SecretKey}; + use bitcoin::sighash::SighashCache; + use bitcoin::sighash::{Prevouts, TapSighashType}; + use bitcoin::transaction::Transaction; use bitcoin::Sequence; - use bitcoin::{Amount, Network, OutPoint, TxOut}; + use bitcoin::{Amount, Network, OutPoint, ScriptBuf}; use bitvm::{ execute_script, signatures::winternitz::{ @@ -529,41 +446,196 @@ mod tests { }, treepp::script, }; - use secp256k1::rand; + use rand::thread_rng; + use secp256k1::{rand, SECP256K1}; use std::env; use std::str::FromStr; + use std::sync::Arc; use std::thread; - /// Returns a valid [`TxHandler`]. - fn create_valid_mock_tx_handler(actor: &Actor) -> TxHandler { - let (op_addr, op_spend) = + // Helper: create a TxHandler with a single key spend input. + fn create_key_spend_tx_handler(actor: &Actor) -> (bitcoin::TxOut, TxHandler) { + let (tap_addr, spend_info) = create_taproot_address(&[], Some(actor.xonly_public_key), Network::Regtest); + // Build a transaction with one input that expects a key spend signature. + let prevtxo = bitcoin::TxOut { + value: Amount::from_sat(1000), + script_pubkey: tap_addr.script_pubkey(), + }; let builder = TxHandlerBuilder::new().add_input( NormalSignatureKind::AlreadyDisproved1, SpendableTxIn::new( OutPoint::default(), - TxOut { - value: Amount::from_sat(1000), - script_pubkey: op_addr.script_pubkey(), - }, + prevtxo.clone(), vec![], - Some(op_spend), + Some(spend_info), ), - crate::builder::script::SpendPath::Unknown, - Sequence::ENABLE_RBF_NO_LOCKTIME, + SpendPath::KeySpend, + bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME, ); - builder - .add_output(UnspentTxOut::new( - TxOut { - value: Amount::from_sat(999), - script_pubkey: actor.address.script_pubkey(), - }, - vec![], - None, - )) - .finalize() + + ( + prevtxo, + builder + .add_output(UnspentTxOut::new( + bitcoin::TxOut { + value: Amount::from_sat(999), + script_pubkey: actor.address.script_pubkey(), + }, + vec![], + None, + )) + .finalize(), + ) + } + + // Helper: create a dummy CheckSig script for script spend. + fn create_dummy_checksig_script(actor: &Actor) -> CheckSig { + // Use a trivial script that is expected to be spent via a signature. + // In production this would be a proper P2TR script. + CheckSig(actor.xonly_public_key) + } + + // Helper: create a TxHandler with a single script spend input using CheckSig. + fn create_script_spend_tx_handler(actor: &Actor) -> (bitcoin::TxOut, TxHandler) { + // Create a dummy spendable input that carries a script. + // Here we simulate that the spendable has one script: a CheckSig script. + let script = create_dummy_checksig_script(actor); + + let (tap_addr, spend_info) = create_taproot_address( + &[script.to_script_buf()], + Some(actor.xonly_public_key), + Network::Regtest, + ); + + let prevutxo = bitcoin::TxOut { + value: Amount::from_sat(1000), + script_pubkey: tap_addr.script_pubkey(), + }; + let spendable_input = SpendableTxIn::new( + OutPoint::default(), + prevutxo.clone(), + vec![Arc::new(script)], + Some(spend_info), + ); + + let builder = TxHandlerBuilder::new().add_input( + NormalSignatureKind::AlreadyDisproved1, + spendable_input, + SpendPath::ScriptSpend(0), + bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME, + ); + + ( + prevutxo, + builder + .add_output(UnspentTxOut::new( + bitcoin::TxOut { + value: Amount::from_sat(999), + script_pubkey: actor.address.script_pubkey(), + }, + vec![], + None, + )) + .finalize(), + ) } + #[test] + fn test_actor_key_spend_verification() { + let sk = SecretKey::new(&mut thread_rng()); + let actor = Actor::new(sk, None, Network::Regtest); + let (utxo, mut txhandler) = create_key_spend_tx_handler(&actor); + + // Actor signs the key spend input. + actor + .partial_sign(&mut txhandler, &[]) + .expect("Key spend signature should succeed"); + + // Retrieve the cached transaction from the txhandler. + let tx: &Transaction = txhandler.get_cached_tx(); + + tx.verify(|_| Some(utxo.clone())) + .expect("Expected valid signature for key spend"); + } + + #[test] + fn test_actor_script_spend_tx_valid() { + let sk = SecretKey::new(&mut thread_rng()); + let actor = Actor::new(sk, None, Network::Regtest); + let (prevutxo, mut txhandler) = create_script_spend_tx_handler(&actor); + + // Actor performs a partial sign for script spend. + // Using an empty signature slice since our dummy CheckSig uses actor signature. + let signatures: Vec<_> = vec![]; + actor + .partial_sign(&mut txhandler, &signatures) + .expect("Script spend partial sign should succeed"); + + // Retrieve the cached transaction. + let tx: &Transaction = txhandler.get_cached_tx(); + + tx.verify(|_| Some(prevutxo.clone())) + .expect("Invalid transaction"); + } + + #[test] + fn test_actor_script_spend_sig_valid() { + let sk = SecretKey::new(&mut thread_rng()); + let actor = Actor::new(sk, None, Network::Regtest); + let (prevutxo, mut txhandler) = create_script_spend_tx_handler(&actor); + + // Actor performs a partial sign for script spend. + // Using an empty signature slice since our dummy CheckSig uses actor signature. + let signatures: Vec<_> = vec![]; + actor + .partial_sign(&mut txhandler, &signatures) + .expect("Script spend partial sign should succeed"); + + // Retrieve the cached transaction. + let tx: &Transaction = txhandler.get_cached_tx(); + + // For script spend, we extract the witness from the corresponding input. + // Our dummy witness is expected to contain the signature. + let witness = &tx.input[0].witness; + assert!(!witness.is_empty(), "Witness should not be empty"); + let sig = schnorr::Signature::from_slice(&witness[0]) + .expect("Failed to parse Schnorr signature from witness"); + + // Compute the sighash expected for a pubkey spend (similar to key spend). + let sighash = txhandler + .calculate_script_spend_sighash_indexed(0, 0, TapSighashType::All) + .expect("Sighash computed"); + + let message = Message::from_digest(*sighash.as_byte_array()); + SECP.verify_schnorr(&sig, &message, &actor.xonly_public_key) + .expect("Script spend signature verification failed"); + } + + // #[test] + // fn verify_cached_tx() { + // let sk = SecretKey::new(&mut rand::thread_rng()); + // let network = Network::Regtest; + // let actor = Actor::new(sk, None, network); + + // let mut txhandler = create_valid_mock_tx_handler(&actor); + + // // Sign the transaction + // actor + // .sign_taproot_pubkey_spend(&mut txhandler, 0, None) + // .unwrap(); + + // // Add witness to the transaction + // let sig = actor + // .sign_taproot_pubkey_spend(&mut txhandler, 0, None) + // .unwrap(); + // txhandler.get_cached_tx().input[0].witness = Witness::p2tr_key_spend(&sig); + + // // Verify the cached transaction + // let cached_tx = txhandler.get_cached_tx(); + // cached_tx.verify().expect("Transaction verification failed"); + // } + #[test] fn actor_new() { let sk = SecretKey::new(&mut rand::thread_rng()); @@ -584,14 +656,16 @@ mod tests { // This transaction is matching with prevouts. Therefore signing will // be successful. - let mut tx_handler = create_valid_mock_tx_handler(&actor); - actor - .sign_taproot_pubkey_spend( - &mut tx_handler, - 0, - Some(bitcoin::TapSighashType::SinglePlusAnyoneCanPay), - ) - .unwrap(); + let mut tx_handler = create_key_spend_tx_handler(&actor).1; + let sighash = tx_handler + .calculate_pubkey_spend_sighash(0, Some(bitcoin::TapSighashType::Default)) + .expect("calculating pubkey spend sighash"); + + let signature = actor.sign(sighash); + + let message = Message::from_digest(*sighash.as_byte_array()); + SECP.verify_schnorr(&signature, &message, &actor.xonly_public_key) + .expect("invalid signature"); } #[test] @@ -602,7 +676,7 @@ mod tests { // This transaction is matching with prevouts. Therefore signing will // be successful. - let tx_handler = create_valid_mock_tx_handler(&actor); + let tx_handler = create_key_spend_tx_handler(&actor).1; let x = tx_handler.calculate_pubkey_spend_sighash(0, None).unwrap(); actor.sign_with_tweak(x, None).unwrap(); } diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 063e472f..abc312dd 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -113,6 +113,7 @@ impl TxHandler { } if let Some(witness) = signer(idx, &self.txins[idx], calc_sighash)? { + self.cached_tx.input[idx].witness = witness.clone(); self.txins[idx].set_witness(witness); } } @@ -269,43 +270,6 @@ impl TxHandler { Ok(()) } - // Candidate refactoring - // pub fn set_p2tr_script_spend_witness_find>( - // &mut self, - // txin_index: usize, - // script_finder: impl Fn(&&Scripts) -> bool, - // script_spender: impl FnOnce(&Scripts) -> Witness, - // ) -> Result<(), BridgeError> { - // let txin = self - // .txins - // .get_mut(txin_index) - // ?; - - // if txin.get_witness().is_some() { - // return Err(BridgeError::WitnessAlreadySet); - // } - - // let script = txin - // .get_witness() - // .get_scripts() - // .iter() - // .find(script_finder) - // .ok_or(BridgeError::TaprootScriptError)?; - - // let spend_control_block = txin - // .get_spendable() - // .get_spend_info() - // .control_block(&((*script).to_script_buf(), LeafVersion::TapScript)) - // .ok_or(BridgeError::ControlBlockError)?; - - // let witness = script_spender(script); - - // txin.set_witness(witness); - - // self.cached_tx.input[txin_index].witness = txin.get_witness().as_ref().unwrap().clone(); - // Ok(()) - // } - pub fn set_p2tr_key_spend_witness( &mut self, signature: &taproot::Signature, @@ -421,23 +385,4 @@ impl TxHandlerBuilder { pub fn finalize_signed(self) -> Result, BridgeError> { self.finalize().promote() } - - // pub fn spend Witness>( - // &mut self, - // txin_index: usize, - // script_index: usize, - // witness_fn: T, - // ) -> Result<(), BridgeError> { - // let spendable = self - // .prev_scripts - // .get(txin_index) - // .ok_or(BridgeError::NoScriptsForTxIn(txin_index))? - // .get(script_index) - // .ok_or(BridgeError::NoScriptAtIndex(script_index))? - // .downcast::() - // .map_err(|_| BridgeError::ScriptTypeMismatch)?; - // let witness = witness_fn(spendable); - // self.tx.input_mut(txin_index).witness = witness; - // Ok(()) - // } } diff --git a/core/tests/taproot.rs b/core/tests/taproot.rs index f2e961bd..a4d12181 100644 --- a/core/tests/taproot.rs +++ b/core/tests/taproot.rs @@ -97,12 +97,10 @@ async fn create_address_and_transaction_then_sign_transaction() { config.winternitz_secret_key, config.network, ); - let sig = signer - .sign_taproot_script_spend_tx_new_tweaked(&mut tx_handler, 0, 0) - .unwrap(); - tx_handler - .set_p2tr_script_spend_witness(&[sig.as_ref()], 0, 0) - .unwrap(); + signer + .partial_sign(&mut tx_handler, &[]) + .expect("failed to sign transaction"); + rpc.mine_blocks(1).await.unwrap(); // New transaction should be OK to send. From bb87b61ecf75620859251614a9c8d13942f83bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Fri, 7 Feb 2025 13:11:31 +0300 Subject: [PATCH 07/17] chore(actor): rename winternitz partial sign --- core/src/actor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 037fe787..cbca5bbe 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -236,7 +236,7 @@ impl Actor { } // - pub fn partial_sign_winternitz_or_preimagereveal( + pub fn partial_sign_commit( &self, txhandler: &mut TxHandler, signatures: &[TaggedSignature], From 9e71637df8b0f55e4b376d0d579c57f2c2808f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Fri, 7 Feb 2025 16:47:25 +0300 Subject: [PATCH 08/17] fix: tests --- core/src/actor.rs | 18 ++++++++-------- core/src/builder/script.rs | 2 +- core/src/builder/sighash.rs | 2 +- core/src/builder/transaction/txhandler.rs | 6 +++--- core/tests/taproot.rs | 25 ++++++----------------- 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index cbca5bbe..25fa4dfe 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -422,7 +422,7 @@ mod tests { use crate::builder::address::create_taproot_address; use super::*; - use crate::builder::script::{CheckSig, ScriptKind, SpendPath, SpendableScript}; + use crate::builder::script::{CheckSig, SpendPath, SpendableScript}; use crate::builder::transaction::input::SpendableTxIn; use crate::builder::transaction::output::UnspentTxOut; use crate::builder::transaction::{TxHandler, TxHandlerBuilder}; @@ -434,11 +434,11 @@ mod tests { initialize_database, }; use bitcoin::secp256k1::{schnorr, Message, SecretKey}; - use bitcoin::sighash::SighashCache; - use bitcoin::sighash::{Prevouts, TapSighashType}; + + use bitcoin::sighash::TapSighashType; use bitcoin::transaction::Transaction; - use bitcoin::Sequence; - use bitcoin::{Amount, Network, OutPoint, ScriptBuf}; + + use bitcoin::{Amount, Network, OutPoint}; use bitvm::{ execute_script, signatures::winternitz::{ @@ -447,7 +447,7 @@ mod tests { treepp::script, }; use rand::thread_rng; - use secp256k1::{rand, SECP256K1}; + use secp256k1::rand; use std::env; use std::str::FromStr; use std::sync::Arc; @@ -583,7 +583,7 @@ mod tests { fn test_actor_script_spend_sig_valid() { let sk = SecretKey::new(&mut thread_rng()); let actor = Actor::new(sk, None, Network::Regtest); - let (prevutxo, mut txhandler) = create_script_spend_tx_handler(&actor); + let (_, mut txhandler) = create_script_spend_tx_handler(&actor); // Actor performs a partial sign for script spend. // Using an empty signature slice since our dummy CheckSig uses actor signature. @@ -604,7 +604,7 @@ mod tests { // Compute the sighash expected for a pubkey spend (similar to key spend). let sighash = txhandler - .calculate_script_spend_sighash_indexed(0, 0, TapSighashType::All) + .calculate_script_spend_sighash_indexed(0, 0, TapSighashType::Default) .expect("Sighash computed"); let message = Message::from_digest(*sighash.as_byte_array()); @@ -656,7 +656,7 @@ mod tests { // This transaction is matching with prevouts. Therefore signing will // be successful. - let mut tx_handler = create_key_spend_tx_handler(&actor).1; + let tx_handler = create_key_spend_tx_handler(&actor).1; let sighash = tx_handler .calculate_pubkey_spend_sighash(0, Some(bitcoin::TapSighashType::Default)) .expect("calculating pubkey spend sighash"); diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 2336a7c5..7cc52587 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -32,7 +32,7 @@ pub enum SpendPath { /// generate a witness, the trait object can be used to generate the script_buf. /// /// We store [`Arc`]s inside a [`super::transaction::TxHandler`] input, and we cast them into a [`ScriptKind`] when signing. -/// +/// /// When creating a new Script, make sure you add it to the [`ScriptKind`] enum and add a test for it below. /// Otherwise, it will not be spendable. pub trait SpendableScript: Send + Sync + 'static + std::any::Any { diff --git a/core/src/builder/sighash.rs b/core/src/builder/sighash.rs index 88823e8e..816640b5 100644 --- a/core/src/builder/sighash.rs +++ b/core/src/builder/sighash.rs @@ -119,7 +119,7 @@ pub fn create_nofn_sighash_stream( bridge_amount_sats: Amount, network: bitcoin::Network, ) -> impl Stream> { - use bitcoin::TapSighashType::All as SighashAll; + use bitcoin::TapSighashType::Default as SighashAll; try_stream! { // Create move_tx handler. This is unique for each deposit tx. let move_txhandler = builder::transaction::create_move_to_vault_txhandler( diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index abc312dd..53e5c2ec 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -75,12 +75,12 @@ impl TxHandler { move || -> Result { match self.txins[idx].get_spend_path() { SpendPath::KeySpend => { - self.calculate_pubkey_spend_sighash(idx, Some(TapSighashType::All)) + self.calculate_pubkey_spend_sighash(idx, Some(TapSighashType::Default)) } SpendPath::ScriptSpend(script_idx) => self.calculate_script_spend_sighash_indexed( idx, script_idx, - TapSighashType::All, + TapSighashType::Default, ), SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), } @@ -167,7 +167,7 @@ impl TxHandler { .to_script_buf(); // TODO: remove copy here - self.calculate_script_spend_sighash(txin_index, &script.clone(), sighash_type) + self.calculate_script_spend_sighash(txin_index, &script, sighash_type) } pub fn calculate_script_spend_sighash( diff --git a/core/tests/taproot.rs b/core/tests/taproot.rs index a4d12181..74c9c904 100644 --- a/core/tests/taproot.rs +++ b/core/tests/taproot.rs @@ -1,6 +1,4 @@ -use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::secp256k1::Scalar; -use bitcoin::{Address, Amount, TapTweakHash, TxOut}; +use bitcoin::{Amount, TxOut}; use bitcoincore_rpc::RpcApi; use clementine_core::actor::Actor; use clementine_core::builder::script::{CheckSig, SpendPath, SpendableScript}; @@ -30,23 +28,11 @@ async fn create_address_and_transaction_then_sign_transaction() { .unwrap(); let (xonly_pk, _) = config.secret_key.public_key(&SECP).x_only_public_key(); - let address = Address::p2tr(&SECP, xonly_pk, None, config.network); - let script = address.script_pubkey(); - let tweaked_pk_script: [u8; 32] = script.as_bytes()[2..].try_into().unwrap(); - - // Calculate tweaked public key. - let mut hasher = TapTweakHash::engine(); - hasher.input(&xonly_pk.serialize()); - xonly_pk - .add_tweak( - &SECP, - &Scalar::from_be_bytes(TapTweakHash::from_engine(hasher).to_byte_array()).unwrap(), - ) - .unwrap(); // Prepare script and address. let script = Arc::new(CheckSig::new( - bitcoin::XOnlyPublicKey::from_slice(&tweaked_pk_script).unwrap(), + // bitcoin::XOnlyPublicKey::from_slice(&tweaked_pk_script).unwrap(), + xonly_pk, )); let scripts: Vec> = vec![script.clone()]; let (taproot_address, taproot_spend_info) = builder::address::create_taproot_address( @@ -66,7 +52,7 @@ async fn create_address_and_transaction_then_sign_transaction() { let mut builder = TxHandlerBuilder::new(); builder = builder.add_input( - NormalSignatureKind::NormalSignatureUnknown, + NormalSignatureKind::NotStored, SpendableTxIn::new( utxo, TxOut { @@ -76,7 +62,7 @@ async fn create_address_and_transaction_then_sign_transaction() { scripts.clone(), Some(taproot_spend_info.clone()), ), - SpendPath::Unknown, + SpendPath::ScriptSpend(0), DEFAULT_SEQUENCE, ); @@ -97,6 +83,7 @@ async fn create_address_and_transaction_then_sign_transaction() { config.winternitz_secret_key, config.network, ); + signer .partial_sign(&mut tx_handler, &[]) .expect("failed to sign transaction"); From 7a02daf6a6300fbd3e8e5cedebd19d58af1ecff1 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 8 Feb 2025 15:48:51 +0300 Subject: [PATCH 09/17] Remove TypeId runtime for SpendableScript to get ScriptKind (#512) --- core/src/actor.rs | 4 +-- core/src/builder/script.rs | 50 +++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 25fa4dfe..06047d4b 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -264,7 +264,7 @@ impl Actor { use crate::builder::script::ScriptKind as Kind; - let mut witness = match script.into() { + let mut witness = match script.kind() { Kind::PreimageRevealScript(script) => { if script.0 != self.xonly_public_key { return Err(BridgeError::NotOwnedScriptPath); @@ -354,7 +354,7 @@ impl Actor { use crate::builder::script::ScriptKind as Kind; // Set the script inputs of the witness - let mut witness: Witness = match script.into() { + let mut witness: Witness = match script.kind() { Kind::DepositScript(script) => { match (sig, script.0 == self.xonly_public_key) { (Some(sig), _) => script.generate_script_inputs(&sig), diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 7cc52587..0506fb17 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -18,7 +18,6 @@ use bitvm::signatures::winternitz::{self, SecretKey}; use bitvm::signatures::winternitz::{Parameters, PublicKey}; use std::any::Any; use std::fmt::Debug; -use std::sync::Arc; #[derive(Debug, Copy, Clone)] pub enum SpendPath { @@ -38,6 +37,8 @@ pub enum SpendPath { pub trait SpendableScript: Send + Sync + 'static + std::any::Any { fn as_any(&self) -> &dyn Any; + fn kind(&self) -> ScriptKind; + fn to_script_buf(&self) -> ScriptBuf; } @@ -62,6 +63,10 @@ impl SpendableScript for OtherSpendable { self } + fn kind(&self) -> ScriptKind { + ScriptKind::Other(self) + } + fn to_script_buf(&self) -> ScriptBuf { self.0.clone() } @@ -89,6 +94,10 @@ impl SpendableScript for CheckSig { self } + fn kind(&self) -> ScriptKind { + ScriptKind::CheckSig(self) + } + fn to_script_buf(&self) -> ScriptBuf { Builder::new() .push_x_only_key(&self.0) @@ -115,6 +124,10 @@ impl SpendableScript for WinternitzCommit { self } + fn kind(&self) -> ScriptKind { + ScriptKind::WinternitzCommit(self) + } + fn to_script_buf(&self) -> ScriptBuf { let winternitz_pubkey = self.0.clone(); let params = self.1.clone(); @@ -171,6 +184,10 @@ impl SpendableScript for TimelockScript { self } + fn kind(&self) -> ScriptKind { + ScriptKind::TimelockScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { let script_builder = Builder::new() .push_int(self.1 as i64) @@ -209,6 +226,10 @@ impl SpendableScript for PreimageRevealScript { self } + fn kind(&self) -> ScriptKind { + ScriptKind::PreimageRevealScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { Builder::new() .push_opcode(OP_HASH160) @@ -245,6 +266,10 @@ impl SpendableScript for DepositScript { self } + fn kind(&self) -> ScriptKind { + ScriptKind::DepositScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { let citrea: [u8; 6] = "citrea".as_bytes().try_into().expect("length == 6"); @@ -281,26 +306,6 @@ pub enum ScriptKind<'a> { Other(&'a OtherSpendable), } -impl<'a> From<&'a Arc> for ScriptKind<'a> { - fn from(script: &'a Arc) -> ScriptKind<'a> { - let type_id = script.as_any().type_id(); - - if type_id == std::any::TypeId::of::() { - Self::CheckSig(script.as_any().downcast_ref().expect("just checked")) - } else if type_id == std::any::TypeId::of::() { - Self::WinternitzCommit(script.as_any().downcast_ref().expect("just checked")) - } else if type_id == std::any::TypeId::of::() { - Self::TimelockScript(script.as_any().downcast_ref().expect("just checked")) - } else if type_id == std::any::TypeId::of::() { - Self::PreimageRevealScript(script.as_any().downcast_ref().expect("just checked")) - } else if type_id == std::any::TypeId::of::() { - Self::DepositScript(script.as_any().downcast_ref().expect("just checked")) - } else { - Self::Other(script.as_any().downcast_ref().expect("just checked")) - } - } -} - #[cfg(test)] fn get_script_from_arr( arr: &Vec>, @@ -312,6 +317,7 @@ fn get_script_from_arr( #[cfg(test)] mod tests { use crate::utils; + use std::sync::Arc; use super::*; @@ -436,7 +442,7 @@ mod tests { ]; for (expected, script) in script_variants { - let kind = ScriptKind::from(&script); + let kind = script.kind(); match (expected, kind) { ("CheckSig", ScriptKind::CheckSig(_)) => (), ("WinternitzCommit", ScriptKind::WinternitzCommit(_)) => (), From a663b7981db04aab1ecc6f9af062a2e670173858 Mon Sep 17 00:00:00 2001 From: atacann Date: Sat, 8 Feb 2025 16:00:11 +0300 Subject: [PATCH 10/17] change option --- core/src/actor.rs | 4 ++-- core/src/builder/script.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 06047d4b..832f7234 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -365,9 +365,9 @@ impl Actor { } } Kind::TimelockScript(script) => match (sig, script.0) { - (Some(sig), Some(_)) => script.generate_script_inputs(&Some(sig)), + (Some(sig), Some(_)) => script.generate_script_inputs(Some(&sig)), (None, Some(xonly_key)) if xonly_key == self.xonly_public_key => { - script.generate_script_inputs(&Some(self.sign(calc_sighash()?))) + script.generate_script_inputs(Some(&self.sign(calc_sighash()?))) } (None, Some(_)) => return Err(BridgeError::SignatureNotFound), (_, None) => Witness::new(), diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 0506fb17..503c4743 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -206,7 +206,7 @@ impl SpendableScript for TimelockScript { } impl TimelockScript { - pub fn generate_script_inputs(&self, signature: &Option) -> Witness { + pub fn generate_script_inputs(&self, signature: Option<&schnorr::Signature>) -> Witness { match signature { Some(sig) => Witness::from_slice(&[sig.serialize()]), None => Witness::default(), From 59c417147f7dc757ab5ba716f12cb584b513b17f Mon Sep 17 00:00:00 2001 From: atacann Date: Sat, 8 Feb 2025 20:48:36 +0300 Subject: [PATCH 11/17] fix: errors from merge, add calculate_sighash --- core/src/actor.rs | 13 ++-- core/src/builder/script.rs | 5 ++ core/src/builder/sighash.rs | 65 +++++++++---------- .../builder/transaction/operator_assert.rs | 16 +++-- core/src/builder/transaction/txhandler.rs | 33 ++++++---- core/src/musig2.rs | 4 +- core/src/operator.rs | 2 +- core/src/rpc/verifier.rs | 2 +- core/src/test_utils.rs | 17 ++--- core/tests/musig2.rs | 8 +-- 10 files changed, 88 insertions(+), 77 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 69375bb4..36d5e2ca 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -288,7 +288,8 @@ impl Actor { Kind::CheckSig(_) | Kind::Other(_) | Kind::DepositScript(_) - | Kind::TimelockScript(_) => return Ok(None), + | Kind::TimelockScript(_) + | Kind::WithdrawalScript(_) => return Ok(None), }; if signed_winternitz { @@ -381,7 +382,8 @@ impl Actor { }, Kind::WinternitzCommit(_) | Kind::PreimageRevealScript(_) - | Kind::Other(_) => return Ok(None), + | Kind::Other(_) + | Kind::WithdrawalScript(_) => return Ok(None), }; // Add P2TR elements (control block and script) to the witness @@ -450,7 +452,6 @@ mod tests { use secp256k1::rand; use std::str::FromStr; use std::sync::Arc; - use std::thread; // Helper: create a TxHandler with a single key spend input. fn create_key_spend_tx_handler(actor: &Actor) -> (bitcoin::TxOut, TxHandler) { @@ -657,7 +658,7 @@ mod tests { // be successful. let tx_handler = create_key_spend_tx_handler(&actor).1; let sighash = tx_handler - .calculate_pubkey_spend_sighash(0, Some(bitcoin::TapSighashType::Default)) + .calculate_pubkey_spend_sighash(0, bitcoin::TapSighashType::Default) .expect("calculating pubkey spend sighash"); let signature = actor.sign(sighash); @@ -676,7 +677,9 @@ mod tests { // This transaction is matching with prevouts. Therefore signing will // be successful. let tx_handler = create_key_spend_tx_handler(&actor).1; - let x = tx_handler.calculate_pubkey_spend_sighash(0, None).unwrap(); + let x = tx_handler + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) + .unwrap(); actor.sign_with_tweak(x, None).unwrap(); } diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 814fb597..87a46278 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -305,6 +305,10 @@ impl SpendableScript for WithdrawalScript { self } + fn kind(&self) -> ScriptKind { + ScriptKind::WithdrawalScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { let mut push_bytes = PushBytesBuf::new(); push_bytes @@ -331,6 +335,7 @@ pub enum ScriptKind<'a> { TimelockScript(&'a TimelockScript), PreimageRevealScript(&'a PreimageRevealScript), DepositScript(&'a DepositScript), + WithdrawalScript(&'a WithdrawalScript), Other(&'a OtherSpendable), } diff --git a/core/src/builder/sighash.rs b/core/src/builder/sighash.rs index 38b623b9..1819402a 100644 --- a/core/src/builder/sighash.rs +++ b/core/src/builder/sighash.rs @@ -11,7 +11,7 @@ use crate::errors::BridgeError; use crate::rpc::clementine::tagged_signature::SignatureId; use crate::{builder, database::Database, EVMAddress}; use async_stream::try_stream; -use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint}; +use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint, TapSighashType}; use bitcoin::{TapSighash, Txid, XOnlyPublicKey}; use futures_core::stream::Stream; @@ -119,7 +119,6 @@ pub fn create_nofn_sighash_stream( bridge_amount_sats: Amount, network: bitcoin::Network, ) -> impl Stream> { - use bitcoin::TapSighashType::Default as SighashAll; try_stream! { // Create move_tx handler. This is unique for each deposit tx. let move_txhandler = builder::transaction::create_move_to_vault_txhandler( @@ -199,9 +198,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the challenge_tx.input[0], which spends kickoff_tx.input[1] using SinglePlusAnyoneCanPay. - yield (challenge_tx.calculate_pubkey_spend_sighash( + yield (challenge_tx.calculate_sighash( 0, - Some(bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay) + bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay )?, partial.complete(challenge_tx.get_signature_id(0)?)); // Creates the start_happy_reimburse_tx handler. @@ -211,9 +210,9 @@ pub fn create_nofn_sighash_stream( network )?; // Yields the sighash for the start_happy_reimburse_tx.input[1], which spends kickoff_tx.output[3]. - yield (start_happy_reimburse_txhandler.calculate_pubkey_spend_sighash( + yield (start_happy_reimburse_txhandler.calculate_sighash( 1, - None + TapSighashType::Default )?, partial.complete(start_happy_reimburse_txhandler.get_signature_id(1)?)); // Creates the happy_reimburse_tx handler. @@ -226,9 +225,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the happy_reimburse_tx.input[0], which spends move_to_vault_tx.output[0]. - yield (happy_reimburse_txhandler.calculate_pubkey_spend_sighash( + yield (happy_reimburse_txhandler.calculate_sighash( 0, - None + TapSighashType::Default )?, partial.complete(happy_reimburse_txhandler.get_signature_id(0)?)); // Collect the challenge Winternitz pubkeys for this specific kickoff_utxo. @@ -244,9 +243,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the watchtower_challenge_kickoff_tx.input[0], which spends kickoff_tx.input[0]. - yield (watchtower_challenge_kickoff_txhandler.calculate_pubkey_spend_sighash( + yield (watchtower_challenge_kickoff_txhandler.calculate_sighash( 0, - None, + TapSighashType::Default, )?, partial.complete(watchtower_challenge_kickoff_txhandler.get_signature_id(0)?)); // Creates the kickoff_timeout_tx handler. @@ -256,10 +255,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the kickoff_timeout_tx.input[0], which spends kickoff_tx.output[3]. - yield (kickoff_timeout_txhandler.calculate_script_spend_sighash_indexed( - 0, + yield (kickoff_timeout_txhandler.calculate_sighash( 0, - SighashAll + TapSighashType::Default )?, partial.complete(kickoff_timeout_txhandler.get_signature_id(0)?)); let public_hashes = db.get_operators_challenge_ack_hashes(None, operator_idx as i32, sequential_collateral_tx_idx as i32, kickoff_idx as i32).await?.ok_or(BridgeError::WatchtowerPublicHashesNotFound(operator_idx as i32, sequential_collateral_tx_idx as i32, kickoff_idx as i32))?; @@ -288,16 +286,15 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the operator_challenge_NACK_tx.input[0], which spends watchtower_challenge_tx.output[0]. - yield (operator_challenge_nack_txhandler.calculate_script_spend_sighash_indexed( + yield (operator_challenge_nack_txhandler.calculate_sighash( 0, - 1, - SighashAll, + TapSighashType::Default, )?, partial.complete(operator_challenge_nack_txhandler.get_signature_id(0)?)); // Yields the sighash for the operator_challenge_NACK_tx.input[1], which spends kickoff_tx.output[2]. - yield (operator_challenge_nack_txhandler.calculate_pubkey_spend_sighash( + yield (operator_challenge_nack_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default )?, partial.complete(operator_challenge_nack_txhandler.get_signature_id(1)?)); } @@ -321,9 +318,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the assert_end_tx, which spends kickoff_tx.output[3]. - yield (assert_end_txhandler.calculate_pubkey_spend_sighash( + yield (assert_end_txhandler.calculate_sighash( PARALLEL_ASSERT_TX_CHAIN_SIZE, - None, + TapSighashType::Default, )?, partial.complete(assert_end_txhandler.get_signature_id(PARALLEL_ASSERT_TX_CHAIN_SIZE)?)); // Creates the disprove_timeout_tx handler. @@ -334,16 +331,15 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the disprove_timeout_tx.input[0], which spends assert_end_tx.output[0]. - yield (disprove_timeout_txhandler.calculate_pubkey_spend_sighash( + yield (disprove_timeout_txhandler.calculate_sighash( 0, - None, + TapSighashType::Default )?, partial.complete(disprove_timeout_txhandler.get_signature_id(0)?)); // Yields the disprove_timeout_tx.input[1], which spends assert_end_tx.output[1]. - yield (disprove_timeout_txhandler.calculate_script_spend_sighash_indexed( + yield (disprove_timeout_txhandler.calculate_sighash( 1, - 0, - SighashAll, + TapSighashType::Default, )?, partial.complete(disprove_timeout_txhandler.get_signature_id(1)?)); // Creates the already_disproved_tx handler. @@ -353,10 +349,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the already_disproved_tx.input[0], which spends assert_end_tx.output[1]. - yield (already_disproved_txhandler.calculate_script_spend_sighash_indexed( + yield (already_disproved_txhandler.calculate_sighash( 0, - 1, - SighashAll, + TapSighashType::Default, )?, partial.complete(already_disproved_txhandler.get_signature_id(0)?)); // Creates the reimburse_tx handler. @@ -369,7 +364,7 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the reimburse_tx.input[0], which spends move_to_vault_tx.output[0]. - yield (reimburse_txhandler.calculate_pubkey_spend_sighash(0, None)?, partial.complete(reimburse_txhandler.get_signature_id(0)?)); + yield (reimburse_txhandler.calculate_sighash(0, TapSighashType::Default)?, partial.complete(reimburse_txhandler.get_signature_id(0)?)); } input_txid = *reimburse_generator_txhandler.get_txid(); @@ -465,9 +460,9 @@ pub fn create_operator_sighash_stream( )?; // Yields the sighash for the kickoff_timeout_tx.input[0], which spends kickoff_tx.output[3]. - yield (kickoff_timeout_txhandler.calculate_pubkey_spend_sighash( + yield (kickoff_timeout_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default, )?, partial.complete(kickoff_timeout_txhandler.get_signature_id(1)?)); let (assert_tx_addrs, root_hash, _public_input_wots) = db.get_bitvm_setup(None, operator_idx as i32, sequential_collateral_idx as i32, kickoff_utxo_idx as i32).await?.ok_or(BridgeError::BitvmSetupNotFound(operator_idx as i32, sequential_collateral_idx as i32, kickoff_utxo_idx as i32))?; @@ -496,9 +491,9 @@ pub fn create_operator_sighash_stream( )?; // Yields the sighash for the already_disproved_tx.input[0], which spends assert_end_tx.output[1]. - yield (already_disproved_txhandler.calculate_pubkey_spend_sighash( + yield (already_disproved_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default, )?, partial.complete(already_disproved_txhandler.get_signature_id(1)?)); let disprove_txhandler = builder::transaction::create_disprove_txhandler( @@ -507,9 +502,9 @@ pub fn create_operator_sighash_stream( )?; // Yields the sighash for the disprove_tx.input[1], which spends sequential_collateral_tx.output[0]. - yield (disprove_txhandler.calculate_pubkey_spend_sighash( + yield (disprove_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default, )?, partial.complete(disprove_txhandler.get_signature_id(1)?)); } diff --git a/core/src/builder/transaction/operator_assert.rs b/core/src/builder/transaction/operator_assert.rs index d9489b89..90b0ab24 100644 --- a/core/src/builder/transaction/operator_assert.rs +++ b/core/src/builder/transaction/operator_assert.rs @@ -196,20 +196,22 @@ pub fn create_assert_end_txhandler( // Add outputs Ok(builder - .add_output(UnspentTxOut::from_partial(TxOut { - value: MIN_TAPROOT_AMOUNT, - script_pubkey: disprove_address.script_pubkey().clone(), - })) + .add_output(UnspentTxOut::new( + TxOut { + value: MIN_TAPROOT_AMOUNT, + script_pubkey: disprove_address.script_pubkey().clone(), + }, + vec![Arc::new(CheckSig::new(nofn_xonly_pk))], + None, // not disprove_taproot_spend_info as it will cause check to fail because we do not store all scripts + )) .add_output(UnspentTxOut::from_scripts( MIN_TAPROOT_AMOUNT, vec![nofn_1week, nofn_2week], None, network, )) - .add_output(UnspentTxOut::new( + .add_output(UnspentTxOut::from_partial( builder::transaction::anchor_output(), - vec![], - None, )) .finalize()) } diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 1aadf009..215e3554 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -73,7 +73,7 @@ impl TxHandler { move || -> Result { match self.txins[idx].get_spend_path() { SpendPath::KeySpend => { - self.calculate_pubkey_spend_sighash(idx, Some(TapSighashType::Default)) + self.calculate_pubkey_spend_sighash(idx, TapSighashType::Default) } SpendPath::ScriptSpend(script_idx) => self.calculate_script_spend_sighash_indexed( idx, @@ -121,7 +121,7 @@ impl TxHandler { pub fn calculate_pubkey_spend_sighash( &self, txin_index: usize, - sighash_type: Option, + sighash_type: TapSighashType, ) -> Result { let prevouts_vec: Vec<&TxOut> = self .txins @@ -130,20 +130,17 @@ impl TxHandler { .collect(); // TODO: Maybe there is a better way to do this let mut sighash_cache: SighashCache<&bitcoin::Transaction> = SighashCache::new(&self.cached_tx); - let prevouts = &match sighash_type { - Some(TapSighashType::SinglePlusAnyoneCanPay) - | Some(TapSighashType::AllPlusAnyoneCanPay) - | Some(TapSighashType::NonePlusAnyoneCanPay) => { + let prevouts = match sighash_type { + TapSighashType::SinglePlusAnyoneCanPay + | TapSighashType::AllPlusAnyoneCanPay + | TapSighashType::NonePlusAnyoneCanPay => { bitcoin::sighash::Prevouts::One(txin_index, prevouts_vec[txin_index]) } _ => bitcoin::sighash::Prevouts::All(&prevouts_vec), }; - let sig_hash = sighash_cache.taproot_key_spend_signature_hash( - txin_index, - prevouts, - sighash_type.unwrap_or(TapSighashType::Default), - )?; + let sig_hash = + sighash_cache.taproot_key_spend_signature_hash(txin_index, &prevouts, sighash_type)?; Ok(sig_hash) } @@ -200,6 +197,20 @@ impl TxHandler { Ok(sig_hash) } + + pub fn calculate_sighash( + &self, + txin_index: usize, + sighash_type: TapSighashType, + ) -> Result { + match self.txins[txin_index].get_spend_path() { + SpendPath::ScriptSpend(idx) => { + self.calculate_script_spend_sighash_indexed(txin_index, idx, sighash_type) + } + SpendPath::KeySpend => self.calculate_pubkey_spend_sighash(txin_index, sighash_type), + SpendPath::Unknown => Err(BridgeError::MissingSpendInfo), + } + } } impl TxHandler { diff --git a/core/src/musig2.rs b/core/src/musig2.rs index a09fa892..a221a9be 100644 --- a/core/src/musig2.rs +++ b/core/src/musig2.rs @@ -244,7 +244,7 @@ mod tests { key::Keypair, script, secp256k1::{schnorr, Message, PublicKey}, - Amount, OutPoint, TapNodeHash, TxOut, Txid, XOnlyPublicKey, + Amount, OutPoint, TapNodeHash, TapSighashType, TxOut, Txid, XOnlyPublicKey, }; use secp256k1::{musig::MusigPartialSignature, rand::Rng}; use std::sync::Arc; @@ -547,7 +547,7 @@ mod tests { let message = Message::from_digest( tx_details - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); diff --git a/core/src/operator.rs b/core/src/operator.rs index 57796d85..c6077e88 100644 --- a/core/src/operator.rs +++ b/core/src/operator.rs @@ -440,7 +440,7 @@ impl Operator { let sighash = payout_txhandler.calculate_pubkey_spend_sighash( 0, - Some(bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay), + bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay, )?; SECP.verify_schnorr( diff --git a/core/src/rpc/verifier.rs b/core/src/rpc/verifier.rs index 1fed0a1a..bc8de114 100644 --- a/core/src/rpc/verifier.rs +++ b/core/src/rpc/verifier.rs @@ -470,7 +470,7 @@ impl ClementineVerifier for Verifier { })?; nonce_idx += 1; - tracing::info!( + tracing::debug!( "Verifier {} signed sighash {} of {}", verifier.idx, nonce_idx, diff --git a/core/src/test_utils.rs b/core/src/test_utils.rs index ef9052b7..ae1a7e8e 100644 --- a/core/src/test_utils.rs +++ b/core/src/test_utils.rs @@ -457,7 +457,7 @@ macro_rules! generate_withdrawal_transaction_and_signature { value: $withdrawal_amount, script_pubkey: $withdrawal_address.script_pubkey(), }; - let txout = builder::transaction::output::UnspentTxOut::new(txout.clone(), vec![], None); + let txout = builder::transaction::output::UnspentTxOut::from_partial(txout.clone()); let tx = builder::transaction::TxHandlerBuilder::new() .add_input( @@ -468,18 +468,13 @@ macro_rules! generate_withdrawal_transaction_and_signature { ) .add_output(txout.clone()) .finalize(); - let mut tx = tx.get_cached_tx().clone(); - let prevouts = vec![dust_utxo.txout.clone()]; - - let sig = signer - .sign_taproot_pubkey_spend_tx_with_sighash( - &mut tx, - &prevouts, - 0, - Some(bitcoin::TapSighashType::SinglePlusAnyoneCanPay), - ) + + let sighash = tx + .calculate_sighash(0, bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay) .unwrap(); + let sig = signer.sign_with_tweak(sighash, None).unwrap(); + (dust_utxo, txout, sig) }}; } diff --git a/core/tests/musig2.rs b/core/tests/musig2.rs index 46ea70b4..4a48af3c 100644 --- a/core/tests/musig2.rs +++ b/core/tests/musig2.rs @@ -1,6 +1,6 @@ use bitcoin::key::Keypair; use bitcoin::secp256k1::{Message, PublicKey}; -use bitcoin::{hashes::Hash, script, Amount}; +use bitcoin::{hashes::Hash, script, Amount, TapSighashType}; use bitcoin::{taproot, Sequence, TxOut, XOnlyPublicKey}; use bitcoincore_rpc::RpcApi; use clementine_core::builder::script::{CheckSig, OtherSpendable, SpendPath, SpendableScript}; @@ -116,7 +116,7 @@ async fn key_spend() { let message = Message::from_digest( tx_details - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); @@ -226,7 +226,7 @@ async fn key_spend_with_script() { let mut tx_details = builder.finalize(); let message = Message::from_digest( tx_details - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); @@ -511,7 +511,7 @@ async fn key_and_script_spend() { ); let sighash_2 = Message::from_digest( test_txhandler_2 - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); From 05e4c5ce8e14e702d2256689252ca7cba6bf7b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Mon, 10 Feb 2025 11:04:55 +0300 Subject: [PATCH 12/17] fix: add spend info to assert end txhandler --- core/src/builder/transaction/operator_assert.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/builder/transaction/operator_assert.rs b/core/src/builder/transaction/operator_assert.rs index 90b0ab24..a1150ecd 100644 --- a/core/src/builder/transaction/operator_assert.rs +++ b/core/src/builder/transaction/operator_assert.rs @@ -1,5 +1,5 @@ use crate::builder; -use crate::builder::script::TimelockScript; +use crate::builder::script::{SpendableScript, TimelockScript}; pub use crate::builder::transaction::txhandler::TxHandler; pub use crate::builder::transaction::*; use crate::constants::{BLOCKS_PER_WEEK, MIN_TAPROOT_AMOUNT, PARALLEL_ASSERT_TX_CHAIN_SIZE}; @@ -176,8 +176,10 @@ pub fn create_assert_end_txhandler( ); let disprove_taproot_spend_info = TaprootBuilder::new() - .add_hidden_node(0, TapNodeHash::from_byte_array(*root_hash)) - .expect("empty taptree will accept a node at depth 0") + .add_hidden_node(1, TapNodeHash::from_byte_array(*root_hash)) + .expect("empty taptree will accept a node at depth 1") + .add_leaf(1, CheckSig(nofn_xonly_pk).to_script_buf()) + .expect("taptree with one node at depth 1 will accept a script node") .finalize(&SECP, nofn_xonly_pk) // TODO: we should convert this to script spend but we only have partial access to the taptree .expect("finalize always succeeds for taptree with single node at depth 0"); @@ -202,7 +204,7 @@ pub fn create_assert_end_txhandler( script_pubkey: disprove_address.script_pubkey().clone(), }, vec![Arc::new(CheckSig::new(nofn_xonly_pk))], - None, // not disprove_taproot_spend_info as it will cause check to fail because we do not store all scripts + Some(disprove_taproot_spend_info), // not disprove_taproot_spend_info as it will cause check to fail because we do not store all scripts )) .add_output(UnspentTxOut::from_scripts( MIN_TAPROOT_AMOUNT, From 5b6de01f691a6c4f3081675d5730786445cf0f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Mon, 10 Feb 2025 16:52:16 +0300 Subject: [PATCH 13/17] fix: winternitz commit script, add tests --- core/src/actor.rs | 99 ++++-- core/src/builder/script.rs | 383 ++++++++++++++++++++-- core/src/builder/transaction/challenge.rs | 5 +- core/src/builder/transaction/txhandler.rs | 6 + core/src/constants.rs | 2 + core/src/errors.rs | 2 + 6 files changed, 444 insertions(+), 53 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index 36d5e2ca..be52c5a2 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -235,15 +235,80 @@ impl Actor { Ok(()) } - // - pub fn partial_sign_commit( + pub fn sign_one_preimage_reveal( + &self, + txhandler: &mut TxHandler, + data: impl AsRef<[u8]>, + ) -> Result<(), BridgeError> { + let mut signed_preimage = false; + + let data = data.as_ref(); + let signer = + move |_: usize, + spt: &SpentTxIn, + calc_sighash: Box Result + '_>| + -> Result, BridgeError> { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or(BridgeError::MissingSpendInfo)?; + match spt.get_spend_path() { + SpendPath::ScriptSpend(script_idx) => { + let script = spt + .get_spendable() + .get_scripts() + .get(script_idx) + .ok_or(BridgeError::NoScriptAtIndex(script_idx))?; + + use crate::builder::script::ScriptKind as Kind; + + let mut witness = match script.kind() { + Kind::PreimageRevealScript(script) => { + if script.0 != self.xonly_public_key { + return Err(BridgeError::NotOwnedScriptPath); + } + script.generate_script_inputs(data, &self.sign(calc_sighash()?)) + } + Kind::WinternitzCommit(_) + | Kind::CheckSig(_) + | Kind::Other(_) + | Kind::DepositScript(_) + | Kind::TimelockScript(_) + | Kind::WithdrawalScript(_) => return Ok(None), + }; + + if signed_preimage { + return Err(BridgeError::MultiplePreimageRevealScripts); + } + + signed_preimage = true; + + Self::add_script_path_to_witness( + &mut witness, + &script.to_script_buf(), + spendinfo, + )?; + + Ok(Some(witness)) + } + SpendPath::KeySpend => Ok(None), + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + }; + + txhandler.sign_txins(signer)?; + Ok(()) + } + pub fn sign_one_winternitz( &self, txhandler: &mut TxHandler, - signatures: &[TaggedSignature], data: &Vec, + path: WinternitzDerivationPath, ) -> Result<(), BridgeError> { let mut signed_winternitz = false; + let data = data.as_ref(); let signer = move |_: usize, spt: &SpentTxIn, @@ -253,14 +318,14 @@ impl Actor { .get_spendable() .get_spend_info() .as_ref() - .ok_or_else(|| BridgeError::MissingSpendInfo)?; + .ok_or(BridgeError::MissingSpendInfo)?; match spt.get_spend_path() { SpendPath::ScriptSpend(script_idx) => { let script = spt .get_spendable() .get_scripts() .get(script_idx) - .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; + .ok_or(BridgeError::NoScriptAtIndex(script_idx))?; use crate::builder::script::ScriptKind as Kind; @@ -272,16 +337,12 @@ impl Actor { script.generate_script_inputs(data, &self.sign(calc_sighash()?)) } Kind::WinternitzCommit(script) => { - if script.2 != self.xonly_public_key { + if script.1 != self.xonly_public_key { return Err(BridgeError::NotOwnedScriptPath); } script.generate_script_inputs( data, - &self - .winternitz_secret_key - .ok_or(BridgeError::NoWinternitzSecretKey)? - .as_ref() - .to_vec(), + &self.get_derived_winternitz_sk(path)?, &self.sign(calc_sighash()?), ) } @@ -303,22 +364,10 @@ impl Actor { &script.to_script_buf(), spendinfo, )?; + Ok(Some(witness)) } - SpendPath::KeySpend => { - let xonly_public_key = spendinfo.internal_key(); - if xonly_public_key == self.xonly_public_key { - let sighash = calc_sighash()?; - // TODO: get Schnorr sigs, not Vec, pref in HashMap - let sig = Self::get_saved_signature(spt.get_signature_id(), signatures); - let sig = match sig { - Some(sig) => sig, - None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, - }; - return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); - } - Err(NotOwnKeyPath) - } + SpendPath::KeySpend => Ok(None), SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), } }; diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 87a46278..8ef410e9 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -5,6 +5,7 @@ // Currently generate_witness functions are not yet used. #![allow(dead_code)] +use crate::constants::WINTERNITZ_LOG_D; use crate::{utils, EVMAddress}; use bitcoin::opcodes::OP_TRUE; use bitcoin::script::PushBytesBuf; @@ -15,7 +16,7 @@ use bitcoin::{ ScriptBuf, XOnlyPublicKey, }; use bitcoin::{Amount, Witness}; -use bitvm::signatures::winternitz::{self, SecretKey}; +use bitvm::signatures::winternitz::SecretKey; use bitvm::signatures::winternitz::{Parameters, PublicKey}; use std::any::Any; use std::fmt::Debug; @@ -118,8 +119,9 @@ impl CheckSig { } /// Struct for scripts that commit to a message using Winternitz keys +/// Contains the Winternitz PK, CheckSig PK, message length respectively #[derive(Clone)] -pub struct WinternitzCommit(PublicKey, Parameters, pub(crate) XOnlyPublicKey); +pub struct WinternitzCommit(PublicKey, pub(crate) XOnlyPublicKey, u32); impl SpendableScript for WinternitzCommit { fn as_any(&self) -> &dyn Any { self @@ -131,38 +133,43 @@ impl SpendableScript for WinternitzCommit { fn to_script_buf(&self) -> ScriptBuf { let winternitz_pubkey = self.0.clone(); - let params = self.1.clone(); - let xonly_pubkey = self.2; - let verifier = winternitz::Winternitz::< - winternitz::ListpickVerifier, - winternitz::TabledConverter, - >::new(); - verifier - .checksig_verify(¶ms, &winternitz_pubkey) - .push_x_only_key(&xonly_pubkey) + let params = self.get_params(); + let xonly_pubkey = self.1; + + let mut a = bitvm::signatures::winternitz_hash::WINTERNITZ_MESSAGE_VERIFIER + .checksig_verify(¶ms, &winternitz_pubkey); + + for _ in 0..self.2 { + a = a.push_opcode(OP_DROP) + } + + a.push_x_only_key(&xonly_pubkey) .push_opcode(OP_CHECKSIG) .compile() } } impl WinternitzCommit { + pub fn get_params(&self) -> Parameters { + Parameters::new(self.2, WINTERNITZ_LOG_D) + } pub fn generate_script_inputs( &self, commit_data: &Vec, secret_key: &SecretKey, signature: &schnorr::Signature, ) -> Witness { - let verifier = winternitz::Winternitz::< - winternitz::ListpickVerifier, - winternitz::TabledConverter, - >::new(); - let mut witness = verifier.sign(&self.1, secret_key, commit_data); + let mut witness = Witness::new(); witness.push(signature.serialize()); + bitvm::signatures::winternitz_hash::WINTERNITZ_MESSAGE_VERIFIER + .sign(&self.get_params(), secret_key, commit_data) + .into_iter() + .for_each(|x| witness.push(x)); witness } - pub fn new(pubkey: PublicKey, params: Parameters, xonly_pubkey: XOnlyPublicKey) -> Self { - Self(pubkey, params, xonly_pubkey) + pub fn new(pubkey: PublicKey, xonly_pubkey: XOnlyPublicKey, msg_len: u32) -> Self { + Self(pubkey, xonly_pubkey, msg_len) } } @@ -248,8 +255,9 @@ impl PreimageRevealScript { preimage: impl AsRef<[u8]>, signature: &schnorr::Signature, ) -> Witness { - let mut witness = Witness::from_slice(&[preimage]); + let mut witness = Witness::new(); witness.push(signature.serialize()); + witness.push(preimage.as_ref()); witness } @@ -292,8 +300,8 @@ impl DepositScript { Witness::from_slice(&[signature.serialize()]) } - pub fn new(xonly_pk: XOnlyPublicKey, evm_address: EVMAddress, amount: Amount) -> Self { - Self(xonly_pk, evm_address, amount) + pub fn new(nofn_xonly_pk: XOnlyPublicKey, evm_address: EVMAddress, amount: Amount) -> Self { + Self(nofn_xonly_pk, evm_address, amount) } } @@ -349,12 +357,17 @@ fn get_script_from_arr( } #[cfg(test)] mod tests { - use crate::utils; + use crate::actor::{Actor, TxType, WinternitzDerivationPath}; + use crate::extended_rpc::ExtendedRpc; + use crate::{create_test_config_with_thread_name, utils}; use std::sync::Arc; use super::*; + use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; + use bitcoincore_rpc::RpcApi; + use secp256k1::rand; // Create some dummy values for testing. // Note: These values are not cryptographically secure and are only used for tests. fn dummy_xonly() -> XOnlyPublicKey { @@ -387,8 +400,8 @@ mod tests { Box::new(CheckSig::new(dummy_xonly())), Box::new(WinternitzCommit::new( vec![[0u8; 20]; 32], - dummy_params(), dummy_xonly(), + 32, )), Box::new(TimelockScript::new(Some(dummy_xonly()), 10)), Box::new(PreimageRevealScript::new(dummy_xonly(), [0; 20])), @@ -451,8 +464,8 @@ mod tests { "WinternitzCommit", Arc::new(WinternitzCommit::new( vec![[0u8; 20]; 32], - dummy_params(), dummy_xonly(), + 32, )), ), ( @@ -487,4 +500,326 @@ mod tests { } } } + // Tests for the spendability of all scripts + use crate::builder; + use crate::builder::transaction::input::SpendableTxIn; + use crate::builder::transaction::output::UnspentTxOut; + use crate::builder::transaction::{TxHandlerBuilder, DEFAULT_SEQUENCE}; + use crate::utils::SECP; + use bitcoin::{Amount, Sequence, TxOut}; + + async fn create_taproot_test_tx( + rpc: &ExtendedRpc, + scripts: Vec>, + spend_path: SpendPath, + amount: Amount, + ) -> (TxHandlerBuilder, bitcoin::Address) { + let (address, taproot_spend_info) = builder::address::create_taproot_address( + &scripts + .iter() + .map(|s| s.to_script_buf()) + .collect::>(), + None, + bitcoin::Network::Regtest, + ); + + let outpoint = rpc.send_to_address(&address, amount).await.unwrap(); + let sequence = if let SpendPath::ScriptSpend(idx) = spend_path { + if let Some(script) = scripts.get(idx) { + match script.kind() { + ScriptKind::TimelockScript(&TimelockScript(_, seq)) => { + Sequence::from_height(seq) + } + _ => DEFAULT_SEQUENCE, + } + } else { + DEFAULT_SEQUENCE + } + } else { + DEFAULT_SEQUENCE + }; + let mut builder = TxHandlerBuilder::new(); + builder = builder.add_input( + crate::rpc::clementine::NormalSignatureKind::NotStored, + SpendableTxIn::new( + outpoint, + TxOut { + value: amount, + script_pubkey: address.script_pubkey(), + }, + scripts.clone(), + Some(taproot_spend_info.clone()), + ), + spend_path, + sequence, + ); + + builder = builder.add_output(UnspentTxOut::new( + TxOut { + value: amount - Amount::from_sat(5000), // Subtract fee + script_pubkey: address.script_pubkey(), + }, + scripts, + Some(taproot_spend_info), + )); + + (builder, address) + } + + use crate::{ + config::BridgeConfig, database::Database, initialize_database, utils::initialize_logger, + }; + + #[tokio::test] + #[serial_test::serial] + async fn test_checksig_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let scripts: Vec> = vec![Arc::new(CheckSig::new(xonly_pk))]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + // Should be able to sign with the key + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .partial_sign(&mut tx, &[]) + .expect("should be able to sign checksig"); + let tx = tx + .promote() + .expect("the transaction should be fully signed"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_winternitz_commit_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let derivation = WinternitzDerivationPath { + message_length: 64, + log_d: 4, + tx_type: TxType::BitVM, + index: Default::default(), + operator_idx: Default::default(), + watchtower_idx: Default::default(), + sequential_collateral_tx_idx: Some(0), + kickoff_idx: Some(0), + intermediate_step_name: None, + }; + let signer = Actor::new( + kp.secret_key(), + Some(kp.secret_key()), + bitcoin::Network::Regtest, + ); + + let script: Arc = Arc::new(WinternitzCommit::new( + signer + .derive_winternitz_pk(derivation) + .expect("failed to derive Winternitz public key"), + xonly_pk, + 64, + )); + + let scripts = vec![script]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + signer + .sign_one_winternitz(&mut tx, &vec![0; 32], derivation) + .expect("failed to partially sign commitments"); + + let tx = tx + .promote() + .expect("the transaction should be fully signed"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_timelock_script_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let scripts: Vec> = + vec![Arc::new(TimelockScript::new(Some(xonly_pk), 15))]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + + let mut tx = builder.finalize(); + + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .partial_sign(&mut tx, &[]) + .expect("should be able to sign timelock"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect_err("should not pass without 15 blocks"); + + rpc.mine_blocks(15).await.expect("failed to mine blocks"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("should pass after 15 blocks"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_preimage_reveal_script_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let preimage = [1; 20]; + let hash = bitcoin::hashes::hash160::Hash::hash(&preimage); + let script: Arc = + Arc::new(PreimageRevealScript::new(xonly_pk, hash.to_byte_array())); + let scripts = vec![script]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .sign_one_preimage_reveal(&mut tx, preimage.to_vec()) + .expect("failed to sign preimage reveal"); + + let final_tx = tx + .promote() + .expect("the transaction should be fully signed"); + + rpc.client + .send_raw_transaction(final_tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_deposit_script_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let script: Arc = Arc::new(DepositScript::new( + xonly_pk, + EVMAddress([2; 20]), + Amount::from_sat(50), + )); + let scripts = vec![script]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .partial_sign(&mut tx, &[]) + .expect("should be able to sign deposit"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } } diff --git a/core/src/builder/transaction/challenge.rs b/core/src/builder/transaction/challenge.rs index b43a3f2a..2ca17fb8 100644 --- a/core/src/builder/transaction/challenge.rs +++ b/core/src/builder/transaction/challenge.rs @@ -7,7 +7,6 @@ use crate::constants::{BLOCKS_PER_WEEK, OPERATOR_CHALLENGE_AMOUNT}; use crate::errors::BridgeError; use crate::rpc::clementine::{NormalSignatureKind, WatchtowerSignatureKind}; use bitcoin::{Amount, ScriptBuf, Sequence, TxOut, XOnlyPublicKey}; -use bitvm::signatures::winternitz; use std::sync::Arc; /// Creates a [`TxHandler`] for the `watchtower_challenge_kickoff_tx`. This transaction can be sent by anyone. @@ -27,13 +26,11 @@ pub fn create_watchtower_challenge_kickoff_txhandler( DEFAULT_SEQUENCE, ); - let wots_params = winternitz::Parameters::new(240, 4); - for i in 0..num_watchtowers { let winternitz_commit = Arc::new(WinternitzCommit::new( watchtower_challenge_winternitz_pks[i as usize].clone(), - wots_params.clone(), watchtower_xonly_pks[i as usize], + 240, )); builder = builder.add_output(UnspentTxOut::from_scripts( Amount::from_sat(2000), // TODO: Hand calculate this diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 215e3554..471edf77 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -211,6 +211,11 @@ impl TxHandler { SpendPath::Unknown => Err(BridgeError::MissingSpendInfo), } } + + #[cfg(test)] + pub fn get_input_txout(&self, input_idx: usize) -> &TxOut { + self.txins[input_idx].get_spendable().get_prevout() + } } impl TxHandler { @@ -394,4 +399,5 @@ impl TxHandlerBuilder { pub fn finalize_signed(self) -> Result, BridgeError> { self.finalize().promote() } + } diff --git a/core/src/constants.rs b/core/src/constants.rs index 63371aca..5fb883a8 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -20,3 +20,5 @@ pub const ANCHOR_AMOUNT: Amount = Amount::from_sat(240); // TODO: This will chan pub const OPERATOR_CHALLENGE_AMOUNT: Amount = Amount::from_sat(200_000_000); pub const BLOCKS_PER_WEEK: u16 = 6 * 24 * 7; + +pub const WINTERNITZ_LOG_D: u32 = 4; \ No newline at end of file diff --git a/core/src/errors.rs b/core/src/errors.rs index 66a42bb4..17a9c335 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -261,6 +261,8 @@ pub enum BridgeError { #[error("Encountered multiple winternitz scripts when attempting to commit to only one.")] MultipleWinternitzScripts, + #[error("Encountered multiple preimage reveal scripts when attempting to commit to only one.")] + MultiplePreimageRevealScripts, } impl From for ErrorObject<'static> { From 51d2f33ea4fec45f5bdade48b15656b0d7cc6dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Mon, 10 Feb 2025 16:53:45 +0300 Subject: [PATCH 14/17] chore: cargo fmt --- core/src/builder/transaction/txhandler.rs | 1 - core/src/constants.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 471edf77..1ffdcb88 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -399,5 +399,4 @@ impl TxHandlerBuilder { pub fn finalize_signed(self) -> Result, BridgeError> { self.finalize().promote() } - } diff --git a/core/src/constants.rs b/core/src/constants.rs index 5fb883a8..99677666 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -21,4 +21,4 @@ pub const OPERATOR_CHALLENGE_AMOUNT: Amount = Amount::from_sat(200_000_000); pub const BLOCKS_PER_WEEK: u16 = 6 * 24 * 7; -pub const WINTERNITZ_LOG_D: u32 = 4; \ No newline at end of file +pub const WINTERNITZ_LOG_D: u32 = 4; From e72107056759fa2675c5b8956cdf57589c571fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Mon, 10 Feb 2025 17:16:00 +0300 Subject: [PATCH 15/17] chore: clippy --- core/src/actor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index be52c5a2..cabfc78d 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -308,7 +308,6 @@ impl Actor { ) -> Result<(), BridgeError> { let mut signed_winternitz = false; - let data = data.as_ref(); let signer = move |_: usize, spt: &SpentTxIn, From 502a5cf2a3b8902cb1c446966d93a36022714f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Mon, 10 Feb 2025 17:20:03 +0300 Subject: [PATCH 16/17] chore: rename functions and clippy --- core/src/actor.rs | 21 ++++++++------------- core/src/builder/script.rs | 10 +++++----- core/tests/taproot.rs | 2 +- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index cabfc78d..e0546e16 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -235,7 +235,7 @@ impl Actor { Ok(()) } - pub fn sign_one_preimage_reveal( + pub fn tx_sign_preimage( &self, txhandler: &mut TxHandler, data: impl AsRef<[u8]>, @@ -300,7 +300,7 @@ impl Actor { txhandler.sign_txins(signer)?; Ok(()) } - pub fn sign_one_winternitz( + pub fn tx_sign_winternitz( &self, txhandler: &mut TxHandler, data: &Vec, @@ -329,12 +329,6 @@ impl Actor { use crate::builder::script::ScriptKind as Kind; let mut witness = match script.kind() { - Kind::PreimageRevealScript(script) => { - if script.0 != self.xonly_public_key { - return Err(BridgeError::NotOwnedScriptPath); - } - script.generate_script_inputs(data, &self.sign(calc_sighash()?)) - } Kind::WinternitzCommit(script) => { if script.1 != self.xonly_public_key { return Err(BridgeError::NotOwnedScriptPath); @@ -345,7 +339,8 @@ impl Actor { &self.sign(calc_sighash()?), ) } - Kind::CheckSig(_) + Kind::PreimageRevealScript(_) + | Kind::CheckSig(_) | Kind::Other(_) | Kind::DepositScript(_) | Kind::TimelockScript(_) @@ -375,7 +370,7 @@ impl Actor { Ok(()) } - pub fn partial_sign( + pub fn tx_sign_and_fill_sigs( &self, txhandler: &mut TxHandler, signatures: &[TaggedSignature], @@ -597,7 +592,7 @@ mod tests { // Actor signs the key spend input. actor - .partial_sign(&mut txhandler, &[]) + .tx_sign_and_fill_sigs(&mut txhandler, &[]) .expect("Key spend signature should succeed"); // Retrieve the cached transaction from the txhandler. @@ -617,7 +612,7 @@ mod tests { // Using an empty signature slice since our dummy CheckSig uses actor signature. let signatures: Vec<_> = vec![]; actor - .partial_sign(&mut txhandler, &signatures) + .tx_sign_and_fill_sigs(&mut txhandler, &signatures) .expect("Script spend partial sign should succeed"); // Retrieve the cached transaction. @@ -637,7 +632,7 @@ mod tests { // Using an empty signature slice since our dummy CheckSig uses actor signature. let signatures: Vec<_> = vec![]; actor - .partial_sign(&mut txhandler, &signatures) + .tx_sign_and_fill_sigs(&mut txhandler, &signatures) .expect("Script spend partial sign should succeed"); // Retrieve the cached transaction. diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 8ef410e9..38e0587e 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -603,7 +603,7 @@ mod tests { ); signer - .partial_sign(&mut tx, &[]) + .tx_sign_and_fill_sigs(&mut tx, &[]) .expect("should be able to sign checksig"); let tx = tx .promote() @@ -666,7 +666,7 @@ mod tests { let mut tx = builder.finalize(); signer - .sign_one_winternitz(&mut tx, &vec![0; 32], derivation) + .tx_sign_winternitz(&mut tx, &vec![0; 32], derivation) .expect("failed to partially sign commitments"); let tx = tx @@ -713,7 +713,7 @@ mod tests { ); signer - .partial_sign(&mut tx, &[]) + .tx_sign_and_fill_sigs(&mut tx, &[]) .expect("should be able to sign timelock"); rpc.client @@ -764,7 +764,7 @@ mod tests { ); signer - .sign_one_preimage_reveal(&mut tx, preimage.to_vec()) + .tx_sign_preimage(&mut tx, preimage.to_vec()) .expect("failed to sign preimage reveal"); let final_tx = tx @@ -814,7 +814,7 @@ mod tests { ); signer - .partial_sign(&mut tx, &[]) + .tx_sign_and_fill_sigs(&mut tx, &[]) .expect("should be able to sign deposit"); rpc.client diff --git a/core/tests/taproot.rs b/core/tests/taproot.rs index 03e5453c..39a673dd 100644 --- a/core/tests/taproot.rs +++ b/core/tests/taproot.rs @@ -84,7 +84,7 @@ async fn create_address_and_transaction_then_sign_transaction() { ); signer - .partial_sign(&mut tx_handler, &[]) + .tx_sign_and_fill_sigs(&mut tx_handler, &[]) .expect("failed to sign transaction"); rpc.mine_blocks(1).await.unwrap(); From 9fce3fbda41cfef2ce8e8dbce5d60f36840be9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Mon, 10 Feb 2025 17:23:34 +0300 Subject: [PATCH 17/17] chore: clippy --- core/src/builder/script.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 38e0587e..1b47a102 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -764,7 +764,7 @@ mod tests { ); signer - .tx_sign_preimage(&mut tx, preimage.to_vec()) + .tx_sign_preimage(&mut tx, preimage) .expect("failed to sign preimage reveal"); let final_tx = tx