@bitgo/wasm-utxo 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/js/coinName.d.ts +3 -0
- package/dist/cjs/js/coinName.js +49 -0
- package/dist/cjs/js/descriptorWallet/Psbt.d.ts +49 -0
- package/dist/cjs/js/descriptorWallet/Psbt.js +126 -0
- package/dist/cjs/js/descriptorWallet/index.d.ts +1 -0
- package/dist/cjs/js/descriptorWallet/index.js +4 -0
- package/dist/cjs/js/index.d.ts +1 -31
- package/dist/cjs/js/index.js +2 -2
- package/dist/cjs/js/testutils/AcidTest.d.ts +3 -3
- package/dist/cjs/js/testutils/AcidTest.js +79 -80
- package/dist/cjs/js/wasm/wasm_utxo.d.ts +27 -110
- package/dist/cjs/js/wasm/wasm_utxo.js +54 -97
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm.d.ts +27 -27
- package/dist/esm/js/coinName.d.ts +3 -0
- package/dist/esm/js/coinName.js +48 -0
- package/dist/esm/js/descriptorWallet/Psbt.d.ts +49 -0
- package/dist/esm/js/descriptorWallet/Psbt.js +122 -0
- package/dist/esm/js/descriptorWallet/index.d.ts +1 -0
- package/dist/esm/js/descriptorWallet/index.js +2 -0
- package/dist/esm/js/index.d.ts +1 -31
- package/dist/esm/js/index.js +1 -1
- package/dist/esm/js/testutils/AcidTest.d.ts +3 -3
- package/dist/esm/js/testutils/AcidTest.js +80 -81
- package/dist/esm/js/wasm/wasm_utxo.d.ts +27 -110
- package/dist/esm/js/wasm/wasm_utxo_bg.js +54 -97
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm.d.ts +27 -27
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { WrapPsbt as WasmPsbt, } from "../wasm/wasm_utxo.js";
|
|
2
|
+
import { Transaction } from "../transaction.js";
|
|
3
|
+
export class Psbt {
|
|
4
|
+
_wasm;
|
|
5
|
+
constructor(versionOrWasm, lockTime) {
|
|
6
|
+
if (versionOrWasm instanceof WasmPsbt) {
|
|
7
|
+
this._wasm = versionOrWasm;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
this._wasm = new WasmPsbt(versionOrWasm, lockTime);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** @internal Access the underlying WASM instance */
|
|
14
|
+
get wasm() {
|
|
15
|
+
return this._wasm;
|
|
16
|
+
}
|
|
17
|
+
// -- Static / Factory --
|
|
18
|
+
static create(version, lockTime) {
|
|
19
|
+
return new Psbt(new WasmPsbt(version, lockTime));
|
|
20
|
+
}
|
|
21
|
+
static deserialize(bytes) {
|
|
22
|
+
return new Psbt(WasmPsbt.deserialize(bytes));
|
|
23
|
+
}
|
|
24
|
+
// -- Serialization --
|
|
25
|
+
serialize() {
|
|
26
|
+
return this._wasm.serialize();
|
|
27
|
+
}
|
|
28
|
+
clone() {
|
|
29
|
+
return new Psbt(this._wasm.clone());
|
|
30
|
+
}
|
|
31
|
+
// -- IPsbt: introspection --
|
|
32
|
+
inputCount() {
|
|
33
|
+
return this._wasm.input_count();
|
|
34
|
+
}
|
|
35
|
+
outputCount() {
|
|
36
|
+
return this._wasm.output_count();
|
|
37
|
+
}
|
|
38
|
+
version() {
|
|
39
|
+
return this._wasm.version();
|
|
40
|
+
}
|
|
41
|
+
lockTime() {
|
|
42
|
+
return this._wasm.lock_time();
|
|
43
|
+
}
|
|
44
|
+
unsignedTxId() {
|
|
45
|
+
return this._wasm.unsigned_tx_id();
|
|
46
|
+
}
|
|
47
|
+
getInputs() {
|
|
48
|
+
return this._wasm.get_inputs();
|
|
49
|
+
}
|
|
50
|
+
getOutputs() {
|
|
51
|
+
return this._wasm.get_outputs();
|
|
52
|
+
}
|
|
53
|
+
getGlobalXpubs() {
|
|
54
|
+
return this._wasm.get_global_xpubs();
|
|
55
|
+
}
|
|
56
|
+
getOutputsWithAddress(coin) {
|
|
57
|
+
return this._wasm.get_outputs_with_address(coin);
|
|
58
|
+
}
|
|
59
|
+
// -- IPsbt: mutation --
|
|
60
|
+
addInputAtIndex(index, txid, vout, value, script, sequence) {
|
|
61
|
+
return this._wasm.add_input_at_index(index, txid, vout, value, script, sequence);
|
|
62
|
+
}
|
|
63
|
+
addInput(txid, vout, value, script, sequence) {
|
|
64
|
+
return this._wasm.add_input(txid, vout, value, script, sequence);
|
|
65
|
+
}
|
|
66
|
+
addOutputAtIndex(index, script, value) {
|
|
67
|
+
return this._wasm.add_output_at_index(index, script, value);
|
|
68
|
+
}
|
|
69
|
+
addOutput(script, value) {
|
|
70
|
+
return this._wasm.add_output(script, value);
|
|
71
|
+
}
|
|
72
|
+
removeInput(index) {
|
|
73
|
+
this._wasm.remove_input(index);
|
|
74
|
+
}
|
|
75
|
+
removeOutput(index) {
|
|
76
|
+
this._wasm.remove_output(index);
|
|
77
|
+
}
|
|
78
|
+
// -- Descriptor updates --
|
|
79
|
+
updateInputWithDescriptor(inputIndex, descriptor) {
|
|
80
|
+
this._wasm.update_input_with_descriptor(inputIndex, descriptor);
|
|
81
|
+
}
|
|
82
|
+
updateOutputWithDescriptor(outputIndex, descriptor) {
|
|
83
|
+
this._wasm.update_output_with_descriptor(outputIndex, descriptor);
|
|
84
|
+
}
|
|
85
|
+
// -- Signing --
|
|
86
|
+
signWithXprv(xprv) {
|
|
87
|
+
return this._wasm.sign_with_xprv(xprv);
|
|
88
|
+
}
|
|
89
|
+
signWithPrv(prv) {
|
|
90
|
+
return this._wasm.sign_with_prv(prv);
|
|
91
|
+
}
|
|
92
|
+
signAll(key) {
|
|
93
|
+
return this._wasm.sign_all(key);
|
|
94
|
+
}
|
|
95
|
+
signAllWithEcpair(key) {
|
|
96
|
+
return this._wasm.sign_all_with_ecpair(key);
|
|
97
|
+
}
|
|
98
|
+
// -- Signature introspection --
|
|
99
|
+
getPartialSignatures(inputIndex) {
|
|
100
|
+
return this._wasm.get_partial_signatures(inputIndex);
|
|
101
|
+
}
|
|
102
|
+
hasPartialSignatures(inputIndex) {
|
|
103
|
+
return this._wasm.has_partial_signatures(inputIndex);
|
|
104
|
+
}
|
|
105
|
+
// -- Validation --
|
|
106
|
+
validateSignatureAtInput(inputIndex, pubkey) {
|
|
107
|
+
return this._wasm.validate_signature_at_input(inputIndex, pubkey);
|
|
108
|
+
}
|
|
109
|
+
verifySignatureWithKey(inputIndex, key) {
|
|
110
|
+
return this._wasm.verify_signature_with_key(inputIndex, key);
|
|
111
|
+
}
|
|
112
|
+
// -- Transaction extraction --
|
|
113
|
+
getUnsignedTx() {
|
|
114
|
+
return this._wasm.get_unsigned_tx();
|
|
115
|
+
}
|
|
116
|
+
finalize() {
|
|
117
|
+
this._wasm.finalize_mut();
|
|
118
|
+
}
|
|
119
|
+
extractTransaction() {
|
|
120
|
+
return Transaction.fromWasm(this._wasm.extract_transaction());
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/esm/js/index.d.ts
CHANGED
|
@@ -17,9 +17,6 @@ export type { AddressFormat } from "./address.js";
|
|
|
17
17
|
export type { TapLeafScript, PreparedInscriptionRevealData } from "./inscriptions.js";
|
|
18
18
|
export type DescriptorPkType = "derivable" | "definite" | "string";
|
|
19
19
|
export type ScriptContext = "tap" | "segwitv0" | "legacy";
|
|
20
|
-
export type SignPsbtResult = {
|
|
21
|
-
[inputIndex: number]: [pubkey: string][];
|
|
22
|
-
};
|
|
23
20
|
declare module "./wasm/wasm_utxo.js" {
|
|
24
21
|
interface WrapDescriptor {
|
|
25
22
|
/** These are not the same types of nodes as in the ast module */
|
|
@@ -64,36 +61,9 @@ declare module "./wasm/wasm_utxo.js" {
|
|
|
64
61
|
interface PsbtOutputDataWithAddress extends PsbtOutputData {
|
|
65
62
|
address: string;
|
|
66
63
|
}
|
|
67
|
-
interface WrapPsbt {
|
|
68
|
-
signWithXprv(this: WrapPsbt, xprv: string): SignPsbtResult;
|
|
69
|
-
signWithPrv(this: WrapPsbt, prv: Uint8Array): SignPsbtResult;
|
|
70
|
-
signAll(this: WrapPsbt, key: WasmBIP32): SignPsbtResult;
|
|
71
|
-
signAllWithEcpair(this: WrapPsbt, key: WasmECPair): SignPsbtResult;
|
|
72
|
-
inputCount(): number;
|
|
73
|
-
outputCount(): number;
|
|
74
|
-
getInputs(): PsbtInputData[];
|
|
75
|
-
getOutputs(): PsbtOutputData[];
|
|
76
|
-
getOutputsWithAddress(coin: import("./coinName.js").CoinName): PsbtOutputDataWithAddress[];
|
|
77
|
-
getGlobalXpubs(): WasmBIP32[];
|
|
78
|
-
getPartialSignatures(inputIndex: number): Array<{
|
|
79
|
-
pubkey: Uint8Array;
|
|
80
|
-
signature: Uint8Array;
|
|
81
|
-
}>;
|
|
82
|
-
hasPartialSignatures(inputIndex: number): boolean;
|
|
83
|
-
validateSignatureAtInput(inputIndex: number, pubkey: Uint8Array): boolean;
|
|
84
|
-
verifySignatureWithKey(inputIndex: number, key: WasmBIP32): boolean;
|
|
85
|
-
extractTransaction(): WasmTransaction;
|
|
86
|
-
addInputAtIndex(index: number, txid: string, vout: number, value: bigint, script: Uint8Array, sequence?: number): number;
|
|
87
|
-
addOutputAtIndex(index: number, script: Uint8Array, value: bigint): number;
|
|
88
|
-
removeInput(index: number): void;
|
|
89
|
-
removeOutput(index: number): void;
|
|
90
|
-
unsignedTxId(): string;
|
|
91
|
-
lockTime(): number;
|
|
92
|
-
version(): number;
|
|
93
|
-
}
|
|
94
64
|
}
|
|
95
65
|
export { WrapDescriptor as Descriptor } from "./wasm/wasm_utxo.js";
|
|
96
66
|
export { WrapMiniscript as Miniscript } from "./wasm/wasm_utxo.js";
|
|
97
|
-
export {
|
|
67
|
+
export { Psbt } from "./descriptorWallet/Psbt.js";
|
|
98
68
|
export { DashTransaction, Transaction, ZcashTransaction } from "./transaction.js";
|
|
99
69
|
export { hasPsbtMagic, type IPsbt, type IPsbtWithAddress } from "./psbt.js";
|
package/dist/esm/js/index.js
CHANGED
|
@@ -21,6 +21,6 @@ export { Dimensions } from "./fixedScriptWallet/Dimensions.js";
|
|
|
21
21
|
export { getMainnet, isMainnet, isTestnet, isCoinName } from "./coinName.js";
|
|
22
22
|
export { WrapDescriptor as Descriptor } from "./wasm/wasm_utxo.js";
|
|
23
23
|
export { WrapMiniscript as Miniscript } from "./wasm/wasm_utxo.js";
|
|
24
|
-
export {
|
|
24
|
+
export { Psbt } from "./descriptorWallet/Psbt.js";
|
|
25
25
|
export { DashTransaction, Transaction, ZcashTransaction } from "./transaction.js";
|
|
26
26
|
export { hasPsbtMagic } from "./psbt.js";
|
|
@@ -94,7 +94,7 @@ export { inputScriptTypes, outputScriptTypes };
|
|
|
94
94
|
* - psbt-lite: Only witness_utxo (no non_witness_utxo)
|
|
95
95
|
*/
|
|
96
96
|
export declare class AcidTest {
|
|
97
|
-
readonly
|
|
97
|
+
readonly coin: CoinName;
|
|
98
98
|
readonly signStage: SignStage;
|
|
99
99
|
readonly txFormat: TxFormat;
|
|
100
100
|
readonly rootWalletKeys: RootWalletKeys;
|
|
@@ -104,11 +104,11 @@ export declare class AcidTest {
|
|
|
104
104
|
private readonly userXprv;
|
|
105
105
|
private readonly backupXprv;
|
|
106
106
|
private readonly bitgoXprv;
|
|
107
|
-
constructor(
|
|
107
|
+
constructor(coin: CoinName, signStage: SignStage, txFormat: TxFormat, rootWalletKeys: RootWalletKeys, otherWalletKeys: RootWalletKeys, inputs: Input[], outputs: Output[], xprvTriple: Triple<BIP32>);
|
|
108
108
|
/**
|
|
109
109
|
* Create an AcidTest with specific configuration
|
|
110
110
|
*/
|
|
111
|
-
static withConfig(
|
|
111
|
+
static withConfig(coin: CoinName, signStage: SignStage, txFormat: TxFormat, suiteConfig?: SuiteConfig): AcidTest;
|
|
112
112
|
/**
|
|
113
113
|
* Get a human-readable name for this test configuration
|
|
114
114
|
*/
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import { BitGoPsbt } from "../fixedScriptWallet/BitGoPsbt.js";
|
|
2
2
|
import { ZcashBitGoPsbt } from "../fixedScriptWallet/ZcashBitGoPsbt.js";
|
|
3
3
|
import { ECPair } from "../ecpair.js";
|
|
4
|
-
import {
|
|
4
|
+
import { Transaction } from "../transaction.js";
|
|
5
|
+
import { ChainCode, createOpReturnScript, inputScriptTypes, outputScript, outputScriptTypes, p2shP2pkOutputScript, supportsScriptType, } from "../fixedScriptWallet/index.js";
|
|
5
6
|
import { coinNames, isMainnet } from "../coinName.js";
|
|
6
7
|
import { getDefaultWalletKeys, getWalletKeysForSeed, getKeyTriple } from "./keys.js";
|
|
7
8
|
export const signStages = ["unsigned", "halfsigned", "fullsigned"];
|
|
8
9
|
export const txFormats = ["psbt", "psbt-lite"];
|
|
9
10
|
// Re-export for convenience
|
|
10
11
|
export { inputScriptTypes, outputScriptTypes };
|
|
12
|
+
/** Map InputScriptType to the OutputScriptType used for chain code derivation */
|
|
13
|
+
function inputScriptTypeToOutputScriptType(scriptType) {
|
|
14
|
+
switch (scriptType) {
|
|
15
|
+
case "p2sh":
|
|
16
|
+
case "p2shP2wsh":
|
|
17
|
+
case "p2wsh":
|
|
18
|
+
case "p2trLegacy":
|
|
19
|
+
return scriptType;
|
|
20
|
+
case "p2shP2pk":
|
|
21
|
+
return "p2sh";
|
|
22
|
+
case "p2trMusig2ScriptPath":
|
|
23
|
+
case "p2trMusig2KeyPath":
|
|
24
|
+
return "p2trMusig2";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
11
27
|
/**
|
|
12
28
|
* Creates a valid PSBT with as many features as possible (kitchen sink).
|
|
13
29
|
*
|
|
@@ -30,7 +46,7 @@ export { inputScriptTypes, outputScriptTypes };
|
|
|
30
46
|
* - psbt-lite: Only witness_utxo (no non_witness_utxo)
|
|
31
47
|
*/
|
|
32
48
|
export class AcidTest {
|
|
33
|
-
|
|
49
|
+
coin;
|
|
34
50
|
signStage;
|
|
35
51
|
txFormat;
|
|
36
52
|
rootWalletKeys;
|
|
@@ -41,8 +57,8 @@ export class AcidTest {
|
|
|
41
57
|
userXprv;
|
|
42
58
|
backupXprv;
|
|
43
59
|
bitgoXprv;
|
|
44
|
-
constructor(
|
|
45
|
-
this.
|
|
60
|
+
constructor(coin, signStage, txFormat, rootWalletKeys, otherWalletKeys, inputs, outputs, xprvTriple) {
|
|
61
|
+
this.coin = coin;
|
|
46
62
|
this.signStage = signStage;
|
|
47
63
|
this.txFormat = txFormat;
|
|
48
64
|
this.rootWalletKeys = rootWalletKeys;
|
|
@@ -56,7 +72,7 @@ export class AcidTest {
|
|
|
56
72
|
/**
|
|
57
73
|
* Create an AcidTest with specific configuration
|
|
58
74
|
*/
|
|
59
|
-
static withConfig(
|
|
75
|
+
static withConfig(coin, signStage, txFormat, suiteConfig = {}) {
|
|
60
76
|
const rootWalletKeys = getDefaultWalletKeys();
|
|
61
77
|
const otherWalletKeys = getWalletKeysForSeed("too many secrets");
|
|
62
78
|
// Filter inputs based on network support
|
|
@@ -67,9 +83,9 @@ export class AcidTest {
|
|
|
67
83
|
return true;
|
|
68
84
|
// Map input script types to output script types for support check
|
|
69
85
|
if (scriptType === "p2trMusig2KeyPath" || scriptType === "p2trMusig2ScriptPath") {
|
|
70
|
-
return supportsScriptType(
|
|
86
|
+
return supportsScriptType(coin, "p2trMusig2");
|
|
71
87
|
}
|
|
72
|
-
return supportsScriptType(
|
|
88
|
+
return supportsScriptType(coin, scriptType);
|
|
73
89
|
})
|
|
74
90
|
.filter((scriptType) => (suiteConfig.includeP2trMusig2ScriptPath ?? false) ||
|
|
75
91
|
scriptType !== "p2trMusig2ScriptPath")
|
|
@@ -79,7 +95,7 @@ export class AcidTest {
|
|
|
79
95
|
}));
|
|
80
96
|
// Filter outputs based on network support
|
|
81
97
|
const outputs = outputScriptTypes
|
|
82
|
-
.filter((scriptType) => supportsScriptType(
|
|
98
|
+
.filter((scriptType) => supportsScriptType(coin, scriptType))
|
|
83
99
|
.map((scriptType, index) => ({
|
|
84
100
|
scriptType,
|
|
85
101
|
value: BigInt(900 + index * 100), // Deterministic amounts
|
|
@@ -92,13 +108,13 @@ export class AcidTest {
|
|
|
92
108
|
outputs.push({ opReturn: "setec astronomy", value: BigInt(0) });
|
|
93
109
|
// Get private keys for signing
|
|
94
110
|
const xprvTriple = getKeyTriple("default");
|
|
95
|
-
return new AcidTest(
|
|
111
|
+
return new AcidTest(coin, signStage, txFormat, rootWalletKeys, otherWalletKeys, inputs, outputs, xprvTriple);
|
|
96
112
|
}
|
|
97
113
|
/**
|
|
98
114
|
* Get a human-readable name for this test configuration
|
|
99
115
|
*/
|
|
100
116
|
get name() {
|
|
101
|
-
return `${this.
|
|
117
|
+
return `${this.coin} ${this.signStage} ${this.txFormat}`;
|
|
102
118
|
}
|
|
103
119
|
/**
|
|
104
120
|
* Get the BIP32 user key for replay protection (p2shP2pk)
|
|
@@ -111,58 +127,55 @@ export class AcidTest {
|
|
|
111
127
|
*/
|
|
112
128
|
createPsbt() {
|
|
113
129
|
// Use ZcashBitGoPsbt for Zcash networks
|
|
114
|
-
const isZcash = this.
|
|
130
|
+
const isZcash = this.coin === "zec" || this.coin === "tzec";
|
|
115
131
|
const psbt = isZcash
|
|
116
|
-
? ZcashBitGoPsbt.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
consensusBranchId: 0xc2d6d0b4, // NU5
|
|
132
|
+
? ZcashBitGoPsbt.createEmpty(this.coin, this.rootWalletKeys, {
|
|
133
|
+
// Sapling activation height: mainnet=419200, testnet=280000
|
|
134
|
+
blockHeight: this.coin === "zec" ? 419200 : 280000,
|
|
120
135
|
})
|
|
121
|
-
: BitGoPsbt.createEmpty(this.
|
|
136
|
+
: BitGoPsbt.createEmpty(this.coin, this.rootWalletKeys, {
|
|
122
137
|
version: 2,
|
|
123
138
|
lockTime: 0,
|
|
124
139
|
});
|
|
140
|
+
// Build a fake previous transaction for non_witness_utxo (psbt format)
|
|
141
|
+
const usePrevTx = this.txFormat === "psbt" && !isZcash;
|
|
142
|
+
const buildPrevTx = (vout, script, value) => {
|
|
143
|
+
if (!usePrevTx)
|
|
144
|
+
return undefined;
|
|
145
|
+
const tx = Transaction.create();
|
|
146
|
+
tx.addInput("0".repeat(64), 0xffffffff);
|
|
147
|
+
for (let i = 0; i < vout; i++) {
|
|
148
|
+
tx.addOutput(new Uint8Array(0), 0n);
|
|
149
|
+
}
|
|
150
|
+
tx.addOutput(script, value);
|
|
151
|
+
return tx.toBytes();
|
|
152
|
+
};
|
|
125
153
|
// Add inputs with deterministic outpoints
|
|
126
154
|
this.inputs.forEach((input, index) => {
|
|
127
|
-
// Resolve scriptId: either from explicit scriptId or from scriptType + index
|
|
128
|
-
const scriptId = input.scriptId ?? {
|
|
129
|
-
chain: ChainCode.value("p2sh", "external"),
|
|
130
|
-
index: input.index ?? index,
|
|
131
|
-
};
|
|
132
155
|
const walletKeys = input.walletKeys ?? this.rootWalletKeys;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const ecpair = ECPair.fromPublicKey(replayKey.publicKey);
|
|
140
|
-
psbt.addReplayProtectionInput({
|
|
141
|
-
txid: "0".repeat(64),
|
|
142
|
-
vout: index,
|
|
143
|
-
value: input.value,
|
|
144
|
-
}, ecpair);
|
|
156
|
+
const outpoint = { txid: "0".repeat(64), vout: index, value: input.value };
|
|
157
|
+
// scriptId variant: caller provides explicit chain + index
|
|
158
|
+
if (input.scriptId) {
|
|
159
|
+
const script = outputScript(walletKeys, input.scriptId.chain, input.scriptId.index, this.coin);
|
|
160
|
+
psbt.addWalletInput({ ...outpoint, prevTx: buildPrevTx(index, script, input.value) }, walletKeys, { scriptId: input.scriptId, signPath: { signer: "user", cosigner: "bitgo" } });
|
|
161
|
+
return;
|
|
145
162
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
// Default: user + bitgo
|
|
155
|
-
signPath = { signer: "user", cosigner: "bitgo" };
|
|
156
|
-
}
|
|
157
|
-
psbt.addWalletInput({
|
|
158
|
-
txid: "0".repeat(64),
|
|
159
|
-
vout: index,
|
|
160
|
-
value: input.value,
|
|
161
|
-
}, walletKeys, {
|
|
162
|
-
scriptId,
|
|
163
|
-
signPath,
|
|
164
|
-
});
|
|
163
|
+
const scriptType = input.scriptType ?? "p2sh";
|
|
164
|
+
if (scriptType === "p2shP2pk") {
|
|
165
|
+
const ecpair = ECPair.fromPublicKey(this.getReplayProtectionKey().publicKey);
|
|
166
|
+
const script = p2shP2pkOutputScript(ecpair.publicKey);
|
|
167
|
+
psbt.addReplayProtectionInput({ ...outpoint, prevTx: buildPrevTx(index, script, input.value) }, ecpair);
|
|
168
|
+
return;
|
|
165
169
|
}
|
|
170
|
+
const scriptId = {
|
|
171
|
+
chain: ChainCode.value(inputScriptTypeToOutputScriptType(scriptType), "external"),
|
|
172
|
+
index: input.index ?? index,
|
|
173
|
+
};
|
|
174
|
+
const signPath = scriptType === "p2trMusig2ScriptPath"
|
|
175
|
+
? { signer: "user", cosigner: "backup" }
|
|
176
|
+
: { signer: "user", cosigner: "bitgo" };
|
|
177
|
+
const script = outputScript(walletKeys, scriptId.chain, scriptId.index, this.coin);
|
|
178
|
+
psbt.addWalletInput({ ...outpoint, prevTx: buildPrevTx(index, script, input.value) }, walletKeys, { scriptId, signPath });
|
|
166
179
|
});
|
|
167
180
|
// Add outputs
|
|
168
181
|
this.outputs.forEach((output, index) => {
|
|
@@ -193,7 +206,7 @@ export class AcidTest {
|
|
|
193
206
|
chain: scriptId.chain,
|
|
194
207
|
index: output.index ?? 1000 + index,
|
|
195
208
|
};
|
|
196
|
-
const script = outputScript(this.rootWalletKeys, externalScriptId.chain, externalScriptId.index, this.
|
|
209
|
+
const script = outputScript(this.rootWalletKeys, externalScriptId.chain, externalScriptId.index, this.coin);
|
|
197
210
|
psbt.addOutput(script, output.value);
|
|
198
211
|
}
|
|
199
212
|
else {
|
|
@@ -224,36 +237,22 @@ export class AcidTest {
|
|
|
224
237
|
// Generate MuSig2 nonces for user if needed
|
|
225
238
|
const hasMusig2Inputs = this.inputs.some((input) => input.scriptType === "p2trMusig2KeyPath" || input.scriptType === "p2trMusig2ScriptPath");
|
|
226
239
|
if (hasMusig2Inputs) {
|
|
227
|
-
|
|
228
|
-
if (isZcash) {
|
|
240
|
+
if (this.coin === "zec" || this.coin === "tzec") {
|
|
229
241
|
throw new Error("Zcash does not support MuSig2/Taproot inputs");
|
|
230
242
|
}
|
|
231
|
-
//
|
|
243
|
+
// MuSig2 requires ALL participant nonces before ANY signing.
|
|
244
|
+
// Generate nonces directly on the same PSBT for each participant key.
|
|
232
245
|
psbt.generateMusig2Nonces(userKey);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
psbt2.generateMusig2Nonces(bitgoKey);
|
|
244
|
-
psbt.combineMusig2Nonces(psbt2);
|
|
245
|
-
}
|
|
246
|
-
else if (hasScriptPath && !hasKeyPath) {
|
|
247
|
-
// Only script path inputs - generate backup nonces for all
|
|
248
|
-
const psbt2 = BitGoPsbt.fromBytes(bytes, this.network);
|
|
249
|
-
psbt2.generateMusig2Nonces(backupKey);
|
|
250
|
-
psbt.combineMusig2Nonces(psbt2);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
const psbt2 = BitGoPsbt.fromBytes(bytes, this.network);
|
|
254
|
-
psbt2.generateMusig2Nonces(bitgoKey);
|
|
255
|
-
psbt.combineMusig2Nonces(psbt2);
|
|
256
|
-
}
|
|
246
|
+
const hasKeyPath = this.inputs.some((input) => input.scriptType === "p2trMusig2KeyPath");
|
|
247
|
+
const hasScriptPath = this.inputs.some((input) => input.scriptType === "p2trMusig2ScriptPath");
|
|
248
|
+
// Key path uses user+bitgo, script path uses user+backup.
|
|
249
|
+
// generateMusig2Nonces fails if the key isn't a participant in any musig2 input,
|
|
250
|
+
// so we only call it for keys that match.
|
|
251
|
+
if (hasKeyPath) {
|
|
252
|
+
psbt.generateMusig2Nonces(bitgoKey);
|
|
253
|
+
}
|
|
254
|
+
if (hasScriptPath) {
|
|
255
|
+
psbt.generateMusig2Nonces(backupKey);
|
|
257
256
|
}
|
|
258
257
|
}
|
|
259
258
|
// Sign all wallet inputs with user key (bulk - more efficient)
|
|
@@ -296,7 +295,7 @@ export class AcidTest {
|
|
|
296
295
|
*/
|
|
297
296
|
static forAllNetworksSignStagesTxFormats(suiteConfig = {}) {
|
|
298
297
|
return coinNames
|
|
299
|
-
.filter((network) => isMainnet(network) && network !== "bsv")
|
|
298
|
+
.filter((network) => isMainnet(network) && network !== "bsv")
|
|
300
299
|
.flatMap((network) => signStages.flatMap((signStage) => txFormats.map((txFormat) => AcidTest.withConfig(network, signStage, txFormat, suiteConfig))));
|
|
301
300
|
}
|
|
302
301
|
}
|