@bitgo/wasm-utxo 1.8.0 → 1.9.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/wasm/wasm_utxo.js +6 -6
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm.d.ts +4 -4
- package/dist/esm/js/wasm/wasm_utxo_bg.js +6 -6
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm.d.ts +4 -4
- package/package.json +3 -3
- package/dist/cjs/package.json +0 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +0 -1
- package/dist/esm/test/address/utxolibCompat.d.ts +0 -1
- package/dist/esm/test/address/utxolibCompat.js +0 -107
- package/dist/esm/test/ast/formatNode.d.ts +0 -1
- package/dist/esm/test/ast/formatNode.js +0 -15
- package/dist/esm/test/bip32.d.ts +0 -1
- package/dist/esm/test/bip32.js +0 -242
- package/dist/esm/test/descriptorFixtures.d.ts +0 -25
- package/dist/esm/test/descriptorFixtures.js +0 -605
- package/dist/esm/test/descriptorUtil.d.ts +0 -13
- package/dist/esm/test/descriptorUtil.js +0 -52
- package/dist/esm/test/ecpair.d.ts +0 -1
- package/dist/esm/test/ecpair.js +0 -137
- package/dist/esm/test/fixedScript/address.d.ts +0 -1
- package/dist/esm/test/fixedScript/address.js +0 -66
- package/dist/esm/test/fixedScript/finalizeExtract.d.ts +0 -1
- package/dist/esm/test/fixedScript/finalizeExtract.js +0 -66
- package/dist/esm/test/fixedScript/fixtureUtil.d.ts +0 -103
- package/dist/esm/test/fixedScript/fixtureUtil.js +0 -65
- package/dist/esm/test/fixedScript/musig2Nonces.d.ts +0 -1
- package/dist/esm/test/fixedScript/musig2Nonces.js +0 -77
- package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.d.ts +0 -1
- package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.js +0 -168
- package/dist/esm/test/fixedScript/signAndVerifySignature.d.ts +0 -1
- package/dist/esm/test/fixedScript/signAndVerifySignature.js +0 -268
- package/dist/esm/test/fixedScript/walletKeys.util.d.ts +0 -12
- package/dist/esm/test/fixedScript/walletKeys.util.js +0 -17
- package/dist/esm/test/fixedScriptToDescriptor.d.ts +0 -1
- package/dist/esm/test/fixedScriptToDescriptor.js +0 -93
- package/dist/esm/test/fixtures.d.ts +0 -1
- package/dist/esm/test/fixtures.js +0 -16
- package/dist/esm/test/opdrop.d.ts +0 -1
- package/dist/esm/test/opdrop.js +0 -85
- package/dist/esm/test/psbt.util.d.ts +0 -8
- package/dist/esm/test/psbt.util.js +0 -113
- package/dist/esm/test/psbtFixedScriptCompat.d.ts +0 -1
- package/dist/esm/test/psbtFixedScriptCompat.js +0 -116
- package/dist/esm/test/psbtFixedScriptCompatFixtures.d.ts +0 -10
- package/dist/esm/test/psbtFixedScriptCompatFixtures.js +0 -53
- package/dist/esm/test/psbtFromDescriptor.d.ts +0 -1
- package/dist/esm/test/psbtFromDescriptor.js +0 -104
- package/dist/esm/test/psbtFromDescriptor.util.d.ts +0 -63
- package/dist/esm/test/psbtFromDescriptor.util.js +0 -101
- package/dist/esm/test/test.d.ts +0 -1
- package/dist/esm/test/test.js +0 -123
- package/dist/esm/tsconfig.tsbuildinfo +0 -1
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import * as assert from "node:assert";
|
|
2
|
-
import * as fs from "fs/promises";
|
|
3
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
4
|
-
import { formatNode } from "../js/ast/index.js";
|
|
5
|
-
async function assertEqualJSON(path, value) {
|
|
6
|
-
try {
|
|
7
|
-
const data = JSON.parse(await fs.readFile(path, "utf8"));
|
|
8
|
-
assert.deepStrictEqual(data, value);
|
|
9
|
-
}
|
|
10
|
-
catch (e) {
|
|
11
|
-
if (typeof e === "object" && e !== null && "code" in e && e.code === "ENOENT") {
|
|
12
|
-
await fs.writeFile(path, JSON.stringify(value, null, 2));
|
|
13
|
-
throw new Error("Expected file not found, wrote it instead");
|
|
14
|
-
}
|
|
15
|
-
throw e;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export async function assertEqualFixture(path, content) {
|
|
19
|
-
await assertEqualJSON(path, content);
|
|
20
|
-
}
|
|
21
|
-
/** Expand a template with the given root wallet keys and chain code */
|
|
22
|
-
function expand(rootWalletKeys, keyIndex, chainCode) {
|
|
23
|
-
if (keyIndex !== 0 && keyIndex !== 1 && keyIndex !== 2) {
|
|
24
|
-
throw new Error("Invalid key index");
|
|
25
|
-
}
|
|
26
|
-
const xpub = rootWalletKeys.triple[keyIndex].neutered().toBase58();
|
|
27
|
-
const prefix = rootWalletKeys.derivationPrefixes[keyIndex];
|
|
28
|
-
return xpub + "/" + prefix + "/" + chainCode + "/*";
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Get a standard output descriptor that corresponds to the proprietary HD wallet setup
|
|
32
|
-
* used in BitGo wallets.
|
|
33
|
-
* Only supports a subset of script types.
|
|
34
|
-
*/
|
|
35
|
-
export function getDescriptorForScriptType(rootWalletKeys, scriptType, scope) {
|
|
36
|
-
const chain = scope === "external"
|
|
37
|
-
? utxolib.bitgo.getExternalChainCode(scriptType)
|
|
38
|
-
: utxolib.bitgo.getInternalChainCode(scriptType);
|
|
39
|
-
const multi = {
|
|
40
|
-
multi: [2, ...rootWalletKeys.triple.map((_, i) => expand(rootWalletKeys, i, chain))],
|
|
41
|
-
};
|
|
42
|
-
switch (scriptType) {
|
|
43
|
-
case "p2sh":
|
|
44
|
-
return formatNode({ sh: multi });
|
|
45
|
-
case "p2shP2wsh":
|
|
46
|
-
return formatNode({ sh: { wsh: multi } });
|
|
47
|
-
case "p2wsh":
|
|
48
|
-
return formatNode({ wsh: multi });
|
|
49
|
-
default:
|
|
50
|
-
throw new Error(`Unsupported script type ${scriptType}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/esm/test/ecpair.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import * as assert from "assert";
|
|
2
|
-
import { ECPair } from "../js/ecpair.js";
|
|
3
|
-
describe("WasmECPair", () => {
|
|
4
|
-
const testPrivateKey = Buffer.from("1111111111111111111111111111111111111111111111111111111111111111", "hex");
|
|
5
|
-
const testWifMainnet = "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn";
|
|
6
|
-
const testWifTestnet = "cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA";
|
|
7
|
-
it("should create from private key", () => {
|
|
8
|
-
const key = ECPair.fromPrivateKey(testPrivateKey);
|
|
9
|
-
assert.ok(key.privateKey instanceof Uint8Array);
|
|
10
|
-
assert.ok(key.publicKey instanceof Uint8Array);
|
|
11
|
-
assert.strictEqual(key.privateKey.length, 32);
|
|
12
|
-
assert.strictEqual(key.publicKey.length, 33); // Always compressed
|
|
13
|
-
});
|
|
14
|
-
it("should create from public key", () => {
|
|
15
|
-
const tempKey = ECPair.fromPrivateKey(testPrivateKey);
|
|
16
|
-
const publicKey = tempKey.publicKey;
|
|
17
|
-
const key = ECPair.fromPublicKey(publicKey);
|
|
18
|
-
assert.strictEqual(key.privateKey, undefined);
|
|
19
|
-
assert.ok(key.publicKey instanceof Uint8Array);
|
|
20
|
-
assert.strictEqual(key.publicKey.length, 33);
|
|
21
|
-
});
|
|
22
|
-
it("should create from mainnet WIF", () => {
|
|
23
|
-
const key = ECPair.fromWIF(testWifMainnet);
|
|
24
|
-
assert.ok(key.privateKey instanceof Uint8Array);
|
|
25
|
-
assert.ok(key.publicKey instanceof Uint8Array);
|
|
26
|
-
assert.strictEqual(key.privateKey.length, 32);
|
|
27
|
-
});
|
|
28
|
-
it("should create from testnet WIF", () => {
|
|
29
|
-
const key = ECPair.fromWIF(testWifTestnet);
|
|
30
|
-
assert.ok(key.privateKey instanceof Uint8Array);
|
|
31
|
-
assert.ok(key.publicKey instanceof Uint8Array);
|
|
32
|
-
assert.strictEqual(key.privateKey.length, 32);
|
|
33
|
-
});
|
|
34
|
-
it("should create from mainnet WIF using fromWIFMainnet", () => {
|
|
35
|
-
const key = ECPair.fromWIFMainnet(testWifMainnet);
|
|
36
|
-
assert.ok(key.privateKey instanceof Uint8Array);
|
|
37
|
-
assert.ok(key.publicKey instanceof Uint8Array);
|
|
38
|
-
});
|
|
39
|
-
it("should create from testnet WIF using fromWIFTestnet", () => {
|
|
40
|
-
const key = ECPair.fromWIFTestnet(testWifTestnet);
|
|
41
|
-
assert.ok(key.privateKey instanceof Uint8Array);
|
|
42
|
-
assert.ok(key.publicKey instanceof Uint8Array);
|
|
43
|
-
});
|
|
44
|
-
it("should fail when using wrong network WIF method", () => {
|
|
45
|
-
assert.throws(() => {
|
|
46
|
-
ECPair.fromWIFMainnet(testWifTestnet);
|
|
47
|
-
});
|
|
48
|
-
assert.throws(() => {
|
|
49
|
-
ECPair.fromWIFTestnet(testWifMainnet);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
it("should export to WIF mainnet", () => {
|
|
53
|
-
const key = ECPair.fromPrivateKey(testPrivateKey);
|
|
54
|
-
const wif = key.toWIF();
|
|
55
|
-
assert.ok(typeof wif === "string");
|
|
56
|
-
assert.ok(wif.length > 0);
|
|
57
|
-
assert.ok(wif.startsWith("K") || wif.startsWith("L")); // Mainnet compressed
|
|
58
|
-
});
|
|
59
|
-
it("should export to WIF testnet", () => {
|
|
60
|
-
const key = ECPair.fromPrivateKey(testPrivateKey);
|
|
61
|
-
const wif = key.toWIFTestnet();
|
|
62
|
-
assert.ok(typeof wif === "string");
|
|
63
|
-
assert.ok(wif.length > 0);
|
|
64
|
-
assert.ok(wif.startsWith("c")); // Testnet compressed
|
|
65
|
-
});
|
|
66
|
-
it("should roundtrip WIF mainnet", () => {
|
|
67
|
-
const key1 = ECPair.fromPrivateKey(testPrivateKey);
|
|
68
|
-
const wif = key1.toWIF();
|
|
69
|
-
const key2 = ECPair.fromWIF(wif);
|
|
70
|
-
assert.deepStrictEqual(key1.privateKey, key2.privateKey);
|
|
71
|
-
assert.deepStrictEqual(key1.publicKey, key2.publicKey);
|
|
72
|
-
});
|
|
73
|
-
it("should roundtrip WIF testnet", () => {
|
|
74
|
-
const key1 = ECPair.fromPrivateKey(testPrivateKey);
|
|
75
|
-
const wif = key1.toWIFTestnet();
|
|
76
|
-
const key2 = ECPair.fromWIF(wif);
|
|
77
|
-
assert.deepStrictEqual(key1.privateKey, key2.privateKey);
|
|
78
|
-
assert.deepStrictEqual(key1.publicKey, key2.publicKey);
|
|
79
|
-
});
|
|
80
|
-
it("should fail to export WIF from public key", () => {
|
|
81
|
-
const tempKey = ECPair.fromPrivateKey(testPrivateKey);
|
|
82
|
-
const publicKey = tempKey.publicKey;
|
|
83
|
-
const key = ECPair.fromPublicKey(publicKey);
|
|
84
|
-
assert.throws(() => {
|
|
85
|
-
key.toWIF();
|
|
86
|
-
});
|
|
87
|
-
assert.throws(() => {
|
|
88
|
-
key.toWIFMainnet();
|
|
89
|
-
});
|
|
90
|
-
assert.throws(() => {
|
|
91
|
-
key.toWIFTestnet();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
it("should reject invalid private keys", () => {
|
|
95
|
-
// All zeros
|
|
96
|
-
assert.throws(() => {
|
|
97
|
-
ECPair.fromPrivateKey(new Uint8Array(32));
|
|
98
|
-
});
|
|
99
|
-
// Wrong length
|
|
100
|
-
assert.throws(() => {
|
|
101
|
-
ECPair.fromPrivateKey(new Uint8Array(31));
|
|
102
|
-
});
|
|
103
|
-
assert.throws(() => {
|
|
104
|
-
ECPair.fromPrivateKey(new Uint8Array(33));
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
it("should reject invalid public keys", () => {
|
|
108
|
-
// Wrong length
|
|
109
|
-
assert.throws(() => {
|
|
110
|
-
ECPair.fromPublicKey(new Uint8Array(32));
|
|
111
|
-
});
|
|
112
|
-
assert.throws(() => {
|
|
113
|
-
ECPair.fromPublicKey(new Uint8Array(34));
|
|
114
|
-
});
|
|
115
|
-
// Invalid format
|
|
116
|
-
assert.throws(() => {
|
|
117
|
-
const invalidPubkey = new Uint8Array(33);
|
|
118
|
-
invalidPubkey[0] = 0x01; // Invalid prefix
|
|
119
|
-
ECPair.fromPublicKey(invalidPubkey);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
it("should always produce compressed public keys", () => {
|
|
123
|
-
const key1 = ECPair.fromPrivateKey(testPrivateKey);
|
|
124
|
-
const key2 = ECPair.fromWIF(testWifMainnet);
|
|
125
|
-
// All public keys should be 33 bytes (compressed)
|
|
126
|
-
assert.strictEqual(key1.publicKey.length, 33);
|
|
127
|
-
assert.strictEqual(key2.publicKey.length, 33);
|
|
128
|
-
// All should start with 0x02 or 0x03 (compressed format)
|
|
129
|
-
assert.ok(key1.publicKey[0] === 0x02 || key1.publicKey[0] === 0x03);
|
|
130
|
-
assert.ok(key2.publicKey[0] === 0x02 || key2.publicKey[0] === 0x03);
|
|
131
|
-
});
|
|
132
|
-
it("should derive same public key from same private key", () => {
|
|
133
|
-
const key1 = ECPair.fromPrivateKey(testPrivateKey);
|
|
134
|
-
const key2 = ECPair.fromPrivateKey(testPrivateKey);
|
|
135
|
-
assert.deepStrictEqual(key1.publicKey, key2.publicKey);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
|
-
import { fixedScriptWallet } from "../../js/index.js";
|
|
4
|
-
function getAddressUtxoLib(keys, chain, index, network, addressFormat) {
|
|
5
|
-
if (!utxolib.bitgo.isChainCode(chain)) {
|
|
6
|
-
throw new Error(`Invalid chain code: ${chain}`);
|
|
7
|
-
}
|
|
8
|
-
const derived = keys.deriveForChainAndIndex(chain, index);
|
|
9
|
-
const script = utxolib.bitgo.outputScripts.createOutputScript2of3(derived.publicKeys, utxolib.bitgo.outputScripts.scriptTypeForChain(chain));
|
|
10
|
-
const address = utxolib.addressFormat.fromOutputScriptWithFormat(script.scriptPubKey, addressFormat, network);
|
|
11
|
-
return address;
|
|
12
|
-
}
|
|
13
|
-
function runTest(network, { derivationPrefixes, addressFormat, } = {}) {
|
|
14
|
-
describe(`address for network ${utxolib.getNetworkName(network)}, derivationPrefixes=${Boolean(derivationPrefixes)}`, function () {
|
|
15
|
-
const keyTriple = utxolib.testutil.getKeyTriple("wasm");
|
|
16
|
-
const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(keyTriple.map((k) => k.neutered()), derivationPrefixes);
|
|
17
|
-
const supportedChainCodes = utxolib.bitgo.chainCodes.filter((chainCode) => {
|
|
18
|
-
const scriptType = utxolib.bitgo.outputScripts.scriptTypeForChain(chainCode);
|
|
19
|
-
return utxolib.bitgo.outputScripts.isSupportedScriptType(network, scriptType);
|
|
20
|
-
});
|
|
21
|
-
it(`can recreate address from wallet keys for chain codes ${supportedChainCodes.join(", ")}`, function () {
|
|
22
|
-
for (const chainCode of supportedChainCodes) {
|
|
23
|
-
for (let index = 0; index < 2; index++) {
|
|
24
|
-
const utxolibAddress = getAddressUtxoLib(rootWalletKeys, chainCode, index, network, addressFormat ?? "default");
|
|
25
|
-
const wasmAddress = fixedScriptWallet.address(rootWalletKeys, chainCode, index, network, addressFormat);
|
|
26
|
-
assert.strictEqual(utxolibAddress, wasmAddress);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
const unsupportedChainCodes = utxolib.bitgo.chainCodes.filter((chainCode) => {
|
|
31
|
-
const scriptType = utxolib.bitgo.outputScripts.scriptTypeForChain(chainCode);
|
|
32
|
-
return !utxolib.bitgo.outputScripts.isSupportedScriptType(network, scriptType);
|
|
33
|
-
});
|
|
34
|
-
if (unsupportedChainCodes.length > 0) {
|
|
35
|
-
it(`throws error for unsupported chain codes ${unsupportedChainCodes.join(", ")}`, function () {
|
|
36
|
-
for (const chainCode of unsupportedChainCodes) {
|
|
37
|
-
const scriptType = utxolib.bitgo.outputScripts.scriptTypeForChain(chainCode);
|
|
38
|
-
assert.throws(() => {
|
|
39
|
-
fixedScriptWallet.address(rootWalletKeys, chainCode, 0, network, addressFormat);
|
|
40
|
-
}, (error) => {
|
|
41
|
-
const errorMessage = error.message.toLowerCase();
|
|
42
|
-
const isSegwitError = scriptType === "p2shP2wsh" || scriptType === "p2wsh";
|
|
43
|
-
const isTaprootError = scriptType === "p2tr" || scriptType === "p2trMusig2";
|
|
44
|
-
if (isSegwitError) {
|
|
45
|
-
return errorMessage.includes("does not support segwit");
|
|
46
|
-
}
|
|
47
|
-
else if (isTaprootError) {
|
|
48
|
-
return errorMessage.includes("does not support taproot");
|
|
49
|
-
}
|
|
50
|
-
return false;
|
|
51
|
-
}, `Expected error for unsupported script type ${scriptType} on network ${utxolib.getNetworkName(network)}`);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
describe("address for networks", function () {
|
|
58
|
-
utxolib.getNetworkList().forEach((network) => {
|
|
59
|
-
runTest(network);
|
|
60
|
-
runTest(network, { derivationPrefixes: ["m/1/2", "m/0/0", "m/0/0"] });
|
|
61
|
-
if (utxolib.getMainnet(network) === utxolib.networks.bitcoincash ||
|
|
62
|
-
utxolib.getMainnet(network) === utxolib.networks.ecash) {
|
|
63
|
-
runTest(network, { addressFormat: "cashaddr" });
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
|
-
import { fixedScriptWallet } from "../../js/index.js";
|
|
4
|
-
import { loadPsbtFixture, getPsbtBuffer, getExtractedTransactionHex, } from "./fixtureUtil.js";
|
|
5
|
-
describe("finalize and extract transaction", function () {
|
|
6
|
-
const supportedNetworks = utxolib.getNetworkList().filter((network) => {
|
|
7
|
-
return (utxolib.isMainnet(network) &&
|
|
8
|
-
network !== utxolib.networks.bitcoincash &&
|
|
9
|
-
network !== utxolib.networks.bitcoingold &&
|
|
10
|
-
network !== utxolib.networks.bitcoinsv &&
|
|
11
|
-
network !== utxolib.networks.ecash &&
|
|
12
|
-
network !== utxolib.networks.zcash);
|
|
13
|
-
});
|
|
14
|
-
supportedNetworks.forEach((network) => {
|
|
15
|
-
const networkName = utxolib.getNetworkName(network);
|
|
16
|
-
describe(`network: ${networkName}`, function () {
|
|
17
|
-
let fullsignedFixture;
|
|
18
|
-
let fullsignedPsbtBuffer;
|
|
19
|
-
let fullsignedBitgoPsbt;
|
|
20
|
-
before(function () {
|
|
21
|
-
fullsignedFixture = loadPsbtFixture(networkName, "fullsigned");
|
|
22
|
-
fullsignedPsbtBuffer = getPsbtBuffer(fullsignedFixture);
|
|
23
|
-
fullsignedBitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(fullsignedPsbtBuffer, networkName);
|
|
24
|
-
});
|
|
25
|
-
it("should serialize and deserialize PSBT (round-trip)", function () {
|
|
26
|
-
const serialized = fullsignedBitgoPsbt.serialize();
|
|
27
|
-
// Verify we can deserialize what we serialized (functional round-trip)
|
|
28
|
-
const deserialized = fixedScriptWallet.BitGoPsbt.fromBytes(serialized, networkName);
|
|
29
|
-
// Verify the deserialized PSBT has the same unsigned txid
|
|
30
|
-
assert.strictEqual(deserialized.unsignedTxid(), fullsignedBitgoPsbt.unsignedTxid(), "Deserialized PSBT should have same unsigned txid after round-trip");
|
|
31
|
-
// Verify the re-deserialized PSBT can be serialized back to bytes
|
|
32
|
-
const reserialized = deserialized.serialize();
|
|
33
|
-
// Verify functional equivalence by deserializing again and checking txid
|
|
34
|
-
const redeserialized = fixedScriptWallet.BitGoPsbt.fromBytes(reserialized, networkName);
|
|
35
|
-
assert.strictEqual(redeserialized.unsignedTxid(), fullsignedBitgoPsbt.unsignedTxid(), "PSBT should maintain consistency through multiple serialize/deserialize cycles");
|
|
36
|
-
});
|
|
37
|
-
it("should finalize all inputs and be extractable", function () {
|
|
38
|
-
// Create a fresh instance for finalization
|
|
39
|
-
const psbt = fixedScriptWallet.BitGoPsbt.fromBytes(fullsignedPsbtBuffer, networkName);
|
|
40
|
-
// Finalize all inputs
|
|
41
|
-
psbt.finalizeAllInputs();
|
|
42
|
-
// Serialize the finalized PSBT
|
|
43
|
-
const serialized = psbt.serialize();
|
|
44
|
-
// Verify we can deserialize the finalized PSBT
|
|
45
|
-
const deserialized = fixedScriptWallet.BitGoPsbt.fromBytes(serialized, networkName);
|
|
46
|
-
// Verify it can be extracted (which confirms finalization worked)
|
|
47
|
-
const extractedTx = deserialized.extractTransaction();
|
|
48
|
-
const extractedTxHex = Buffer.from(extractedTx).toString("hex");
|
|
49
|
-
const expectedTxHex = getExtractedTransactionHex(fullsignedFixture);
|
|
50
|
-
assert.strictEqual(extractedTxHex, expectedTxHex, "Extracted transaction from finalized PSBT should match expected transaction");
|
|
51
|
-
});
|
|
52
|
-
it("should extract transaction from finalized PSBT", function () {
|
|
53
|
-
// Create a fresh instance for extraction
|
|
54
|
-
const psbt = fixedScriptWallet.BitGoPsbt.fromBytes(fullsignedPsbtBuffer, networkName);
|
|
55
|
-
// Finalize all inputs
|
|
56
|
-
psbt.finalizeAllInputs();
|
|
57
|
-
// Extract transaction
|
|
58
|
-
const extractedTx = psbt.extractTransaction();
|
|
59
|
-
const extractedTxHex = Buffer.from(extractedTx).toString("hex");
|
|
60
|
-
// Get expected transaction hex from fixture
|
|
61
|
-
const expectedTxHex = getExtractedTransactionHex(fullsignedFixture);
|
|
62
|
-
assert.strictEqual(extractedTxHex, expectedTxHex, "Extracted transaction should match expected transaction");
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { RootWalletKeys } from "../../js/fixedScriptWallet/RootWalletKeys.js";
|
|
2
|
-
import { ECPair } from "../../js/ecpair.js";
|
|
3
|
-
import type { BitGoPsbt, NetworkName } from "../../js/fixedScriptWallet/index.js";
|
|
4
|
-
export type SignatureState = "unsigned" | "halfsigned" | "fullsigned";
|
|
5
|
-
export type Triple<T> = [T, T, T];
|
|
6
|
-
export type Bip32Derivation = {
|
|
7
|
-
masterFingerprint: string;
|
|
8
|
-
pubkey: string;
|
|
9
|
-
path: string;
|
|
10
|
-
};
|
|
11
|
-
export type TapBip32Derivation = Bip32Derivation & {
|
|
12
|
-
leafHashes: string[];
|
|
13
|
-
};
|
|
14
|
-
export type WitnessUtxo = {
|
|
15
|
-
value: string;
|
|
16
|
-
script: string;
|
|
17
|
-
};
|
|
18
|
-
export type TapLeafScript = {
|
|
19
|
-
controlBlock: string;
|
|
20
|
-
script: string;
|
|
21
|
-
leafVersion: number;
|
|
22
|
-
};
|
|
23
|
-
export type PsbtInput = {
|
|
24
|
-
type: string;
|
|
25
|
-
sighashType: number;
|
|
26
|
-
redeemScript?: string;
|
|
27
|
-
witnessScript?: string;
|
|
28
|
-
bip32Derivation?: Bip32Derivation[];
|
|
29
|
-
tapBip32Derivation?: TapBip32Derivation[];
|
|
30
|
-
witnessUtxo?: WitnessUtxo;
|
|
31
|
-
tapLeafScript?: TapLeafScript[];
|
|
32
|
-
tapInternalKey?: string;
|
|
33
|
-
tapMerkleRoot?: string;
|
|
34
|
-
musig2Participants?: {
|
|
35
|
-
tapOutputKey: string;
|
|
36
|
-
tapInternalKey: string;
|
|
37
|
-
participantPubKeys: string[];
|
|
38
|
-
};
|
|
39
|
-
unknownKeyVals?: Array<{
|
|
40
|
-
key: string;
|
|
41
|
-
value: string;
|
|
42
|
-
}>;
|
|
43
|
-
};
|
|
44
|
-
export type Input = {
|
|
45
|
-
hash: string;
|
|
46
|
-
index: number;
|
|
47
|
-
sequence: number;
|
|
48
|
-
};
|
|
49
|
-
export type Output = {
|
|
50
|
-
script: string;
|
|
51
|
-
value: string;
|
|
52
|
-
address?: string;
|
|
53
|
-
};
|
|
54
|
-
export type TapTreeLeaf = {
|
|
55
|
-
depth: number;
|
|
56
|
-
leafVersion: number;
|
|
57
|
-
script: string;
|
|
58
|
-
};
|
|
59
|
-
export type PsbtOutput = {
|
|
60
|
-
redeemScript?: string;
|
|
61
|
-
witnessScript?: string;
|
|
62
|
-
bip32Derivation?: Bip32Derivation[];
|
|
63
|
-
tapBip32Derivation?: TapBip32Derivation[];
|
|
64
|
-
tapInternalKey?: string;
|
|
65
|
-
tapTree?: {
|
|
66
|
-
leaves: TapTreeLeaf[];
|
|
67
|
-
};
|
|
68
|
-
};
|
|
69
|
-
export type Fixture = {
|
|
70
|
-
walletKeys: [string, string, string];
|
|
71
|
-
psbtBase64: string;
|
|
72
|
-
psbtBase64Finalized: string | null;
|
|
73
|
-
inputs: Input[];
|
|
74
|
-
psbtInputs: PsbtInput[];
|
|
75
|
-
psbtInputsFinalized: PsbtInput[] | null;
|
|
76
|
-
outputs: Output[];
|
|
77
|
-
psbtOutputs: PsbtOutput[];
|
|
78
|
-
extractedTransaction: string | null;
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* Get PSBT buffer from a fixture
|
|
82
|
-
*/
|
|
83
|
-
export declare function getPsbtBuffer(fixture: Fixture): Buffer;
|
|
84
|
-
/**
|
|
85
|
-
* Get BitGoPsbt from a fixture
|
|
86
|
-
* @param fixture - The test fixture
|
|
87
|
-
* @param networkName - The network name for deserializing the PSBT
|
|
88
|
-
* @returns A BitGoPsbt instance
|
|
89
|
-
*/
|
|
90
|
-
export declare function getBitGoPsbt(fixture: Fixture, networkName: NetworkName): BitGoPsbt;
|
|
91
|
-
/**
|
|
92
|
-
* Load a PSBT fixture from JSON file
|
|
93
|
-
*/
|
|
94
|
-
export declare function loadPsbtFixture(network: string, signatureState: string): Fixture;
|
|
95
|
-
/**
|
|
96
|
-
* Load wallet keys from fixture
|
|
97
|
-
*/
|
|
98
|
-
export declare function loadWalletKeysFromFixture(fixture: Fixture): RootWalletKeys;
|
|
99
|
-
export declare function loadReplayProtectionKeyFromFixture(fixture: Fixture): ECPair;
|
|
100
|
-
/**
|
|
101
|
-
* Get extracted transaction hex from fixture
|
|
102
|
-
*/
|
|
103
|
-
export declare function getExtractedTransactionHex(fixture: Fixture): string;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { dirname } from "node:path";
|
|
6
|
-
import { BIP32 } from "../../js/bip32.js";
|
|
7
|
-
import { RootWalletKeys } from "../../js/fixedScriptWallet/RootWalletKeys.js";
|
|
8
|
-
import { ECPair } from "../../js/ecpair.js";
|
|
9
|
-
import { fixedScriptWallet } from "../../js/index.js";
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = dirname(__filename);
|
|
12
|
-
/**
|
|
13
|
-
* Get PSBT buffer from a fixture
|
|
14
|
-
*/
|
|
15
|
-
export function getPsbtBuffer(fixture) {
|
|
16
|
-
return Buffer.from(fixture.psbtBase64, "base64");
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Get BitGoPsbt from a fixture
|
|
20
|
-
* @param fixture - The test fixture
|
|
21
|
-
* @param networkName - The network name for deserializing the PSBT
|
|
22
|
-
* @returns A BitGoPsbt instance
|
|
23
|
-
*/
|
|
24
|
-
export function getBitGoPsbt(fixture, networkName) {
|
|
25
|
-
return fixedScriptWallet.BitGoPsbt.fromBytes(getPsbtBuffer(fixture), networkName);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Load a PSBT fixture from JSON file
|
|
29
|
-
*/
|
|
30
|
-
export function loadPsbtFixture(network, signatureState) {
|
|
31
|
-
const fixturePath = path.join(__dirname, "..", "fixtures", "fixed-script", `psbt-lite.${network}.${signatureState}.json`);
|
|
32
|
-
const fixtureContent = fs.readFileSync(fixturePath, "utf-8");
|
|
33
|
-
return JSON.parse(fixtureContent);
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Load wallet keys from fixture
|
|
37
|
-
*/
|
|
38
|
-
export function loadWalletKeysFromFixture(fixture) {
|
|
39
|
-
// Parse xprvs and convert to xpubs
|
|
40
|
-
const xpubs = fixture.walletKeys.map((xprv) => {
|
|
41
|
-
const key = BIP32.fromBase58(xprv);
|
|
42
|
-
return key.neutered();
|
|
43
|
-
});
|
|
44
|
-
const walletKeysLike = {
|
|
45
|
-
triple: xpubs,
|
|
46
|
-
derivationPrefixes: ["0/0", "0/0", "0/0"],
|
|
47
|
-
};
|
|
48
|
-
return RootWalletKeys.from(walletKeysLike);
|
|
49
|
-
}
|
|
50
|
-
export function loadReplayProtectionKeyFromFixture(fixture) {
|
|
51
|
-
// underived user key
|
|
52
|
-
const userBip32 = BIP32.fromBase58(fixture.walletKeys[0]);
|
|
53
|
-
assert(userBip32.privateKey);
|
|
54
|
-
const userECPair = ECPair.fromPrivateKey(Buffer.from(userBip32.privateKey));
|
|
55
|
-
return userECPair;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Get extracted transaction hex from fixture
|
|
59
|
-
*/
|
|
60
|
-
export function getExtractedTransactionHex(fixture) {
|
|
61
|
-
if (fixture.extractedTransaction === null) {
|
|
62
|
-
throw new Error("Fixture does not have an extracted transaction");
|
|
63
|
-
}
|
|
64
|
-
return fixture.extractedTransaction;
|
|
65
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import assert from "assert";
|
|
2
|
-
import { BIP32 } from "../../js/bip32.js";
|
|
3
|
-
import { loadPsbtFixture, getBitGoPsbt } from "./fixtureUtil.js";
|
|
4
|
-
describe("MuSig2 nonce management", function () {
|
|
5
|
-
describe("Bitcoin mainnet", function () {
|
|
6
|
-
const networkName = "bitcoin";
|
|
7
|
-
let fixture;
|
|
8
|
-
let userKey;
|
|
9
|
-
let backupKey;
|
|
10
|
-
let bitgoKey;
|
|
11
|
-
before(function () {
|
|
12
|
-
fixture = loadPsbtFixture(networkName, "unsigned");
|
|
13
|
-
userKey = BIP32.fromBase58(fixture.walletKeys[0]);
|
|
14
|
-
backupKey = BIP32.fromBase58(fixture.walletKeys[1]);
|
|
15
|
-
bitgoKey = BIP32.fromBase58(fixture.walletKeys[2]);
|
|
16
|
-
});
|
|
17
|
-
it("should generate nonces for MuSig2 inputs with auto-generated session ID", function () {
|
|
18
|
-
const unsignedBitgoPsbt = getBitGoPsbt(fixture, networkName);
|
|
19
|
-
// Generate nonces with auto-generated session ID (no second parameter)
|
|
20
|
-
assert.doesNotThrow(() => {
|
|
21
|
-
unsignedBitgoPsbt.generateMusig2Nonces(userKey);
|
|
22
|
-
});
|
|
23
|
-
// Verify nonces were stored by serializing and deserializing
|
|
24
|
-
const serializedWithUserNonces = unsignedBitgoPsbt.serialize();
|
|
25
|
-
assert.ok(serializedWithUserNonces.length > getBitGoPsbt(fixture, networkName).serialize().length);
|
|
26
|
-
assert.doesNotThrow(() => {
|
|
27
|
-
unsignedBitgoPsbt.generateMusig2Nonces(bitgoKey);
|
|
28
|
-
});
|
|
29
|
-
const serializedWithBitgoNonces = unsignedBitgoPsbt.serialize();
|
|
30
|
-
assert.ok(serializedWithBitgoNonces.length > serializedWithUserNonces.length);
|
|
31
|
-
assert.throws(() => {
|
|
32
|
-
unsignedBitgoPsbt.generateMusig2Nonces(backupKey);
|
|
33
|
-
}, "Should throw error when generating nonces for backup key");
|
|
34
|
-
});
|
|
35
|
-
it("implements combineMusig2Nonces", function () {
|
|
36
|
-
const unsignedBitgoPsbtWithUserNonces = getBitGoPsbt(fixture, networkName);
|
|
37
|
-
unsignedBitgoPsbtWithUserNonces.generateMusig2Nonces(userKey);
|
|
38
|
-
const unsignedBitgoPsbtWithBitgoNonces = getBitGoPsbt(fixture, networkName);
|
|
39
|
-
unsignedBitgoPsbtWithBitgoNonces.generateMusig2Nonces(bitgoKey);
|
|
40
|
-
const unsignedBitgoPsbtWithBothNonces = getBitGoPsbt(fixture, networkName);
|
|
41
|
-
unsignedBitgoPsbtWithBothNonces.combineMusig2Nonces(unsignedBitgoPsbtWithUserNonces);
|
|
42
|
-
unsignedBitgoPsbtWithBothNonces.combineMusig2Nonces(unsignedBitgoPsbtWithBitgoNonces);
|
|
43
|
-
{
|
|
44
|
-
const psbt = getBitGoPsbt(fixture, networkName);
|
|
45
|
-
psbt.combineMusig2Nonces(unsignedBitgoPsbtWithUserNonces);
|
|
46
|
-
assert.strictEqual(psbt.serialize().length, unsignedBitgoPsbtWithUserNonces.serialize().length);
|
|
47
|
-
}
|
|
48
|
-
{
|
|
49
|
-
const psbt = getBitGoPsbt(fixture, networkName);
|
|
50
|
-
psbt.combineMusig2Nonces(unsignedBitgoPsbtWithBitgoNonces);
|
|
51
|
-
assert.strictEqual(psbt.serialize().length, unsignedBitgoPsbtWithBitgoNonces.serialize().length);
|
|
52
|
-
}
|
|
53
|
-
{
|
|
54
|
-
const psbt = getBitGoPsbt(fixture, networkName);
|
|
55
|
-
psbt.combineMusig2Nonces(unsignedBitgoPsbtWithUserNonces);
|
|
56
|
-
psbt.combineMusig2Nonces(unsignedBitgoPsbtWithBitgoNonces);
|
|
57
|
-
assert.strictEqual(psbt.serialize().length, unsignedBitgoPsbtWithBothNonces.serialize().length);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
it("should reject invalid session ID length", function () {
|
|
61
|
-
const unsignedBitgoPsbt = getBitGoPsbt(fixture, networkName);
|
|
62
|
-
// Invalid session ID (wrong length)
|
|
63
|
-
const invalidSessionId = new Uint8Array(16); // Should be 32 bytes
|
|
64
|
-
assert.throws(() => {
|
|
65
|
-
unsignedBitgoPsbt.generateMusig2Nonces(userKey, invalidSessionId);
|
|
66
|
-
}, "Should throw error for invalid session ID length");
|
|
67
|
-
});
|
|
68
|
-
it("should reject custom session ID on mainnet (security)", function () {
|
|
69
|
-
const unsignedBitgoPsbt = getBitGoPsbt(fixture, "bitcoin");
|
|
70
|
-
// Custom session ID should be rejected on mainnet for security
|
|
71
|
-
const customSessionId = new Uint8Array(32).fill(1);
|
|
72
|
-
assert.throws(() => {
|
|
73
|
-
unsignedBitgoPsbt.generateMusig2Nonces(userKey, customSessionId);
|
|
74
|
-
}, /Custom session_id is only allowed on testnets/, "Should throw error when providing custom session_id on mainnet");
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|