@@ -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
@@ -377,16 +374,6 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
377374 this . _network = network ;
378375 }
379376
380- /**
381- * Key Value: Unsigned tx id => PSBT
382- * It is used to cache PSBTs with taproot key path (MuSig2) inputs during external express signer is activated.
383- * Reason: MuSig2 signer secure nonce is cached in the UtxoPsbt object. It will be required during the signing step.
384- * For more info, check SignTransactionOptions.signingStep
385- *
386- * TODO BTC-276: This cache may need to be done with LRU like memory safe caching if memory issues comes up.
387- */
388- private static readonly PSBT_CACHE = new Map < string , utxolib . bitgo . UtxoPsbt > ( ) ;
389-
390377 get network ( ) {
391378 return this . _network ;
392379 }
@@ -850,132 +837,7 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
850837 async signTransaction < TNumber extends number | bigint = number > (
851838 params : SignTransactionOptions < TNumber >
852839 ) : Promise < SignedTransaction | HalfSignedUtxoTransaction > {
853- const txPrebuild = params . txPrebuild ;
854-
855- if ( _ . isUndefined ( txPrebuild ) || ! _ . isObject ( txPrebuild ) ) {
856- if ( ! _ . isUndefined ( txPrebuild ) && ! _ . isObject ( txPrebuild ) ) {
857- throw new Error ( `txPrebuild must be an object, got type ${ typeof txPrebuild } ` ) ;
858- }
859- throw new Error ( 'missing txPrebuild parameter' ) ;
860- }
861-
862- let tx = this . decodeTransactionFromPrebuild ( params . txPrebuild ) ;
863-
864- const isTxWithKeyPathSpendInput = tx instanceof bitgo . UtxoPsbt && bitgo . isTransactionWithKeyPathSpendInput ( tx ) ;
865-
866- let isLastSignature = false ;
867- if ( _ . isBoolean ( params . isLastSignature ) ) {
868- // We can only be the first signature on a transaction with taproot key path spend inputs because
869- // we require the secret nonce in the cache of the first signer, which is impossible to retrieve if
870- // deserialized from a hex.
871- if ( params . isLastSignature && isTxWithKeyPathSpendInput ) {
872- throw new Error ( 'Cannot be last signature on a transaction with key path spend inputs' ) ;
873- }
874-
875- // if build is called instead of buildIncomplete, no signature placeholders are left in the sig script
876- isLastSignature = params . isLastSignature ;
877- }
878-
879- const getSignerKeychain = ( ) : utxolib . BIP32Interface => {
880- const userPrv = params . prv ;
881- if ( _ . isUndefined ( userPrv ) || ! _ . isString ( userPrv ) ) {
882- if ( ! _ . isUndefined ( userPrv ) ) {
883- throw new Error ( `prv must be a string, got type ${ typeof userPrv } ` ) ;
884- }
885- throw new Error ( 'missing prv parameter to sign transaction' ) ;
886- }
887- const signerKeychain = bip32 . fromBase58 ( userPrv , utxolib . networks . bitcoin ) ;
888- if ( signerKeychain . isNeutered ( ) ) {
889- throw new Error ( 'expected user private key but received public key' ) ;
890- }
891- debug ( `Here is the public key of the xprv you used to sign: ${ signerKeychain . neutered ( ) . toBase58 ( ) } ` ) ;
892- return signerKeychain ;
893- } ;
894-
895- const setSignerMusigNonceWithOverride = (
896- psbt : utxolib . bitgo . UtxoPsbt ,
897- signerKeychain : utxolib . BIP32Interface ,
898- nonSegwitOverride : boolean
899- ) => {
900- utxolib . bitgo . withUnsafeNonSegwit ( psbt , ( ) => psbt . setAllInputsMusig2NonceHD ( signerKeychain ) , nonSegwitOverride ) ;
901- } ;
902-
903- let signerKeychain : utxolib . BIP32Interface | undefined ;
904-
905- if ( tx instanceof bitgo . UtxoPsbt && isTxWithKeyPathSpendInput ) {
906- switch ( params . signingStep ) {
907- case 'signerNonce' :
908- signerKeychain = getSignerKeychain ( ) ;
909- setSignerMusigNonceWithOverride ( tx , signerKeychain , ! ! params . allowNonSegwitSigningWithoutPrevTx ) ;
910- AbstractUtxoCoin . PSBT_CACHE . set ( tx . getUnsignedTx ( ) . getId ( ) , tx ) ;
911- return { txHex : tx . toHex ( ) } ;
912- case 'cosignerNonce' :
913- assert ( txPrebuild . walletId , 'walletId is required for MuSig2 bitgo nonce' ) ;
914- return { txHex : ( await this . signPsbt ( tx . toHex ( ) , txPrebuild . walletId ) ) . psbt } ;
915- case 'signerSignature' :
916- const txId = tx . getUnsignedTx ( ) . getId ( ) ;
917- const psbt = AbstractUtxoCoin . PSBT_CACHE . get ( txId ) ;
918- assert (
919- psbt ,
920- `Psbt is missing from txCache (cache size ${ AbstractUtxoCoin . PSBT_CACHE . size } ).
921- This may be due to the request being routed to a different BitGo-Express instance that for signing step 'signerNonce'.`
922- ) ;
923- AbstractUtxoCoin . PSBT_CACHE . delete ( txId ) ;
924- tx = psbt . combine ( tx ) ;
925- break ;
926- default :
927- // this instance is not an external signer
928- assert ( txPrebuild . walletId , 'walletId is required for MuSig2 bitgo nonce' ) ;
929- signerKeychain = getSignerKeychain ( ) ;
930- setSignerMusigNonceWithOverride ( tx , signerKeychain , ! ! params . allowNonSegwitSigningWithoutPrevTx ) ;
931- const response = await this . signPsbt ( tx . toHex ( ) , txPrebuild . walletId ) ;
932- tx . combine ( bitgo . createPsbtFromHex ( response . psbt , this . network ) ) ;
933- break ;
934- }
935- } else {
936- switch ( params . signingStep ) {
937- case 'signerNonce' :
938- case 'cosignerNonce' :
939- /**
940- * In certain cases, the caller of this method may not know whether the txHex contains a psbt with taproot key path spend input(s).
941- * Instead of throwing error, no-op and return the txHex. So that the caller can call this method in the same sequence.
942- */
943- return { txHex : tx . toHex ( ) } ;
944- }
945- }
946-
947- if ( signerKeychain === undefined ) {
948- signerKeychain = getSignerKeychain ( ) ;
949- }
950-
951- let signedTransaction : bitgo . UtxoTransaction < bigint > | bitgo . UtxoPsbt ;
952- if ( tx instanceof bitgo . UtxoPsbt ) {
953- signedTransaction = signAndVerifyPsbt ( tx , signerKeychain , {
954- isLastSignature,
955- allowNonSegwitSigningWithoutPrevTx : params . allowNonSegwitSigningWithoutPrevTx ,
956- } ) ;
957- } else {
958- if ( tx . ins . length !== txPrebuild . txInfo ?. unspents ?. length ) {
959- throw new Error ( 'length of unspents array should equal to the number of transaction inputs' ) ;
960- }
961-
962- if ( ! params . pubs || ! isTriple ( params . pubs ) ) {
963- throw new Error ( `must provide xpub array` ) ;
964- }
965-
966- const keychains = params . pubs . map ( ( pub ) => bip32 . fromBase58 ( pub ) ) as Triple < BIP32Interface > ;
967- const cosignerPub = params . cosignerPub ?? params . pubs [ 2 ] ;
968- const cosignerKeychain = bip32 . fromBase58 ( cosignerPub ) ;
969-
970- const walletSigner = new bitgo . WalletUnspentSigner < RootWalletKeys > ( keychains , signerKeychain , cosignerKeychain ) ;
971- signedTransaction = signAndVerifyWalletTransaction ( tx , txPrebuild . txInfo . unspents , walletSigner , {
972- isLastSignature,
973- } ) as bitgo . UtxoTransaction < bigint > ;
974- }
975-
976- return {
977- txHex : signedTransaction . toBuffer ( ) . toString ( 'hex' ) ,
978- } ;
840+ return signTransaction < TNumber > ( this , params ) ;
979841 }
980842
981843 /**
0 commit comments