@bitgo/wasm-utxo 1.7.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/fixedScriptWallet/BitGoPsbt.d.ts +99 -0
- package/dist/cjs/js/fixedScriptWallet/BitGoPsbt.js +122 -0
- package/dist/cjs/js/wasm/wasm_utxo.d.ts +86 -0
- package/dist/cjs/js/wasm/wasm_utxo.js +276 -24
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm.d.ts +21 -17
- package/dist/esm/js/fixedScriptWallet/BitGoPsbt.d.ts +99 -0
- package/dist/esm/js/fixedScriptWallet/BitGoPsbt.js +122 -0
- package/dist/esm/js/wasm/wasm_utxo.d.ts +86 -0
- package/dist/esm/js/wasm/wasm_utxo_bg.js +276 -24
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm.d.ts +21 -17
- 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 -95
- package/dist/esm/test/fixedScript/fixtureUtil.js +0 -55
- package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.d.ts +0 -1
- package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.js +0 -168
- package/dist/esm/test/fixedScript/verifySignature.d.ts +0 -1
- package/dist/esm/test/fixedScript/verifySignature.js +0 -187
- 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,95 +0,0 @@
|
|
|
1
|
-
import { RootWalletKeys } from "../../js/fixedScriptWallet/RootWalletKeys.js";
|
|
2
|
-
import { ECPair } from "../../js/ecpair.js";
|
|
3
|
-
export type SignatureState = "unsigned" | "halfsigned" | "fullsigned";
|
|
4
|
-
export type Triple<T> = [T, T, T];
|
|
5
|
-
export type Bip32Derivation = {
|
|
6
|
-
masterFingerprint: string;
|
|
7
|
-
pubkey: string;
|
|
8
|
-
path: string;
|
|
9
|
-
};
|
|
10
|
-
export type TapBip32Derivation = Bip32Derivation & {
|
|
11
|
-
leafHashes: string[];
|
|
12
|
-
};
|
|
13
|
-
export type WitnessUtxo = {
|
|
14
|
-
value: string;
|
|
15
|
-
script: string;
|
|
16
|
-
};
|
|
17
|
-
export type TapLeafScript = {
|
|
18
|
-
controlBlock: string;
|
|
19
|
-
script: string;
|
|
20
|
-
leafVersion: number;
|
|
21
|
-
};
|
|
22
|
-
export type PsbtInput = {
|
|
23
|
-
type: string;
|
|
24
|
-
sighashType: number;
|
|
25
|
-
redeemScript?: string;
|
|
26
|
-
witnessScript?: string;
|
|
27
|
-
bip32Derivation?: Bip32Derivation[];
|
|
28
|
-
tapBip32Derivation?: TapBip32Derivation[];
|
|
29
|
-
witnessUtxo?: WitnessUtxo;
|
|
30
|
-
tapLeafScript?: TapLeafScript[];
|
|
31
|
-
tapInternalKey?: string;
|
|
32
|
-
tapMerkleRoot?: string;
|
|
33
|
-
musig2Participants?: {
|
|
34
|
-
tapOutputKey: string;
|
|
35
|
-
tapInternalKey: string;
|
|
36
|
-
participantPubKeys: string[];
|
|
37
|
-
};
|
|
38
|
-
unknownKeyVals?: Array<{
|
|
39
|
-
key: string;
|
|
40
|
-
value: string;
|
|
41
|
-
}>;
|
|
42
|
-
};
|
|
43
|
-
export type Input = {
|
|
44
|
-
hash: string;
|
|
45
|
-
index: number;
|
|
46
|
-
sequence: number;
|
|
47
|
-
};
|
|
48
|
-
export type Output = {
|
|
49
|
-
script: string;
|
|
50
|
-
value: string;
|
|
51
|
-
address?: string;
|
|
52
|
-
};
|
|
53
|
-
export type TapTreeLeaf = {
|
|
54
|
-
depth: number;
|
|
55
|
-
leafVersion: number;
|
|
56
|
-
script: string;
|
|
57
|
-
};
|
|
58
|
-
export type PsbtOutput = {
|
|
59
|
-
redeemScript?: string;
|
|
60
|
-
witnessScript?: string;
|
|
61
|
-
bip32Derivation?: Bip32Derivation[];
|
|
62
|
-
tapBip32Derivation?: TapBip32Derivation[];
|
|
63
|
-
tapInternalKey?: string;
|
|
64
|
-
tapTree?: {
|
|
65
|
-
leaves: TapTreeLeaf[];
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
|
-
export type Fixture = {
|
|
69
|
-
walletKeys: [string, string, string];
|
|
70
|
-
psbtBase64: string;
|
|
71
|
-
psbtBase64Finalized: string | null;
|
|
72
|
-
inputs: Input[];
|
|
73
|
-
psbtInputs: PsbtInput[];
|
|
74
|
-
psbtInputsFinalized: PsbtInput[] | null;
|
|
75
|
-
outputs: Output[];
|
|
76
|
-
psbtOutputs: PsbtOutput[];
|
|
77
|
-
extractedTransaction: string | null;
|
|
78
|
-
};
|
|
79
|
-
/**
|
|
80
|
-
* Get PSBT buffer from a fixture
|
|
81
|
-
*/
|
|
82
|
-
export declare function getPsbtBuffer(fixture: Fixture): Buffer;
|
|
83
|
-
/**
|
|
84
|
-
* Load a PSBT fixture from JSON file
|
|
85
|
-
*/
|
|
86
|
-
export declare function loadPsbtFixture(network: string, signatureState: string): Fixture;
|
|
87
|
-
/**
|
|
88
|
-
* Load wallet keys from fixture
|
|
89
|
-
*/
|
|
90
|
-
export declare function loadWalletKeysFromFixture(fixture: Fixture): RootWalletKeys;
|
|
91
|
-
export declare function loadReplayProtectionKeyFromFixture(fixture: Fixture): ECPair;
|
|
92
|
-
/**
|
|
93
|
-
* Get extracted transaction hex from fixture
|
|
94
|
-
*/
|
|
95
|
-
export declare function getExtractedTransactionHex(fixture: Fixture): string;
|
|
@@ -1,55 +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
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
/**
|
|
12
|
-
* Get PSBT buffer from a fixture
|
|
13
|
-
*/
|
|
14
|
-
export function getPsbtBuffer(fixture) {
|
|
15
|
-
return Buffer.from(fixture.psbtBase64, "base64");
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Load a PSBT fixture from JSON file
|
|
19
|
-
*/
|
|
20
|
-
export function loadPsbtFixture(network, signatureState) {
|
|
21
|
-
const fixturePath = path.join(__dirname, "..", "fixtures", "fixed-script", `psbt-lite.${network}.${signatureState}.json`);
|
|
22
|
-
const fixtureContent = fs.readFileSync(fixturePath, "utf-8");
|
|
23
|
-
return JSON.parse(fixtureContent);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Load wallet keys from fixture
|
|
27
|
-
*/
|
|
28
|
-
export function loadWalletKeysFromFixture(fixture) {
|
|
29
|
-
// Parse xprvs and convert to xpubs
|
|
30
|
-
const xpubs = fixture.walletKeys.map((xprv) => {
|
|
31
|
-
const key = BIP32.fromBase58(xprv);
|
|
32
|
-
return key.neutered();
|
|
33
|
-
});
|
|
34
|
-
const walletKeysLike = {
|
|
35
|
-
triple: xpubs,
|
|
36
|
-
derivationPrefixes: ["0/0", "0/0", "0/0"],
|
|
37
|
-
};
|
|
38
|
-
return RootWalletKeys.from(walletKeysLike);
|
|
39
|
-
}
|
|
40
|
-
export function loadReplayProtectionKeyFromFixture(fixture) {
|
|
41
|
-
// underived user key
|
|
42
|
-
const userBip32 = BIP32.fromBase58(fixture.walletKeys[0]);
|
|
43
|
-
assert(userBip32.privateKey);
|
|
44
|
-
const userECPair = ECPair.fromPrivateKey(Buffer.from(userBip32.privateKey));
|
|
45
|
-
return userECPair;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Get extracted transaction hex from fixture
|
|
49
|
-
*/
|
|
50
|
-
export function getExtractedTransactionHex(fixture) {
|
|
51
|
-
if (fixture.extractedTransaction === null) {
|
|
52
|
-
throw new Error("Fixture does not have an extracted transaction");
|
|
53
|
-
}
|
|
54
|
-
return fixture.extractedTransaction;
|
|
55
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,168 +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, loadWalletKeysFromFixture, getPsbtBuffer, loadReplayProtectionKeyFromFixture, } from "./fixtureUtil.js";
|
|
5
|
-
function getExpectedInputScriptType(fixtureScriptType) {
|
|
6
|
-
// Map fixture types to InputScriptType values
|
|
7
|
-
// Based on the Rust mapping in src/fixed_script_wallet/test_utils/fixtures.rs
|
|
8
|
-
switch (fixtureScriptType) {
|
|
9
|
-
case "p2shP2pk":
|
|
10
|
-
case "p2sh":
|
|
11
|
-
case "p2shP2wsh":
|
|
12
|
-
case "p2wsh":
|
|
13
|
-
return fixtureScriptType;
|
|
14
|
-
case "p2tr":
|
|
15
|
-
return "p2trLegacy";
|
|
16
|
-
case "p2trMusig2":
|
|
17
|
-
return "p2trMusig2ScriptPath";
|
|
18
|
-
case "taprootKeyPathSpend":
|
|
19
|
-
return "p2trMusig2KeyPath";
|
|
20
|
-
default:
|
|
21
|
-
throw new Error(`Unknown fixture script type: ${fixtureScriptType}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function getOtherWalletKeys() {
|
|
25
|
-
const otherWalletKeys = utxolib.testutil.getKeyTriple("too many secrets");
|
|
26
|
-
return new utxolib.bitgo.RootWalletKeys(otherWalletKeys);
|
|
27
|
-
}
|
|
28
|
-
describe("parseTransactionWithWalletKeys", function () {
|
|
29
|
-
const supportedNetworks = utxolib.getNetworkList().filter((network) => {
|
|
30
|
-
return (utxolib.isMainnet(network) &&
|
|
31
|
-
network !== utxolib.networks.bitcoincash &&
|
|
32
|
-
network !== utxolib.networks.bitcoingold &&
|
|
33
|
-
network !== utxolib.networks.bitcoinsv &&
|
|
34
|
-
network !== utxolib.networks.ecash &&
|
|
35
|
-
network !== utxolib.networks.zcash);
|
|
36
|
-
});
|
|
37
|
-
supportedNetworks.forEach((network) => {
|
|
38
|
-
const networkName = utxolib.getNetworkName(network);
|
|
39
|
-
describe(`network: ${networkName}`, function () {
|
|
40
|
-
let fullsignedPsbtBytes;
|
|
41
|
-
let bitgoPsbt;
|
|
42
|
-
let rootWalletKeys;
|
|
43
|
-
let replayProtectionKey;
|
|
44
|
-
let fixture;
|
|
45
|
-
before(function () {
|
|
46
|
-
fixture = loadPsbtFixture(networkName, "fullsigned");
|
|
47
|
-
fullsignedPsbtBytes = getPsbtBuffer(fixture);
|
|
48
|
-
bitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(fullsignedPsbtBytes, networkName);
|
|
49
|
-
rootWalletKeys = loadWalletKeysFromFixture(fixture);
|
|
50
|
-
replayProtectionKey = loadReplayProtectionKeyFromFixture(fixture);
|
|
51
|
-
});
|
|
52
|
-
it("should have matching unsigned transaction ID", function () {
|
|
53
|
-
const unsignedTxid = bitgoPsbt.unsignedTxid();
|
|
54
|
-
const expectedUnsignedTxid = utxolib.bitgo
|
|
55
|
-
.createPsbtFromBuffer(fullsignedPsbtBytes, network)
|
|
56
|
-
.getUnsignedTx()
|
|
57
|
-
.getId();
|
|
58
|
-
assert.strictEqual(unsignedTxid, expectedUnsignedTxid);
|
|
59
|
-
});
|
|
60
|
-
it("should parse transaction and identify internal/external outputs", function () {
|
|
61
|
-
const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
|
|
62
|
-
publicKeys: [replayProtectionKey],
|
|
63
|
-
});
|
|
64
|
-
// Verify all inputs have addresses and values
|
|
65
|
-
parsed.inputs.forEach((input, i) => {
|
|
66
|
-
assert.ok(input.address, `Input ${i} should have an address`);
|
|
67
|
-
assert.ok(typeof input.value === "bigint", `Input ${i} value should be bigint`);
|
|
68
|
-
assert.ok(input.value > 0n, `Input ${i} value should be > 0`);
|
|
69
|
-
});
|
|
70
|
-
// Validate outputs
|
|
71
|
-
assert.ok(parsed.outputs.length > 0, "Should have at least one output");
|
|
72
|
-
// Count internal outputs (scriptId is defined and not null)
|
|
73
|
-
const internalOutputs = parsed.outputs.filter((o) => o.scriptId);
|
|
74
|
-
// Count external outputs (scriptId is null or undefined)
|
|
75
|
-
const externalOutputs = parsed.outputs.filter((o) => o.scriptId === null);
|
|
76
|
-
assert.ok(externalOutputs.every((o) => o.address || o.script));
|
|
77
|
-
const nonAddressOutputs = externalOutputs.filter((o) => o.address === null);
|
|
78
|
-
assert.strictEqual(nonAddressOutputs.length, 1);
|
|
79
|
-
const [opReturnOutput] = nonAddressOutputs;
|
|
80
|
-
const expectedOpReturn = utxolib.payments.embed({
|
|
81
|
-
data: [Buffer.from("setec astronomy")],
|
|
82
|
-
}).output;
|
|
83
|
-
assert.strictEqual(Buffer.from(opReturnOutput.script).toString("hex"), expectedOpReturn.toString("hex"));
|
|
84
|
-
// Fixtures now have 3 external outputs
|
|
85
|
-
assert.ok(internalOutputs.length > 0, "Should have internal outputs (have scriptId)");
|
|
86
|
-
assert.strictEqual(externalOutputs.length, 3, "Should have 3 external outputs in test fixture");
|
|
87
|
-
// Verify all outputs have proper structure
|
|
88
|
-
parsed.outputs.forEach((output, i) => {
|
|
89
|
-
assert.ok(output.script instanceof Uint8Array, `Output ${i} script should be Uint8Array`);
|
|
90
|
-
assert.ok(typeof output.value === "bigint", `Output ${i} value should be bigint`);
|
|
91
|
-
assert.ok(output.value > 0n, `Output ${i} value should be > 0`);
|
|
92
|
-
// Address is optional for non-standard scripts
|
|
93
|
-
});
|
|
94
|
-
// Verify spend amount (should be > 0 since there are external outputs)
|
|
95
|
-
assert.strictEqual(parsed.spendAmount, 900n * 3n);
|
|
96
|
-
// Verify miner fee calculation
|
|
97
|
-
const totalInputValue = parsed.inputs.reduce((sum, i) => sum + i.value, 0n);
|
|
98
|
-
const totalOutputValue = parsed.outputs.reduce((sum, o) => sum + o.value, 0n);
|
|
99
|
-
assert.strictEqual(parsed.minerFee, totalInputValue - totalOutputValue, "Miner fee should equal inputs minus outputs");
|
|
100
|
-
assert.ok(parsed.minerFee > 0n, "Miner fee should be > 0");
|
|
101
|
-
// Verify virtual size
|
|
102
|
-
assert.ok(typeof parsed.virtualSize === "number", "Virtual size should be a number");
|
|
103
|
-
assert.ok(parsed.virtualSize > 0, "Virtual size should be > 0");
|
|
104
|
-
});
|
|
105
|
-
it("should parse inputs with correct scriptType", function () {
|
|
106
|
-
const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
|
|
107
|
-
publicKeys: [replayProtectionKey],
|
|
108
|
-
});
|
|
109
|
-
// Verify all inputs have scriptType matching fixture
|
|
110
|
-
parsed.inputs.forEach((input, i) => {
|
|
111
|
-
const fixtureInput = fixture.psbtInputs[i];
|
|
112
|
-
const expectedScriptType = getExpectedInputScriptType(fixtureInput.type);
|
|
113
|
-
assert.strictEqual(input.scriptType, expectedScriptType, `Input ${i} scriptType should be ${expectedScriptType}, got ${input.scriptType}`);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
it("should fail to parse with other wallet keys", function () {
|
|
117
|
-
assert.throws(() => {
|
|
118
|
-
bitgoPsbt.parseTransactionWithWalletKeys(getOtherWalletKeys(), {
|
|
119
|
-
publicKeys: [replayProtectionKey],
|
|
120
|
-
});
|
|
121
|
-
}, (error) => {
|
|
122
|
-
return error.message.includes("Failed to parse transaction: Input 0: wallet validation failed");
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
it("should recognize output for other wallet keys", function () {
|
|
126
|
-
const parsedOutputs = bitgoPsbt.parseOutputsWithWalletKeys(getOtherWalletKeys());
|
|
127
|
-
// Should return an array of parsed outputs
|
|
128
|
-
assert.ok(Array.isArray(parsedOutputs), "Should return an array");
|
|
129
|
-
assert.ok(parsedOutputs.length > 0, "Should have at least one output");
|
|
130
|
-
// Verify all outputs have proper structure
|
|
131
|
-
parsedOutputs.forEach((output, i) => {
|
|
132
|
-
assert.ok(output.script instanceof Uint8Array, `Output ${i} script should be Uint8Array`);
|
|
133
|
-
assert.ok(typeof output.value === "bigint", `Output ${i} value should be bigint`);
|
|
134
|
-
assert.ok(output.value > 0n, `Output ${i} value should be > 0`);
|
|
135
|
-
// Address can be null for non-standard scripts
|
|
136
|
-
assert.ok(typeof output.address === "string" || output.address === null, `Output ${i} address should be string or null`);
|
|
137
|
-
// scriptId can be null for external outputs
|
|
138
|
-
assert.ok(output.scriptId === null ||
|
|
139
|
-
(typeof output.scriptId === "object" &&
|
|
140
|
-
typeof output.scriptId.chain === "number" &&
|
|
141
|
-
typeof output.scriptId.index === "number"), `Output ${i} scriptId should be null or an object with chain and index`);
|
|
142
|
-
});
|
|
143
|
-
// Compare with the original wallet keys to verify we get different results
|
|
144
|
-
const originalParsedOutputs = bitgoPsbt.parseOutputsWithWalletKeys(rootWalletKeys);
|
|
145
|
-
// Should have the same number of outputs
|
|
146
|
-
assert.strictEqual(parsedOutputs.length, originalParsedOutputs.length, "Should parse the same number of outputs");
|
|
147
|
-
// Find outputs that belong to the other wallet keys (scriptId !== null)
|
|
148
|
-
const otherWalletOutputs = parsedOutputs.filter((o) => o.scriptId !== null);
|
|
149
|
-
// Should have exactly one output for the other wallet keys
|
|
150
|
-
assert.strictEqual(otherWalletOutputs.length, 1, "Should have exactly one output belonging to the other wallet keys");
|
|
151
|
-
// Verify that this output is marked as external (scriptId === null) under regular wallet keys
|
|
152
|
-
const otherWalletOutputIndex = parsedOutputs.findIndex((o) => o.scriptId !== null);
|
|
153
|
-
const sameOutputWithRegularKeys = originalParsedOutputs[otherWalletOutputIndex];
|
|
154
|
-
assert.strictEqual(sameOutputWithRegularKeys.scriptId, null, "The output belonging to other wallet keys should be marked as external (scriptId === null) when parsed with regular wallet keys");
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
describe("error handling", function () {
|
|
159
|
-
it("should throw error for invalid PSBT bytes", function () {
|
|
160
|
-
const invalidBytes = new Uint8Array([0x00, 0x01, 0x02]);
|
|
161
|
-
assert.throws(() => {
|
|
162
|
-
fixedScriptWallet.BitGoPsbt.fromBytes(invalidBytes, "bitcoin");
|
|
163
|
-
}, (error) => {
|
|
164
|
-
return error.message.includes("Failed to deserialize PSBT");
|
|
165
|
-
}, "Should throw error for invalid PSBT bytes");
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|