@@ -2,8 +2,7 @@ import assert from 'assert';
22import { randomBytes } from 'crypto' ;
33import _ from 'lodash' ;
44import * as utxolib from '@bitgo/utxo-lib' ;
5- import { bip32 , BIP32Interface , bitgo , getMainnet , isMainnet , isTestnet } from '@bitgo/utxo-lib' ;
6- import debugLib from 'debug' ;
5+ import { bip32 , bitgo , getMainnet , isMainnet , isTestnet } from '@bitgo/utxo-lib' ;
76
87import {
98 backupKeyRecovery ,
@@ -59,7 +58,6 @@ import {
5958 Wallet ,
6059} from '@bitgo/sdk-core' ;
6160import { isReplayProtectionUnspent } from './replayProtection' ;
62- import { signAndVerifyPsbt , signAndVerifyWalletTransaction } from './sign' ;
6361import { supportedCrossChainRecoveries } from './config' ;
6462import {
6563 assertValidTransactionRecipient ,
@@ -76,10 +74,9 @@ import { CustomChangeOptions } from './transaction/fixedScript';
7674import { toBip32Triple , UtxoKeychain , UtxoNamedKeychains } from './keychains' ;
7775import { verifyKeySignature , verifyUserPublicKey } from './verifyKey' ;
7876import { getPolicyForEnv } from './descriptor/validatePolicy' ;
77+ import { signTransaction } from './transaction/signTransaction' ;
7978import { UtxoWallet } from './wallet' ;
8079
81- const debug = debugLib ( 'bitgo:v2:utxo' ) ;
82-
8380import ScriptType2Of3 = utxolib . bitgo . outputScripts . ScriptType2Of3 ;
8481
8582type UtxoCustomSigningFunction < TNumber extends number | bigint > = {
@@ -111,11 +108,11 @@ const { getExternalChainCode, isChainCode, scriptTypeForChain, outputScripts } =
111108
112109type Unspent < TNumber extends number | bigint = number > = bitgo . Unspent < TNumber > ;
113110
114- type DecodedTransaction < TNumber extends number | bigint > =
111+ export type DecodedTransaction < TNumber extends number | bigint > =
115112 | utxolib . bitgo . UtxoTransaction < TNumber >
116113 | utxolib . bitgo . UtxoPsbt ;
117114
118- type RootWalletKeys = bitgo . RootWalletKeys ;
115+ export type RootWalletKeys = bitgo . RootWalletKeys ;
119116
120117export type UtxoCoinSpecific = AddressCoinSpecific | DescriptorAddressCoinSpecific ;
121118
@@ -358,16 +355,6 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
358355 this . _network = network ;
359356 }
360357
361- /**
362- * Key Value: Unsigned tx id => PSBT
363- * It is used to cache PSBTs with taproot key path (MuSig2) inputs during external express signer is activated.
364- * Reason: MuSig2 signer secure nonce is cached in the UtxoPsbt object. It will be required during the signing step.
365- * For more info, check SignTransactionOptions.signingStep
366- *
367- * TODO BTC-276: This cache may need to be done with LRU like memory safe caching if memory issues comes up.
368- */
369- private static readonly PSBT_CACHE = new Map < string , utxolib . bitgo . UtxoPsbt > ( ) ;
370-
371358 get network ( ) {
372359 return this . _network ;
373360 }
@@ -831,132 +818,7 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
831818 async signTransaction < TNumber extends number | bigint = number > (
832819 params : SignTransactionOptions < TNumber >
833820 ) : Promise < SignedTransaction | HalfSignedUtxoTransaction > {
834- const txPrebuild = params . txPrebuild ;
835-
836- if ( _ . isUndefined ( txPrebuild ) || ! _ . isObject ( txPrebuild ) ) {
837- if ( ! _ . isUndefined ( txPrebuild ) && ! _ . isObject ( txPrebuild ) ) {
838- throw new Error ( `txPrebuild must be an object, got type ${ typeof txPrebuild } ` ) ;
839- }
840- throw new Error ( 'missing txPrebuild parameter' ) ;
841- }
842-
843- let tx = this . decodeTransactionFromPrebuild ( params . txPrebuild ) ;
844-
845- const isTxWithKeyPathSpendInput = tx instanceof bitgo . UtxoPsbt && bitgo . isTransactionWithKeyPathSpendInput ( tx ) ;
846-
847- let isLastSignature = false ;
848- if ( _ . isBoolean ( params . isLastSignature ) ) {
849- // We can only be the first signature on a transaction with taproot key path spend inputs because
850- // we require the secret nonce in the cache of the first signer, which is impossible to retrieve if
851- // deserialized from a hex.
852- if ( params . isLastSignature && isTxWithKeyPathSpendInput ) {
853- throw new Error ( 'Cannot be last signature on a transaction with key path spend inputs' ) ;
854- }
855-
856- // if build is called instead of buildIncomplete, no signature placeholders are left in the sig script
857- isLastSignature = params . isLastSignature ;
858- }
859-
860- const getSignerKeychain = ( ) : utxolib . BIP32Interface => {
861- const userPrv = params . prv ;
862- if ( _ . isUndefined ( userPrv ) || ! _ . isString ( userPrv ) ) {
863- if ( ! _ . isUndefined ( userPrv ) ) {
864- throw new Error ( `prv must be a string, got type ${ typeof userPrv } ` ) ;
865- }
866- throw new Error ( 'missing prv parameter to sign transaction' ) ;
867- }
868- const signerKeychain = bip32 . fromBase58 ( userPrv , utxolib . networks . bitcoin ) ;
869- if ( signerKeychain . isNeutered ( ) ) {
870- throw new Error ( 'expected user private key but received public key' ) ;
871- }
872- debug ( `Here is the public key of the xprv you used to sign: ${ signerKeychain . neutered ( ) . toBase58 ( ) } ` ) ;
873- return signerKeychain ;
874- } ;
875-
876- const setSignerMusigNonceWithOverride = (
877- psbt : utxolib . bitgo . UtxoPsbt ,
878- signerKeychain : utxolib . BIP32Interface ,
879- nonSegwitOverride : boolean
880- ) => {
881- utxolib . bitgo . withUnsafeNonSegwit ( psbt , ( ) => psbt . setAllInputsMusig2NonceHD ( signerKeychain ) , nonSegwitOverride ) ;
882- } ;
883-
884- let signerKeychain : utxolib . BIP32Interface | undefined ;
885-
886- if ( tx instanceof bitgo . UtxoPsbt && isTxWithKeyPathSpendInput ) {
887- switch ( params . signingStep ) {
888- case 'signerNonce' :
889- signerKeychain = getSignerKeychain ( ) ;
890- setSignerMusigNonceWithOverride ( tx , signerKeychain , ! ! params . allowNonSegwitSigningWithoutPrevTx ) ;
891- AbstractUtxoCoin . PSBT_CACHE . set ( tx . getUnsignedTx ( ) . getId ( ) , tx ) ;
892- return { txHex : tx . toHex ( ) } ;
893- case 'cosignerNonce' :
894- assert ( txPrebuild . walletId , 'walletId is required for MuSig2 bitgo nonce' ) ;
895- return { txHex : ( await this . signPsbt ( tx . toHex ( ) , txPrebuild . walletId ) ) . psbt } ;
896- case 'signerSignature' :
897- const txId = tx . getUnsignedTx ( ) . getId ( ) ;
898- const psbt = AbstractUtxoCoin . PSBT_CACHE . get ( txId ) ;
899- assert (
900- psbt ,
901- `Psbt is missing from txCache (cache size ${ AbstractUtxoCoin . PSBT_CACHE . size } ).
902- This may be due to the request being routed to a different BitGo-Express instance that for signing step 'signerNonce'.`
903- ) ;
904- AbstractUtxoCoin . PSBT_CACHE . delete ( txId ) ;
905- tx = psbt . combine ( tx ) ;
906- break ;
907- default :
908- // this instance is not an external signer
909- assert ( txPrebuild . walletId , 'walletId is required for MuSig2 bitgo nonce' ) ;
910- signerKeychain = getSignerKeychain ( ) ;
911- setSignerMusigNonceWithOverride ( tx , signerKeychain , ! ! params . allowNonSegwitSigningWithoutPrevTx ) ;
912- const response = await this . signPsbt ( tx . toHex ( ) , txPrebuild . walletId ) ;
913- tx . combine ( bitgo . createPsbtFromHex ( response . psbt , this . network ) ) ;
914- break ;
915- }
916- } else {
917- switch ( params . signingStep ) {
918- case 'signerNonce' :
919- case 'cosignerNonce' :
920- /**
921- * In certain cases, the caller of this method may not know whether the txHex contains a psbt with taproot key path spend input(s).
922- * Instead of throwing error, no-op and return the txHex. So that the caller can call this method in the same sequence.
923- */
924- return { txHex : tx . toHex ( ) } ;
925- }
926- }
927-
928- if ( signerKeychain === undefined ) {
929- signerKeychain = getSignerKeychain ( ) ;
930- }
931-
932- let signedTransaction : bitgo . UtxoTransaction < bigint > | bitgo . UtxoPsbt ;
933- if ( tx instanceof bitgo . UtxoPsbt ) {
934- signedTransaction = signAndVerifyPsbt ( tx , signerKeychain , {
935- isLastSignature,
936- allowNonSegwitSigningWithoutPrevTx : params . allowNonSegwitSigningWithoutPrevTx ,
937- } ) ;
938- } else {
939- if ( tx . ins . length !== txPrebuild . txInfo ?. unspents ?. length ) {
940- throw new Error ( 'length of unspents array should equal to the number of transaction inputs' ) ;
941- }
942-
943- if ( ! params . pubs || ! isTriple ( params . pubs ) ) {
944- throw new Error ( `must provide xpub array` ) ;
945- }
946-
947- const keychains = params . pubs . map ( ( pub ) => bip32 . fromBase58 ( pub ) ) as Triple < BIP32Interface > ;
948- const cosignerPub = params . cosignerPub ?? params . pubs [ 2 ] ;
949- const cosignerKeychain = bip32 . fromBase58 ( cosignerPub ) ;
950-
951- const walletSigner = new bitgo . WalletUnspentSigner < RootWalletKeys > ( keychains , signerKeychain , cosignerKeychain ) ;
952- signedTransaction = signAndVerifyWalletTransaction ( tx , txPrebuild . txInfo . unspents , walletSigner , {
953- isLastSignature,
954- } ) as bitgo . UtxoTransaction < bigint > ;
955- }
956-
957- return {
958- txHex : signedTransaction . toBuffer ( ) . toString ( 'hex' ) ,
959- } ;
821+ return signTransaction < TNumber > ( this , params ) ;
960822 }
961823
962824 /**
0 commit comments