Skip to content

Commit 4895f9c

Browse files
OttoAllmendingerllm-git
andcommitted
feat(abstract-utxo): use wasm-utxo instead of utxo-lib
Replace utxo-lib with wasm-utxo for all descriptor-related functionality, including BIP32 interfaces, message signing/verification, and PSBT handling. Issue: BTC-2650 Co-authored-by: llm-git <llm-git@ttll.de>
1 parent bb2389f commit 4895f9c

15 files changed

Lines changed: 85 additions & 72 deletions

File tree

modules/abstract-utxo/src/descriptor/NamedDescriptor.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import * as t from 'io-ts';
2-
import { Descriptor, DescriptorPkType } from '@bitgo/wasm-utxo';
3-
import { BIP32Interface, networks } from '@bitgo/utxo-lib';
4-
import { signMessage, verifyMessage } from '@bitgo/sdk-core';
2+
import { Descriptor, DescriptorPkType, bip32, message } from '@bitgo/wasm-utxo';
53

64
export const NamedDescriptor = t.intersection(
75
[
@@ -27,32 +25,36 @@ export type NamedDescriptorNative = NamedDescriptor<Descriptor>;
2725
export function createNamedDescriptorWithSignature(
2826
name: string,
2927
descriptor: string | Descriptor,
30-
signingKey: BIP32Interface
28+
signingKey: bip32.BIP32Interface
3129
): NamedDescriptor {
3230
if (typeof descriptor === 'string') {
3331
descriptor = Descriptor.fromString(descriptor, 'derivable');
3432
}
3533
const value = descriptor.toString();
36-
const signature = signMessage(value, signingKey, networks.bitcoin).toString('hex');
34+
const signature = Buffer.from(message.signMessage(value, signingKey.privateKey!)).toString('hex');
3735
return { name, value, signatures: [signature] };
3836
}
3937

4038
export function toNamedDescriptorNative(e: NamedDescriptor, pkType: DescriptorPkType): NamedDescriptorNative {
4139
return { ...e, value: Descriptor.fromString(e.value, pkType) };
4240
}
4341

44-
export function hasValidSignature(descriptor: string | Descriptor, key: BIP32Interface, signatures: string[]): boolean {
42+
export function hasValidSignature(
43+
descriptor: string | Descriptor,
44+
key: bip32.BIP32Interface,
45+
signatures: string[]
46+
): boolean {
4547
if (typeof descriptor === 'string') {
4648
descriptor = Descriptor.fromString(descriptor, 'derivable');
4749
}
4850

49-
const message = descriptor.toString();
51+
const descriptorString = descriptor.toString();
5052
return signatures.some((signature) => {
51-
return verifyMessage(message, key, Buffer.from(signature, 'hex'), networks.bitcoin);
53+
return message.verifyMessage(descriptorString, key.publicKey, Buffer.from(signature, 'hex'));
5254
});
5355
}
5456

55-
export function assertHasValidSignature(namedDescriptor: NamedDescriptor, key: BIP32Interface): void {
57+
export function assertHasValidSignature(namedDescriptor: NamedDescriptor, key: bip32.BIP32Interface): void {
5658
if (!hasValidSignature(namedDescriptor.value, key, namedDescriptor.signatures ?? [])) {
5759
throw new Error(`Descriptor ${namedDescriptor.name} does not have a valid signature (key=${key.toBase58()})`);
5860
}

modules/abstract-utxo/src/descriptor/builder/builder.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { BIP32Interface } from '@bitgo/utxo-lib';
2-
import { Descriptor } from '@bitgo/wasm-utxo';
1+
import { bip32, Descriptor } from '@bitgo/wasm-utxo';
32

43
type DescriptorWithKeys<TName extends string> = {
54
name: TName;
6-
keys: BIP32Interface[];
5+
keys: bip32.BIP32Interface[];
76
path: string;
87
};
98

@@ -17,14 +16,14 @@ export type DescriptorBuilder =
1716
*/
1817
| (DescriptorWithKeys<'ShWsh2Of3CltvDrop' | 'Wsh2Of3CltvDrop'> & { locktime: number });
1918

20-
function toXPub(k: BIP32Interface | string): string {
19+
function toXPub(k: bip32.BIP32Interface | string): string {
2120
if (typeof k === 'string') {
2221
return k;
2322
}
2423
return k.neutered().toBase58();
2524
}
2625

27-
function multi(m: number, n: number, keys: BIP32Interface[] | string[], path: string): string {
26+
function multi(m: number, n: number, keys: bip32.BIP32Interface[] | string[], path: string): string {
2827
if (n < m) {
2928
throw new Error(`Cannot create ${m} of ${n} multisig`);
3029
}

modules/abstract-utxo/src/descriptor/builder/parse.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { BIP32Interface, bip32 } from '@bitgo/secp256k1';
2-
import { Descriptor } from '@bitgo/wasm-utxo';
1+
import { BIP32, bip32, Descriptor } from '@bitgo/wasm-utxo';
32

43
import { DescriptorBuilder, getDescriptorFromBuilder } from './builder';
54

@@ -26,7 +25,7 @@ function unwrapNode(node: unknown, path: string[]): unknown {
2625

2726
function parseMulti(node: unknown): {
2827
threshold: number;
29-
keys: BIP32Interface[];
28+
keys: bip32.BIP32Interface[];
3029
path: string;
3130
} {
3231
if (!Array.isArray(node)) {
@@ -54,7 +53,7 @@ function parseMulti(node: unknown): {
5453
});
5554
return {
5655
threshold,
57-
keys: keyWithPath.map((k) => bip32.fromBase58(k.xpub)),
56+
keys: keyWithPath.map((k) => BIP32.fromBase58(k.xpub)),
5857
path: paths[0],
5958
};
6059
}

modules/abstract-utxo/src/descriptor/createWallet/createDescriptors.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { BIP32Interface } from '@bitgo/utxo-lib';
1+
import { bip32 } from '@bitgo/wasm-utxo';
22

33
import { createNamedDescriptorWithSignature, NamedDescriptor } from '../NamedDescriptor';
44
import { getDescriptorFromBuilder, DescriptorBuilder } from '../builder';
55

6-
export type DescriptorFromKeys = (userKey: BIP32Interface, cosigners: BIP32Interface[]) => NamedDescriptor[];
6+
export type DescriptorFromKeys = (
7+
userKey: bip32.BIP32Interface,
8+
cosigners: bip32.BIP32Interface[]
9+
) => NamedDescriptor[];
710

811
/**
912
* Create a pair of external and internal descriptors for a 2-of-3 multisig wallet.
@@ -14,7 +17,7 @@ export type DescriptorFromKeys = (userKey: BIP32Interface, cosigners: BIP32Inter
1417
*/
1518
function createExternalInternalPair(
1619
builder: DescriptorBuilder,
17-
userKey: BIP32Interface
20+
userKey: bip32.BIP32Interface
1821
): [NamedDescriptor, NamedDescriptor] {
1922
if (userKey.isNeutered()) {
2023
throw new Error('User key must be private');

modules/abstract-utxo/src/descriptor/validatePolicy.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { EnvironmentName, Triple } from '@bitgo/sdk-core';
2-
import * as utxolib from '@bitgo/utxo-lib';
3-
import { descriptorWallet } from '@bitgo/wasm-utxo';
2+
import { bip32, descriptorWallet } from '@bitgo/wasm-utxo';
43

54
import type { DescriptorMap } from '../wasmUtil';
65

76
import { parseDescriptor } from './builder';
87
import { hasValidSignature, NamedDescriptor, NamedDescriptorNative, toNamedDescriptorNative } from './NamedDescriptor';
98

10-
export type KeyTriple = Triple<utxolib.BIP32Interface>;
9+
export type KeyTriple = Triple<bip32.BIP32Interface>;
1110

1211
export interface DescriptorValidationPolicy {
1312
name: string;

modules/abstract-utxo/test/unit/descriptor/NamedDescriptor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import assert from 'assert';
22

3-
import { getFixture, getKeyTriple } from '@bitgo/utxo-core/testutil';
3+
import * as testutils from '@bitgo/wasm-utxo/testutils';
44

55
import { assertHasValidSignature, createNamedDescriptorWithSignature } from '../../../src/descriptor/NamedDescriptor';
66
import { getDescriptorFromBuilder } from '../../../src/descriptor/builder';
77

88
describe('NamedDescriptor', function () {
99
it('creates named descriptor with signature', async function () {
10-
const keys = getKeyTriple();
10+
const keys = testutils.getKeyTriple();
1111
const namedDescriptor = createNamedDescriptorWithSignature(
1212
'foo',
1313
getDescriptorFromBuilder({ name: 'Wsh2Of2', keys, path: '0/*' }),
1414
keys[0]
1515
);
1616
assert.deepStrictEqual(
17-
await getFixture(__dirname + '/fixtures/NamedDescriptorWithSignature.json', namedDescriptor),
17+
await testutils.getFixture(__dirname + '/fixtures/NamedDescriptorWithSignature.json', namedDescriptor),
1818
namedDescriptor
1919
);
2020
assertHasValidSignature(namedDescriptor, keys[0]);

modules/abstract-utxo/test/unit/descriptor/builder.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as assert from 'assert';
22

3-
import { getKeyTriple } from '@bitgo/utxo-core/testutil';
3+
import * as testutils from '@bitgo/wasm-utxo/testutils';
44

55
import { parseDescriptor, DescriptorBuilder, getDescriptorFromBuilder } from '../../../src/descriptor/builder';
66

77
function getDescriptorBuilderForType(name: DescriptorBuilder['name']): DescriptorBuilder {
8-
const keys = getKeyTriple().map((k) => k.neutered());
8+
const keys = testutils.getKeyTriple().map((k) => k.neutered());
99
switch (name) {
1010
case 'Wsh2Of2':
1111
case 'Wsh2Of3':
@@ -25,12 +25,19 @@ function getDescriptorBuilderForType(name: DescriptorBuilder['name']): Descripto
2525
}
2626
}
2727

28+
function toComparable(builder: DescriptorBuilder): Record<string, unknown> {
29+
return {
30+
...builder,
31+
keys: builder.keys.map((k) => k.toBase58()),
32+
};
33+
}
34+
2835
function describeForName(n: DescriptorBuilder['name']) {
2936
describe(`DescriptorBuilder ${n}`, () => {
3037
it('parses descriptor template', () => {
3138
const builder = getDescriptorBuilderForType(n);
3239
const descriptor = getDescriptorFromBuilder(builder);
33-
assert.deepStrictEqual(builder, parseDescriptor(descriptor));
40+
assert.deepStrictEqual(toComparable(builder), toComparable(parseDescriptor(descriptor)));
3441
});
3542
});
3643
}

modules/abstract-utxo/test/unit/descriptor/createWallet/createDescriptors.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import assert from 'assert';
22

3-
import { getFixture, getKeyTriple } from '@bitgo/utxo-core/testutil';
3+
import * as testutils from '@bitgo/wasm-utxo/testutils';
44

55
import { assertHasValidSignature } from '../../../../src/descriptor/NamedDescriptor';
66
import { DefaultWsh2Of3 } from '../../../../src/descriptor/createWallet';
77

88
describe('createDescriptors', function () {
99
it('should create standard named descriptors', async function () {
10-
const keys = getKeyTriple();
10+
const keys = testutils.getKeyTriple();
1111
const namedDescriptors = DefaultWsh2Of3(keys[0], keys.slice(1));
1212
assert.deepStrictEqual(
1313
namedDescriptors,
14-
await getFixture(__dirname + '/fixtures/DefaultWsh2Of3.json', namedDescriptors)
14+
await testutils.getFixture(__dirname + '/fixtures/DefaultWsh2Of3.json', namedDescriptors)
1515
);
1616
for (const namedDescriptor of namedDescriptors) {
1717
assertHasValidSignature(namedDescriptor, keys[0]);

modules/abstract-utxo/test/unit/descriptor/descriptorWallet.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import assert from 'assert';
22

3-
import { getDefaultXPubs, getDescriptorMap } from '@bitgo/utxo-core/testutil/descriptor';
3+
import * as testutils from '@bitgo/wasm-utxo/testutils';
44

55
import { getDescriptorMapFromWallet, isDescriptorWallet } from '../../../src/descriptor';
66
import { UtxoWallet } from '../../../src/wallet';
77
import { toBip32Triple } from '../../../src/keychains';
88
import { policyAllowAll } from '../../../src/descriptor/validatePolicy';
99

10+
const { getDefaultXPubs, getDescriptorMap } = testutils.descriptor;
11+
1012
describe('isDescriptorWalletData', function () {
1113
const descriptorMap = getDescriptorMap('Wsh2Of3');
1214
it('should return true for valid DescriptorWalletData', function () {

modules/abstract-utxo/test/unit/descriptorAddress.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as assert from 'node:assert';
22

3-
import * as utxolib from '@bitgo/utxo-lib';
43
import { address as wasmAddress, CoinName } from '@bitgo/wasm-utxo';
4+
import * as testutils from '@bitgo/wasm-utxo/testutils';
55
import { IWallet, WalletCoinSpecific } from '@bitgo/sdk-core';
66

77
import { descriptor as utxod } from '../../src';
@@ -15,7 +15,7 @@ export function getDescriptorAddress(d: string, index: number, coinName: CoinNam
1515

1616
describe('descriptor wallets', function () {
1717
const coin = getUtxoCoin('tbtc');
18-
const xpubs = utxolib.testutil.getKeyTriple('setec astronomy').map((k) => k.neutered().toBase58());
18+
const xpubs = testutils.getKeyTriple('setec astronomy').map((k) => k.neutered().toBase58());
1919

2020
function withChecksum(descriptor: string): string {
2121
return utxod.Descriptor.fromString(descriptor, 'derivable').toString();

0 commit comments

Comments
 (0)