1- import { BitGoPsbt , type SignerKey } from "../fixedScriptWallet/BitGoPsbt.js" ;
1+ import { BitGoPsbt , type NetworkName , type SignerKey } from "../fixedScriptWallet/BitGoPsbt.js" ;
22import { ZcashBitGoPsbt } from "../fixedScriptWallet/ZcashBitGoPsbt.js" ;
33import { RootWalletKeys } from "../fixedScriptWallet/RootWalletKeys.js" ;
44import { BIP32 } from "../bip32.js" ;
55import { ECPair } from "../ecpair.js" ;
66import {
7- assertChainCode ,
87 ChainCode ,
98 createOpReturnScript ,
109 inputScriptTypes ,
@@ -16,7 +15,7 @@ import {
1615 type ScriptId ,
1716} from "../fixedScriptWallet/index.js" ;
1817import type { CoinName } from "../coinName.js" ;
19- import { coinNames , isMainnet } from "../coinName.js" ;
18+ import { coinNames , isMainnet , toCoinName } from "../coinName.js" ;
2019import { getDefaultWalletKeys , getWalletKeysForSeed , getKeyTriple } from "./keys.js" ;
2120import type { Triple } from "../triple.js" ;
2221
@@ -91,6 +90,22 @@ type SuiteConfig = {
9190// Re-export for convenience
9291export { inputScriptTypes , outputScriptTypes } ;
9392
93+ /** Map InputScriptType to the OutputScriptType used for chain code derivation */
94+ function inputScriptTypeToOutputScriptType ( scriptType : InputScriptType ) : OutputScriptType {
95+ switch ( scriptType ) {
96+ case "p2sh" :
97+ case "p2shP2wsh" :
98+ case "p2wsh" :
99+ case "p2trLegacy" :
100+ return scriptType ;
101+ case "p2shP2pk" :
102+ return "p2sh" ;
103+ case "p2trMusig2ScriptPath" :
104+ case "p2trMusig2KeyPath" :
105+ return "p2trMusig2" ;
106+ }
107+ }
108+
94109/**
95110 * Creates a valid PSBT with as many features as possible (kitchen sink).
96111 *
@@ -113,7 +128,7 @@ export { inputScriptTypes, outputScriptTypes };
113128 * - psbt-lite: Only witness_utxo (no non_witness_utxo)
114129 */
115130export class AcidTest {
116- public readonly network : CoinName ;
131+ public readonly network : CoinName | NetworkName ;
117132 public readonly signStage : SignStage ;
118133 public readonly txFormat : TxFormat ;
119134 public readonly rootWalletKeys : RootWalletKeys ;
@@ -126,7 +141,7 @@ export class AcidTest {
126141 private readonly bitgoXprv : BIP32 ;
127142
128143 constructor (
129- network : CoinName ,
144+ network : CoinName | NetworkName ,
130145 signStage : SignStage ,
131146 txFormat : TxFormat ,
132147 rootWalletKeys : RootWalletKeys ,
@@ -151,13 +166,14 @@ export class AcidTest {
151166 * Create an AcidTest with specific configuration
152167 */
153168 static withConfig (
154- network : CoinName ,
169+ network : CoinName | NetworkName ,
155170 signStage : SignStage ,
156171 txFormat : TxFormat ,
157172 suiteConfig : SuiteConfig = { } ,
158173 ) : AcidTest {
159174 const rootWalletKeys = getDefaultWalletKeys ( ) ;
160175 const otherWalletKeys = getWalletKeysForSeed ( "too many secrets" ) ;
176+ const coin = toCoinName ( network ) ;
161177
162178 // Filter inputs based on network support
163179 const inputs : Input [ ] = inputScriptTypes
@@ -167,9 +183,9 @@ export class AcidTest {
167183
168184 // Map input script types to output script types for support check
169185 if ( scriptType === "p2trMusig2KeyPath" || scriptType === "p2trMusig2ScriptPath" ) {
170- return supportsScriptType ( network , "p2trMusig2" ) ;
186+ return supportsScriptType ( coin , "p2trMusig2" ) ;
171187 }
172- return supportsScriptType ( network , scriptType ) ;
188+ return supportsScriptType ( coin , scriptType ) ;
173189 } )
174190 . filter (
175191 ( scriptType ) =>
@@ -183,7 +199,7 @@ export class AcidTest {
183199
184200 // Filter outputs based on network support
185201 const outputs : Output [ ] = outputScriptTypes
186- . filter ( ( scriptType ) => supportsScriptType ( network , scriptType ) )
202+ . filter ( ( scriptType ) => supportsScriptType ( coin , scriptType ) )
187203 . map ( ( scriptType , index ) => ( {
188204 scriptType,
189205 value : BigInt ( 900 + index * 100 ) , // Deterministic amounts
@@ -232,12 +248,16 @@ export class AcidTest {
232248 */
233249 createPsbt ( ) : BitGoPsbt {
234250 // Use ZcashBitGoPsbt for Zcash networks
235- const isZcash = this . network === "zec" || this . network === "tzec" ;
251+ const isZcash =
252+ this . network === "zec" ||
253+ this . network === "tzec" ||
254+ this . network === "zcash" ||
255+ this . network === "zcashTest" ;
236256 const psbt = isZcash
237- ? ZcashBitGoPsbt . createEmptyWithConsensusBranchId ( this . network , this . rootWalletKeys , {
238- version : 2 ,
239- lockTime : 0 ,
240- consensusBranchId : 0xc2d6d0b4 , // NU5
257+ ? ZcashBitGoPsbt . createEmpty ( this . network , this . rootWalletKeys , {
258+ // Sapling activation height: mainnet=419200, testnet=280000
259+ blockHeight :
260+ this . network === "zec" || this . network === "zcash" ? 419200 : 280000 ,
241261 } )
242262 : BitGoPsbt . createEmpty ( this . network , this . rootWalletKeys , {
243263 version : 2 ,
@@ -246,54 +266,36 @@ export class AcidTest {
246266
247267 // Add inputs with deterministic outpoints
248268 this . inputs . forEach ( ( input , index ) => {
249- // Resolve scriptId: either from explicit scriptId or from scriptType + index
250- const scriptId : ScriptId = input . scriptId ?? {
251- chain : ChainCode . value ( "p2sh" , "external" ) ,
252- index : input . index ?? index ,
253- } ;
254269 const walletKeys = input . walletKeys ?? this . rootWalletKeys ;
270+ const outpoint = { txid : "0" . repeat ( 64 ) , vout : index , value : input . value } ;
255271
256- // Get scriptType: either explicit or derive from scriptId chain
257- const scriptType = input . scriptType ?? ChainCode . scriptType ( assertChainCode ( scriptId . chain ) ) ;
272+ // scriptId variant: caller provides explicit chain + index
273+ if ( input . scriptId ) {
274+ psbt . addWalletInput ( outpoint , walletKeys , {
275+ scriptId : input . scriptId ,
276+ signPath : { signer : "user" , cosigner : "bitgo" } ,
277+ } ) ;
278+ return ;
279+ }
280+
281+ const scriptType = input . scriptType ?? "p2sh" ;
258282
259283 if ( scriptType === "p2shP2pk" ) {
260- // Add replay protection input
261- const replayKey = this . getReplayProtectionKey ( ) ;
262- // Convert BIP32 to ECPair using public key
263- const ecpair = ECPair . fromPublicKey ( replayKey . publicKey ) ;
264- psbt . addReplayProtectionInput (
265- {
266- txid : "0" . repeat ( 64 ) ,
267- vout : index ,
268- value : input . value ,
269- } ,
270- ecpair ,
271- ) ;
272- } else {
273- // Determine signing path based on input type
274- let signPath : { signer : SignerKey ; cosigner : SignerKey } ;
284+ const ecpair = ECPair . fromPublicKey ( this . getReplayProtectionKey ( ) . publicKey ) ;
285+ psbt . addReplayProtectionInput ( outpoint , ecpair ) ;
286+ return ;
287+ }
275288
276- if ( scriptType === "p2trMusig2ScriptPath" ) {
277- // Script path uses user + backup
278- signPath = { signer : "user" , cosigner : "backup" } ;
279- } else {
280- // Default: user + bitgo
281- signPath = { signer : "user" , cosigner : "bitgo" } ;
282- }
289+ const scriptId : ScriptId = {
290+ chain : ChainCode . value ( inputScriptTypeToOutputScriptType ( scriptType ) , "external" ) ,
291+ index : input . index ?? index ,
292+ } ;
293+ const signPath : { signer : SignerKey ; cosigner : SignerKey } =
294+ scriptType === "p2trMusig2ScriptPath"
295+ ? { signer : "user" , cosigner : "backup" }
296+ : { signer : "user" , cosigner : "bitgo" } ;
283297
284- psbt . addWalletInput (
285- {
286- txid : "0" . repeat ( 64 ) ,
287- vout : index ,
288- value : input . value ,
289- } ,
290- walletKeys ,
291- {
292- scriptId,
293- signPath,
294- } ,
295- ) ;
296- }
298+ psbt . addWalletInput ( outpoint , walletKeys , { scriptId, signPath } ) ;
297299 } ) ;
298300
299301 // Add outputs
@@ -366,40 +368,32 @@ export class AcidTest {
366368 ) ;
367369
368370 if ( hasMusig2Inputs ) {
369- const isZcash = this . network === "zec" || this . network === "tzec" ;
371+ const isZcash =
372+ this . network === "zec" ||
373+ this . network === "tzec" ||
374+ this . network === "zcash" ||
375+ this . network === "zcashTest" ;
370376 if ( isZcash ) {
371377 throw new Error ( "Zcash does not support MuSig2/Taproot inputs" ) ;
372378 }
373379
374- // Generate nonces with user key
380+ // MuSig2 requires ALL participant nonces before ANY signing.
381+ // Generate nonces directly on the same PSBT for each participant key.
375382 psbt . generateMusig2Nonces ( userKey ) ;
376383
377- if ( this . signStage === "fullsigned" ) {
378- // Create a second PSBT with cosigner nonces for combination
379- // For p2trMusig2ScriptPath use backup, for p2trMusig2KeyPath use bitgo
380- // Since we might have both types, we need to generate nonces separately
381- const bytes = psbt . serialize ( ) ;
382-
383- const hasKeyPath = this . inputs . some ( ( input ) => input . scriptType === "p2trMusig2KeyPath" ) ;
384- const hasScriptPath = this . inputs . some (
385- ( input ) => input . scriptType === "p2trMusig2ScriptPath" ,
386- ) ;
387-
388- if ( hasKeyPath && ! hasScriptPath ) {
389- // Only key path inputs - generate bitgo nonces for all
390- const psbt2 = BitGoPsbt . fromBytes ( bytes , this . network ) ;
391- psbt2 . generateMusig2Nonces ( bitgoKey ) ;
392- psbt . combineMusig2Nonces ( psbt2 ) ;
393- } else if ( hasScriptPath && ! hasKeyPath ) {
394- // Only script path inputs - generate backup nonces for all
395- const psbt2 = BitGoPsbt . fromBytes ( bytes , this . network ) ;
396- psbt2 . generateMusig2Nonces ( backupKey ) ;
397- psbt . combineMusig2Nonces ( psbt2 ) ;
398- } else {
399- const psbt2 = BitGoPsbt . fromBytes ( bytes , this . network ) ;
400- psbt2 . generateMusig2Nonces ( bitgoKey ) ;
401- psbt . combineMusig2Nonces ( psbt2 ) ;
402- }
384+ const hasKeyPath = this . inputs . some ( ( input ) => input . scriptType === "p2trMusig2KeyPath" ) ;
385+ const hasScriptPath = this . inputs . some (
386+ ( input ) => input . scriptType === "p2trMusig2ScriptPath" ,
387+ ) ;
388+
389+ // Key path uses user+bitgo, script path uses user+backup.
390+ // generateMusig2Nonces fails if the key isn't a participant in any musig2 input,
391+ // so we only call it for keys that match.
392+ if ( hasKeyPath ) {
393+ psbt . generateMusig2Nonces ( bitgoKey ) ;
394+ }
395+ if ( hasScriptPath ) {
396+ psbt . generateMusig2Nonces ( backupKey ) ;
403397 }
404398 }
405399
0 commit comments