@@ -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,63 @@ 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 ,
392454 unsigned_bitgo_psbt : & BitGoPsbt ,
455+ halfsigned_bitgo_psbt : & BitGoPsbt ,
393456 wallet_keys : & fixtures:: XprvTriple ,
394457 input_index : usize ,
395- input_fixture : & fixtures:: PsbtInputFixture ,
396- halfsigned_fixture : & fixtures:: PsbtInputFixture ,
397458 ) -> Result < ( ) , String > {
398459 let user_key = wallet_keys. user_key ( ) ;
460+
461+ // Clone the unsigned PSBT and sign with user key
462+ let mut signed_psbt = unsigned_bitgo_psbt. clone ( ) ;
463+ let secp = secp256k1:: Secp256k1 :: new ( ) ;
464+
465+ // Sign with user key using the new sign method
466+ signed_psbt
467+ . sign ( user_key, & secp)
468+ . map_err ( |( _num_keys, errors) | format ! ( "Failed to sign PSBT: {:?}" , errors) ) ?;
469+
470+ // Extract partial signatures from the signed input
471+ let signed_input = match & signed_psbt {
472+ BitGoPsbt :: BitcoinLike ( psbt, _) => & psbt. inputs [ input_index] ,
473+ BitGoPsbt :: Zcash ( _, _) => {
474+ return Err ( "Zcash signing not yet implemented" . to_string ( ) ) ;
475+ }
476+ } ;
477+ let actual_partial_sigs = signed_input. partial_sigs . clone ( ) ;
478+
479+ // Get expected partial signatures from halfsigned fixture
480+ let expected_partial_sigs = halfsigned_bitgo_psbt. clone ( ) . into_psbt ( ) . inputs [ input_index]
481+ . partial_sigs
482+ . clone ( ) ;
483+
484+ assert_eq_partial_signatures ( & actual_partial_sigs, & expected_partial_sigs) ?;
485+
399486 Ok ( ( ) )
400487 }
401488
@@ -527,18 +614,23 @@ mod tests {
527614
528615 let psbt_input_stages = psbt_input_stages. unwrap ( ) ;
529616
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- ) ?;
617+ if script_type != fixtures:: ScriptType :: TaprootKeypath
618+ && script_type != fixtures:: ScriptType :: P2trMusig2
619+ && script_type != fixtures:: ScriptType :: P2tr
620+ {
621+ assert_half_sign (
622+ & psbt_stages
623+ . unsigned
624+ . to_bitgo_psbt ( network)
625+ . expect ( "Failed to convert to BitGo PSBT" ) ,
626+ & psbt_stages
627+ . halfsigned
628+ . to_bitgo_psbt ( network)
629+ . expect ( "Failed to convert to BitGo PSBT" ) ,
630+ & psbt_input_stages. wallet_keys ,
631+ psbt_input_stages. input_index ,
632+ ) ?;
633+ }
542634
543635 assert_full_signed_matches_wallet_scripts (
544636 network,
0 commit comments