@@ -229,14 +229,55 @@ impl BitGoPsbt {
229229 ) ) ,
230230 }
231231 }
232+
233+ /// Sign the PSBT with the provided key.
234+ /// Wraps the underlying PSBT's sign method from miniscript::psbt::PsbtExt.
235+ ///
236+ /// # Type Parameters
237+ /// - `C`: Signing context from secp256k1
238+ /// - `K`: Key type that implements `psbt::GetKey` trait
239+ ///
240+ /// # Returns
241+ /// - `Ok(SigningKeysMap)` on success, mapping input index to keys used for signing
242+ /// - `Err((SigningKeysMap, SigningErrors))` on failure, containing both partial success info and errors
243+ pub fn sign < C , K > (
244+ & mut self ,
245+ k : & K ,
246+ secp : & secp256k1:: Secp256k1 < C > ,
247+ ) -> Result <
248+ miniscript:: bitcoin:: psbt:: SigningKeysMap ,
249+ (
250+ miniscript:: bitcoin:: psbt:: SigningKeysMap ,
251+ miniscript:: bitcoin:: psbt:: SigningErrors ,
252+ ) ,
253+ >
254+ where
255+ C : secp256k1:: Signing + secp256k1:: Verification ,
256+ K : miniscript:: bitcoin:: psbt:: GetKey ,
257+ {
258+ match self {
259+ BitGoPsbt :: BitcoinLike ( ref mut psbt, _network) => psbt. sign ( k, secp) ,
260+ BitGoPsbt :: Zcash ( _zcash_psbt, _network) => {
261+ // Return an error indicating Zcash signing is not implemented
262+ Err ( (
263+ Default :: default ( ) ,
264+ std:: collections:: BTreeMap :: from_iter ( [ (
265+ 0 ,
266+ miniscript:: bitcoin:: psbt:: SignError :: KeyNotFound ,
267+ ) ] ) ,
268+ ) )
269+ }
270+ }
271+ }
232272}
233273
234274#[ cfg( test) ]
235275mod tests {
236276 use super :: * ;
237277 use crate :: fixed_script_wallet:: Chain ;
238- use crate :: fixed_script_wallet:: { RootWalletKeys , WalletScripts } ;
278+ use crate :: fixed_script_wallet:: WalletScripts ;
239279 use crate :: test_utils:: fixtures;
280+ use crate :: test_utils:: fixtures:: assert_hex_eq;
240281 use base64:: engine:: { general_purpose:: STANDARD as BASE64_STANDARD , Engine } ;
241282 use miniscript:: bitcoin:: consensus:: Decodable ;
242283 use miniscript:: bitcoin:: Transaction ;
@@ -385,17 +426,65 @@ mod tests {
385426 output. script_pubkey . to_hex_string ( )
386427 }
387428
429+ type PartialSignatures =
430+ std:: collections:: BTreeMap < crate :: bitcoin:: PublicKey , crate :: bitcoin:: ecdsa:: Signature > ;
431+
432+ fn assert_eq_partial_signatures (
433+ actual : & PartialSignatures ,
434+ expected : & PartialSignatures ,
435+ ) -> Result < ( ) , String > {
436+ assert_eq ! (
437+ actual. len( ) ,
438+ expected. len( ) ,
439+ "Partial signatures should match"
440+ ) ;
441+ for ( actual_sig, expected_sig) in actual. iter ( ) . zip ( expected. iter ( ) ) {
442+ assert_eq ! ( actual_sig. 0 , expected_sig. 0 , "Public key should match" ) ;
443+ assert_hex_eq (
444+ & hex:: encode ( actual_sig. 1 . serialize ( ) ) ,
445+ & hex:: encode ( expected_sig. 1 . serialize ( ) ) ,
446+ "Signature" ,
447+ ) ?;
448+ }
449+ Ok ( ( ) )
450+ }
451+
388452 // ensure we can put the first signature (user signature) on an unsigned PSBT
389453 fn assert_half_sign (
390- network : Network ,
391- tx_format : fixtures:: TxFormat ,
454+ _network : Network ,
455+ _tx_format : fixtures:: TxFormat ,
392456 unsigned_bitgo_psbt : & BitGoPsbt ,
457+ halfsigned_bitgo_psbt : & BitGoPsbt ,
393458 wallet_keys : & fixtures:: XprvTriple ,
394459 input_index : usize ,
395- input_fixture : & fixtures:: PsbtInputFixture ,
396- halfsigned_fixture : & fixtures:: PsbtInputFixture ,
397460 ) -> Result < ( ) , String > {
398461 let user_key = wallet_keys. user_key ( ) ;
462+
463+ // Clone the unsigned PSBT and sign with user key
464+ let mut signed_psbt = unsigned_bitgo_psbt. clone ( ) ;
465+ let secp = secp256k1:: Secp256k1 :: new ( ) ;
466+
467+ // Sign with user key using the new sign method
468+ signed_psbt
469+ . sign ( user_key, & secp)
470+ . map_err ( |( _num_keys, errors) | format ! ( "Failed to sign PSBT: {:?}" , errors) ) ?;
471+
472+ // Extract partial signatures from the signed input
473+ let signed_input = match & signed_psbt {
474+ BitGoPsbt :: BitcoinLike ( psbt, _) => & psbt. inputs [ input_index] ,
475+ BitGoPsbt :: Zcash ( _, _) => {
476+ return Err ( "Zcash signing not yet implemented" . to_string ( ) ) ;
477+ }
478+ } ;
479+ let actual_partial_sigs = signed_input. partial_sigs . clone ( ) ;
480+
481+ // Get expected partial signatures from halfsigned fixture
482+ let expected_partial_sigs = halfsigned_bitgo_psbt. clone ( ) . into_psbt ( ) . inputs [ input_index]
483+ . partial_sigs
484+ . clone ( ) ;
485+
486+ assert_eq_partial_signatures ( & actual_partial_sigs, & expected_partial_sigs) ?;
487+
399488 Ok ( ( ) )
400489 }
401490
@@ -527,18 +616,25 @@ mod tests {
527616
528617 let psbt_input_stages = psbt_input_stages. unwrap ( ) ;
529618
530- assert_half_sign (
531- network,
532- tx_format,
533- & psbt_stages
534- . unsigned
535- . to_bitgo_psbt ( network)
536- . expect ( "Failed to convert to BitGo PSBT" ) ,
537- & psbt_input_stages. wallet_keys ,
538- psbt_input_stages. input_index ,
539- & psbt_input_stages. input_fixture_unsigned ,
540- & psbt_input_stages. input_fixture_halfsigned ,
541- ) ?;
619+ if script_type != fixtures:: ScriptType :: TaprootKeypath
620+ && script_type != fixtures:: ScriptType :: P2trMusig2
621+ && script_type != fixtures:: ScriptType :: P2tr
622+ {
623+ assert_half_sign (
624+ network,
625+ tx_format,
626+ & psbt_stages
627+ . unsigned
628+ . to_bitgo_psbt ( network)
629+ . expect ( "Failed to convert to BitGo PSBT" ) ,
630+ & psbt_stages
631+ . halfsigned
632+ . to_bitgo_psbt ( network)
633+ . expect ( "Failed to convert to BitGo PSBT" ) ,
634+ & psbt_input_stages. wallet_keys ,
635+ psbt_input_stages. input_index ,
636+ ) ?;
637+ }
542638
543639 assert_full_signed_matches_wallet_scripts (
544640 network,
0 commit comments