Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions modules/abstract-utxo/src/descriptor/NamedDescriptor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as t from 'io-ts';
import { Descriptor, DescriptorPkType } from '@bitgo/wasm-utxo';
import { BIP32Interface, networks } from '@bitgo/utxo-lib';
import { signMessage, verifyMessage } from '@bitgo/sdk-core';
import { Descriptor, DescriptorPkType, bip32, message } from '@bitgo/wasm-utxo';

export const NamedDescriptor = t.intersection(
[
Expand All @@ -27,32 +25,36 @@ export type NamedDescriptorNative = NamedDescriptor<Descriptor>;
export function createNamedDescriptorWithSignature(
name: string,
descriptor: string | Descriptor,
signingKey: BIP32Interface
signingKey: bip32.BIP32Interface
): NamedDescriptor {
if (typeof descriptor === 'string') {
descriptor = Descriptor.fromString(descriptor, 'derivable');
}
const value = descriptor.toString();
const signature = signMessage(value, signingKey, networks.bitcoin).toString('hex');
const signature = Buffer.from(message.signMessage(value, signingKey.privateKey!)).toString('hex');
return { name, value, signatures: [signature] };
}

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

export function hasValidSignature(descriptor: string | Descriptor, key: BIP32Interface, signatures: string[]): boolean {
export function hasValidSignature(
descriptor: string | Descriptor,
key: bip32.BIP32Interface,
signatures: string[]
): boolean {
if (typeof descriptor === 'string') {
descriptor = Descriptor.fromString(descriptor, 'derivable');
}

const message = descriptor.toString();
const descriptorString = descriptor.toString();
return signatures.some((signature) => {
return verifyMessage(message, key, Buffer.from(signature, 'hex'), networks.bitcoin);
return message.verifyMessage(descriptorString, key.publicKey, Buffer.from(signature, 'hex'));
});
}

export function assertHasValidSignature(namedDescriptor: NamedDescriptor, key: BIP32Interface): void {
export function assertHasValidSignature(namedDescriptor: NamedDescriptor, key: bip32.BIP32Interface): void {
if (!hasValidSignature(namedDescriptor.value, key, namedDescriptor.signatures ?? [])) {
throw new Error(`Descriptor ${namedDescriptor.name} does not have a valid signature (key=${key.toBase58()})`);
}
Expand Down
9 changes: 4 additions & 5 deletions modules/abstract-utxo/src/descriptor/builder/builder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { BIP32Interface } from '@bitgo/utxo-lib';
import { Descriptor } from '@bitgo/wasm-utxo';
import { bip32, Descriptor } from '@bitgo/wasm-utxo';

type DescriptorWithKeys<TName extends string> = {
name: TName;
keys: BIP32Interface[];
keys: bip32.BIP32Interface[];
path: string;
};

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

function toXPub(k: BIP32Interface | string): string {
function toXPub(k: bip32.BIP32Interface | string): string {
if (typeof k === 'string') {
return k;
}
return k.neutered().toBase58();
}

function multi(m: number, n: number, keys: BIP32Interface[] | string[], path: string): string {
function multi(m: number, n: number, keys: bip32.BIP32Interface[] | string[], path: string): string {
if (n < m) {
throw new Error(`Cannot create ${m} of ${n} multisig`);
}
Expand Down
7 changes: 3 additions & 4 deletions modules/abstract-utxo/src/descriptor/builder/parse.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { BIP32Interface, bip32 } from '@bitgo/secp256k1';
import { Descriptor } from '@bitgo/wasm-utxo';
import { BIP32, bip32, Descriptor } from '@bitgo/wasm-utxo';

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

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

function parseMulti(node: unknown): {
threshold: number;
keys: BIP32Interface[];
keys: bip32.BIP32Interface[];
path: string;
} {
if (!Array.isArray(node)) {
Expand Down Expand Up @@ -54,7 +53,7 @@ function parseMulti(node: unknown): {
});
return {
threshold,
keys: keyWithPath.map((k) => bip32.fromBase58(k.xpub)),
keys: keyWithPath.map((k) => BIP32.fromBase58(k.xpub)),
path: paths[0],
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { BIP32Interface } from '@bitgo/utxo-lib';
import { bip32 } from '@bitgo/wasm-utxo';

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

export type DescriptorFromKeys = (userKey: BIP32Interface, cosigners: BIP32Interface[]) => NamedDescriptor[];
export type DescriptorFromKeys = (
userKey: bip32.BIP32Interface,
cosigners: bip32.BIP32Interface[]
) => NamedDescriptor[];

/**
* Create a pair of external and internal descriptors for a 2-of-3 multisig wallet.
Expand All @@ -14,7 +17,7 @@ export type DescriptorFromKeys = (userKey: BIP32Interface, cosigners: BIP32Inter
*/
function createExternalInternalPair(
builder: DescriptorBuilder,
userKey: BIP32Interface
userKey: bip32.BIP32Interface
): [NamedDescriptor, NamedDescriptor] {
if (userKey.isNeutered()) {
throw new Error('User key must be private');
Expand Down
5 changes: 2 additions & 3 deletions modules/abstract-utxo/src/descriptor/validatePolicy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { EnvironmentName, Triple } from '@bitgo/sdk-core';
import * as utxolib from '@bitgo/utxo-lib';
import { descriptorWallet } from '@bitgo/wasm-utxo';
import { bip32, descriptorWallet } from '@bitgo/wasm-utxo';

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

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

export type KeyTriple = Triple<utxolib.BIP32Interface>;
export type KeyTriple = Triple<bip32.BIP32Interface>;

export interface DescriptorValidationPolicy {
name: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import assert from 'assert';

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

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

describe('NamedDescriptor', function () {
it('creates named descriptor with signature', async function () {
const keys = getKeyTriple();
const keys = testutils.getKeyTriple('default');
const namedDescriptor = createNamedDescriptorWithSignature(
'foo',
getDescriptorFromBuilder({ name: 'Wsh2Of2', keys, path: '0/*' }),
keys[0]
);
assert.deepStrictEqual(
await getFixture(__dirname + '/fixtures/NamedDescriptorWithSignature.json', namedDescriptor),
await testutils.getFixture(__dirname + '/fixtures/NamedDescriptorWithSignature.json', namedDescriptor),
namedDescriptor
);
assertHasValidSignature(namedDescriptor, keys[0]);
Expand Down
13 changes: 10 additions & 3 deletions modules/abstract-utxo/test/unit/descriptor/builder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as assert from 'assert';

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

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

function getDescriptorBuilderForType(name: DescriptorBuilder['name']): DescriptorBuilder {
const keys = getKeyTriple().map((k) => k.neutered());
const keys = testutils.getKeyTriple('default').map((k) => k.neutered());
switch (name) {
case 'Wsh2Of2':
case 'Wsh2Of3':
Expand All @@ -25,12 +25,19 @@ function getDescriptorBuilderForType(name: DescriptorBuilder['name']): Descripto
}
}

function toComparable(builder: DescriptorBuilder): Record<string, unknown> {
return {
...builder,
keys: builder.keys.map((k) => k.toBase58()),
};
}

function describeForName(n: DescriptorBuilder['name']) {
describe(`DescriptorBuilder ${n}`, () => {
it('parses descriptor template', () => {
const builder = getDescriptorBuilderForType(n);
const descriptor = getDescriptorFromBuilder(builder);
assert.deepStrictEqual(builder, parseDescriptor(descriptor));
assert.deepStrictEqual(toComparable(builder), toComparable(parseDescriptor(descriptor)));
});
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import assert from 'assert';

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

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

describe('createDescriptors', function () {
it('should create standard named descriptors', async function () {
const keys = getKeyTriple();
const keys = testutils.getKeyTriple('default');
const namedDescriptors = DefaultWsh2Of3(keys[0], keys.slice(1));
assert.deepStrictEqual(
namedDescriptors,
await getFixture(__dirname + '/fixtures/DefaultWsh2Of3.json', namedDescriptors)
await testutils.getFixture(__dirname + '/fixtures/DefaultWsh2Of3.json', namedDescriptors)
);
for (const namedDescriptor of namedDescriptors) {
assertHasValidSignature(namedDescriptor, keys[0]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
[
{
"name": "Wsh2Of3/external",
"value": "wsh(multi(2,xpub661MyMwAqRbcFXS2qwsTkaFc7PEpwDXWgY1Hx2MS7XywhW24sTjQzxiUgnGNW5v6DsW9Z8JcAqf8a22v21jDSA3DwLbbpt2ra3WbP83QNvP/0/*,xpub661MyMwAqRbcGfiaWcoeKLerFu3qRfy6zSYAwnmxSKW8JSauRajFsAsRHs2pV4q5rxkb4ynx4Bm8t54McTCp8V27s7XsdpD8T4s56etpjro/0/*,xpub661MyMwAqRbcH4HzWiCwmYajy1SXZngxHvpYDNEX4xjrDAneAm6rnpPuPPXcBRsSgxupDBmH2tzPHkikNrnLbsvTHemPFHSFbZxonZcCwFi/0/*))#x382fygz",
"value": "wsh(multi(2,xpub661MyMwAqRbcGZfgedgcQiBJpkHoZ37k6uotUVLoC6Px4Y46Yrdmy1CUogcJMoAsosY381gPjhGe9jFx1uYAcxy2gTHh9YFP32tUWycqHnV/0/*,xpub661MyMwAqRbcF8FoYWukS8eTn2gVEojzwf5DYETpB8uqC8t5sqDCEFnuJ39DaPjLRerBDK9QqSMvSYpT4WSugCVbUK5HEevSKAu1wUkVWsS/0/*,xpub661MyMwAqRbcFdScyinA4JpCViqkKsd37MQ6fwuZQQ4shdaGRRX9a8bWvR9QC1AFqKongweJJfyrm7uCoWmCw7UixwGZkZnCT2mchFr7cQb/0/*))#ve5dkda0",
"signatures": [
"20f303b798a75260ce7934e8fff7d8da12fd1b99dbd74879596c201d6acc344cbd151291c25d3d5fba428e844a6eb63a45f645deb2a5ae44e51d968614d4de9a39"
"1fe5bd14b540ae1c21110d49afb650d6fdc48621165e5e24012ffef42e97a38b0d1d9e9955692b9d8be4919b977438c4cde4d366a5e7ce5237fc9f01b4154dc4e1"
]
},
{
"name": "Wsh2Of3/internal",
"value": "wsh(multi(2,xpub661MyMwAqRbcFXS2qwsTkaFc7PEpwDXWgY1Hx2MS7XywhW24sTjQzxiUgnGNW5v6DsW9Z8JcAqf8a22v21jDSA3DwLbbpt2ra3WbP83QNvP/1/*,xpub661MyMwAqRbcGfiaWcoeKLerFu3qRfy6zSYAwnmxSKW8JSauRajFsAsRHs2pV4q5rxkb4ynx4Bm8t54McTCp8V27s7XsdpD8T4s56etpjro/1/*,xpub661MyMwAqRbcH4HzWiCwmYajy1SXZngxHvpYDNEX4xjrDAneAm6rnpPuPPXcBRsSgxupDBmH2tzPHkikNrnLbsvTHemPFHSFbZxonZcCwFi/1/*))#73gjjc7a",
"value": "wsh(multi(2,xpub661MyMwAqRbcGZfgedgcQiBJpkHoZ37k6uotUVLoC6Px4Y46Yrdmy1CUogcJMoAsosY381gPjhGe9jFx1uYAcxy2gTHh9YFP32tUWycqHnV/1/*,xpub661MyMwAqRbcF8FoYWukS8eTn2gVEojzwf5DYETpB8uqC8t5sqDCEFnuJ39DaPjLRerBDK9QqSMvSYpT4WSugCVbUK5HEevSKAu1wUkVWsS/1/*,xpub661MyMwAqRbcFdScyinA4JpCViqkKsd37MQ6fwuZQQ4shdaGRRX9a8bWvR9QC1AFqKongweJJfyrm7uCoWmCw7UixwGZkZnCT2mchFr7cQb/1/*))#5em4d3ts",
"signatures": [
"1fd2b2d9e294e9fe56ccba37d9992220c9bc16bb4c05fbd4f700de2eff1ae1dc051d1b3573304c470a61d5f5c4fa652a785cf9a69865b44da7059701bc9ee8173f"
"2001654c1ce7a8c94dc510c0cd25a0eee5d06da44e146b3c18546084206f53bbec194e14bc4ffd97eedad97a2176db0e4215323a30f683b8fd8bebcd135f19687e"
]
}
]
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import assert from 'assert';

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

import { getDescriptorMapFromWallet, isDescriptorWallet } from '../../../src/descriptor';
import { UtxoWallet } from '../../../src/wallet';
import { toBip32Triple } from '../../../src/keychains';
import { policyAllowAll } from '../../../src/descriptor/validatePolicy';

const { getDefaultXPubs, getDescriptorMap } = testutils.descriptor;

describe('isDescriptorWalletData', function () {
const descriptorMap = getDescriptorMap('Wsh2Of3');
it('should return true for valid DescriptorWalletData', function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "foo",
"value": "wsh(multi(2,xpub661MyMwAqRbcFXS2qwsTkaFc7PEpwDXWgY1Hx2MS7XywhW24sTjQzxiUgnGNW5v6DsW9Z8JcAqf8a22v21jDSA3DwLbbpt2ra3WbP83QNvP/0/*,xpub661MyMwAqRbcGfiaWcoeKLerFu3qRfy6zSYAwnmxSKW8JSauRajFsAsRHs2pV4q5rxkb4ynx4Bm8t54McTCp8V27s7XsdpD8T4s56etpjro/0/*))#qd26atvj",
"value": "wsh(multi(2,xpub661MyMwAqRbcGZfgedgcQiBJpkHoZ37k6uotUVLoC6Px4Y46Yrdmy1CUogcJMoAsosY381gPjhGe9jFx1uYAcxy2gTHh9YFP32tUWycqHnV/0/*,xpub661MyMwAqRbcF8FoYWukS8eTn2gVEojzwf5DYETpB8uqC8t5sqDCEFnuJ39DaPjLRerBDK9QqSMvSYpT4WSugCVbUK5HEevSKAu1wUkVWsS/0/*))#uduawd88",
"signatures": [
"20c5eac59397664cc8cdf07b92b85bc55f6d91051aac522a78f7d1ab72043543cd17c3436e11d96d3326274754812c3dd0e693c6330a6cdab1e3e53d618a066091"
"203743318c66496fd3d8be965ceff76c8d975436eb5ee8f8ae2115ae29ee0d60b971445de6984348318fe044b68d08327df55d44ec03c463f27ed209dea107a641"
]
}
4 changes: 2 additions & 2 deletions modules/abstract-utxo/test/unit/descriptorAddress.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as assert from 'node:assert';

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

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

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

function withChecksum(descriptor: string): string {
return utxod.Descriptor.fromString(descriptor, 'derivable').toString();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import assert from 'assert';

import { getKeyTriple } from '@bitgo/utxo-core/testutil';
import { getDescriptorMap, mockPsbtDefaultWithDescriptorTemplate } from '@bitgo/utxo-core/testutil/descriptor';
import { descriptorWallet } from '@bitgo/wasm-utxo';
import * as testutils from '@bitgo/wasm-utxo/testutils';

import type { TransactionExplanation } from '../../../../src/transaction/fixedScript/explainTransaction';
import { explainPsbt } from '../../../../src/transaction/descriptor';
import { toWasmPsbt } from '../../../../src/wasmUtil';

import { getFixtureRoot } from './fixtures.utils';

const { getDescriptorMap, mockPsbtDefaultWithDescriptorTemplate } = testutils.descriptor;
const { assertEqualFixture } = getFixtureRoot(__dirname + '/fixtures');

function assertSignatureCount(expl: TransactionExplanation, signatures: number, inputSignatures: number[]) {
Expand All @@ -21,8 +20,8 @@ function assertSignatureCount(expl: TransactionExplanation, signatures: number,

describe('explainPsbt', function () {
it('has expected values', async function () {
const psbt = toWasmPsbt(mockPsbtDefaultWithDescriptorTemplate('Wsh2Of3'));
const keys = getKeyTriple('a');
const psbt = mockPsbtDefaultWithDescriptorTemplate('Wsh2Of3');
const keys = testutils.getKeyTriple('a');
const descriptorMap = getDescriptorMap('Wsh2Of3', keys);
await assertEqualFixture('explainPsbt.a.json', explainPsbt(psbt, descriptorMap, 'btc'));
descriptorWallet.signWithKey(psbt, keys[0]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import assert from 'assert';
import path from 'path';

import { getFixture, jsonNormalize } from '@bitgo/utxo-core/testutil';
import * as testutils from '@bitgo/wasm-utxo/testutils';

const { getFixture, jsonNormalize } = testutils;

interface FixtureRoot {
assertEqualFixture(name: string, v: unknown): Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
],
"signatures": 0,
"locktime": 0,
"id": "b9b272a1f0407ea461aca2da115b3399311f424949653c37d5c0023102add158",
"id": "3ffdea969f3486a8c3bddee13349663e5eb25729bfb805ac662b6ebcd9df3f9a",
"outputs": [
{
"address": "bc1qvn279lx29cg843u77p3c37npay7w4uc4xw5d92xxa92z8gd3lkuq4w8477",
"address": "bc1qxnay2ql0dtu8873435w2eg37lxvwj0vhf77702me7sdv3pn795fqk5j2ng",
"amount": "400000"
}
],
"outputAmount": "400000",
"changeOutputs": [
{
"address": "bc1q2yau645jl7k577lmanqn9a0ulcgcqm0wrrmx09dppd3kcwguvyzqk86umj",
"address": "bc1qs86wxj8uk6hwd9sa3glcr3u2a49y8lq2r2ess5rs9ajzgnvwjgtsqeu5j7",
"amount": "400000"
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
{
"outputs": [
{
"address": "bc1qvn279lx29cg843u77p3c37npay7w4uc4xw5d92xxa92z8gd3lkuq4w8477",
"address": "bc1qxnay2ql0dtu8873435w2eg37lxvwj0vhf77702me7sdv3pn795fqk5j2ng",
"amount": "400000",
"external": true
},
{
"address": "bc1q2yau645jl7k577lmanqn9a0ulcgcqm0wrrmx09dppd3kcwguvyzqk86umj",
"address": "bc1qs86wxj8uk6hwd9sa3glcr3u2a49y8lq2r2ess5rs9ajzgnvwjgtsqeu5j7",
"amount": "400000",
"external": false
}
],
"changeOutputs": [
{
"address": "bc1q2yau645jl7k577lmanqn9a0ulcgcqm0wrrmx09dppd3kcwguvyzqk86umj",
"address": "bc1qs86wxj8uk6hwd9sa3glcr3u2a49y8lq2r2ess5rs9ajzgnvwjgtsqeu5j7",
"amount": "400000",
"external": false
}
],
"explicitExternalOutputs": [
{
"address": "bc1qvn279lx29cg843u77p3c37npay7w4uc4xw5d92xxa92z8gd3lkuq4w8477",
"address": "bc1qxnay2ql0dtu8873435w2eg37lxvwj0vhf77702me7sdv3pn795fqk5j2ng",
"amount": "400000",
"external": true
}
Expand Down
Loading