From 574a287d309a312017f620c3c814e386cb220557 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 17:53:15 +0300 Subject: [PATCH 01/73] chore: add bn.js to dependencies (previously it was present in devDependencies) --- package-lock.json | 21 ++++++++++++++++----- package.json | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e5bd51b5..9a60ab056 100644 --- a/package-lock.json +++ b/package-lock.json @@ -607,10 +607,9 @@ "dev": true }, "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, "brace-expansion": { "version": "1.1.11", @@ -2379,7 +2378,19 @@ "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==", "dev": true, "requires": { - "uint8array-tools": "0.0.6" + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } } }, "to-fast-properties": { diff --git a/package.json b/package.json index c5553d57c..172aa18b8 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "dependencies": { "bech32": "^2.0.0", "bip174": "^2.0.1", + "bn.js": "^5.2.0", "bs58check": "^2.1.2", "create-hash": "^1.1.0", "typeforce": "^1.11.3", @@ -70,7 +71,6 @@ "bip39": "^3.0.2", "bip65": "^1.0.1", "bip68": "^1.0.3", - "bn.js": "^4.11.8", "bs58": "^4.0.0", "dhttp": "^3.0.0", "ecpair": "^2.0.1", From 7ae53689a2be0801070cd968c35e3a393be44b99 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 17:55:41 +0300 Subject: [PATCH 02/73] feat: add liftX() function (first version) --- ts_src/types.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/ts_src/types.ts b/ts_src/types.ts index c035b4008..d4b37cc66 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -1,4 +1,7 @@ import { Buffer as NBuffer } from 'buffer'; +// todo, use import? +const BN = require('bn.js'); + export const typeforce = require('typeforce'); const ZERO32 = NBuffer.alloc(32, 0); @@ -6,6 +9,7 @@ const EC_P = NBuffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); + export function isPoint(p: Buffer | number | undefined | null): boolean { if (!NBuffer.isBuffer(p)) return false; if (p.length < 33) return false; @@ -25,6 +29,43 @@ export function isPoint(p: Buffer | number | undefined | null): boolean { return false; } +// todo review. Do not add dependcy to BN? +const EC_P_BN = new BN(EC_P) +const EC_P_REDUCTION = BN.red(EC_P_BN); +const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); +const BN_2 = new BN(2); +const BN_3 = new BN(3); +const BN_7 = new BN(7); + +export function liftX(buffer: Buffer): Buffer | null { + if (!NBuffer.isBuffer(buffer)) return null; + if (buffer.length !== 32) return null; + + if (buffer.compare(ZERO32) === 0) return null; + if (buffer.compare(EC_P) >= 0) return null; + + const x = new BN(buffer); + + const x1 = x.toRed(EC_P_REDUCTION); + const ySq = x1 + .redPow(BN_3) + .add(BN_7) + .mod(EC_P_BN); + + const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); + + if (!ySq.eq(y.redPow(BN_2))) { + return null; + } + const y1 = y.isEven() ? y : EC_P_BN.sub(y); + + return NBuffer.concat([ + NBuffer.from([0x04]), + NBuffer.from(x1.toBuffer('be', 32)), + NBuffer.from(y1.toBuffer('be', 32)), + ]); +} + const UINT31_MAX: number = Math.pow(2, 31) - 1; export function UInt31(value: number): boolean { return typeforce.UInt32(value) && value <= UINT31_MAX; From 8fb2f0a9bbb88e45ecf3d52579088a3143655718 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 17:57:14 +0300 Subject: [PATCH 03/73] feat: update the Payment interface with taproot specific fields - add `internalPubkey` and `redeems` fields - add some temp comments --- ts_src/payments/index.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 4b7f1117e..d42649b3f 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -10,18 +10,20 @@ import { p2wsh } from './p2wsh'; export interface Payment { name?: string; network?: Network; - output?: Buffer; + output?: Buffer; // the full scriptPubKey data?: Buffer[]; m?: number; n?: number; pubkeys?: Buffer[]; input?: Buffer; signatures?: Buffer[]; - pubkey?: Buffer; + internalPubkey?: Buffer; // taproot: output key + pubkey?: Buffer; // taproot: output key signature?: Buffer; - address?: string; - hash?: Buffer; - redeem?: Payment; + address?: string; // taproot: betch32m + hash?: Buffer; // taproot: MAST root + redeem?: Payment; // taproot: when script path spending is used spending + redeems?: Payment; // taproot can have more than one redeem script witness?: Buffer[]; } From 47eab68b044ff87d3016570cff9ee9289c9bac79 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 18:02:59 +0300 Subject: [PATCH 04/73] feat: add first version of p2tr; basic logic for key path construct/spend --- ts_src/payments/p2tr.ts | 134 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 ts_src/payments/p2tr.ts diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts new file mode 100644 index 000000000..439834247 --- /dev/null +++ b/ts_src/payments/p2tr.ts @@ -0,0 +1,134 @@ +// import * as bcrypto from '../../crypto'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { liftX, typeforce as typef } from '../types'; +import { Payment, PaymentOpts } from './index'; +import * as lazy from './lazy'; +import { bech32m } from 'bech32'; +const OPS = bscript.OPS; + +const TAPROOT_VERSION = 0x01; + +// witness: {signature} +// input: <> +// output: OP_1 {pubKey} +export function p2tr(a: Payment, opts?: PaymentOpts): Payment { + if (!a.address && !a.output && !a.pubkey && !a.output) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + typef( + { + // todo: revisit + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + input: typef.maybe(typef.BufferN(0)), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.BufferN(34)), + pubkey: typef.maybe(typef.BufferN(32)), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + + const _address = lazy.value(() => { + const result = bech32m.decode(a.address!); + const version = result.words.shift(); + const data = bech32m.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + + // todo: clean-up withness (annex), etc + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { name: 'p2tr', network }; + + lazy.prop(o, 'address', () => { + if (!o.pubkey) return; + + const words = bech32m.toWords(o.pubkey); + words.unshift(TAPROOT_VERSION); + return bech32m.encode(network.bech32, words); + }); + + + lazy.prop(o, 'hash', () => { + // compute from MAST + }); + lazy.prop(o, 'output', () => { + if (!o.pubkey) return; + return bscript.compile([OPS.OP_1, o.pubkey]); + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (a.output) return a.output.slice(2) + if (!a.address) return; + return _address().data; + }); + lazy.prop(o, 'signature', () => { + if (a.witness?.length !== 1) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + // todo: not sure + }); + lazy.prop(o, 'witness', () => { + if (!a.signature) return; + return [a.signature]; + }); + + // extended validation + if (opts.validate) { + let pubkey: Buffer = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== TAPROOT_VERSION) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + pubkey = _address().data; + } + + if (a.pubkey) { + if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.pubkey; + } + + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_1 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.output.slice(2); + } + + if (pubkey) { + if (liftX(pubkey) === null) + throw new TypeError('Invalid pubkey for p2tr'); + } + + if (a.witness) { + if (a.witness.length !== 1) throw new TypeError('Witness is invalid'); + + // todo: recheck + // if (!bscript.isCanonicalScriptSignature(a.witness[0])) + // throw new TypeError('Witness has invalid signature'); + + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + } + } + + return Object.assign(o, a); +} From 38fe8546501524afda62ce31a39d5a8643c622d5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 18:04:54 +0300 Subject: [PATCH 05/73] test: add first tests for p2tr --- test/fixtures/p2tr.json | 169 ++++++++++++++++++++++++++++++++++++++++ test/payments.spec.ts | 2 +- 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/p2tr.json diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json new file mode 100644 index 000000000..02cb6abfc --- /dev/null +++ b/test/fixtures/p2tr.json @@ -0,0 +1,169 @@ +{ + "valid": [ + { + "description": "output and pubkey from address", + "arguments": { + "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx" + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address and pubkey from output", + "arguments": { + "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5" + }, + "expected": { + "name": "p2tr", + "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx", + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address and output from pubkey", + "arguments": { + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5" + }, + "expected": { + "name": "p2tr", + "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx", + "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address, output and witness from pubkey and signature", + "arguments": { + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "signature": "300602010002010001" + }, + "expected": { + "name": "p2tr", + "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx", + "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "input": null, + "witness": [ + "300602010002010001" + ] + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "signature": "300602010002010001" + } + }, + { + "description": "Incorrect Witness Version", + "exception": "Output is invalid", + "arguments": { + "output": "OP_0 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5" + } + }, + { + "description": "Invalid x coordinate for pubkey in pubkey", + "exception": "Invalid pubkey for p2tr", + "arguments": { + "pubkey": "f136e956540197c21ff3c075d32a6e3c82f1ee1e646cc0f08f51b0b5edafa762" + } + }, + { + "description": "Invalid x coordinate for pubkey in output", + "exception": "Invalid pubkey for p2tr", + "arguments": { + "output": "OP_1 f136e956540197c21ff3c075d32a6e3c82f1ee1e646cc0f08f51b0b5edafa762" + } + }, + { + "description": "Invalid x coordinate for pubkey in address", + "exception": "Invalid pubkey for p2tr", + "arguments": { + "address": "bc1p7ymwj4j5qxtuy8lncp6ax2nw8jp0rms7v3kvpuy02xcttmd05a3qmwlnez" + } + }, + { + "description": "Pubkey mismatch between pubkey and output", + "exception": "Pubkey mismatch", + "options": {}, + "arguments": { + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "output": "OP_1 12d7dac98d69a086a50b30959a3537950f356ffc6f50a263ab75c8a3ec9d44c1" + } + }, + { + "description": "Pubkey mismatch between pubkey and address", + "exception": "Pubkey mismatch", + "options": {}, + "arguments": { + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "address": "bc1pztta4jvddxsgdfgtxz2e5dfhj58n2mludag2ycatwhy28myagnqsnl7mv7" + } + }, + { + "description": "Pubkey mismatch between output and address", + "exception": "Pubkey mismatch", + "options": {}, + "arguments": { + "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "address": "bc1pztta4jvddxsgdfgtxz2e5dfhj58n2mludag2ycatwhy28myagnqsnl7mv7" + } + }, + { + "exception": "Signature mismatch", + "arguments": { + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "signature": "300602010002010002", + "witness": [ + "300602010002010001" + ] + } + }, + { + "exception": "Invalid prefix or Network mismatch", + "arguments": { + "address": "bcrt1prhepe49mpmhclwcqmkzpaz43revunykc7fc0f9az6pq08sn4qe7sxtrd8y" + } + }, + { + "exception": "Invalid address version", + "arguments": { + "address": "bc1z4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6s6rxhwd" + } + }, + { + "exception": "Invalid address data", + "arguments": { + "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82qh3d2w3" + } + }, + { + "exception": "Witness is invalid", + "arguments": { + "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx", + "witness": [] + } + } + ], + "dynamic": { + "depends": {}, + "details": [] + } +} \ No newline at end of file diff --git a/test/payments.spec.ts b/test/payments.spec.ts index bc123cba3..9e28501ae 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; import { PaymentCreator } from '../src/payments'; import * as u from './payments.utils'; -['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(p => { +['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach(p => { describe(p, () => { let fn: PaymentCreator; const payment = require('../src/payments/' + p); From 1bf104e981b2ddb58ce82bbce39d6d0eca60668b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 18:08:20 +0300 Subject: [PATCH 06/73] feat: add generated files --- src/payments/index.d.ts | 2 + src/payments/p2tr.d.ts | 2 + src/payments/p2tr.js | 121 ++++++++++++++++++++++++++++++++++++++++ src/types.d.ts | 1 + src/types.js | 34 ++++++++++- 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/payments/p2tr.d.ts create mode 100644 src/payments/p2tr.js diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 1edf07167..e569aa3cf 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -17,11 +17,13 @@ export interface Payment { pubkeys?: Buffer[]; input?: Buffer; signatures?: Buffer[]; + internalPubkey?: Buffer; pubkey?: Buffer; signature?: Buffer; address?: string; hash?: Buffer; redeem?: Payment; + redeems?: Payment; witness?: Buffer[]; } export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; diff --git a/src/payments/p2tr.d.ts b/src/payments/p2tr.d.ts new file mode 100644 index 000000000..350ed0ffc --- /dev/null +++ b/src/payments/p2tr.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2tr(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js new file mode 100644 index 000000000..2c3a0c71b --- /dev/null +++ b/src/payments/p2tr.js @@ -0,0 +1,121 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2tr = void 0; +// import * as bcrypto from '../../crypto'; +const networks_1 = require('../networks'); +const bscript = require('../script'); +const types_1 = require('../types'); +const lazy = require('./lazy'); +const bech32_1 = require('bech32'); +const OPS = bscript.OPS; +const TAPROOT_VERSION = 0x01; +// witness: {signature} +// input: <> +// output: OP_1 {pubKey} +function p2tr(a, opts) { + if (!a.address && !a.output && !a.pubkey && !a.output) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + (0, types_1.typeforce)( + { + // todo: revisit + address: types_1.typeforce.maybe(types_1.typeforce.String), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(20)), + input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)), + pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), + }, + a, + ); + const _address = lazy.value(() => { + const result = bech32_1.bech32m.decode(a.address); + const version = result.words.shift(); + const data = bech32_1.bech32m.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + // todo: clean-up withness (annex), etc + const network = a.network || networks_1.bitcoin; + const o = { name: 'p2tr', network }; + lazy.prop(o, 'address', () => { + if (!o.pubkey) return; + const words = bech32_1.bech32m.toWords(o.pubkey); + words.unshift(TAPROOT_VERSION); + return bech32_1.bech32m.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + // compute from MAST + }); + lazy.prop(o, 'output', () => { + if (!o.pubkey) return; + return bscript.compile([OPS.OP_1, o.pubkey]); + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (a.output) return a.output.slice(2); + if (!a.address) return; + return _address().data; + }); + lazy.prop(o, 'signature', () => { + if (a.witness?.length !== 1) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + // todo: not sure + }); + lazy.prop(o, 'witness', () => { + if (!a.signature) return; + return [a.signature]; + }); + // extended validation + if (opts.validate) { + let pubkey = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== TAPROOT_VERSION) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + pubkey = _address().data; + } + if (a.pubkey) { + if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.pubkey; + } + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_1 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.output.slice(2); + } + if (pubkey) { + if ((0, types_1.liftX)(pubkey) === null) + throw new TypeError('Invalid pubkey for p2tr'); + } + if (a.witness) { + if (a.witness.length !== 1) throw new TypeError('Witness is invalid'); + // todo: recheck + // if (!bscript.isCanonicalScriptSignature(a.witness[0])) + // throw new TypeError('Witness has invalid signature'); + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2tr = p2tr; diff --git a/src/types.d.ts b/src/types.d.ts index 5a8505d34..0fe6419f8 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,6 +1,7 @@ /// export declare const typeforce: any; export declare function isPoint(p: Buffer | number | undefined | null): boolean; +export declare function liftX(buffer: Buffer): Buffer | null; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; export declare namespace BIP32Path { diff --git a/src/types.js b/src/types.js index a6d1efa16..a7a89b0d2 100644 --- a/src/types.js +++ b/src/types.js @@ -1,7 +1,9 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.liftX = exports.isPoint = exports.typeforce = void 0; const buffer_1 = require('buffer'); +// todo, use import? +const BN = require('bn.js'); exports.typeforce = require('typeforce'); const ZERO32 = buffer_1.Buffer.alloc(32, 0); const EC_P = buffer_1.Buffer.from( @@ -25,6 +27,36 @@ function isPoint(p) { return false; } exports.isPoint = isPoint; +// todo review. Do not add dependcy to BN? +const EC_P_BN = new BN(EC_P); +const EC_P_REDUCTION = BN.red(EC_P_BN); +const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); +const BN_2 = new BN(2); +const BN_3 = new BN(3); +const BN_7 = new BN(7); +function liftX(buffer) { + if (!buffer_1.Buffer.isBuffer(buffer)) return null; + if (buffer.length !== 32) return null; + if (buffer.compare(ZERO32) === 0) return null; + if (buffer.compare(EC_P) >= 0) return null; + const x = new BN(buffer); + const x1 = x.toRed(EC_P_REDUCTION); + const ySq = x1 + .redPow(BN_3) + .add(BN_7) + .mod(EC_P_BN); + const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); + if (!ySq.eq(y.redPow(BN_2))) { + return null; + } + const y1 = y.isEven() ? y : EC_P_BN.sub(y); + return buffer_1.Buffer.concat([ + buffer_1.Buffer.from([0x04]), + buffer_1.Buffer.from(x1.toBuffer('be', 32)), + buffer_1.Buffer.from(y1.toBuffer('be', 32)), + ]); +} +exports.liftX = liftX; const UINT31_MAX = Math.pow(2, 31) - 1; function UInt31(value) { return exports.typeforce.UInt32(value) && value <= UINT31_MAX; From a598134b17a29da9465e42b2d1ddd48e1d618b74 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 29 Oct 2021 14:21:58 +0300 Subject: [PATCH 07/73] feat: compute "taproot output key" when "taoroot internal key" is known and script path is not used --- src/payments/p2tr.js | 30 +++++++++++++++++------- src/types.d.ts | 5 ++++ src/types.js | 38 +++++++++++++++++++++++++++++- test/fixtures/p2tr.json | 24 +++++++++++++++++++ test/payments.utils.ts | 1 + ts_src/payments/p2tr.ts | 32 ++++++++++++++++++-------- ts_src/types.ts | 51 ++++++++++++++++++++++++++++++++++++++++- 7 files changed, 162 insertions(+), 19 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 2c3a0c71b..cde6d705a 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -1,7 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.p2tr = void 0; -// import * as bcrypto from '../../crypto'; const networks_1 = require('../networks'); const bscript = require('../script'); const types_1 = require('../types'); @@ -13,17 +12,17 @@ const TAPROOT_VERSION = 0x01; // input: <> // output: OP_1 {pubKey} function p2tr(a, opts) { - if (!a.address && !a.output && !a.pubkey && !a.output) + if (!a.address && !a.output && !a.pubkey && !a.output && !a.internalPubkey) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); (0, types_1.typeforce)( { - // todo: revisit address: types_1.typeforce.maybe(types_1.typeforce.String), - hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(20)), input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), network: types_1.typeforce.maybe(types_1.typeforce.Object), output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)), + internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), witness: types_1.typeforce.maybe( @@ -52,7 +51,9 @@ function p2tr(a, opts) { return bech32_1.bech32m.encode(network.bech32, words); }); lazy.prop(o, 'hash', () => { - // compute from MAST + if (a.hash) return a.hash; + // todo: if (a.redeems?.length) compute from MAST root from redeems + return null; }); lazy.prop(o, 'output', () => { if (!o.pubkey) return; @@ -61,8 +62,12 @@ function p2tr(a, opts) { lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; if (a.output) return a.output.slice(2); - if (!a.address) return; - return _address().data; + if (a.address) return _address().data; + if (a.internalPubkey) { + const tweakedKey = (0, types_1.tweakPublicKey)(a.internalPubkey, o.hash); + if (tweakedKey) return tweakedKey.x; + } + return null; }); lazy.prop(o, 'signature', () => { if (a.witness?.length !== 1) return; @@ -103,7 +108,16 @@ function p2tr(a, opts) { throw new TypeError('Pubkey mismatch'); else pubkey = a.output.slice(2); } - if (pubkey) { + // todo: optimze o.hash? + if (a.internalPubkey) { + const tweakedKey = (0, types_1.tweakPublicKey)(a.internalPubkey, o.hash); + if (tweakedKey === null) + throw new TypeError('Invalid internalPubkey for p2tr'); + if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) + throw new TypeError('Pubkey mismatch'); + else pubkey = tweakedKey.x; + } + if (pubkey?.length) { if ((0, types_1.liftX)(pubkey) === null) throw new TypeError('Invalid pubkey for p2tr'); } diff --git a/src/types.d.ts b/src/types.d.ts index 0fe6419f8..cb24d6a48 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -2,6 +2,7 @@ export declare const typeforce: any; export declare function isPoint(p: Buffer | number | undefined | null): boolean; export declare function liftX(buffer: Buffer): Buffer | null; +export declare function tweakPublicKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; export declare namespace BIP32Path { @@ -11,6 +12,10 @@ export declare function Signer(obj: any): boolean; export declare function Satoshi(value: number): boolean; export declare const ECPoint: any; export declare const Network: any; +export interface TweakedPublicKey { + isOdd: boolean; + x: Buffer; +} export declare const Buffer256bit: any; export declare const Hash160bit: any; export declare const Hash256bit: any; diff --git a/src/types.js b/src/types.js index a7a89b0d2..1113ee89a 100644 --- a/src/types.js +++ b/src/types.js @@ -1,7 +1,11 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.liftX = exports.isPoint = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.tweakPublicKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; const buffer_1 = require('buffer'); +const bcrypto = require('./crypto'); +// Temp, to be replaced +// Only works because bip32 has it as dependecy. Linting will fail. +const ecc = require('tiny-secp256k1'); // todo, use import? const BN = require('bn.js'); exports.typeforce = require('typeforce'); @@ -57,6 +61,38 @@ function liftX(buffer) { ]); } exports.liftX = liftX; +const TAP_TWEAK_TAG = buffer_1.Buffer.from('TapTweak', 'utf8'); +const GROUP_ORDER = new BN( + buffer_1.Buffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex', + ), +); +function tweakPublicKey(pubKey, h) { + if (!buffer_1.Buffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + const tweakHash = bcrypto.taggedHash( + TAP_TWEAK_TAG, + buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), + ); + const t = new BN(tweakHash); + if (t.gte(GROUP_ORDER)) { + throw new Error('Tweak value over the SECP256K1 Order'); + } + const P = liftX(pubKey); + if (P === null) return null; + const Q = pointAddScalar(P, tweakHash); + return { + isOdd: Q[64] % 2 === 1, + x: Q.slice(1, 33), + }; +} +exports.tweakPublicKey = tweakPublicKey; +// todo: do not use ecc +function pointAddScalar(P, h) { + return ecc.pointAddScalar(P, h); +} const UINT31_MAX = Math.pow(2, 31) - 1; function UInt31(value) { return exports.typeforce.UInt32(value) && value <= UINT31_MAX; diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 02cb6abfc..8916e0fad 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -58,6 +58,21 @@ "300602010002010001" ] } + }, + { + "description": "address, pubkey and output from internalPubkey", + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7" + }, + "expected": { + "name": "p2tr", + "address": "bc1prs7pxymu7jhsptzjlwlqnk8jyg5qmq4sdlc3rwcy7pd3ydz92xjq5ap2sg", + "pubkey": "1c3c13137cf4af00ac52fbbe09d8f222280d82b06ff111bb04f05b12344551a4", + "output": "OP_1 1c3c13137cf4af00ac52fbbe09d8f222280d82b06ff111bb04f05b12344551a4", + "signature": null, + "input": null, + "witness": null + } } ], "invalid": [ @@ -126,6 +141,15 @@ "address": "bc1pztta4jvddxsgdfgtxz2e5dfhj58n2mludag2ycatwhy28myagnqsnl7mv7" } }, + { + "description": "Pubkey mismatch between internalPubkey and pubkey", + "exception": "Pubkey mismatch", + "options": {}, + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5" + } + }, { "exception": "Signature mismatch", "arguments": { diff --git a/test/payments.utils.ts b/test/payments.utils.ts index c0635f3cf..fbd6e58a6 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -129,6 +129,7 @@ export function preform(x: any): any { if (x.data) x.data = x.data.map(fromHex); if (x.hash) x.hash = Buffer.from(x.hash, 'hex'); if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex'); + if (x.internalPubkey) x.internalPubkey = Buffer.from(x.internalPubkey, 'hex'); if (x.signature) x.signature = Buffer.from(x.signature, 'hex'); if (x.pubkeys) x.pubkeys = x.pubkeys.map(fromHex); if (x.signatures) diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 439834247..ff83ff43f 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,7 +1,6 @@ -// import * as bcrypto from '../../crypto'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { liftX, typeforce as typef } from '../types'; +import { liftX, tweakPublicKey, typeforce as typef } from '../types'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; @@ -13,18 +12,18 @@ const TAPROOT_VERSION = 0x01; // input: <> // output: OP_1 {pubKey} export function p2tr(a: Payment, opts?: PaymentOpts): Payment { - if (!a.address && !a.output && !a.pubkey && !a.output) + if (!a.address && !a.output && !a.pubkey && !a.output && !a.internalPubkey) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); typef( { - // todo: revisit address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(20)), input: typef.maybe(typef.BufferN(0)), network: typef.maybe(typef.Object), output: typef.maybe(typef.BufferN(34)), + internalPubkey: typef.maybe(typef.BufferN(32)), + hash: typef.maybe(typef.BufferN(32)), pubkey: typef.maybe(typef.BufferN(32)), signature: typef.maybe(bscript.isCanonicalScriptSignature), witness: typef.maybe(typef.arrayOf(typef.Buffer)), @@ -58,7 +57,9 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'hash', () => { - // compute from MAST + if (a.hash) return a.hash; + // todo: if (a.redeems?.length) compute from MAST root from redeems + return null }); lazy.prop(o, 'output', () => { if (!o.pubkey) return; @@ -67,8 +68,12 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; if (a.output) return a.output.slice(2) - if (!a.address) return; - return _address().data; + if (a.address) return _address().data; + if (a.internalPubkey) { + const tweakedKey = tweakPublicKey(a.internalPubkey, o.hash) + if (tweakedKey) return tweakedKey.x + } + return null }); lazy.prop(o, 'signature', () => { if (a.witness?.length !== 1) return; @@ -113,7 +118,16 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { else pubkey = a.output.slice(2); } - if (pubkey) { + // todo: optimze o.hash? + if (a.internalPubkey) { + const tweakedKey = tweakPublicKey(a.internalPubkey, o.hash) + if (tweakedKey === null) throw new TypeError('Invalid internalPubkey for p2tr'); + if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) + throw new TypeError('Pubkey mismatch'); + else pubkey = tweakedKey.x; + } + + if (pubkey?.length) { if (liftX(pubkey) === null) throw new TypeError('Invalid pubkey for p2tr'); } diff --git a/ts_src/types.ts b/ts_src/types.ts index d4b37cc66..8a8a4e4f9 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -1,4 +1,9 @@ import { Buffer as NBuffer } from 'buffer'; +import * as bcrypto from './crypto'; + +// Temp, to be replaced +// Only works because bip32 has it as dependecy. Linting will fail. +const ecc = require('tiny-secp256k1'); // todo, use import? const BN = require('bn.js'); @@ -30,7 +35,7 @@ export function isPoint(p: Buffer | number | undefined | null): boolean { } // todo review. Do not add dependcy to BN? -const EC_P_BN = new BN(EC_P) +const EC_P_BN = new BN(EC_P); const EC_P_REDUCTION = BN.red(EC_P_BN); const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); const BN_2 = new BN(2); @@ -66,6 +71,46 @@ export function liftX(buffer: Buffer): Buffer | null { ]); } +const TAP_TWEAK_TAG = NBuffer.from('TapTweak', 'utf8'); +const GROUP_ORDER = new BN( + NBuffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex', + ), +); + +export function tweakPublicKey( + pubKey: Buffer, + h: Buffer | undefined, +): TweakedPublicKey | null { + if (!NBuffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + + const tweakHash = bcrypto.taggedHash( + TAP_TWEAK_TAG, + NBuffer.concat(h ? [pubKey, h] : [pubKey]), + ); + const t = new BN(tweakHash); + if (t.gte(GROUP_ORDER)) { + throw new Error('Tweak value over the SECP256K1 Order'); + } + + const P = liftX(pubKey); + if (P === null) return null; + + const Q = pointAddScalar(P, tweakHash); + return { + isOdd: Q[64] % 2 === 1, + x: Q.slice(1, 33), + }; +} + +// todo: do not use ecc +function pointAddScalar(P: Buffer, h: Buffer): Buffer { + return ecc.pointAddScalar(P, h); +} + const UINT31_MAX: number = Math.pow(2, 31) - 1; export function UInt31(value: number): boolean { return typeforce.UInt32(value) && value <= UINT31_MAX; @@ -106,6 +151,10 @@ export const Network = typeforce.compile({ wif: typeforce.UInt8, }); +export interface TweakedPublicKey { + isOdd: boolean; + x: Buffer; +} export const Buffer256bit = typeforce.BufferN(32); export const Hash160bit = typeforce.BufferN(20); export const Hash256bit = typeforce.BufferN(32); From 33c2bd94c2e5e43335fc9ff80390b47798038db4 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 29 Oct 2021 14:43:10 +0300 Subject: [PATCH 08/73] tests: improve test coverage --- src/payments/p2tr.js | 1 - test/fixtures/p2tr.json | 26 ++++++++++++++++++++++++++ ts_src/payments/p2tr.ts | 1 - 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index cde6d705a..c84579aea 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -67,7 +67,6 @@ function p2tr(a, opts) { const tweakedKey = (0, types_1.tweakPublicKey)(a.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } - return null; }); lazy.prop(o, 'signature', () => { if (a.witness?.length !== 1) return; diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 8916e0fad..e4bc86af6 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -59,6 +59,25 @@ ] } }, + { + "description": "address, output and signature from pubkey and witness", + "arguments": { + "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "witness": [ + "300602010002010001" + ] + }, + "expected": { + "name": "p2tr", + "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx", + "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", + "input": null, + "signature": "300602010002010001", + "witness": [ + "300602010002010001" + ] + } + }, { "description": "address, pubkey and output from internalPubkey", "arguments": { @@ -150,6 +169,13 @@ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5" } }, + { + "exception": "Invalid internalPubkey for p2t", + "options": {}, + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f8" + } + }, { "exception": "Signature mismatch", "arguments": { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index ff83ff43f..926301f85 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -73,7 +73,6 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const tweakedKey = tweakPublicKey(a.internalPubkey, o.hash) if (tweakedKey) return tweakedKey.x } - return null }); lazy.prop(o, 'signature', () => { if (a.witness?.length !== 1) return; From f04d8b2598fb43f4f79239e5ae231ddcb8e93a88 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:11:59 +0200 Subject: [PATCH 09/73] feat: add function `computeMastRoot()` --- src/merkle.d.ts | 1 + src/merkle.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- ts_src/merkle.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/merkle.d.ts b/src/merkle.d.ts index d602201b9..d4b43f3ba 100644 --- a/src/merkle.d.ts +++ b/src/merkle.d.ts @@ -1,2 +1,3 @@ /// export declare function fastMerkleRoot(values: Buffer[], digestFn: (b: Buffer) => Buffer): Buffer; +export declare function computeMastRoot(scripts: any): Buffer; diff --git a/src/merkle.js b/src/merkle.js index e93f9cab6..ba00ec4d3 100644 --- a/src/merkle.js +++ b/src/merkle.js @@ -1,6 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.fastMerkleRoot = void 0; +exports.computeMastRoot = exports.fastMerkleRoot = void 0; +const buffer_1 = require('buffer'); +const bcrypto = require('./crypto'); +// todo: use varuint-bitcoin?? +const varuint = require('bip174/src/lib/converter/varint'); +const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); +const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); +const LEAF_VERSION_TAPSCRIPT = 0xc0; function fastMerkleRoot(values, digestFn) { if (!Array.isArray(values)) throw TypeError('Expected values Array'); if (typeof digestFn !== 'function') @@ -20,3 +27,40 @@ function fastMerkleRoot(values, digestFn) { return results[0]; } exports.fastMerkleRoot = fastMerkleRoot; +// todo: solve any[] +function computeMastRoot(scripts) { + if (scripts.length === 1) { + const script = scripts[0]; + if (Array.isArray(script)) { + return computeMastRoot(script); + } + script.version = script.version || LEAF_VERSION_TAPSCRIPT; + if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error + // todo: if (script.output)scheck is bytes + const scriptOutput = buffer_1.Buffer.from(script.output, 'hex'); + return bcrypto.taggedHash( + TAP_LEAF_TAG, + buffer_1.Buffer.concat([ + buffer_1.Buffer.from([script.version]), + serializeScript(scriptOutput), + ]), + ); + } + // todo: this is a binary tree, use zero an one index + const half = Math.trunc(scripts.length / 2); + let leftHash = computeMastRoot(scripts.slice(0, half)); + let rightHash = computeMastRoot(scripts.slice(half)); + if (leftHash.compare(rightHash) === 1) + [leftHash, rightHash] = [rightHash, leftHash]; + return bcrypto.taggedHash( + TAP_BRANCH_TAG, + buffer_1.Buffer.concat([leftHash, rightHash]), + ); +} +exports.computeMastRoot = computeMastRoot; +function serializeScript(s) { + const varintLen = varuint.encodingLength(s.length); + const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better + varuint.encode(s.length, buffer); + return buffer_1.Buffer.concat([buffer, s]); +} diff --git a/ts_src/merkle.ts b/ts_src/merkle.ts index 8ff8c3f8c..d328acea9 100644 --- a/ts_src/merkle.ts +++ b/ts_src/merkle.ts @@ -1,3 +1,14 @@ +import { Buffer as NBuffer } from 'buffer'; +import * as bcrypto from './crypto'; +// todo: use varuint-bitcoin?? +import * as varuint from 'bip174/src/lib/converter/varint'; + + +const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); +const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); +const LEAF_VERSION_TAPSCRIPT = 0xc0 + + export function fastMerkleRoot( values: Buffer[], digestFn: (b: Buffer) => Buffer, @@ -25,3 +36,32 @@ export function fastMerkleRoot( return results[0]; } + +// todo: solve any[] +export function computeMastRoot(scripts: any): Buffer { + if (scripts.length === 1) { + const script = scripts[0] + if (Array.isArray(script)) { + return computeMastRoot(script) + } + script.version = script.version || LEAF_VERSION_TAPSCRIPT + if ((script.version & 1) !== 0) throw new Error("Invalid script version") // todo typedef error + // todo: if (script.output)scheck is bytes + const scriptOutput = NBuffer.from(script.output, 'hex') + return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([script.version]), serializeScript(scriptOutput)])) + } + // todo: this is a binary tree, use zero an one index + const half = Math.trunc(scripts.length / 2) + let leftHash = computeMastRoot(scripts.slice(0, half)) + let rightHash = computeMastRoot(scripts.slice(half)) + + if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash] + return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([leftHash, rightHash])) +} + +function serializeScript(s: Buffer) { + const varintLen = varuint.encodingLength(s.length); + const buffer = NBuffer.allocUnsafe(varintLen); // better + varuint.encode(s.length, buffer); + return NBuffer.concat([buffer, s]) +} \ No newline at end of file From 583174d3485231acfd44515cbd48947e55c1fc8a Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:12:55 +0200 Subject: [PATCH 10/73] feat: compute p2tr hash based on the script tree --- src/payments/p2tr.js | 4 +++- ts_src/payments/p2tr.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index c84579aea..6572ac080 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -4,6 +4,7 @@ exports.p2tr = void 0; const networks_1 = require('../networks'); const bscript = require('../script'); const types_1 = require('../types'); +const merkle_1 = require('../merkle'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); const OPS = bscript.OPS; @@ -28,6 +29,7 @@ function p2tr(a, opts) { witness: types_1.typeforce.maybe( types_1.typeforce.arrayOf(types_1.typeforce.Buffer), ), + // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? }, a, ); @@ -52,7 +54,7 @@ function p2tr(a, opts) { }); lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - // todo: if (a.redeems?.length) compute from MAST root from redeems + if (a.scriptsTree) return (0, merkle_1.computeMastRoot)(a.scriptsTree); return null; }); lazy.prop(o, 'output', () => { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 926301f85..c0365cfca 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,6 +1,7 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; import { liftX, tweakPublicKey, typeforce as typef } from '../types'; +import { computeMastRoot } from '../merkle'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; @@ -27,6 +28,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { pubkey: typef.maybe(typef.BufferN(32)), signature: typef.maybe(bscript.isCanonicalScriptSignature), witness: typef.maybe(typef.arrayOf(typef.Buffer)), + // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? }, a, ); @@ -58,7 +60,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - // todo: if (a.redeems?.length) compute from MAST root from redeems + if (a.scriptsTree) return computeMastRoot(a.scriptsTree) return null }); lazy.prop(o, 'output', () => { From 1b96e6dbbe5bcb3a6ee1ae068d05acb0c77fea7d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:13:57 +0200 Subject: [PATCH 11/73] feat: add scriptsTree field to Payment interface; export p2tr --- src/payments/index.d.ts | 5 +++-- src/payments/index.js | 9 ++++++++- ts_src/payments/index.ts | 5 +++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index e569aa3cf..dc1978ab0 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -7,6 +7,7 @@ import { p2pkh } from './p2pkh'; import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; +import { p2tr } from './p2tr'; export interface Payment { name?: string; network?: Network; @@ -23,7 +24,7 @@ export interface Payment { address?: string; hash?: Buffer; redeem?: Payment; - redeems?: Payment; + scriptsTree?: any; witness?: Buffer[]; } export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; @@ -35,4 +36,4 @@ export interface PaymentOpts { export declare type StackElement = Buffer | number; export declare type Stack = StackElement[]; export declare type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; diff --git a/src/payments/index.js b/src/payments/index.js index c23c529c6..9ce55f859 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; +exports.p2tr = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; const embed_1 = require('./embed'); Object.defineProperty(exports, 'embed', { enumerable: true, @@ -50,5 +50,12 @@ Object.defineProperty(exports, 'p2wsh', { return p2wsh_1.p2wsh; }, }); +const p2tr_1 = require('./p2tr'); +Object.defineProperty(exports, 'p2tr', { + enumerable: true, + get: function() { + return p2tr_1.p2tr; + }, +}); // TODO // witness commitment diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index d42649b3f..5243902d4 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -6,6 +6,7 @@ import { p2pkh } from './p2pkh'; import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; +import { p2tr } from './p2tr'; export interface Payment { name?: string; @@ -23,7 +24,7 @@ export interface Payment { address?: string; // taproot: betch32m hash?: Buffer; // taproot: MAST root redeem?: Payment; // taproot: when script path spending is used spending - redeems?: Payment; // taproot can have more than one redeem script + scriptsTree?: any // todo: solve witness?: Buffer[]; } @@ -40,7 +41,7 @@ export type StackElement = Buffer | number; export type Stack = StackElement[]; export type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; // TODO // witness commitment From fc1f1b2d0cde88b4e1026147ad7e052987904e5f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:15:45 +0200 Subject: [PATCH 12/73] feat: convert `scriptsTree` output to Buffer --- test/payments.utils.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/payments.utils.ts b/test/payments.utils.ts index fbd6e58a6..71863a602 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -147,7 +147,8 @@ export function preform(x: any): any { if (x.redeem.network) x.redeem.network = (BNETWORKS as any)[x.redeem.network]; } - + if (x.scriptsTree) + x.scriptsTree = convertScriptsTree(x.scriptsTree) return x; } @@ -170,3 +171,15 @@ export function from(path: string, object: any, result?: any): any { return result; } + +// todo: solve any type +function convertScriptsTree(scriptsTree: any): any { + if (Array.isArray(scriptsTree)) + return scriptsTree.map(convertScriptsTree) + + + const script = Object.assign({}, scriptsTree); + if ((typeof script.output === 'string')) + script.output = asmToBuffer(scriptsTree.output) + return script +} From 91ccef3f14ab250413b04a20bec36b956922fed1 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:16:17 +0200 Subject: [PATCH 13/73] tests: add tests for script tree --- test/fixtures/p2tr.json | 206 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index e4bc86af6..3f088bb60 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -92,6 +92,212 @@ "input": null, "witness": null } + }, + { + "description": "address, pubkey, output and hash from internalPubkey and a script tree with one leaf", + "arguments": { + "internalPubkey": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "scriptsTree": [ + { + "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" + } + ] + }, + "expected": { + "name": "p2tr", + "address": "bc1pjegs09vkeder9m4sw3ycjf2rnpa8nljdqmuleunk9eshu8cq3xysvhgp2u", + "pubkey": "9651079596cb7232eeb07449892543987a79fe4d06f9fcf2762e617e1f008989", + "output": "OP_1 9651079596cb7232eeb07449892543987a79fe4d06f9fcf2762e617e1f008989", + "hash": "16e3f3b8b9c1e453c56b547785cdd25259d65823a2064f30783acc58ef012633", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address, pubkey, output and hash from internalPubkey and a script tree with two leafs", + "arguments": { + "internalPubkey": "2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7", + "scriptsTree": [ + { + "output": "d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b OP_CHECKSIG" + }, + { + "output": "d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b OP_CHECKSIG" + } + ] + }, + "expected": { + "name": "p2tr", + "address": "bc1ptj0v8rwcj6s36p4r26ws6htx0fct43n0mxdvdeh9043whlxlq3kq9965ke", + "pubkey": "5c9ec38dd896a11d06a3569d0d5d667a70bac66fd99ac6e6e57d62ebfcdf046c", + "output": "OP_1 5c9ec38dd896a11d06a3569d0d5d667a70bac66fd99ac6e6e57d62ebfcdf046c", + "hash": "ce00198cd4667abae1f94aa5862d089e2967af5aec20715c692db74e3d66bb73", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address, pubkey, output and hash from internalPubkey and a script tree with three leafs", + "arguments": { + "internalPubkey": "7631cacec3343052d87ef4d0065f61dde82d7d2db0c1cc02ef61ef3c982ea763", + "scriptsTree": [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + { + "output": "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744 OP_CHECKSIG" + } + ] + ] + }, + "expected": { + "name": "p2tr", + "address": "bc1pkq0t8nkmqswn3qjg9uy6ux2hsyyz4as25v8unfjc9s8q2e4c00sqku9lxh", + "pubkey": "b01eb3cedb041d3882482f09ae195781082af60aa30fc9a6582c0e0566b87be0", + "output": "OP_1 b01eb3cedb041d3882482f09ae195781082af60aa30fc9a6582c0e0566b87be0", + "hash": "7ae0cc2057b1a7bf0e09c787e1d7b6b2355ac112a7b80380a5c1e942155b0c0f", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address, pubkey, output and hash from internalPubkey and a script tree with four leafs", + "arguments": { + "internalPubkey": "d0c19def28bb1b39451c1a814737615983967780d223b79969ba692182c6006b", + "scriptsTree": [ + [ + { + "output": "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744 OP_CHECKSIG" + }, + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + } + ], + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + { + "output": "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744 OP_CHECKSIG" + } + ] + ] + }, + "expected": { + "name": "p2tr", + "address": "bc1pstdzevc40j059s0473rghhv9e05l9f5xv7l6dtlavvq22rzfna3syjvjut", + "pubkey": "82da2cb3157c9f42c1f5f4468bdd85cbe9f2a68667bfa6affd6300a50c499f63", + "output": "OP_1 82da2cb3157c9f42c1f5f4468bdd85cbe9f2a68667bfa6affd6300a50c499f63", + "hash": "d673e784eac9b70289130a0bd359023a0fbdde51dc069b9efb4157c2cdce3ea5", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs", + "arguments": { + "internalPubkey": "f95886b02a84928c5c15bdca32784993105f73de27fa6ad8c1a60389b999267c", + "scriptsTree": [ + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + { + "output": "2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7 OP_CHECKSIG" + } + ] + ], + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + { + "output": "03a669ea926f381582ec4a000b9472ba8a17347f5fb159eddd4a07036a6718eb OP_CHECKSIG" + } + ] + ] + ] + ] + }, + "expected": { + "name": "p2tr", + "address": "bc1pfas4r5s5208puwzj20hvwg2dw2kanc06yxczzdd66729z63pk43q7zwlu6", + "pubkey": "4f6151d21453ce1e385253eec7214d72add9e1fa21b02135bad794516a21b562", + "output": "OP_1 4f6151d21453ce1e385253eec7214d72add9e1fa21b02135bad794516a21b562", + "hash": "16fb2e99bdf86f67ee6980d0418658f15df7e19476053b58f45a89df2e219b1b", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs (2)", + "arguments": { + "internalPubkey": "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247", + "scriptsTree": [ + { + "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG" + }, + [ + [ + { + "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG" + }, + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + } + ] + ], + [ + [ + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + }, + { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + } + ], + { + "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG" + } + ] + ] + ] + }, + "expected": { + "name": "p2tr", + "address": "bc1pmu8qwr9zljs9anger0d6q3uyr43yzjetmjmzf8p93ltycrwj28lsee3e0n", + "pubkey": "df0e070ca2fca05ecd191bdba047841d62414b2bdcb6249c258fd64c0dd251ff", + "output": "OP_1 df0e070ca2fca05ecd191bdba047841d62414b2bdcb6249c258fd64c0dd251ff", + "hash": "027391d0aac8d94725e4fcec4b07214d7c8a14bcdca2b1c08e4bc786308bdae5", + "signature": null, + "input": null, + "witness": null + } } ], "invalid": [ From d840ab618ec78d11dc8e39d533ac9b5ec4326abd Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:17:13 +0200 Subject: [PATCH 14/73] feat: add simple type for TaprootLeaf and TaprootNode --- src/types.d.ts | 2 ++ src/types.js | 13 ++++++++++++- ts_src/types.ts | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/types.d.ts b/src/types.d.ts index cb24d6a48..45bfcd1c6 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -16,6 +16,8 @@ export interface TweakedPublicKey { isOdd: boolean; x: Buffer; } +export declare const TaprootLeaf: any; +export declare const TaprootNode: any; export declare const Buffer256bit: any; export declare const Hash160bit: any; export declare const Hash256bit: any; diff --git a/src/types.js b/src/types.js index 1113ee89a..668af87bd 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.tweakPublicKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.tweakPublicKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; const buffer_1 = require('buffer'); const bcrypto = require('./crypto'); // Temp, to be replaced @@ -136,6 +136,17 @@ exports.Network = exports.typeforce.compile({ scriptHash: exports.typeforce.UInt8, wif: exports.typeforce.UInt8, }); +exports.TaprootLeaf = exports.typeforce.compile({ + output: exports.typeforce.BufferN(34), + version: exports.typeforce.maybe(exports.typeforce.UInt8), // todo: recheck +}); +// / todo: revisit +exports.TaprootNode = exports.typeforce.arrayOf( + exports.typeforce.oneOf( + exports.TaprootLeaf, + exports.typeforce.arrayOf(exports.TaprootLeaf), + ), +); exports.Buffer256bit = exports.typeforce.BufferN(32); exports.Hash160bit = exports.typeforce.BufferN(20); exports.Hash256bit = exports.typeforce.BufferN(32); diff --git a/ts_src/types.ts b/ts_src/types.ts index 8a8a4e4f9..875df29f7 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -155,6 +155,16 @@ export interface TweakedPublicKey { isOdd: boolean; x: Buffer; } + +export const TaprootLeaf = typeforce.compile({ + output: typeforce.BufferN(34), + version: typeforce.maybe(typeforce.UInt8) // todo: recheck +}) + +// / todo: revisit +export const TaprootNode = typeforce.arrayOf(typeforce.oneOf(TaprootLeaf, typeforce.arrayOf(TaprootLeaf))) + + export const Buffer256bit = typeforce.BufferN(32); export const Hash160bit = typeforce.BufferN(20); export const Hash256bit = typeforce.BufferN(32); From fb7df4acf0fd132ff3a446c59ed3eced4d96372b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:36:41 +0200 Subject: [PATCH 15/73] feat: check for hash mismatch between the input hash and the computed hash from the taproot tree --- src/payments/p2tr.js | 4 ++++ test/fixtures/p2tr.json | 14 ++++++++++++++ ts_src/payments/p2tr.ts | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 6572ac080..be912a4f3 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -122,6 +122,10 @@ function p2tr(a, opts) { if ((0, types_1.liftX)(pubkey) === null) throw new TypeError('Invalid pubkey for p2tr'); } + if (a.hash && a.scriptsTree) { + const hash = (0, merkle_1.computeMastRoot)(a.scriptsTree); + if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); + } if (a.witness) { if (a.witness.length !== 1) throw new TypeError('Witness is invalid'); // todo: recheck diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 3f088bb60..25fec29ba 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -375,6 +375,20 @@ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5" } }, + { + "description": "Hash mismatch between scriptsTree and hash", + "exception": "Hash mismatch", + "options": {}, + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", + "scriptsTree": [ + { + "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" + } + ], + "hash": "b76077013c8e303085e300000000000000000000000000000000000000000000" + } + }, { "exception": "Invalid internalPubkey for p2t", "options": {}, diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index c0365cfca..6bbbfa101 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -133,6 +133,12 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Invalid pubkey for p2tr'); } + if (a.hash && a.scriptsTree) { + const hash = computeMastRoot(a.scriptsTree) + if (!a.hash.equals(hash)) + throw new TypeError('Hash mismatch'); + } + if (a.witness) { if (a.witness.length !== 1) throw new TypeError('Witness is invalid'); From af639f9eb580275e347d679f5376575bc1c8694f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 15:34:37 +0200 Subject: [PATCH 16/73] feat: validate witness data (partial) --- src/merkle.js | 1 + src/payments/p2tr.js | 95 +++++++++++++++++++++++---- src/types.d.ts | 2 + src/types.js | 63 ++++++++++++++++-- test/fixtures/p2tr.json | 139 +++++++++++++++++++++++++++++++++++++++- test/payments.utils.ts | 2 + ts_src/merkle.ts | 1 + ts_src/payments/p2tr.ts | 87 ++++++++++++++++++++----- ts_src/types.ts | 50 +++++++++++++-- 9 files changed, 396 insertions(+), 44 deletions(-) diff --git a/src/merkle.js b/src/merkle.js index ba00ec4d3..009260ac4 100644 --- a/src/merkle.js +++ b/src/merkle.js @@ -5,6 +5,7 @@ const buffer_1 = require('buffer'); const bcrypto = require('./crypto'); // todo: use varuint-bitcoin?? const varuint = require('bip174/src/lib/converter/varint'); +// todo: find better place for these consts const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); const LEAF_VERSION_TAPSCRIPT = 0xc0; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index be912a4f3..7d7e93a43 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -9,11 +9,19 @@ const lazy = require('./lazy'); const bech32_1 = require('bech32'); const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; +const ANNEX_PREFIX = 0x50; // witness: {signature} // input: <> // output: OP_1 {pubKey} function p2tr(a, opts) { - if (!a.address && !a.output && !a.pubkey && !a.output && !a.internalPubkey) + if ( + !a.address && + !a.output && + !a.pubkey && + !a.output && + !a.internalPubkey && + !(a.witness && a.witness.length > 1) + ) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); (0, types_1.typeforce)( @@ -43,7 +51,17 @@ function p2tr(a, opts) { data: Buffer.from(data), }; }); - // todo: clean-up withness (annex), etc + const _witness = lazy.value(() => { + if (!a.witness || !a.witness.length) return; + if ( + a.witness.length >= 2 && + a.witness[a.witness.length - 1][0] === ANNEX_PREFIX + ) { + // remove annex, ignored by taproot + return a.witness.slice(0, -1); + } + return a.witness.slice(); + }); const network = a.network || networks_1.bitcoin; const o = { name: 'p2tr', network }; lazy.prop(o, 'address', () => { @@ -55,6 +73,7 @@ function p2tr(a, opts) { lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; if (a.scriptsTree) return (0, merkle_1.computeMastRoot)(a.scriptsTree); + // todo: compute from witness return null; }); lazy.prop(o, 'output', () => { @@ -65,11 +84,17 @@ function p2tr(a, opts) { if (a.pubkey) return a.pubkey; if (a.output) return a.output.slice(2); if (a.address) return _address().data; - if (a.internalPubkey) { - const tweakedKey = (0, types_1.tweakPublicKey)(a.internalPubkey, o.hash); + if (o.internalPubkey) { + const tweakedKey = (0, types_1.tweakPublicKey)(o.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } }); + lazy.prop(o, 'internalPubkey', () => { + if (a.internalPubkey) return a.internalPubkey; + const witness = _witness(); + if (witness && witness.length > 1) + return witness[witness.length - 1].slice(1, 33); + }); lazy.prop(o, 'signature', () => { if (a.witness?.length !== 1) return; return a.witness[0]; @@ -78,6 +103,7 @@ function p2tr(a, opts) { // todo: not sure }); lazy.prop(o, 'witness', () => { + if (a.witness) return a.witness; if (!a.signature) return; return [a.signature]; }); @@ -109,7 +135,6 @@ function p2tr(a, opts) { throw new TypeError('Pubkey mismatch'); else pubkey = a.output.slice(2); } - // todo: optimze o.hash? if (a.internalPubkey) { const tweakedKey = (0, types_1.tweakPublicKey)(a.internalPubkey, o.hash); if (tweakedKey === null) @@ -126,13 +151,59 @@ function p2tr(a, opts) { const hash = (0, merkle_1.computeMastRoot)(a.scriptsTree); if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } - if (a.witness) { - if (a.witness.length !== 1) throw new TypeError('Witness is invalid'); - // todo: recheck - // if (!bscript.isCanonicalScriptSignature(a.witness[0])) - // throw new TypeError('Witness has invalid signature'); - if (a.signature && !a.signature.equals(a.witness[0])) - throw new TypeError('Signature mismatch'); + // todo: review cache + const witness = _witness(); + if (witness && witness.length) { + if (witness.length === 1) { + // key spending + if (a.signature && !a.signature.equals(witness[0])) + throw new TypeError('Signature mismatch'); + // todo: recheck + // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) + // throw new TypeError('Witness has invalid signature'); + } else { + // script path spending + const controlBlock = witness[witness.length - 1]; + if (controlBlock.length < 33) + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); + if ((controlBlock.length - 33) % 32 !== 0) + throw new TypeError( + `The control-block length of ${controlBlock.length} is incorrect!`, + ); + const m = (controlBlock.length - 33) / 32; + if (m > 128) + throw new TypeError( + `The script path is too long. Got ${m}, expected max 128.`, + ); + const internalPubkey = controlBlock.slice(1, 33); + if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) + throw new TypeError('Internal pubkey mismatch'); + const internalPubkeyPoint = (0, types_1.liftX)(internalPubkey); + if (!internalPubkeyPoint) + throw new TypeError('Invalid internalPubkey for p2tr witness'); + const leafVersion = controlBlock[0] & 0b11111110; + const script = witness[witness.length - 2]; + const tweak = (0, types_1.computeTweakFromScriptPath)( + controlBlock, + script, + internalPubkey, + m, + leafVersion, + ); + const outputKey = (0, types_1.tweakPublicKey)(internalPubkey, tweak); + if (!outputKey) + // todo: needs test data + throw new TypeError('Invalid outputKey for p2tr witness'); + if (pubkey.length && !pubkey.equals(outputKey.x)) + throw new TypeError('Pubkey mismatch for p2tr witness'); + const controlBlockOddParity = (controlBlock[0] & 1) === 1; + if (outputKey.isOdd !== controlBlockOddParity) + throw new Error('Incorrect parity'); + } } } return Object.assign(o, a); diff --git a/src/types.d.ts b/src/types.d.ts index 45bfcd1c6..a4813ee4f 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,8 +1,10 @@ /// +import { Buffer as NBuffer } from 'buffer'; export declare const typeforce: any; export declare function isPoint(p: Buffer | number | undefined | null): boolean; export declare function liftX(buffer: Buffer): Buffer | null; export declare function tweakPublicKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; +export declare function computeTweakFromScriptPath(controlBlock: Buffer, script: Buffer, internalPubkey: Buffer, m: number, v: number): NBuffer; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; export declare namespace BIP32Path { diff --git a/src/types.js b/src/types.js index 668af87bd..07756ecc4 100644 --- a/src/types.js +++ b/src/types.js @@ -1,8 +1,9 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.tweakPublicKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.computeTweakFromScriptPath = exports.tweakPublicKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; const buffer_1 = require('buffer'); const bcrypto = require('./crypto'); +const varuint = require('bip174/src/lib/converter/varint'); // Temp, to be replaced // Only works because bip32 has it as dependecy. Linting will fail. const ecc = require('tiny-secp256k1'); @@ -62,12 +63,12 @@ function liftX(buffer) { } exports.liftX = liftX; const TAP_TWEAK_TAG = buffer_1.Buffer.from('TapTweak', 'utf8'); -const GROUP_ORDER = new BN( - buffer_1.Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', - ), +const GROUP_ORDER = buffer_1.Buffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex', ); +// todo: compare buffers dirrectly +const GROUP_ORDER_BN = new BN(GROUP_ORDER); function tweakPublicKey(pubKey, h) { if (!buffer_1.Buffer.isBuffer(pubKey)) return null; if (pubKey.length !== 32) return null; @@ -77,7 +78,8 @@ function tweakPublicKey(pubKey, h) { buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), ); const t = new BN(tweakHash); - if (t.gte(GROUP_ORDER)) { + if (t.gte(GROUP_ORDER_BN)) { + // todo: add test for this case throw new Error('Tweak value over the SECP256K1 Order'); } const P = liftX(pubKey); @@ -89,6 +91,53 @@ function tweakPublicKey(pubKey, h) { }; } exports.tweakPublicKey = tweakPublicKey; +const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); +const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); +function computeTweakFromScriptPath( + controlBlock, + script, + internalPubkey, + m, + v, +) { + const k = []; + const e = []; + const tapLeafMsg = buffer_1.Buffer.concat([ + buffer_1.Buffer.from([v]), + serializeScript(script), + ]); + k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); + for (let j = 0; j < m; j++) { + e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); + if (k[j].compare(e[j]) < 0) { + k[j + 1] = bcrypto.taggedHash( + TAP_BRANCH_TAG, + buffer_1.Buffer.concat([k[j], e[j]]), + ); + } else { + k[j + 1] = bcrypto.taggedHash( + TAP_BRANCH_TAG, + buffer_1.Buffer.concat([e[j], k[j]]), + ); + } + } + const t = bcrypto.taggedHash( + TAP_TWEAK_TAG, + buffer_1.Buffer.concat([internalPubkey, k[m]]), + ); + if (t.compare(GROUP_ORDER) >= 0) { + throw new Error('Over the order of secp256k1'); + } + return t; +} +exports.computeTweakFromScriptPath = computeTweakFromScriptPath; +// todo: move out +function serializeScript(s) { + const varintLen = varuint.encodingLength(s.length); + const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better + varuint.encode(s.length, buffer); + return buffer_1.Buffer.concat([buffer, s]); +} // todo: do not use ecc function pointAddScalar(P, h) { return ecc.pointAddScalar(P, h); diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 25fec29ba..532fe2297 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -93,6 +93,60 @@ "witness": null } }, + { + "description": "address, pubkey, internalPubkey and output from witness", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ] + }, + "expected": { + "name": "p2tr", + "internalPubkey": "a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a36", + "pubkey": "a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", + "address": "bc1p53rcf44j6q2eeftl2t37vq36fuvkp53vpzwsr8ek2mv7ywfxmnysdul78t", + "output": "OP_1 a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", + "signature": null, + "input": null, + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ] + } + }, + { + "description": "address, pubkey, internalPubkey and output from witness with annex", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c", + "5000000000000000001111111111111111" + ] + }, + "expected": { + "name": "p2tr", + "internalPubkey": "a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a36", + "pubkey": "a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", + "address": "bc1p53rcf44j6q2eeftl2t37vq36fuvkp53vpzwsr8ek2mv7ywfxmnysdul78t", + "output": "OP_1 a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", + "signature": null, + "input": null, + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c", + "5000000000000000001111111111111111" + ] + } + }, { "description": "address, pubkey, output and hash from internalPubkey and a script tree with one leaf", "arguments": { @@ -425,10 +479,89 @@ } }, { - "exception": "Witness is invalid", + "description": "Control block length too small", + "exception": "The control-block length is too small. Got 16, expected min 33.", "arguments": { - "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx", - "witness": [] + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8" + ] + } + }, + { + "description": "Control block must have a length of 33 + 32m (0 <= m <= 128)", + "exception": "The control-block length of 40 is incorrect!", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf77" + ] + } + }, + { + "description": "Control block length too large", + "exception": "The script path is too long. Got 129, expected max 128.", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ] + } + }, + { + "description": "Invalid internalPubkey in control block", + "exception": "Invalid internalPubkey for p2tr witness", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c14444444444444444453d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ] + } + }, + { + "description": "internalPubkey mismatch between control block and internalKey", + "exception": "Internal pubkey mismatch", + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ] + } + }, + { + "description": "pubkey mismatch between outputKey and pubkey", + "exception": "Pubkey mismatch for p2tr witness", + "arguments": { + "pubkey": "df0e070ca2fca05ecd191bdba047841d62414b2bdcb6249c258fd64c0dd251ff", + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ] + } + }, + { + "description": "parity", + "exception": "Incorrect parity", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c0a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ] } } ], diff --git a/test/payments.utils.ts b/test/payments.utils.ts index 71863a602..d1b40aa5e 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -86,6 +86,8 @@ export function equate(a: any, b: any, args?: any): void { t.strictEqual(tryHex(a.hash), tryHex(b.hash), 'Inequal *.hash'); if ('pubkey' in b) t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey'); + if ('internalPubkey' in b) + t.strictEqual(tryHex(a.internalPubkey), tryHex(b.internalPubkey), 'Inequal *.internalPubkey'); if ('signature' in b) t.strictEqual( tryHex(a.signature), diff --git a/ts_src/merkle.ts b/ts_src/merkle.ts index d328acea9..125769215 100644 --- a/ts_src/merkle.ts +++ b/ts_src/merkle.ts @@ -4,6 +4,7 @@ import * as bcrypto from './crypto'; import * as varuint from 'bip174/src/lib/converter/varint'; +// todo: find better place for these consts const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); const LEAF_VERSION_TAPSCRIPT = 0xc0 diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 6bbbfa101..2abc6a72e 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,6 +1,6 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { liftX, tweakPublicKey, typeforce as typef } from '../types'; +import { liftX, tweakPublicKey, computeTweakFromScriptPath, typeforce as typef } from '../types'; import { computeMastRoot } from '../merkle'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; @@ -8,12 +8,13 @@ import { bech32m } from 'bech32'; const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; +const ANNEX_PREFIX = 0x50; // witness: {signature} // input: <> // output: OP_1 {pubKey} export function p2tr(a: Payment, opts?: PaymentOpts): Payment { - if (!a.address && !a.output && !a.pubkey && !a.output && !a.internalPubkey) + if (!a.address && !a.output && !a.pubkey && !a.output && !a.internalPubkey && !(a.witness && a.witness.length > 1)) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); @@ -44,7 +45,14 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }; }); - // todo: clean-up withness (annex), etc + const _witness = lazy.value(() => { + if (!a.witness || !a.witness.length) return + if (a.witness.length >= 2 && (a.witness[a.witness.length - 1][0] === ANNEX_PREFIX)) { + // remove annex, ignored by taproot + return a.witness.slice(0, -1); + } + return a.witness.slice() + }) const network = a.network || BITCOIN_NETWORK; const o: Payment = { name: 'p2tr', network }; @@ -61,6 +69,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; if (a.scriptsTree) return computeMastRoot(a.scriptsTree) + // todo: compute from witness return null }); lazy.prop(o, 'output', () => { @@ -71,11 +80,17 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.pubkey) return a.pubkey; if (a.output) return a.output.slice(2) if (a.address) return _address().data; - if (a.internalPubkey) { - const tweakedKey = tweakPublicKey(a.internalPubkey, o.hash) + if (o.internalPubkey) { + const tweakedKey = tweakPublicKey(o.internalPubkey, o.hash) if (tweakedKey) return tweakedKey.x } }); + lazy.prop(o, 'internalPubkey', () => { + if (a.internalPubkey) return a.internalPubkey; + const witness = _witness() + if (witness && witness.length > 1) + return witness[witness.length - 1].slice(1, 33); + }); lazy.prop(o, 'signature', () => { if (a.witness?.length !== 1) return; return a.witness[0]; @@ -84,6 +99,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { // todo: not sure }); lazy.prop(o, 'witness', () => { + if (a.witness) return a.witness if (!a.signature) return; return [a.signature]; }); @@ -98,7 +114,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Invalid address version'); if (_address().data.length !== 32) throw new TypeError('Invalid address data'); - pubkey = _address().data; + pubkey = _address().data; } if (a.pubkey) { @@ -110,7 +126,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.output) { if ( a.output.length !== 34 || - a.output[0] !== OPS.OP_1 || + a.output[0] !== OPS.OP_1 || a.output[1] !== 0x20 ) throw new TypeError('Output is invalid'); @@ -119,10 +135,9 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { else pubkey = a.output.slice(2); } - // todo: optimze o.hash? if (a.internalPubkey) { const tweakedKey = tweakPublicKey(a.internalPubkey, o.hash) - if (tweakedKey === null) throw new TypeError('Invalid internalPubkey for p2tr'); + if (tweakedKey === null) throw new TypeError('Invalid internalPubkey for p2tr'); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) throw new TypeError('Pubkey mismatch'); else pubkey = tweakedKey.x; @@ -139,15 +154,57 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Hash mismatch'); } - if (a.witness) { - if (a.witness.length !== 1) throw new TypeError('Witness is invalid'); + // todo: review cache + const witness = _witness() - // todo: recheck - // if (!bscript.isCanonicalScriptSignature(a.witness[0])) + if (witness && witness.length) { + if (witness.length === 1) { + // key spending + if (a.signature && !a.signature.equals(witness[0])) + throw new TypeError('Signature mismatch'); + // todo: recheck + // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) // throw new TypeError('Witness has invalid signature'); + } else { + // script path spending + const controlBlock = witness[witness.length - 1]; + if (controlBlock.length < 33) + throw new TypeError(`The control-block length is too small. Got ${controlBlock.length}, expected min 33.`); + + if ((controlBlock.length - 33) % 32 !== 0) + throw new TypeError(`The control-block length of ${controlBlock.length} is incorrect!`); + + const m = (controlBlock.length - 33) / 32; + if (m > 128) + throw new TypeError(`The script path is too long. Got ${m}, expected max 128.`); + + const internalPubkey = controlBlock.slice(1, 33); + if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) + throw new TypeError('Internal pubkey mismatch'); + + const internalPubkeyPoint = liftX(internalPubkey); + if (!internalPubkeyPoint) + throw new TypeError('Invalid internalPubkey for p2tr witness'); + + + const leafVersion = controlBlock[0] & 0b11111110; + const script = witness[witness.length - 2]; + const tweak = computeTweakFromScriptPath(controlBlock, script, internalPubkey, m, leafVersion) + + const outputKey = tweakPublicKey(internalPubkey, tweak) + if (!outputKey) + // todo: needs test data + throw new TypeError('Invalid outputKey for p2tr witness'); + + if (pubkey.length && !pubkey.equals(outputKey.x)) + throw new TypeError('Pubkey mismatch for p2tr witness'); + + const controlBlockOddParity = (controlBlock[0] & 1) === 1 + if (outputKey.isOdd !== controlBlockOddParity) + throw new Error('Incorrect parity') + + } - if (a.signature && !a.signature.equals(a.witness[0])) - throw new TypeError('Signature mismatch'); } } diff --git a/ts_src/types.ts b/ts_src/types.ts index 875df29f7..6fe711450 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -1,5 +1,6 @@ import { Buffer as NBuffer } from 'buffer'; import * as bcrypto from './crypto'; +import * as varuint from 'bip174/src/lib/converter/varint'; // Temp, to be replaced // Only works because bip32 has it as dependecy. Linting will fail. @@ -72,12 +73,10 @@ export function liftX(buffer: Buffer): Buffer | null { } const TAP_TWEAK_TAG = NBuffer.from('TapTweak', 'utf8'); -const GROUP_ORDER = new BN( - NBuffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', - ), -); + +const GROUP_ORDER = NBuffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex'); +// todo: compare buffers dirrectly +const GROUP_ORDER_BN = new BN(GROUP_ORDER); export function tweakPublicKey( pubKey: Buffer, @@ -92,7 +91,8 @@ export function tweakPublicKey( NBuffer.concat(h ? [pubKey, h] : [pubKey]), ); const t = new BN(tweakHash); - if (t.gte(GROUP_ORDER)) { + if (t.gte(GROUP_ORDER_BN)) { + // todo: add test for this case throw new Error('Tweak value over the SECP256K1 Order'); } @@ -106,6 +106,42 @@ export function tweakPublicKey( }; } +const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); +const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); + +export function computeTweakFromScriptPath(controlBlock: Buffer, script: Buffer, internalPubkey: Buffer, m: number, v: number) { + const k = []; + const e = []; + + const tapLeafMsg = NBuffer.concat([NBuffer.from([v]), serializeScript(script)]); + k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); + + + for (let j = 0; j < m; j++) { + e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); + if (k[j].compare(e[j]) < 0) { + k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([k[j], e[j]])); + } else { + k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([e[j], k[j]])); + } + } + + const t = bcrypto.taggedHash(TAP_TWEAK_TAG, NBuffer.concat([internalPubkey, k[m]])); + if (t.compare(GROUP_ORDER) >= 0) { + throw new Error('Over the order of secp256k1') + } + + return t +} + +// todo: move out +function serializeScript(s: Buffer) { + const varintLen = varuint.encodingLength(s.length); + const buffer = NBuffer.allocUnsafe(varintLen); // better + varuint.encode(s.length, buffer); + return NBuffer.concat([buffer, s]) +} + // todo: do not use ecc function pointAddScalar(P: Buffer, h: Buffer): Buffer { return ecc.pointAddScalar(P, h); From 9947571aaee1676271beab50461defb046f796d3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 16:06:01 +0200 Subject: [PATCH 17/73] refactor: split `computeTweakFromScriptPath()` into `rootHash()` and `leafHash()`` --- src/payments/p2tr.js | 15 +++++---------- src/types.d.ts | 6 +++--- src/types.js | 37 ++++++++++++++----------------------- ts_src/payments/p2tr.ts | 12 +++++++----- ts_src/types.ts | 19 +++++++++---------- 5 files changed, 38 insertions(+), 51 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 7d7e93a43..b9db56038 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -85,7 +85,7 @@ function p2tr(a, opts) { if (a.output) return a.output.slice(2); if (a.address) return _address().data; if (o.internalPubkey) { - const tweakedKey = (0, types_1.tweakPublicKey)(o.internalPubkey, o.hash); + const tweakedKey = (0, types_1.tweakKey)(o.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } }); @@ -136,7 +136,7 @@ function p2tr(a, opts) { else pubkey = a.output.slice(2); } if (a.internalPubkey) { - const tweakedKey = (0, types_1.tweakPublicKey)(a.internalPubkey, o.hash); + const tweakedKey = (0, types_1.tweakKey)(a.internalPubkey, o.hash); if (tweakedKey === null) throw new TypeError('Invalid internalPubkey for p2tr'); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) @@ -187,14 +187,9 @@ function p2tr(a, opts) { throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - const tweak = (0, types_1.computeTweakFromScriptPath)( - controlBlock, - script, - internalPubkey, - m, - leafVersion, - ); - const outputKey = (0, types_1.tweakPublicKey)(internalPubkey, tweak); + const tapLeafHash = (0, types_1.leafHash)(script, leafVersion); + const hash = (0, types_1.rootHash)(controlBlock, tapLeafHash); + const outputKey = (0, types_1.tweakKey)(internalPubkey, hash); if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); diff --git a/src/types.d.ts b/src/types.d.ts index a4813ee4f..b63d2c26b 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,10 +1,10 @@ /// -import { Buffer as NBuffer } from 'buffer'; export declare const typeforce: any; export declare function isPoint(p: Buffer | number | undefined | null): boolean; export declare function liftX(buffer: Buffer): Buffer | null; -export declare function tweakPublicKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; -export declare function computeTweakFromScriptPath(controlBlock: Buffer, script: Buffer, internalPubkey: Buffer, m: number, v: number): NBuffer; +export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; +export declare function leafHash(script: Buffer, version: number): Buffer; +export declare function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; export declare namespace BIP32Path { diff --git a/src/types.js b/src/types.js index 07756ecc4..d7fd587fe 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.computeTweakFromScriptPath = exports.tweakPublicKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.rootHash = exports.leafHash = exports.tweakKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; const buffer_1 = require('buffer'); const bcrypto = require('./crypto'); const varuint = require('bip174/src/lib/converter/varint'); @@ -69,7 +69,7 @@ const GROUP_ORDER = buffer_1.Buffer.from( ); // todo: compare buffers dirrectly const GROUP_ORDER_BN = new BN(GROUP_ORDER); -function tweakPublicKey(pubKey, h) { +function tweakKey(pubKey, h) { if (!buffer_1.Buffer.isBuffer(pubKey)) return null; if (pubKey.length !== 32) return null; if (h && h.length !== 32) return null; @@ -90,22 +90,20 @@ function tweakPublicKey(pubKey, h) { x: Q.slice(1, 33), }; } -exports.tweakPublicKey = tweakPublicKey; +exports.tweakKey = tweakKey; const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); -function computeTweakFromScriptPath( - controlBlock, - script, - internalPubkey, - m, - v, -) { - const k = []; - const e = []; - const tapLeafMsg = buffer_1.Buffer.concat([ - buffer_1.Buffer.from([v]), +function leafHash(script, version) { + return buffer_1.Buffer.concat([ + buffer_1.Buffer.from([version]), serializeScript(script), ]); +} +exports.leafHash = leafHash; +function rootHash(controlBlock, tapLeafMsg) { + const k = []; + const e = []; + const m = (controlBlock.length - 33) / 32; k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); for (let j = 0; j < m; j++) { e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); @@ -121,16 +119,9 @@ function computeTweakFromScriptPath( ); } } - const t = bcrypto.taggedHash( - TAP_TWEAK_TAG, - buffer_1.Buffer.concat([internalPubkey, k[m]]), - ); - if (t.compare(GROUP_ORDER) >= 0) { - throw new Error('Over the order of secp256k1'); - } - return t; + return k[m]; } -exports.computeTweakFromScriptPath = computeTweakFromScriptPath; +exports.rootHash = rootHash; // todo: move out function serializeScript(s) { const varintLen = varuint.encodingLength(s.length); diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 2abc6a72e..8992a9ca8 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,6 +1,6 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { liftX, tweakPublicKey, computeTweakFromScriptPath, typeforce as typef } from '../types'; +import { liftX, leafHash, rootHash, tweakKey, typeforce as typef } from '../types'; import { computeMastRoot } from '../merkle'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; @@ -81,7 +81,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.output) return a.output.slice(2) if (a.address) return _address().data; if (o.internalPubkey) { - const tweakedKey = tweakPublicKey(o.internalPubkey, o.hash) + const tweakedKey = tweakKey(o.internalPubkey, o.hash) if (tweakedKey) return tweakedKey.x } }); @@ -136,7 +136,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.internalPubkey) { - const tweakedKey = tweakPublicKey(a.internalPubkey, o.hash) + const tweakedKey = tweakKey(a.internalPubkey, o.hash) if (tweakedKey === null) throw new TypeError('Invalid internalPubkey for p2tr'); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) throw new TypeError('Pubkey mismatch'); @@ -189,9 +189,11 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - const tweak = computeTweakFromScriptPath(controlBlock, script, internalPubkey, m, leafVersion) + + const tapLeafHash = leafHash(script, leafVersion) + const hash = rootHash(controlBlock, tapLeafHash) - const outputKey = tweakPublicKey(internalPubkey, tweak) + const outputKey = tweakKey(internalPubkey, hash) if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); diff --git a/ts_src/types.ts b/ts_src/types.ts index 6fe711450..54f3b816d 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -78,7 +78,7 @@ const GROUP_ORDER = NBuffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a0 // todo: compare buffers dirrectly const GROUP_ORDER_BN = new BN(GROUP_ORDER); -export function tweakPublicKey( +export function tweakKey( pubKey: Buffer, h: Buffer | undefined, ): TweakedPublicKey | null { @@ -109,14 +109,18 @@ export function tweakPublicKey( const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); -export function computeTweakFromScriptPath(controlBlock: Buffer, script: Buffer, internalPubkey: Buffer, m: number, v: number) { + +export function leafHash(script: Buffer, version: number): Buffer { + return NBuffer.concat([NBuffer.from([version]), serializeScript(script)]); +} + +export function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { const k = []; const e = []; - const tapLeafMsg = NBuffer.concat([NBuffer.from([v]), serializeScript(script)]); + const m = (controlBlock.length - 33) / 32; k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); - for (let j = 0; j < m; j++) { e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); if (k[j].compare(e[j]) < 0) { @@ -126,12 +130,7 @@ export function computeTweakFromScriptPath(controlBlock: Buffer, script: Buffer, } } - const t = bcrypto.taggedHash(TAP_TWEAK_TAG, NBuffer.concat([internalPubkey, k[m]])); - if (t.compare(GROUP_ORDER) >= 0) { - throw new Error('Over the order of secp256k1') - } - - return t + return k[m] } // todo: move out From b8f8c91d68c2432b66a0dc00a6ef8d4646160e7b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 16:57:31 +0200 Subject: [PATCH 18/73] feat: compute hash from witness control-block --- src/payments/p2tr.js | 9 ++++++++- test/fixtures/p2tr.json | 14 ++++++++------ ts_src/payments/p2tr.ts | 12 +++++++++--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index b9db56038..a88332b22 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -73,7 +73,14 @@ function p2tr(a, opts) { lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; if (a.scriptsTree) return (0, merkle_1.computeMastRoot)(a.scriptsTree); - // todo: compute from witness + const w = _witness(); + if (w && w.length > 1) { + const controlBlock = w[w.length - 1]; + const leafVersion = controlBlock[0] & 0b11111110; + const script = w[w.length - 2]; + const tapLeafHash = (0, types_1.leafHash)(script, leafVersion); + return (0, types_1.rootHash)(controlBlock, tapLeafHash); + } return null; }); lazy.prop(o, 'output', () => { diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 532fe2297..24cb6472f 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -106,9 +106,10 @@ "expected": { "name": "p2tr", "internalPubkey": "a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a36", - "pubkey": "a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", - "address": "bc1p53rcf44j6q2eeftl2t37vq36fuvkp53vpzwsr8ek2mv7ywfxmnysdul78t", - "output": "OP_1 a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", + "pubkey": "1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc", + "hash": "c5c62d7fc595ba5fbe61602eb1a29e2e4763408fe1e2b161beb7cb3c71ebcad9", + "address": "bc1pr6lghypk80gf025lx5kgkgv3fcvgd0qfl608psylx0hj624a7j7qay80rv", + "output": "OP_1 1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc", "signature": null, "input": null, "witness": [ @@ -133,9 +134,10 @@ "expected": { "name": "p2tr", "internalPubkey": "a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a36", - "pubkey": "a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", - "address": "bc1p53rcf44j6q2eeftl2t37vq36fuvkp53vpzwsr8ek2mv7ywfxmnysdul78t", - "output": "OP_1 a44784d6b2d0159ca57f52e3e6023a4f1960d22c089d019f3656d9e23926dcc9", + "pubkey": "1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc", + "hash": "c5c62d7fc595ba5fbe61602eb1a29e2e4763408fe1e2b161beb7cb3c71ebcad9", + "address": "bc1pr6lghypk80gf025lx5kgkgv3fcvgd0qfl608psylx0hj624a7j7qay80rv", + "output": "OP_1 1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc", "signature": null, "input": null, "witness": [ diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 8992a9ca8..b1ed834e6 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -69,7 +69,14 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; if (a.scriptsTree) return computeMastRoot(a.scriptsTree) - // todo: compute from witness + const w = _witness() + if (w && w.length > 1) { + const controlBlock = w[w.length - 1]; + const leafVersion = controlBlock[0] & 0b11111110; + const script = w[w.length - 2]; + const tapLeafHash = leafHash(script, leafVersion) + return rootHash(controlBlock, tapLeafHash) + } return null }); lazy.prop(o, 'output', () => { @@ -186,10 +193,9 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (!internalPubkeyPoint) throw new TypeError('Invalid internalPubkey for p2tr witness'); - const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - + const tapLeafHash = leafHash(script, leafVersion) const hash = rootHash(controlBlock, tapLeafHash) From 8fd07fc7b073ae686d85226580707fc746154b69 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 17:26:04 +0200 Subject: [PATCH 19/73] refactor: extract taproot related logic to taproot.ts file --- src/merkle.d.ts | 1 - src/merkle.js | 47 +------------ src/payments/p2tr.js | 24 +++---- src/taproot.d.ts | 7 ++ src/taproot.js | 142 ++++++++++++++++++++++++++++++++++++++++ src/types.d.ts | 7 +- src/types.js | 122 ++-------------------------------- ts_src/merkle.ts | 41 ------------ ts_src/payments/p2tr.ts | 4 +- ts_src/taproot.ts | 140 +++++++++++++++++++++++++++++++++++++++ ts_src/types.ts | 123 +--------------------------------- 11 files changed, 316 insertions(+), 342 deletions(-) create mode 100644 src/taproot.d.ts create mode 100644 src/taproot.js create mode 100644 ts_src/taproot.ts diff --git a/src/merkle.d.ts b/src/merkle.d.ts index d4b43f3ba..d602201b9 100644 --- a/src/merkle.d.ts +++ b/src/merkle.d.ts @@ -1,3 +1,2 @@ /// export declare function fastMerkleRoot(values: Buffer[], digestFn: (b: Buffer) => Buffer): Buffer; -export declare function computeMastRoot(scripts: any): Buffer; diff --git a/src/merkle.js b/src/merkle.js index 009260ac4..e93f9cab6 100644 --- a/src/merkle.js +++ b/src/merkle.js @@ -1,14 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.computeMastRoot = exports.fastMerkleRoot = void 0; -const buffer_1 = require('buffer'); -const bcrypto = require('./crypto'); -// todo: use varuint-bitcoin?? -const varuint = require('bip174/src/lib/converter/varint'); -// todo: find better place for these consts -const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); -const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); -const LEAF_VERSION_TAPSCRIPT = 0xc0; +exports.fastMerkleRoot = void 0; function fastMerkleRoot(values, digestFn) { if (!Array.isArray(values)) throw TypeError('Expected values Array'); if (typeof digestFn !== 'function') @@ -28,40 +20,3 @@ function fastMerkleRoot(values, digestFn) { return results[0]; } exports.fastMerkleRoot = fastMerkleRoot; -// todo: solve any[] -function computeMastRoot(scripts) { - if (scripts.length === 1) { - const script = scripts[0]; - if (Array.isArray(script)) { - return computeMastRoot(script); - } - script.version = script.version || LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error - // todo: if (script.output)scheck is bytes - const scriptOutput = buffer_1.Buffer.from(script.output, 'hex'); - return bcrypto.taggedHash( - TAP_LEAF_TAG, - buffer_1.Buffer.concat([ - buffer_1.Buffer.from([script.version]), - serializeScript(scriptOutput), - ]), - ); - } - // todo: this is a binary tree, use zero an one index - const half = Math.trunc(scripts.length / 2); - let leftHash = computeMastRoot(scripts.slice(0, half)); - let rightHash = computeMastRoot(scripts.slice(half)); - if (leftHash.compare(rightHash) === 1) - [leftHash, rightHash] = [rightHash, leftHash]; - return bcrypto.taggedHash( - TAP_BRANCH_TAG, - buffer_1.Buffer.concat([leftHash, rightHash]), - ); -} -exports.computeMastRoot = computeMastRoot; -function serializeScript(s) { - const varintLen = varuint.encodingLength(s.length); - const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better - varuint.encode(s.length, buffer); - return buffer_1.Buffer.concat([buffer, s]); -} diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index a88332b22..53767318a 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -4,7 +4,7 @@ exports.p2tr = void 0; const networks_1 = require('../networks'); const bscript = require('../script'); const types_1 = require('../types'); -const merkle_1 = require('../merkle'); +const taproot_1 = require('../taproot'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); const OPS = bscript.OPS; @@ -72,14 +72,14 @@ function p2tr(a, opts) { }); lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return (0, merkle_1.computeMastRoot)(a.scriptsTree); + if (a.scriptsTree) return (0, taproot_1.computeMastRoot)(a.scriptsTree); const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; const script = w[w.length - 2]; - const tapLeafHash = (0, types_1.leafHash)(script, leafVersion); - return (0, types_1.rootHash)(controlBlock, tapLeafHash); + const tapLeafHash = (0, taproot_1.leafHash)(script, leafVersion); + return (0, taproot_1.rootHash)(controlBlock, tapLeafHash); } return null; }); @@ -92,7 +92,7 @@ function p2tr(a, opts) { if (a.output) return a.output.slice(2); if (a.address) return _address().data; if (o.internalPubkey) { - const tweakedKey = (0, types_1.tweakKey)(o.internalPubkey, o.hash); + const tweakedKey = (0, taproot_1.tweakKey)(o.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } }); @@ -143,7 +143,7 @@ function p2tr(a, opts) { else pubkey = a.output.slice(2); } if (a.internalPubkey) { - const tweakedKey = (0, types_1.tweakKey)(a.internalPubkey, o.hash); + const tweakedKey = (0, taproot_1.tweakKey)(a.internalPubkey, o.hash); if (tweakedKey === null) throw new TypeError('Invalid internalPubkey for p2tr'); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) @@ -151,11 +151,11 @@ function p2tr(a, opts) { else pubkey = tweakedKey.x; } if (pubkey?.length) { - if ((0, types_1.liftX)(pubkey) === null) + if ((0, taproot_1.liftX)(pubkey) === null) throw new TypeError('Invalid pubkey for p2tr'); } if (a.hash && a.scriptsTree) { - const hash = (0, merkle_1.computeMastRoot)(a.scriptsTree); + const hash = (0, taproot_1.computeMastRoot)(a.scriptsTree); if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } // todo: review cache @@ -189,14 +189,14 @@ function p2tr(a, opts) { const internalPubkey = controlBlock.slice(1, 33); if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) throw new TypeError('Internal pubkey mismatch'); - const internalPubkeyPoint = (0, types_1.liftX)(internalPubkey); + const internalPubkeyPoint = (0, taproot_1.liftX)(internalPubkey); if (!internalPubkeyPoint) throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - const tapLeafHash = (0, types_1.leafHash)(script, leafVersion); - const hash = (0, types_1.rootHash)(controlBlock, tapLeafHash); - const outputKey = (0, types_1.tweakKey)(internalPubkey, hash); + const tapLeafHash = (0, taproot_1.leafHash)(script, leafVersion); + const hash = (0, taproot_1.rootHash)(controlBlock, tapLeafHash); + const outputKey = (0, taproot_1.tweakKey)(internalPubkey, hash); if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); diff --git a/src/taproot.d.ts b/src/taproot.d.ts new file mode 100644 index 000000000..0391f6bf1 --- /dev/null +++ b/src/taproot.d.ts @@ -0,0 +1,7 @@ +/// +import { TweakedPublicKey } from './types'; +export declare function liftX(buffer: Buffer): Buffer | null; +export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; +export declare function leafHash(script: Buffer, version: number): Buffer; +export declare function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; +export declare function computeMastRoot(scripts: any): Buffer; diff --git a/src/taproot.js b/src/taproot.js new file mode 100644 index 000000000..4930f4331 --- /dev/null +++ b/src/taproot.js @@ -0,0 +1,142 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.computeMastRoot = exports.rootHash = exports.leafHash = exports.tweakKey = exports.liftX = void 0; +const buffer_1 = require('buffer'); +const BN = require('bn.js'); +const bcrypto = require('./crypto'); +// todo: use varuint-bitcoin?? +const varuint = require('bip174/src/lib/converter/varint'); +const types_1 = require('./types'); +// todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. +const ecc = require('tiny-secp256k1'); +const LEAF_VERSION_TAPSCRIPT = 0xc0; +const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); +const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); +const TAP_TWEAK_TAG = buffer_1.Buffer.from('TapTweak', 'utf8'); +// todo: compare buffers dirrectly +const GROUP_ORDER = buffer_1.Buffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex', +); +const GROUP_ORDER_BN = new BN(GROUP_ORDER); +const EC_P_BN = new BN(types_1.EC_P); +const EC_P_REDUCTION = BN.red(EC_P_BN); +const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); +const BN_2 = new BN(2); +const BN_3 = new BN(3); +const BN_7 = new BN(7); +function liftX(buffer) { + if (!buffer_1.Buffer.isBuffer(buffer)) return null; + if (buffer.length !== 32) return null; + if (buffer.compare(types_1.ZERO32) === 0) return null; + if (buffer.compare(types_1.EC_P) >= 0) return null; + const x = new BN(buffer); + const x1 = x.toRed(EC_P_REDUCTION); + const ySq = x1 + .redPow(BN_3) + .add(BN_7) + .mod(EC_P_BN); + const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); + if (!ySq.eq(y.redPow(BN_2))) { + return null; + } + const y1 = y.isEven() ? y : EC_P_BN.sub(y); + return buffer_1.Buffer.concat([ + buffer_1.Buffer.from([0x04]), + buffer_1.Buffer.from(x1.toBuffer('be', 32)), + buffer_1.Buffer.from(y1.toBuffer('be', 32)), + ]); +} +exports.liftX = liftX; +function tweakKey(pubKey, h) { + if (!buffer_1.Buffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + const tweakHash = bcrypto.taggedHash( + TAP_TWEAK_TAG, + buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), + ); + const t = new BN(tweakHash); + if (t.gte(GROUP_ORDER_BN)) { + // todo: add test for this case + throw new Error('Tweak value over the SECP256K1 Order'); + } + const P = liftX(pubKey); + if (P === null) return null; + const Q = pointAddScalar(P, tweakHash); + return { + isOdd: Q[64] % 2 === 1, + x: Q.slice(1, 33), + }; +} +exports.tweakKey = tweakKey; +function leafHash(script, version) { + return buffer_1.Buffer.concat([ + buffer_1.Buffer.from([version]), + serializeScript(script), + ]); +} +exports.leafHash = leafHash; +function rootHash(controlBlock, tapLeafMsg) { + const k = []; + const e = []; + const m = (controlBlock.length - 33) / 32; + k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); + for (let j = 0; j < m; j++) { + e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); + if (k[j].compare(e[j]) < 0) { + k[j + 1] = bcrypto.taggedHash( + TAP_BRANCH_TAG, + buffer_1.Buffer.concat([k[j], e[j]]), + ); + } else { + k[j + 1] = bcrypto.taggedHash( + TAP_BRANCH_TAG, + buffer_1.Buffer.concat([e[j], k[j]]), + ); + } + } + return k[m]; +} +exports.rootHash = rootHash; +// todo: solve any[] +function computeMastRoot(scripts) { + if (scripts.length === 1) { + const script = scripts[0]; + if (Array.isArray(script)) { + return computeMastRoot(script); + } + script.version = script.version || LEAF_VERSION_TAPSCRIPT; + if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error + // todo: if (script.output)scheck is bytes + const scriptOutput = buffer_1.Buffer.from(script.output, 'hex'); + return bcrypto.taggedHash( + TAP_LEAF_TAG, + buffer_1.Buffer.concat([ + buffer_1.Buffer.from([script.version]), + serializeScript(scriptOutput), + ]), + ); + } + // todo: this is a binary tree, use zero an one index + const half = Math.trunc(scripts.length / 2); + let leftHash = computeMastRoot(scripts.slice(0, half)); + let rightHash = computeMastRoot(scripts.slice(half)); + if (leftHash.compare(rightHash) === 1) + [leftHash, rightHash] = [rightHash, leftHash]; + return bcrypto.taggedHash( + TAP_BRANCH_TAG, + buffer_1.Buffer.concat([leftHash, rightHash]), + ); +} +exports.computeMastRoot = computeMastRoot; +function serializeScript(s) { + const varintLen = varuint.encodingLength(s.length); + const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better + varuint.encode(s.length, buffer); + return buffer_1.Buffer.concat([buffer, s]); +} +// todo: do not use ecc +function pointAddScalar(P, h) { + return ecc.pointAddScalar(P, h); +} diff --git a/src/types.d.ts b/src/types.d.ts index b63d2c26b..6e72a6d47 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,10 +1,9 @@ /// +import { Buffer as NBuffer } from 'buffer'; export declare const typeforce: any; +export declare const ZERO32: NBuffer; +export declare const EC_P: NBuffer; export declare function isPoint(p: Buffer | number | undefined | null): boolean; -export declare function liftX(buffer: Buffer): Buffer | null; -export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; -export declare function leafHash(script: Buffer, version: number): Buffer; -export declare function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; export declare namespace BIP32Path { diff --git a/src/types.js b/src/types.js index d7fd587fe..ba62bd813 100644 --- a/src/types.js +++ b/src/types.js @@ -1,17 +1,10 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.rootHash = exports.leafHash = exports.tweakKey = exports.liftX = exports.isPoint = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.EC_P = exports.ZERO32 = exports.typeforce = void 0; const buffer_1 = require('buffer'); -const bcrypto = require('./crypto'); -const varuint = require('bip174/src/lib/converter/varint'); -// Temp, to be replaced -// Only works because bip32 has it as dependecy. Linting will fail. -const ecc = require('tiny-secp256k1'); -// todo, use import? -const BN = require('bn.js'); exports.typeforce = require('typeforce'); -const ZERO32 = buffer_1.Buffer.alloc(32, 0); -const EC_P = buffer_1.Buffer.from( +exports.ZERO32 = buffer_1.Buffer.alloc(32, 0); +exports.EC_P = buffer_1.Buffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); @@ -20,119 +13,18 @@ function isPoint(p) { if (p.length < 33) return false; const t = p[0]; const x = p.slice(1, 33); - if (x.compare(ZERO32) === 0) return false; - if (x.compare(EC_P) >= 0) return false; + if (x.compare(exports.ZERO32) === 0) return false; + if (x.compare(exports.EC_P) >= 0) return false; if ((t === 0x02 || t === 0x03) && p.length === 33) { return true; } const y = p.slice(33); - if (y.compare(ZERO32) === 0) return false; - if (y.compare(EC_P) >= 0) return false; + if (y.compare(exports.ZERO32) === 0) return false; + if (y.compare(exports.EC_P) >= 0) return false; if (t === 0x04 && p.length === 65) return true; return false; } exports.isPoint = isPoint; -// todo review. Do not add dependcy to BN? -const EC_P_BN = new BN(EC_P); -const EC_P_REDUCTION = BN.red(EC_P_BN); -const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); -const BN_2 = new BN(2); -const BN_3 = new BN(3); -const BN_7 = new BN(7); -function liftX(buffer) { - if (!buffer_1.Buffer.isBuffer(buffer)) return null; - if (buffer.length !== 32) return null; - if (buffer.compare(ZERO32) === 0) return null; - if (buffer.compare(EC_P) >= 0) return null; - const x = new BN(buffer); - const x1 = x.toRed(EC_P_REDUCTION); - const ySq = x1 - .redPow(BN_3) - .add(BN_7) - .mod(EC_P_BN); - const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); - if (!ySq.eq(y.redPow(BN_2))) { - return null; - } - const y1 = y.isEven() ? y : EC_P_BN.sub(y); - return buffer_1.Buffer.concat([ - buffer_1.Buffer.from([0x04]), - buffer_1.Buffer.from(x1.toBuffer('be', 32)), - buffer_1.Buffer.from(y1.toBuffer('be', 32)), - ]); -} -exports.liftX = liftX; -const TAP_TWEAK_TAG = buffer_1.Buffer.from('TapTweak', 'utf8'); -const GROUP_ORDER = buffer_1.Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', -); -// todo: compare buffers dirrectly -const GROUP_ORDER_BN = new BN(GROUP_ORDER); -function tweakKey(pubKey, h) { - if (!buffer_1.Buffer.isBuffer(pubKey)) return null; - if (pubKey.length !== 32) return null; - if (h && h.length !== 32) return null; - const tweakHash = bcrypto.taggedHash( - TAP_TWEAK_TAG, - buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), - ); - const t = new BN(tweakHash); - if (t.gte(GROUP_ORDER_BN)) { - // todo: add test for this case - throw new Error('Tweak value over the SECP256K1 Order'); - } - const P = liftX(pubKey); - if (P === null) return null; - const Q = pointAddScalar(P, tweakHash); - return { - isOdd: Q[64] % 2 === 1, - x: Q.slice(1, 33), - }; -} -exports.tweakKey = tweakKey; -const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); -const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); -function leafHash(script, version) { - return buffer_1.Buffer.concat([ - buffer_1.Buffer.from([version]), - serializeScript(script), - ]); -} -exports.leafHash = leafHash; -function rootHash(controlBlock, tapLeafMsg) { - const k = []; - const e = []; - const m = (controlBlock.length - 33) / 32; - k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); - for (let j = 0; j < m; j++) { - e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); - if (k[j].compare(e[j]) < 0) { - k[j + 1] = bcrypto.taggedHash( - TAP_BRANCH_TAG, - buffer_1.Buffer.concat([k[j], e[j]]), - ); - } else { - k[j + 1] = bcrypto.taggedHash( - TAP_BRANCH_TAG, - buffer_1.Buffer.concat([e[j], k[j]]), - ); - } - } - return k[m]; -} -exports.rootHash = rootHash; -// todo: move out -function serializeScript(s) { - const varintLen = varuint.encodingLength(s.length); - const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better - varuint.encode(s.length, buffer); - return buffer_1.Buffer.concat([buffer, s]); -} -// todo: do not use ecc -function pointAddScalar(P, h) { - return ecc.pointAddScalar(P, h); -} const UINT31_MAX = Math.pow(2, 31) - 1; function UInt31(value) { return exports.typeforce.UInt32(value) && value <= UINT31_MAX; diff --git a/ts_src/merkle.ts b/ts_src/merkle.ts index 125769215..814819c79 100644 --- a/ts_src/merkle.ts +++ b/ts_src/merkle.ts @@ -1,15 +1,3 @@ -import { Buffer as NBuffer } from 'buffer'; -import * as bcrypto from './crypto'; -// todo: use varuint-bitcoin?? -import * as varuint from 'bip174/src/lib/converter/varint'; - - -// todo: find better place for these consts -const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); -const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); -const LEAF_VERSION_TAPSCRIPT = 0xc0 - - export function fastMerkleRoot( values: Buffer[], digestFn: (b: Buffer) => Buffer, @@ -36,33 +24,4 @@ export function fastMerkleRoot( } return results[0]; -} - -// todo: solve any[] -export function computeMastRoot(scripts: any): Buffer { - if (scripts.length === 1) { - const script = scripts[0] - if (Array.isArray(script)) { - return computeMastRoot(script) - } - script.version = script.version || LEAF_VERSION_TAPSCRIPT - if ((script.version & 1) !== 0) throw new Error("Invalid script version") // todo typedef error - // todo: if (script.output)scheck is bytes - const scriptOutput = NBuffer.from(script.output, 'hex') - return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([script.version]), serializeScript(scriptOutput)])) - } - // todo: this is a binary tree, use zero an one index - const half = Math.trunc(scripts.length / 2) - let leftHash = computeMastRoot(scripts.slice(0, half)) - let rightHash = computeMastRoot(scripts.slice(half)) - - if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash] - return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([leftHash, rightHash])) -} - -function serializeScript(s: Buffer) { - const varintLen = varuint.encodingLength(s.length); - const buffer = NBuffer.allocUnsafe(varintLen); // better - varuint.encode(s.length, buffer); - return NBuffer.concat([buffer, s]) } \ No newline at end of file diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index b1ed834e6..aef500c85 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,7 +1,7 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { liftX, leafHash, rootHash, tweakKey, typeforce as typef } from '../types'; -import { computeMastRoot } from '../merkle'; +import { typeforce as typef } from '../types'; +import { computeMastRoot, leafHash, rootHash, tweakKey, liftX } from '../taproot'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts new file mode 100644 index 000000000..5370e326f --- /dev/null +++ b/ts_src/taproot.ts @@ -0,0 +1,140 @@ +import { Buffer as NBuffer } from 'buffer'; +const BN = require('bn.js'); + +import * as bcrypto from './crypto'; +// todo: use varuint-bitcoin?? +import * as varuint from 'bip174/src/lib/converter/varint'; +import { TweakedPublicKey, ZERO32, EC_P } from './types' + +// todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. +const ecc = require('tiny-secp256k1'); + +const LEAF_VERSION_TAPSCRIPT = 0xc0 +const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); +const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); +const TAP_TWEAK_TAG = NBuffer.from('TapTweak', 'utf8'); + +// todo: compare buffers dirrectly +const GROUP_ORDER = NBuffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex'); +const GROUP_ORDER_BN = new BN(GROUP_ORDER); + +const EC_P_BN = new BN(EC_P); +const EC_P_REDUCTION = BN.red(EC_P_BN); +const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); +const BN_2 = new BN(2); +const BN_3 = new BN(3); +const BN_7 = new BN(7); + +export function liftX(buffer: Buffer): Buffer | null { + if (!NBuffer.isBuffer(buffer)) return null; + if (buffer.length !== 32) return null; + + if (buffer.compare(ZERO32) === 0) return null; + if (buffer.compare(EC_P) >= 0) return null; + + const x = new BN(buffer); + + const x1 = x.toRed(EC_P_REDUCTION); + const ySq = x1 + .redPow(BN_3) + .add(BN_7) + .mod(EC_P_BN); + + const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); + + if (!ySq.eq(y.redPow(BN_2))) { + return null; + } + const y1 = y.isEven() ? y : EC_P_BN.sub(y); + + return NBuffer.concat([ + NBuffer.from([0x04]), + NBuffer.from(x1.toBuffer('be', 32)), + NBuffer.from(y1.toBuffer('be', 32)), + ]); +} + +export function tweakKey( + pubKey: Buffer, + h: Buffer | undefined, +): TweakedPublicKey | null { + if (!NBuffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + + const tweakHash = bcrypto.taggedHash( + TAP_TWEAK_TAG, + NBuffer.concat(h ? [pubKey, h] : [pubKey]), + ); + const t = new BN(tweakHash); + if (t.gte(GROUP_ORDER_BN)) { + // todo: add test for this case + throw new Error('Tweak value over the SECP256K1 Order'); + } + + const P = liftX(pubKey); + if (P === null) return null; + + const Q = pointAddScalar(P, tweakHash); + return { + isOdd: Q[64] % 2 === 1, + x: Q.slice(1, 33), + }; +} + +export function leafHash(script: Buffer, version: number): Buffer { + return NBuffer.concat([NBuffer.from([version]), serializeScript(script)]); +} + +export function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { + const k = []; + const e = []; + + const m = (controlBlock.length - 33) / 32; + k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); + + for (let j = 0; j < m; j++) { + e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); + if (k[j].compare(e[j]) < 0) { + k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([k[j], e[j]])); + } else { + k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([e[j], k[j]])); + } + } + + return k[m] +} + +// todo: solve any[] +export function computeMastRoot(scripts: any): Buffer { + if (scripts.length === 1) { + const script = scripts[0] + if (Array.isArray(script)) { + return computeMastRoot(script) + } + script.version = script.version || LEAF_VERSION_TAPSCRIPT + if ((script.version & 1) !== 0) throw new Error("Invalid script version") // todo typedef error + // todo: if (script.output)scheck is bytes + const scriptOutput = NBuffer.from(script.output, 'hex') + return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([script.version]), serializeScript(scriptOutput)])) + } + // todo: this is a binary tree, use zero an one index + const half = Math.trunc(scripts.length / 2) + let leftHash = computeMastRoot(scripts.slice(0, half)) + let rightHash = computeMastRoot(scripts.slice(half)) + + if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash] + return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([leftHash, rightHash])) +} + +function serializeScript(s: Buffer) { + const varintLen = varuint.encodingLength(s.length); + const buffer = NBuffer.allocUnsafe(varintLen); // better + varuint.encode(s.length, buffer); + return NBuffer.concat([buffer, s]) +} + +// todo: do not use ecc +function pointAddScalar(P: Buffer, h: Buffer): Buffer { + return ecc.pointAddScalar(P, h); +} diff --git a/ts_src/types.ts b/ts_src/types.ts index 54f3b816d..014bfaafc 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -1,17 +1,9 @@ import { Buffer as NBuffer } from 'buffer'; -import * as bcrypto from './crypto'; -import * as varuint from 'bip174/src/lib/converter/varint'; - -// Temp, to be replaced -// Only works because bip32 has it as dependecy. Linting will fail. -const ecc = require('tiny-secp256k1'); -// todo, use import? -const BN = require('bn.js'); export const typeforce = require('typeforce'); -const ZERO32 = NBuffer.alloc(32, 0); -const EC_P = NBuffer.from( +export const ZERO32 = NBuffer.alloc(32, 0); +export const EC_P = NBuffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); @@ -35,117 +27,6 @@ export function isPoint(p: Buffer | number | undefined | null): boolean { return false; } -// todo review. Do not add dependcy to BN? -const EC_P_BN = new BN(EC_P); -const EC_P_REDUCTION = BN.red(EC_P_BN); -const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); -const BN_2 = new BN(2); -const BN_3 = new BN(3); -const BN_7 = new BN(7); - -export function liftX(buffer: Buffer): Buffer | null { - if (!NBuffer.isBuffer(buffer)) return null; - if (buffer.length !== 32) return null; - - if (buffer.compare(ZERO32) === 0) return null; - if (buffer.compare(EC_P) >= 0) return null; - - const x = new BN(buffer); - - const x1 = x.toRed(EC_P_REDUCTION); - const ySq = x1 - .redPow(BN_3) - .add(BN_7) - .mod(EC_P_BN); - - const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); - - if (!ySq.eq(y.redPow(BN_2))) { - return null; - } - const y1 = y.isEven() ? y : EC_P_BN.sub(y); - - return NBuffer.concat([ - NBuffer.from([0x04]), - NBuffer.from(x1.toBuffer('be', 32)), - NBuffer.from(y1.toBuffer('be', 32)), - ]); -} - -const TAP_TWEAK_TAG = NBuffer.from('TapTweak', 'utf8'); - -const GROUP_ORDER = NBuffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex'); -// todo: compare buffers dirrectly -const GROUP_ORDER_BN = new BN(GROUP_ORDER); - -export function tweakKey( - pubKey: Buffer, - h: Buffer | undefined, -): TweakedPublicKey | null { - if (!NBuffer.isBuffer(pubKey)) return null; - if (pubKey.length !== 32) return null; - if (h && h.length !== 32) return null; - - const tweakHash = bcrypto.taggedHash( - TAP_TWEAK_TAG, - NBuffer.concat(h ? [pubKey, h] : [pubKey]), - ); - const t = new BN(tweakHash); - if (t.gte(GROUP_ORDER_BN)) { - // todo: add test for this case - throw new Error('Tweak value over the SECP256K1 Order'); - } - - const P = liftX(pubKey); - if (P === null) return null; - - const Q = pointAddScalar(P, tweakHash); - return { - isOdd: Q[64] % 2 === 1, - x: Q.slice(1, 33), - }; -} - -const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); -const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); - - -export function leafHash(script: Buffer, version: number): Buffer { - return NBuffer.concat([NBuffer.from([version]), serializeScript(script)]); -} - -export function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { - const k = []; - const e = []; - - const m = (controlBlock.length - 33) / 32; - k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); - - for (let j = 0; j < m; j++) { - e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); - if (k[j].compare(e[j]) < 0) { - k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([k[j], e[j]])); - } else { - k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([e[j], k[j]])); - } - } - - return k[m] -} - -// todo: move out -function serializeScript(s: Buffer) { - const varintLen = varuint.encodingLength(s.length); - const buffer = NBuffer.allocUnsafe(varintLen); // better - varuint.encode(s.length, buffer); - return NBuffer.concat([buffer, s]) -} - -// todo: do not use ecc -function pointAddScalar(P: Buffer, h: Buffer): Buffer { - return ecc.pointAddScalar(P, h); -} - const UINT31_MAX: number = Math.pow(2, 31) - 1; export function UInt31(value: number): boolean { return typeforce.UInt32(value) && value <= UINT31_MAX; From c987b0c03668061a7d519222f17b012e5ab14bd5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 17:40:25 +0200 Subject: [PATCH 20/73] chore: code format and lint --- src/payments/p2tr.js | 4 +- test/payments.utils.ts | 19 ++-- ts_src/merkle.ts | 2 +- ts_src/payments/index.ts | 2 +- ts_src/payments/p2tr.ts | 91 +++++++++++-------- ts_src/taproot.ts | 189 +++++++++++++++++++++------------------ ts_src/types.ts | 9 +- 7 files changed, 179 insertions(+), 137 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 53767318a..22c03b088 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -103,7 +103,7 @@ function p2tr(a, opts) { return witness[witness.length - 1].slice(1, 33); }); lazy.prop(o, 'signature', () => { - if (a.witness?.length !== 1) return; + if (!a.witness || a.witness.length !== 1) return; return a.witness[0]; }); lazy.prop(o, 'input', () => { @@ -150,7 +150,7 @@ function p2tr(a, opts) { throw new TypeError('Pubkey mismatch'); else pubkey = tweakedKey.x; } - if (pubkey?.length) { + if (pubkey && pubkey.length) { if ((0, taproot_1.liftX)(pubkey) === null) throw new TypeError('Invalid pubkey for p2tr'); } diff --git a/test/payments.utils.ts b/test/payments.utils.ts index d1b40aa5e..1aa401830 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -87,7 +87,11 @@ export function equate(a: any, b: any, args?: any): void { if ('pubkey' in b) t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey'); if ('internalPubkey' in b) - t.strictEqual(tryHex(a.internalPubkey), tryHex(b.internalPubkey), 'Inequal *.internalPubkey'); + t.strictEqual( + tryHex(a.internalPubkey), + tryHex(b.internalPubkey), + 'Inequal *.internalPubkey', + ); if ('signature' in b) t.strictEqual( tryHex(a.signature), @@ -149,8 +153,7 @@ export function preform(x: any): any { if (x.redeem.network) x.redeem.network = (BNETWORKS as any)[x.redeem.network]; } - if (x.scriptsTree) - x.scriptsTree = convertScriptsTree(x.scriptsTree) + if (x.scriptsTree) x.scriptsTree = convertScriptsTree(x.scriptsTree); return x; } @@ -176,12 +179,10 @@ export function from(path: string, object: any, result?: any): any { // todo: solve any type function convertScriptsTree(scriptsTree: any): any { - if (Array.isArray(scriptsTree)) - return scriptsTree.map(convertScriptsTree) - + if (Array.isArray(scriptsTree)) return scriptsTree.map(convertScriptsTree); const script = Object.assign({}, scriptsTree); - if ((typeof script.output === 'string')) - script.output = asmToBuffer(scriptsTree.output) - return script + if (typeof script.output === 'string') + script.output = asmToBuffer(scriptsTree.output); + return script; } diff --git a/ts_src/merkle.ts b/ts_src/merkle.ts index 814819c79..8ff8c3f8c 100644 --- a/ts_src/merkle.ts +++ b/ts_src/merkle.ts @@ -24,4 +24,4 @@ export function fastMerkleRoot( } return results[0]; -} \ No newline at end of file +} diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 5243902d4..f6223a3d0 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -24,7 +24,7 @@ export interface Payment { address?: string; // taproot: betch32m hash?: Buffer; // taproot: MAST root redeem?: Payment; // taproot: when script path spending is used spending - scriptsTree?: any // todo: solve + scriptsTree?: any; // todo: solve witness?: Buffer[]; } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index aef500c85..25e52696c 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,7 +1,13 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; import { typeforce as typef } from '../types'; -import { computeMastRoot, leafHash, rootHash, tweakKey, liftX } from '../taproot'; +import { + computeMastRoot, + leafHash, + rootHash, + tweakKey, + liftX, +} from '../taproot'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; @@ -14,7 +20,14 @@ const ANNEX_PREFIX = 0x50; // input: <> // output: OP_1 {pubKey} export function p2tr(a: Payment, opts?: PaymentOpts): Payment { - if (!a.address && !a.output && !a.pubkey && !a.output && !a.internalPubkey && !(a.witness && a.witness.length > 1)) + if ( + !a.address && + !a.output && + !a.pubkey && + !a.output && + !a.internalPubkey && + !(a.witness && a.witness.length > 1) + ) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); @@ -46,13 +59,16 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }); const _witness = lazy.value(() => { - if (!a.witness || !a.witness.length) return - if (a.witness.length >= 2 && (a.witness[a.witness.length - 1][0] === ANNEX_PREFIX)) { + if (!a.witness || !a.witness.length) return; + if ( + a.witness.length >= 2 && + a.witness[a.witness.length - 1][0] === ANNEX_PREFIX + ) { // remove annex, ignored by taproot return a.witness.slice(0, -1); } - return a.witness.slice() - }) + return a.witness.slice(); + }); const network = a.network || BITCOIN_NETWORK; const o: Payment = { name: 'p2tr', network }; @@ -65,19 +81,18 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return bech32m.encode(network.bech32, words); }); - lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return computeMastRoot(a.scriptsTree) - const w = _witness() + if (a.scriptsTree) return computeMastRoot(a.scriptsTree); + const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; const script = w[w.length - 2]; - const tapLeafHash = leafHash(script, leafVersion) - return rootHash(controlBlock, tapLeafHash) + const tapLeafHash = leafHash(script, leafVersion); + return rootHash(controlBlock, tapLeafHash); } - return null + return null; }); lazy.prop(o, 'output', () => { if (!o.pubkey) return; @@ -85,28 +100,28 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; - if (a.output) return a.output.slice(2) + if (a.output) return a.output.slice(2); if (a.address) return _address().data; if (o.internalPubkey) { - const tweakedKey = tweakKey(o.internalPubkey, o.hash) - if (tweakedKey) return tweakedKey.x + const tweakedKey = tweakKey(o.internalPubkey, o.hash); + if (tweakedKey) return tweakedKey.x; } }); lazy.prop(o, 'internalPubkey', () => { if (a.internalPubkey) return a.internalPubkey; - const witness = _witness() + const witness = _witness(); if (witness && witness.length > 1) return witness[witness.length - 1].slice(1, 33); }); lazy.prop(o, 'signature', () => { - if (a.witness?.length !== 1) return; + if (!a.witness || a.witness.length !== 1) return; return a.witness[0]; }); lazy.prop(o, 'input', () => { // todo: not sure }); lazy.prop(o, 'witness', () => { - if (a.witness) return a.witness + if (a.witness) return a.witness; if (!a.signature) return; return [a.signature]; }); @@ -143,26 +158,26 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.internalPubkey) { - const tweakedKey = tweakKey(a.internalPubkey, o.hash) - if (tweakedKey === null) throw new TypeError('Invalid internalPubkey for p2tr'); + const tweakedKey = tweakKey(a.internalPubkey, o.hash); + if (tweakedKey === null) + throw new TypeError('Invalid internalPubkey for p2tr'); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) throw new TypeError('Pubkey mismatch'); else pubkey = tweakedKey.x; } - if (pubkey?.length) { + if (pubkey && pubkey.length) { if (liftX(pubkey) === null) throw new TypeError('Invalid pubkey for p2tr'); } if (a.hash && a.scriptsTree) { - const hash = computeMastRoot(a.scriptsTree) - if (!a.hash.equals(hash)) - throw new TypeError('Hash mismatch'); + const hash = computeMastRoot(a.scriptsTree); + if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } // todo: review cache - const witness = _witness() + const witness = _witness(); if (witness && witness.length) { if (witness.length === 1) { @@ -176,14 +191,22 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { // script path spending const controlBlock = witness[witness.length - 1]; if (controlBlock.length < 33) - throw new TypeError(`The control-block length is too small. Got ${controlBlock.length}, expected min 33.`); + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); if ((controlBlock.length - 33) % 32 !== 0) - throw new TypeError(`The control-block length of ${controlBlock.length} is incorrect!`); + throw new TypeError( + `The control-block length of ${controlBlock.length} is incorrect!`, + ); const m = (controlBlock.length - 33) / 32; if (m > 128) - throw new TypeError(`The script path is too long. Got ${m}, expected max 128.`); + throw new TypeError( + `The script path is too long. Got ${m}, expected max 128.`, + ); const internalPubkey = controlBlock.slice(1, 33); if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) @@ -196,10 +219,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - const tapLeafHash = leafHash(script, leafVersion) - const hash = rootHash(controlBlock, tapLeafHash) + const tapLeafHash = leafHash(script, leafVersion); + const hash = rootHash(controlBlock, tapLeafHash); - const outputKey = tweakKey(internalPubkey, hash) + const outputKey = tweakKey(internalPubkey, hash); if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); @@ -207,12 +230,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (pubkey.length && !pubkey.equals(outputKey.x)) throw new TypeError('Pubkey mismatch for p2tr witness'); - const controlBlockOddParity = (controlBlock[0] & 1) === 1 + const controlBlockOddParity = (controlBlock[0] & 1) === 1; if (outputKey.isOdd !== controlBlockOddParity) - throw new Error('Incorrect parity') - + throw new Error('Incorrect parity'); } - } } diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index 5370e326f..de4083b0e 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -4,18 +4,21 @@ const BN = require('bn.js'); import * as bcrypto from './crypto'; // todo: use varuint-bitcoin?? import * as varuint from 'bip174/src/lib/converter/varint'; -import { TweakedPublicKey, ZERO32, EC_P } from './types' +import { TweakedPublicKey, ZERO32, EC_P } from './types'; // todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. const ecc = require('tiny-secp256k1'); -const LEAF_VERSION_TAPSCRIPT = 0xc0 +const LEAF_VERSION_TAPSCRIPT = 0xc0; const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); const TAP_TWEAK_TAG = NBuffer.from('TapTweak', 'utf8'); // todo: compare buffers dirrectly -const GROUP_ORDER = NBuffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex'); +const GROUP_ORDER = NBuffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex', +); const GROUP_ORDER_BN = new BN(GROUP_ORDER); const EC_P_BN = new BN(EC_P); @@ -26,115 +29,131 @@ const BN_3 = new BN(3); const BN_7 = new BN(7); export function liftX(buffer: Buffer): Buffer | null { - if (!NBuffer.isBuffer(buffer)) return null; - if (buffer.length !== 32) return null; + if (!NBuffer.isBuffer(buffer)) return null; + if (buffer.length !== 32) return null; - if (buffer.compare(ZERO32) === 0) return null; - if (buffer.compare(EC_P) >= 0) return null; + if (buffer.compare(ZERO32) === 0) return null; + if (buffer.compare(EC_P) >= 0) return null; - const x = new BN(buffer); + const x = new BN(buffer); - const x1 = x.toRed(EC_P_REDUCTION); - const ySq = x1 - .redPow(BN_3) - .add(BN_7) - .mod(EC_P_BN); + const x1 = x.toRed(EC_P_REDUCTION); + const ySq = x1 + .redPow(BN_3) + .add(BN_7) + .mod(EC_P_BN); - const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); + const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); - if (!ySq.eq(y.redPow(BN_2))) { - return null; - } - const y1 = y.isEven() ? y : EC_P_BN.sub(y); + if (!ySq.eq(y.redPow(BN_2))) { + return null; + } + const y1 = y.isEven() ? y : EC_P_BN.sub(y); - return NBuffer.concat([ - NBuffer.from([0x04]), - NBuffer.from(x1.toBuffer('be', 32)), - NBuffer.from(y1.toBuffer('be', 32)), - ]); + return NBuffer.concat([ + NBuffer.from([0x04]), + NBuffer.from(x1.toBuffer('be', 32)), + NBuffer.from(y1.toBuffer('be', 32)), + ]); } export function tweakKey( - pubKey: Buffer, - h: Buffer | undefined, + pubKey: Buffer, + h: Buffer | undefined, ): TweakedPublicKey | null { - if (!NBuffer.isBuffer(pubKey)) return null; - if (pubKey.length !== 32) return null; - if (h && h.length !== 32) return null; - - const tweakHash = bcrypto.taggedHash( - TAP_TWEAK_TAG, - NBuffer.concat(h ? [pubKey, h] : [pubKey]), - ); - const t = new BN(tweakHash); - if (t.gte(GROUP_ORDER_BN)) { - // todo: add test for this case - throw new Error('Tweak value over the SECP256K1 Order'); - } - - const P = liftX(pubKey); - if (P === null) return null; - - const Q = pointAddScalar(P, tweakHash); - return { - isOdd: Q[64] % 2 === 1, - x: Q.slice(1, 33), - }; + if (!NBuffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + + const tweakHash = bcrypto.taggedHash( + TAP_TWEAK_TAG, + NBuffer.concat(h ? [pubKey, h] : [pubKey]), + ); + const t = new BN(tweakHash); + if (t.gte(GROUP_ORDER_BN)) { + // todo: add test for this case + throw new Error('Tweak value over the SECP256K1 Order'); + } + + const P = liftX(pubKey); + if (P === null) return null; + + const Q = pointAddScalar(P, tweakHash); + return { + isOdd: Q[64] % 2 === 1, + x: Q.slice(1, 33), + }; } export function leafHash(script: Buffer, version: number): Buffer { - return NBuffer.concat([NBuffer.from([version]), serializeScript(script)]); + return NBuffer.concat([NBuffer.from([version]), serializeScript(script)]); } export function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { - const k = []; - const e = []; - - const m = (controlBlock.length - 33) / 32; - k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); - - for (let j = 0; j < m; j++) { - e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); - if (k[j].compare(e[j]) < 0) { - k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([k[j], e[j]])); - } else { - k[j + 1] = bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([e[j], k[j]])); - } + const k = []; + const e = []; + + const m = (controlBlock.length - 33) / 32; + k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); + + for (let j = 0; j < m; j++) { + e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); + if (k[j].compare(e[j]) < 0) { + k[j + 1] = bcrypto.taggedHash( + TAP_BRANCH_TAG, + NBuffer.concat([k[j], e[j]]), + ); + } else { + k[j + 1] = bcrypto.taggedHash( + TAP_BRANCH_TAG, + NBuffer.concat([e[j], k[j]]), + ); } + } - return k[m] + return k[m]; } // todo: solve any[] export function computeMastRoot(scripts: any): Buffer { - if (scripts.length === 1) { - const script = scripts[0] - if (Array.isArray(script)) { - return computeMastRoot(script) - } - script.version = script.version || LEAF_VERSION_TAPSCRIPT - if ((script.version & 1) !== 0) throw new Error("Invalid script version") // todo typedef error - // todo: if (script.output)scheck is bytes - const scriptOutput = NBuffer.from(script.output, 'hex') - return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([script.version]), serializeScript(scriptOutput)])) + if (scripts.length === 1) { + const script = scripts[0]; + if (Array.isArray(script)) { + return computeMastRoot(script); } - // todo: this is a binary tree, use zero an one index - const half = Math.trunc(scripts.length / 2) - let leftHash = computeMastRoot(scripts.slice(0, half)) - let rightHash = computeMastRoot(scripts.slice(half)) - - if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash] - return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([leftHash, rightHash])) + script.version = script.version || LEAF_VERSION_TAPSCRIPT; + if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error + // todo: if (script.output)scheck is bytes + const scriptOutput = NBuffer.from(script.output, 'hex'); + return bcrypto.taggedHash( + TAP_LEAF_TAG, + NBuffer.concat([ + NBuffer.from([script.version]), + serializeScript(scriptOutput), + ]), + ); + } + // todo: this is a binary tree, use zero an one index + const half = Math.trunc(scripts.length / 2); + let leftHash = computeMastRoot(scripts.slice(0, half)); + let rightHash = computeMastRoot(scripts.slice(half)); + + if (leftHash.compare(rightHash) === 1) + [leftHash, rightHash] = [rightHash, leftHash]; + return bcrypto.taggedHash( + TAP_BRANCH_TAG, + NBuffer.concat([leftHash, rightHash]), + ); } -function serializeScript(s: Buffer) { - const varintLen = varuint.encodingLength(s.length); - const buffer = NBuffer.allocUnsafe(varintLen); // better - varuint.encode(s.length, buffer); - return NBuffer.concat([buffer, s]) +function serializeScript(s: Buffer): Buffer { + const varintLen = varuint.encodingLength(s.length); + const buffer = NBuffer.allocUnsafe(varintLen); // better + varuint.encode(s.length, buffer); + return NBuffer.concat([buffer, s]); } // todo: do not use ecc function pointAddScalar(P: Buffer, h: Buffer): Buffer { - return ecc.pointAddScalar(P, h); + return ecc.pointAddScalar(P, h); } diff --git a/ts_src/types.ts b/ts_src/types.ts index 014bfaafc..fd0893df5 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -74,12 +74,13 @@ export interface TweakedPublicKey { export const TaprootLeaf = typeforce.compile({ output: typeforce.BufferN(34), - version: typeforce.maybe(typeforce.UInt8) // todo: recheck -}) + version: typeforce.maybe(typeforce.UInt8), // todo: recheck +}); // / todo: revisit -export const TaprootNode = typeforce.arrayOf(typeforce.oneOf(TaprootLeaf, typeforce.arrayOf(TaprootLeaf))) - +export const TaprootNode = typeforce.arrayOf( + typeforce.oneOf(TaprootLeaf, typeforce.arrayOf(TaprootLeaf)), +); export const Buffer256bit = typeforce.BufferN(32); export const Hash160bit = typeforce.BufferN(20); From 2feff5dd58014a76ba90022cfb2171e6789d3f70 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 17:49:59 +0200 Subject: [PATCH 21/73] refactor: compare GROUP_ORDER as buffer (instead of using BN.js) --- src/taproot.js | 9 +-------- src/types.d.ts | 1 + src/types.js | 6 +++++- ts_src/taproot.ts | 13 +++---------- ts_src/types.ts | 4 ++++ 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/taproot.js b/src/taproot.js index 4930f4331..f0cade074 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -13,12 +13,6 @@ const LEAF_VERSION_TAPSCRIPT = 0xc0; const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); const TAP_TWEAK_TAG = buffer_1.Buffer.from('TapTweak', 'utf8'); -// todo: compare buffers dirrectly -const GROUP_ORDER = buffer_1.Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', -); -const GROUP_ORDER_BN = new BN(GROUP_ORDER); const EC_P_BN = new BN(types_1.EC_P); const EC_P_REDUCTION = BN.red(EC_P_BN); const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); @@ -56,8 +50,7 @@ function tweakKey(pubKey, h) { TAP_TWEAK_TAG, buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), ); - const t = new BN(tweakHash); - if (t.gte(GROUP_ORDER_BN)) { + if (tweakHash.compare(types_1.GROUP_ORDER) >= 0) { // todo: add test for this case throw new Error('Tweak value over the SECP256K1 Order'); } diff --git a/src/types.d.ts b/src/types.d.ts index 6e72a6d47..7f58a0a87 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -3,6 +3,7 @@ import { Buffer as NBuffer } from 'buffer'; export declare const typeforce: any; export declare const ZERO32: NBuffer; export declare const EC_P: NBuffer; +export declare const GROUP_ORDER: NBuffer; export declare function isPoint(p: Buffer | number | undefined | null): boolean; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; diff --git a/src/types.js b/src/types.js index ba62bd813..ff1786256 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.EC_P = exports.ZERO32 = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.GROUP_ORDER = exports.EC_P = exports.ZERO32 = exports.typeforce = void 0; const buffer_1 = require('buffer'); exports.typeforce = require('typeforce'); exports.ZERO32 = buffer_1.Buffer.alloc(32, 0); @@ -8,6 +8,10 @@ exports.EC_P = buffer_1.Buffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); +exports.GROUP_ORDER = buffer_1.Buffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex', +); function isPoint(p) { if (!buffer_1.Buffer.isBuffer(p)) return false; if (p.length < 33) return false; diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index de4083b0e..b09e17508 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -4,7 +4,7 @@ const BN = require('bn.js'); import * as bcrypto from './crypto'; // todo: use varuint-bitcoin?? import * as varuint from 'bip174/src/lib/converter/varint'; -import { TweakedPublicKey, ZERO32, EC_P } from './types'; +import { TweakedPublicKey, ZERO32, EC_P, GROUP_ORDER } from './types'; // todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. const ecc = require('tiny-secp256k1'); @@ -14,13 +14,6 @@ const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); const TAP_TWEAK_TAG = NBuffer.from('TapTweak', 'utf8'); -// todo: compare buffers dirrectly -const GROUP_ORDER = NBuffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', -); -const GROUP_ORDER_BN = new BN(GROUP_ORDER); - const EC_P_BN = new BN(EC_P); const EC_P_REDUCTION = BN.red(EC_P_BN); const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); @@ -69,8 +62,8 @@ export function tweakKey( TAP_TWEAK_TAG, NBuffer.concat(h ? [pubKey, h] : [pubKey]), ); - const t = new BN(tweakHash); - if (t.gte(GROUP_ORDER_BN)) { + + if (tweakHash.compare(GROUP_ORDER) >= 0) { // todo: add test for this case throw new Error('Tweak value over the SECP256K1 Order'); } diff --git a/ts_src/types.ts b/ts_src/types.ts index fd0893df5..e04a05589 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -7,6 +7,10 @@ export const EC_P = NBuffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); +export const GROUP_ORDER = NBuffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex', +); export function isPoint(p: Buffer | number | undefined | null): boolean { if (!NBuffer.isBuffer(p)) return false; From 2f55aad419f63d289a3319c7eb9116a5b966700f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 09:43:38 +0200 Subject: [PATCH 22/73] refactor: rename `rootHash` to `rootHashFromPath` and `computeMastRoot` to `rootHashFromTree` --- src/payments/p2tr.js | 8 ++++---- src/taproot.d.ts | 4 ++-- src/taproot.js | 16 ++++++++-------- ts_src/payments/p2tr.ts | 13 ++++++------- ts_src/taproot.ts | 10 +++++----- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 22c03b088..50e99536d 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -72,14 +72,14 @@ function p2tr(a, opts) { }); lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return (0, taproot_1.computeMastRoot)(a.scriptsTree); + if (a.scriptsTree) return (0, taproot_1.rootHashFromTree)(a.scriptsTree); const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; const script = w[w.length - 2]; const tapLeafHash = (0, taproot_1.leafHash)(script, leafVersion); - return (0, taproot_1.rootHash)(controlBlock, tapLeafHash); + return (0, taproot_1.rootHashFromPath)(controlBlock, tapLeafHash); } return null; }); @@ -155,7 +155,7 @@ function p2tr(a, opts) { throw new TypeError('Invalid pubkey for p2tr'); } if (a.hash && a.scriptsTree) { - const hash = (0, taproot_1.computeMastRoot)(a.scriptsTree); + const hash = (0, taproot_1.rootHashFromTree)(a.scriptsTree); if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } // todo: review cache @@ -195,7 +195,7 @@ function p2tr(a, opts) { const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; const tapLeafHash = (0, taproot_1.leafHash)(script, leafVersion); - const hash = (0, taproot_1.rootHash)(controlBlock, tapLeafHash); + const hash = (0, taproot_1.rootHashFromPath)(controlBlock, tapLeafHash); const outputKey = (0, taproot_1.tweakKey)(internalPubkey, hash); if (!outputKey) // todo: needs test data diff --git a/src/taproot.d.ts b/src/taproot.d.ts index 0391f6bf1..581c09f13 100644 --- a/src/taproot.d.ts +++ b/src/taproot.d.ts @@ -3,5 +3,5 @@ import { TweakedPublicKey } from './types'; export declare function liftX(buffer: Buffer): Buffer | null; export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; export declare function leafHash(script: Buffer, version: number): Buffer; -export declare function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; -export declare function computeMastRoot(scripts: any): Buffer; +export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; +export declare function rootHashFromTree(scripts: any): Buffer; diff --git a/src/taproot.js b/src/taproot.js index f0cade074..10452a88e 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.computeMastRoot = exports.rootHash = exports.leafHash = exports.tweakKey = exports.liftX = void 0; +exports.rootHashFromTree = exports.rootHashFromPath = exports.leafHash = exports.tweakKey = exports.liftX = void 0; const buffer_1 = require('buffer'); const BN = require('bn.js'); const bcrypto = require('./crypto'); @@ -70,7 +70,7 @@ function leafHash(script, version) { ]); } exports.leafHash = leafHash; -function rootHash(controlBlock, tapLeafMsg) { +function rootHashFromPath(controlBlock, tapLeafMsg) { const k = []; const e = []; const m = (controlBlock.length - 33) / 32; @@ -91,13 +91,13 @@ function rootHash(controlBlock, tapLeafMsg) { } return k[m]; } -exports.rootHash = rootHash; +exports.rootHashFromPath = rootHashFromPath; // todo: solve any[] -function computeMastRoot(scripts) { +function rootHashFromTree(scripts) { if (scripts.length === 1) { const script = scripts[0]; if (Array.isArray(script)) { - return computeMastRoot(script); + return rootHashFromTree(script); } script.version = script.version || LEAF_VERSION_TAPSCRIPT; if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error @@ -113,8 +113,8 @@ function computeMastRoot(scripts) { } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); - let leftHash = computeMastRoot(scripts.slice(0, half)); - let rightHash = computeMastRoot(scripts.slice(half)); + let leftHash = rootHashFromTree(scripts.slice(0, half)); + let rightHash = rootHashFromTree(scripts.slice(half)); if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash]; return bcrypto.taggedHash( @@ -122,7 +122,7 @@ function computeMastRoot(scripts) { buffer_1.Buffer.concat([leftHash, rightHash]), ); } -exports.computeMastRoot = computeMastRoot; +exports.rootHashFromTree = rootHashFromTree; function serializeScript(s) { const varintLen = varuint.encodingLength(s.length); const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 25e52696c..ccbf4790d 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -2,9 +2,9 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; import { typeforce as typef } from '../types'; import { - computeMastRoot, + rootHashFromTree, + rootHashFromPath, leafHash, - rootHash, tweakKey, liftX, } from '../taproot'; @@ -83,14 +83,14 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return computeMastRoot(a.scriptsTree); + if (a.scriptsTree) return rootHashFromTree(a.scriptsTree); const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; const script = w[w.length - 2]; const tapLeafHash = leafHash(script, leafVersion); - return rootHash(controlBlock, tapLeafHash); + return rootHashFromPath(controlBlock, tapLeafHash); } return null; }); @@ -172,11 +172,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.hash && a.scriptsTree) { - const hash = computeMastRoot(a.scriptsTree); + const hash = rootHashFromTree(a.scriptsTree); if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } - // todo: review cache const witness = _witness(); if (witness && witness.length) { @@ -220,7 +219,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const script = witness[witness.length - 2]; const tapLeafHash = leafHash(script, leafVersion); - const hash = rootHash(controlBlock, tapLeafHash); + const hash = rootHashFromPath(controlBlock, tapLeafHash); const outputKey = tweakKey(internalPubkey, hash); if (!outputKey) diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index b09e17508..36213b757 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -82,7 +82,7 @@ export function leafHash(script: Buffer, version: number): Buffer { return NBuffer.concat([NBuffer.from([version]), serializeScript(script)]); } -export function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { +export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { const k = []; const e = []; @@ -108,11 +108,11 @@ export function rootHash(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { } // todo: solve any[] -export function computeMastRoot(scripts: any): Buffer { +export function rootHashFromTree(scripts: any): Buffer { if (scripts.length === 1) { const script = scripts[0]; if (Array.isArray(script)) { - return computeMastRoot(script); + return rootHashFromTree(script); } script.version = script.version || LEAF_VERSION_TAPSCRIPT; if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error @@ -128,8 +128,8 @@ export function computeMastRoot(scripts: any): Buffer { } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); - let leftHash = computeMastRoot(scripts.slice(0, half)); - let rightHash = computeMastRoot(scripts.slice(half)); + let leftHash = rootHashFromTree(scripts.slice(0, half)); + let rightHash = rootHashFromTree(scripts.slice(half)); if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash]; From 2a4e64b691bf1c36895c74945fbb04b2f5ebbfe3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 15:47:49 +0200 Subject: [PATCH 23/73] tests: add bib341 tests by @sipa; plus refactoring --- src/payments/index.d.ts | 2 + src/payments/p2tr.js | 10 ++- src/taproot.d.ts | 8 +- src/taproot.js | 30 +++---- src/types.d.ts | 6 +- src/types.js | 13 +-- test/fixtures/p2tr.json | 176 +++++++++++++++++++++++++++++++++++++++ ts_src/payments/index.ts | 4 +- ts_src/payments/p2tr.ts | 16 +++- ts_src/taproot.ts | 25 ++---- ts_src/types.ts | 13 +-- 11 files changed, 236 insertions(+), 67 deletions(-) diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index dc1978ab0..0170dd093 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -1,5 +1,6 @@ /// import { Network } from '../networks'; +import { TaprootLeaf } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -25,6 +26,7 @@ export interface Payment { hash?: Buffer; redeem?: Payment; scriptsTree?: any; + scriptLeaf?: TaprootLeaf; witness?: Buffer[]; } export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 50e99536d..1d66fb5f7 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -38,6 +38,10 @@ function p2tr(a, opts) { types_1.typeforce.arrayOf(types_1.typeforce.Buffer), ), // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? + scriptLeaf: types_1.typeforce.maybe({ + version: types_1.typeforce.maybe(types_1.typeforce.Number), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + }), }, a, ); @@ -87,6 +91,9 @@ function p2tr(a, opts) { if (!o.pubkey) return; return bscript.compile([OPS.OP_1, o.pubkey]); }); + lazy.prop(o, 'scriptLeaf', () => { + if (!a.scriptLeaf) return a.scriptLeaf; + }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; if (a.output) return a.output.slice(2); @@ -107,7 +114,7 @@ function p2tr(a, opts) { return a.witness[0]; }); lazy.prop(o, 'input', () => { - // todo: not sure + // todo }); lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; @@ -158,7 +165,6 @@ function p2tr(a, opts) { const hash = (0, taproot_1.rootHashFromTree)(a.scriptsTree); if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } - // todo: review cache const witness = _witness(); if (witness && witness.length) { if (witness.length === 1) { diff --git a/src/taproot.d.ts b/src/taproot.d.ts index 581c09f13..39f333958 100644 --- a/src/taproot.d.ts +++ b/src/taproot.d.ts @@ -1,7 +1,11 @@ /// -import { TweakedPublicKey } from './types'; +import { TweakedPublicKey, TaprootLeaf } from './types'; export declare function liftX(buffer: Buffer): Buffer | null; export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; export declare function leafHash(script: Buffer, version: number): Buffer; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; -export declare function rootHashFromTree(scripts: any): Buffer; +export interface HashTree { + rootHash: Buffer; + scritptPath?: Buffer; +} +export declare function rootHashFromTree(scripts: TaprootLeaf[]): Buffer; diff --git a/src/taproot.js b/src/taproot.js index 10452a88e..e749d237e 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -52,7 +52,7 @@ function tweakKey(pubKey, h) { ); if (tweakHash.compare(types_1.GROUP_ORDER) >= 0) { // todo: add test for this case - throw new Error('Tweak value over the SECP256K1 Order'); + throw new TypeError('Tweak value over the SECP256K1 Order'); } const P = liftX(pubKey); if (P === null) return null; @@ -64,17 +64,19 @@ function tweakKey(pubKey, h) { } exports.tweakKey = tweakKey; function leafHash(script, version) { - return buffer_1.Buffer.concat([ - buffer_1.Buffer.from([version]), - serializeScript(script), - ]); + return bcrypto.taggedHash( + TAP_LEAF_TAG, + buffer_1.Buffer.concat([ + buffer_1.Buffer.from([version]), + serializeScript(script), + ]), + ); } exports.leafHash = leafHash; function rootHashFromPath(controlBlock, tapLeafMsg) { - const k = []; + const k = [tapLeafMsg]; const e = []; const m = (controlBlock.length - 33) / 32; - k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); for (let j = 0; j < m; j++) { e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); if (k[j].compare(e[j]) < 0) { @@ -92,7 +94,6 @@ function rootHashFromPath(controlBlock, tapLeafMsg) { return k[m]; } exports.rootHashFromPath = rootHashFromPath; -// todo: solve any[] function rootHashFromTree(scripts) { if (scripts.length === 1) { const script = scripts[0]; @@ -100,16 +101,9 @@ function rootHashFromTree(scripts) { return rootHashFromTree(script); } script.version = script.version || LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error - // todo: if (script.output)scheck is bytes - const scriptOutput = buffer_1.Buffer.from(script.output, 'hex'); - return bcrypto.taggedHash( - TAP_LEAF_TAG, - buffer_1.Buffer.concat([ - buffer_1.Buffer.from([script.version]), - serializeScript(scriptOutput), - ]), - ); + if ((script.version & 1) !== 0) + throw new TypeError('Invalid script version'); + return leafHash(script.output, script.version); } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); diff --git a/src/types.d.ts b/src/types.d.ts index 7f58a0a87..b33872800 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -18,8 +18,10 @@ export interface TweakedPublicKey { isOdd: boolean; x: Buffer; } -export declare const TaprootLeaf: any; -export declare const TaprootNode: any; +export interface TaprootLeaf { + output: Buffer; + version?: number; +} export declare const Buffer256bit: any; export declare const Hash160bit: any; export declare const Hash256bit: any; diff --git a/src/types.js b/src/types.js index ff1786256..a8acbef86 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.TaprootNode = exports.TaprootLeaf = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.GROUP_ORDER = exports.EC_P = exports.ZERO32 = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.GROUP_ORDER = exports.EC_P = exports.ZERO32 = exports.typeforce = void 0; const buffer_1 = require('buffer'); exports.typeforce = require('typeforce'); exports.ZERO32 = buffer_1.Buffer.alloc(32, 0); @@ -72,17 +72,6 @@ exports.Network = exports.typeforce.compile({ scriptHash: exports.typeforce.UInt8, wif: exports.typeforce.UInt8, }); -exports.TaprootLeaf = exports.typeforce.compile({ - output: exports.typeforce.BufferN(34), - version: exports.typeforce.maybe(exports.typeforce.UInt8), // todo: recheck -}); -// / todo: revisit -exports.TaprootNode = exports.typeforce.arrayOf( - exports.typeforce.oneOf( - exports.TaprootLeaf, - exports.typeforce.arrayOf(exports.TaprootLeaf), - ), -); exports.Buffer256bit = exports.typeforce.BufferN(32); exports.Hash160bit = exports.typeforce.BufferN(20); exports.Hash256bit = exports.typeforce.BufferN(32); diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 24cb6472f..cff1fe28f 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -354,6 +354,182 @@ "input": null, "witness": null } + }, + { + "description": "BIP341 Test case 1", + "arguments": { + "internalPubkey": "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d" + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + "pubkey": "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + "address": "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "BIP341 Test case 2", + "arguments": { + "internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + "scriptsTree": [ + { + "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", + "version": 192 + } + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + "pubkey": "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + "address": "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", + "hash": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 3", + "arguments": { + "internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + "scriptsTree": [ + { + "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", + "version": 192 + } + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + "pubkey": "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + "address": "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", + "hash": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 4", + "arguments": { + "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + "scriptsTree": [ + { + "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", + "version": 192 + }, + { + "output": "424950333431", + "version": 152 + } + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561", + "pubkey": "0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561", + "address": "bc1ppa3u5trk8xumkjlqgewvp237u79qwcd6ta0h6mlca2e5puya54ssw9zq0y", + "hash": "f3004d6c183e038105d436db1424f321613366cbb7b05939bf05d763a9ebb962", + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 5", + "arguments": { + "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + "scriptsTree": [ + { + "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", + "version": 192 + }, + { + "output": "546170726f6f74", + "version": 82 + } + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587", + "pubkey": "053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587", + "address": "bc1pq5mfpw474wahs5xr9m4dpt8cm7vsemte7733udv040extz6tckrs29g04c", + "hash": "d9c2c32808b41c0301d876d49c0af72e1d98e84b99ca9b4bb67fea1a7424b755", + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 6", + "arguments": { + "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + "scriptsTree": [ + { + "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", + "version": 192 + }, + [ + { + "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", + "version": 192 + }, + { + "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", + "version": 192 + } + ] + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + "pubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + "address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + "hash": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 7", + "arguments": { + "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + "scriptsTree": [ + { + "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", + "version": 192 + }, + [ + { + "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", + "version": 192 + }, + { + "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", + "version": 192 + } + ] + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + "pubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + "address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + "hash": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + "signature": null, + "input": null + } } ], "invalid": [ diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index f6223a3d0..bcf09e2a6 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,4 +1,5 @@ import { Network } from '../networks'; +import { TaprootLeaf } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -23,8 +24,9 @@ export interface Payment { signature?: Buffer; address?: string; // taproot: betch32m hash?: Buffer; // taproot: MAST root - redeem?: Payment; // taproot: when script path spending is used spending + redeem?: Payment; scriptsTree?: any; // todo: solve + scriptLeaf?: TaprootLeaf; witness?: Buffer[]; } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index ccbf4790d..eb6d7a443 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -2,7 +2,7 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; import { typeforce as typef } from '../types'; import { - rootHashFromTree, + rootHashFromTree, rootHashFromPath, leafHash, tweakKey, @@ -42,7 +42,12 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { pubkey: typef.maybe(typef.BufferN(32)), signature: typef.maybe(bscript.isCanonicalScriptSignature), witness: typef.maybe(typef.arrayOf(typef.Buffer)), + // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? + scriptLeaf: typef.maybe({ + version: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), + }), }, a, ); @@ -98,6 +103,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (!o.pubkey) return; return bscript.compile([OPS.OP_1, o.pubkey]); }); + lazy.prop(o, 'scriptLeaf', () => { + if (!a.scriptLeaf) return a.scriptLeaf; + + }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; if (a.output) return a.output.slice(2); @@ -118,7 +127,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return a.witness[0]; }); lazy.prop(o, 'input', () => { - // todo: not sure + // todo }); lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; @@ -191,8 +200,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const controlBlock = witness[witness.length - 1]; if (controlBlock.length < 33) throw new TypeError( - `The control-block length is too small. Got ${ - controlBlock.length + `The control-block length is too small. Got ${controlBlock.length }, expected min 33.`, ); diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index 36213b757..0df09e833 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -4,7 +4,7 @@ const BN = require('bn.js'); import * as bcrypto from './crypto'; // todo: use varuint-bitcoin?? import * as varuint from 'bip174/src/lib/converter/varint'; -import { TweakedPublicKey, ZERO32, EC_P, GROUP_ORDER } from './types'; +import { TweakedPublicKey, TaprootLeaf, ZERO32, EC_P, GROUP_ORDER } from './types'; // todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. const ecc = require('tiny-secp256k1'); @@ -65,7 +65,7 @@ export function tweakKey( if (tweakHash.compare(GROUP_ORDER) >= 0) { // todo: add test for this case - throw new Error('Tweak value over the SECP256K1 Order'); + throw new TypeError('Tweak value over the SECP256K1 Order'); } const P = liftX(pubKey); @@ -79,15 +79,14 @@ export function tweakKey( } export function leafHash(script: Buffer, version: number): Buffer { - return NBuffer.concat([NBuffer.from([version]), serializeScript(script)]); + return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([version]), serializeScript(script)])); } export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { - const k = []; + const k = [tapLeafMsg]; const e = []; const m = (controlBlock.length - 33) / 32; - k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); for (let j = 0; j < m; j++) { e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); @@ -107,24 +106,16 @@ export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buff return k[m]; } -// todo: solve any[] -export function rootHashFromTree(scripts: any): Buffer { +export function rootHashFromTree(scripts: TaprootLeaf[]): Buffer { if (scripts.length === 1) { const script = scripts[0]; if (Array.isArray(script)) { return rootHashFromTree(script); } script.version = script.version || LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error - // todo: if (script.output)scheck is bytes - const scriptOutput = NBuffer.from(script.output, 'hex'); - return bcrypto.taggedHash( - TAP_LEAF_TAG, - NBuffer.concat([ - NBuffer.from([script.version]), - serializeScript(scriptOutput), - ]), - ); + if ((script.version & 1) !== 0) throw new TypeError('Invalid script version'); + + return leafHash(script.output, script.version); } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); diff --git a/ts_src/types.ts b/ts_src/types.ts index e04a05589..b22ab7261 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -76,15 +76,10 @@ export interface TweakedPublicKey { x: Buffer; } -export const TaprootLeaf = typeforce.compile({ - output: typeforce.BufferN(34), - version: typeforce.maybe(typeforce.UInt8), // todo: recheck -}); - -// / todo: revisit -export const TaprootNode = typeforce.arrayOf( - typeforce.oneOf(TaprootLeaf, typeforce.arrayOf(TaprootLeaf)), -); +export interface TaprootLeaf { + output: Buffer; + version?: number; +} export const Buffer256bit = typeforce.BufferN(32); export const Hash160bit = typeforce.BufferN(20); From f7d01b8c54e0aba55351bfbc5e780e5f471a6446 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 16:10:00 +0200 Subject: [PATCH 24/73] refactor: extract `tapBranchHash()` rename `leafHash()` to `tapBranchHash()` --- src/payments/p2tr.js | 8 ++++---- src/taproot.d.ts | 6 +----- src/taproot.js | 41 ++++++++++++++++++----------------------- ts_src/payments/p2tr.ts | 10 +++++----- ts_src/taproot.ts | 30 +++++++++++++----------------- 5 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 1d66fb5f7..17b8b404a 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -82,8 +82,8 @@ function p2tr(a, opts) { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; const script = w[w.length - 2]; - const tapLeafHash = (0, taproot_1.leafHash)(script, leafVersion); - return (0, taproot_1.rootHashFromPath)(controlBlock, tapLeafHash); + const leafHash = (0, taproot_1.tapLeafHash)(script, leafVersion); + return (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); } return null; }); @@ -200,8 +200,8 @@ function p2tr(a, opts) { throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - const tapLeafHash = (0, taproot_1.leafHash)(script, leafVersion); - const hash = (0, taproot_1.rootHashFromPath)(controlBlock, tapLeafHash); + const leafHash = (0, taproot_1.tapLeafHash)(script, leafVersion); + const hash = (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); const outputKey = (0, taproot_1.tweakKey)(internalPubkey, hash); if (!outputKey) // todo: needs test data diff --git a/src/taproot.d.ts b/src/taproot.d.ts index 39f333958..a552b95b0 100644 --- a/src/taproot.d.ts +++ b/src/taproot.d.ts @@ -2,10 +2,6 @@ import { TweakedPublicKey, TaprootLeaf } from './types'; export declare function liftX(buffer: Buffer): Buffer | null; export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; -export declare function leafHash(script: Buffer, version: number): Buffer; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; -export interface HashTree { - rootHash: Buffer; - scritptPath?: Buffer; -} export declare function rootHashFromTree(scripts: TaprootLeaf[]): Buffer; +export declare function tapLeafHash(script: Buffer, version: number): Buffer; diff --git a/src/taproot.js b/src/taproot.js index e749d237e..7cc6ab850 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.rootHashFromTree = exports.rootHashFromPath = exports.leafHash = exports.tweakKey = exports.liftX = void 0; +exports.tapLeafHash = exports.rootHashFromTree = exports.rootHashFromPath = exports.tweakKey = exports.liftX = void 0; const buffer_1 = require('buffer'); const BN = require('bn.js'); const bcrypto = require('./crypto'); @@ -63,16 +63,6 @@ function tweakKey(pubKey, h) { }; } exports.tweakKey = tweakKey; -function leafHash(script, version) { - return bcrypto.taggedHash( - TAP_LEAF_TAG, - buffer_1.Buffer.concat([ - buffer_1.Buffer.from([version]), - serializeScript(script), - ]), - ); -} -exports.leafHash = leafHash; function rootHashFromPath(controlBlock, tapLeafMsg) { const k = [tapLeafMsg]; const e = []; @@ -80,15 +70,9 @@ function rootHashFromPath(controlBlock, tapLeafMsg) { for (let j = 0; j < m; j++) { e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); if (k[j].compare(e[j]) < 0) { - k[j + 1] = bcrypto.taggedHash( - TAP_BRANCH_TAG, - buffer_1.Buffer.concat([k[j], e[j]]), - ); + k[j + 1] = tapBranchHash(k[j], e[j]); } else { - k[j + 1] = bcrypto.taggedHash( - TAP_BRANCH_TAG, - buffer_1.Buffer.concat([e[j], k[j]]), - ); + k[j + 1] = tapBranchHash(e[j], k[j]); } } return k[m]; @@ -103,7 +87,7 @@ function rootHashFromTree(scripts) { script.version = script.version || LEAF_VERSION_TAPSCRIPT; if ((script.version & 1) !== 0) throw new TypeError('Invalid script version'); - return leafHash(script.output, script.version); + return tapLeafHash(script.output, script.version); } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); @@ -111,12 +95,23 @@ function rootHashFromTree(scripts) { let rightHash = rootHashFromTree(scripts.slice(half)); if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash]; + return tapBranchHash(leftHash, rightHash); +} +exports.rootHashFromTree = rootHashFromTree; +// todo: rename to tapLeafHash +function tapLeafHash(script, version) { return bcrypto.taggedHash( - TAP_BRANCH_TAG, - buffer_1.Buffer.concat([leftHash, rightHash]), + TAP_LEAF_TAG, + buffer_1.Buffer.concat([ + buffer_1.Buffer.from([version]), + serializeScript(script), + ]), ); } -exports.rootHashFromTree = rootHashFromTree; +exports.tapLeafHash = tapLeafHash; +function tapBranchHash(a, b) { + return bcrypto.taggedHash(TAP_BRANCH_TAG, buffer_1.Buffer.concat([a, b])); +} function serializeScript(s) { const varintLen = varuint.encodingLength(s.length); const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index eb6d7a443..8a79c10f7 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -4,7 +4,7 @@ import { typeforce as typef } from '../types'; import { rootHashFromTree, rootHashFromPath, - leafHash, + tapLeafHash, tweakKey, liftX, } from '../taproot'; @@ -94,8 +94,8 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; const script = w[w.length - 2]; - const tapLeafHash = leafHash(script, leafVersion); - return rootHashFromPath(controlBlock, tapLeafHash); + const leafHash = tapLeafHash(script, leafVersion); + return rootHashFromPath(controlBlock, leafHash); } return null; }); @@ -226,8 +226,8 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - const tapLeafHash = leafHash(script, leafVersion); - const hash = rootHashFromPath(controlBlock, tapLeafHash); + const leafHash = tapLeafHash(script, leafVersion); + const hash = rootHashFromPath(controlBlock, leafHash); const outputKey = tweakKey(internalPubkey, hash); if (!outputKey) diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index 0df09e833..6c2477895 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -78,10 +78,6 @@ export function tweakKey( }; } -export function leafHash(script: Buffer, version: number): Buffer { - return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([version]), serializeScript(script)])); -} - export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { const k = [tapLeafMsg]; const e = []; @@ -91,15 +87,9 @@ export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buff for (let j = 0; j < m; j++) { e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); if (k[j].compare(e[j]) < 0) { - k[j + 1] = bcrypto.taggedHash( - TAP_BRANCH_TAG, - NBuffer.concat([k[j], e[j]]), - ); + k[j + 1] = tapBranchHash(k[j], e[j]); } else { - k[j + 1] = bcrypto.taggedHash( - TAP_BRANCH_TAG, - NBuffer.concat([e[j], k[j]]), - ); + k[j + 1] = tapBranchHash(e[j], k[j]); } } @@ -115,7 +105,7 @@ export function rootHashFromTree(scripts: TaprootLeaf[]): Buffer { script.version = script.version || LEAF_VERSION_TAPSCRIPT; if ((script.version & 1) !== 0) throw new TypeError('Invalid script version'); - return leafHash(script.output, script.version); + return tapLeafHash(script.output, script.version); } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); @@ -124,10 +114,16 @@ export function rootHashFromTree(scripts: TaprootLeaf[]): Buffer { if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash]; - return bcrypto.taggedHash( - TAP_BRANCH_TAG, - NBuffer.concat([leftHash, rightHash]), - ); + return tapBranchHash(leftHash, rightHash); +} + +// todo: rename to tapLeafHash +export function tapLeafHash(script: Buffer, version: number): Buffer { + return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([version]), serializeScript(script)])); +} + +function tapBranchHash(a: Buffer, b: Buffer): Buffer { + return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b]), ); } function serializeScript(s: Buffer): Buffer { From a3550c1e6551d5441b42e389f810429492ace362 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 19:15:08 +0200 Subject: [PATCH 25/73] feat: build control-block as part of witness; update tests --- src/payments/p2tr.js | 36 +++-- src/taproot.d.ts | 10 +- src/taproot.js | 45 +++++-- src/types.d.ts | 2 +- test/fixtures/p2tr.json | 284 +++++++++++++++++++++++++++++++++++++++- test/payments.utils.ts | 5 + ts_src/payments/p2tr.ts | 30 +++-- ts_src/taproot.ts | 58 ++++++-- ts_src/types.ts | 2 +- 9 files changed, 426 insertions(+), 46 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 17b8b404a..8e48aea87 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -1,6 +1,7 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.p2tr = void 0; +const buffer_1 = require('buffer'); const networks_1 = require('../networks'); const bscript = require('../script'); const types_1 = require('../types'); @@ -52,7 +53,7 @@ function p2tr(a, opts) { return { version, prefix: result.prefix, - data: Buffer.from(data), + data: buffer_1.Buffer.from(data), }; }); const _witness = lazy.value(() => { @@ -76,7 +77,7 @@ function p2tr(a, opts) { }); lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return (0, taproot_1.rootHashFromTree)(a.scriptsTree); + if (a.scriptsTree) return (0, taproot_1.toHashTree)(a.scriptsTree).hash; const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; @@ -118,12 +119,32 @@ function p2tr(a, opts) { }); lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; - if (!a.signature) return; - return [a.signature]; + if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + const hashTree = (0, taproot_1.toHashTree)(a.scriptsTree); + const leafHash = (0, taproot_1.tapLeafHash)( + a.scriptLeaf.output, + a.scriptLeaf.version, + ); + const path = (0, taproot_1.findScriptPath)(hashTree, leafHash); + const outputKey = (0, taproot_1.tweakKey)( + a.internalPubkey, + hashTree.hash, + ); + if (!outputKey) return; + const version = a.scriptLeaf.version || 0xc0; + const controlBock = buffer_1.Buffer.concat( + [ + buffer_1.Buffer.from([version | outputKey.parity]), + a.internalPubkey, + ].concat(path.reverse()), + ); + return [a.scriptLeaf.output, controlBock]; + } + if (a.signature) return [a.signature]; }); // extended validation if (opts.validate) { - let pubkey = Buffer.from([]); + let pubkey = buffer_1.Buffer.from([]); if (a.address) { if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch'); @@ -162,7 +183,7 @@ function p2tr(a, opts) { throw new TypeError('Invalid pubkey for p2tr'); } if (a.hash && a.scriptsTree) { - const hash = (0, taproot_1.rootHashFromTree)(a.scriptsTree); + const hash = (0, taproot_1.toHashTree)(a.scriptsTree).hash; if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } const witness = _witness(); @@ -208,8 +229,7 @@ function p2tr(a, opts) { throw new TypeError('Invalid outputKey for p2tr witness'); if (pubkey.length && !pubkey.equals(outputKey.x)) throw new TypeError('Pubkey mismatch for p2tr witness'); - const controlBlockOddParity = (controlBlock[0] & 1) === 1; - if (outputKey.isOdd !== controlBlockOddParity) + if (outputKey.parity !== (controlBlock[0] & 1)) throw new Error('Incorrect parity'); } } diff --git a/src/taproot.d.ts b/src/taproot.d.ts index a552b95b0..c55f10d72 100644 --- a/src/taproot.d.ts +++ b/src/taproot.d.ts @@ -3,5 +3,11 @@ import { TweakedPublicKey, TaprootLeaf } from './types'; export declare function liftX(buffer: Buffer): Buffer | null; export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; -export declare function rootHashFromTree(scripts: TaprootLeaf[]): Buffer; -export declare function tapLeafHash(script: Buffer, version: number): Buffer; +export interface HashTree { + hash: Buffer; + left?: HashTree; + right?: HashTree; +} +export declare function toHashTree(scripts: TaprootLeaf[]): HashTree; +export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[]; +export declare function tapLeafHash(script: Buffer, version?: number): Buffer; diff --git a/src/taproot.js b/src/taproot.js index 7cc6ab850..d11e77799 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapLeafHash = exports.rootHashFromTree = exports.rootHashFromPath = exports.tweakKey = exports.liftX = void 0; +exports.tapLeafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.tweakKey = exports.liftX = void 0; const buffer_1 = require('buffer'); const BN = require('bn.js'); const bcrypto = require('./crypto'); @@ -58,7 +58,7 @@ function tweakKey(pubKey, h) { if (P === null) return null; const Q = pointAddScalar(P, tweakHash); return { - isOdd: Q[64] % 2 === 1, + parity: Q[64] % 2, x: Q.slice(1, 33), }; } @@ -78,28 +78,53 @@ function rootHashFromPath(controlBlock, tapLeafMsg) { return k[m]; } exports.rootHashFromPath = rootHashFromPath; -function rootHashFromTree(scripts) { +function toHashTree(scripts) { if (scripts.length === 1) { const script = scripts[0]; if (Array.isArray(script)) { - return rootHashFromTree(script); + return toHashTree(script); } script.version = script.version || LEAF_VERSION_TAPSCRIPT; if ((script.version & 1) !== 0) throw new TypeError('Invalid script version'); - return tapLeafHash(script.output, script.version); + return { + hash: tapLeafHash(script.output, script.version), + }; } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); - let leftHash = rootHashFromTree(scripts.slice(0, half)); - let rightHash = rootHashFromTree(scripts.slice(half)); + const left = toHashTree(scripts.slice(0, half)); + const right = toHashTree(scripts.slice(half)); + let leftHash = left.hash; + let rightHash = right.hash; if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash]; - return tapBranchHash(leftHash, rightHash); + return { + hash: tapBranchHash(leftHash, rightHash), + left, + right, + }; +} +exports.toHashTree = toHashTree; +function findScriptPath(node, hash) { + if (node.left) { + if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : []; + const leftPath = findScriptPath(node.left, hash); + if (leftPath.length) + return node.right ? [node.right.hash].concat(leftPath) : leftPath; + } + if (node.right) { + if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : []; + const rightPath = findScriptPath(node.right, hash); + if (rightPath.length) { + } + return node.left ? [node.left.hash].concat(rightPath) : rightPath; + } + return []; } -exports.rootHashFromTree = rootHashFromTree; -// todo: rename to tapLeafHash +exports.findScriptPath = findScriptPath; function tapLeafHash(script, version) { + version = version || LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( TAP_LEAF_TAG, buffer_1.Buffer.concat([ diff --git a/src/types.d.ts b/src/types.d.ts index b33872800..1bcb12426 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -15,7 +15,7 @@ export declare function Satoshi(value: number): boolean; export declare const ECPoint: any; export declare const Network: any; export interface TweakedPublicKey { - isOdd: boolean; + parity: number; x: Buffer; } export interface TaprootLeaf { diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index cff1fe28f..1ef6ed62d 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -375,6 +375,10 @@ "description": "BIP341 Test case 2", "arguments": { "internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + "scriptLeaf": { + "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", + "version": 192 + }, "scriptsTree": [ { "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", @@ -389,6 +393,10 @@ "pubkey": "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", "address": "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", "hash": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + "witness": [ + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ], "signature": null, "input": null } @@ -397,6 +405,10 @@ "description": "BIP341 Test case 3", "arguments": { "internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + "scriptLeaf": { + "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", + "version": 192 + }, "scriptsTree": [ { "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", @@ -411,14 +423,56 @@ "pubkey": "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", "address": "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", "hash": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + "witness": [ + "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac", + "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" + ], + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 4 - spend leaf 0", + "arguments": { + "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + "scriptLeaf": { + "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", + "version": 192 + }, + "scriptsTree": [ + { + "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", + "version": 192 + }, + { + "output": "424950333431", + "version": 152 + } + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561", + "pubkey": "0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561", + "address": "bc1ppa3u5trk8xumkjlqgewvp237u79qwcd6ta0h6mlca2e5puya54ssw9zq0y", + "hash": "f3004d6c183e038105d436db1424f321613366cbb7b05939bf05d763a9ebb962", + "witness": [ + "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac", + "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865927b2c2af8aa3e8b7bfe2f62a155f91427489c5c3b32be47e0b3fac755fc780e0e" + ], "signature": null, "input": null } }, { - "description": "BIP341 Test case 4", + "description": "BIP341 Test case 4 - spend leaf 1", "arguments": { "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + "scriptLeaf": { + "output": "424950333431 OP_CHECKSIG", + "version": 152 + }, "scriptsTree": [ { "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", @@ -437,14 +491,22 @@ "pubkey": "0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561", "address": "bc1ppa3u5trk8xumkjlqgewvp237u79qwcd6ta0h6mlca2e5puya54ssw9zq0y", "hash": "f3004d6c183e038105d436db1424f321613366cbb7b05939bf05d763a9ebb962", + "witness": [ + "06424950333431ac", + "98ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" + ], "signature": null, "input": null } }, { - "description": "BIP341 Test case 5", + "description": "BIP341 Test case 5 - spend leaf 0", "arguments": { "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + "scriptLeaf": { + "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", + "version": 192 + }, "scriptsTree": [ { "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", @@ -463,14 +525,136 @@ "pubkey": "053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587", "address": "bc1pq5mfpw474wahs5xr9m4dpt8cm7vsemte7733udv040extz6tckrs29g04c", "hash": "d9c2c32808b41c0301d876d49c0af72e1d98e84b99ca9b4bb67fea1a7424b755", + "witness": [ + "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac", + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8e44d5f8fa5892c8b6d4d09a08d36edd0b08636e30311302e2448ad8172fb3433" + ], + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 5 - spend leaf 1", + "arguments": { + "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + "scriptLeaf": { + "output": "546170726f6f74", + "version": 82 + }, + "scriptsTree": [ + { + "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", + "version": 192 + }, + { + "output": "546170726f6f74", + "version": 82 + } + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587", + "pubkey": "053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587", + "address": "bc1pq5mfpw474wahs5xr9m4dpt8cm7vsemte7733udv040extz6tckrs29g04c", + "hash": "d9c2c32808b41c0301d876d49c0af72e1d98e84b99ca9b4bb67fea1a7424b755", + "witness": [ + "07546170726f6f74", + "53f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" + ], + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 6 - spend leaf 0", + "arguments": { + "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + "scriptLeaf": { + "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", + "version": 192 + }, + "scriptsTree": [ + { + "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", + "version": 192 + }, + [ + { + "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", + "version": 192 + }, + { + "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", + "version": 192 + } + ] + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + "pubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + "address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + "hash": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + "witness": [ + "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553" + ], + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 6 - spend leaf 1", + "arguments": { + "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + "scriptLeaf": { + "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", + "version": 192 + }, + "scriptsTree": [ + { + "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", + "version": 192 + }, + [ + { + "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", + "version": 192 + }, + { + "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", + "version": 192 + } + ] + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + "pubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + "address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + "hash": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + "witness": [ + "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ], "signature": null, "input": null } }, { - "description": "BIP341 Test case 6", + "description": "BIP341 Test case 6 - spend leaf 2", "arguments": { "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + "scriptLeaf": { + "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", + "version": 192 + }, "scriptsTree": [ { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", @@ -495,14 +679,22 @@ "pubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", "address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", "hash": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + "witness": [ + "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ], "signature": null, "input": null } }, { - "description": "BIP341 Test case 7", + "description": "BIP341 Test case 7 - spend leaf 0", "arguments": { "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + "scriptLeaf": { + "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", + "version": 192 + }, "scriptsTree": [ { "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", @@ -527,6 +719,90 @@ "pubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", "address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", "hash": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + "witness": [ + "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91" + ], + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 7 - spend leaf 1", + "arguments": { + "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + "scriptLeaf": { + "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", + "version": 192 + }, + "scriptsTree": [ + { + "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", + "version": 192 + }, + [ + { + "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", + "version": 192 + }, + { + "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", + "version": 192 + } + ] + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + "pubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + "address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + "hash": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + "witness": [ + "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ], + "signature": null, + "input": null + } + }, + { + "description": "BIP341 Test case 7 - spend leaf 2", + "arguments": { + "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + "scriptLeaf": { + "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", + "version": 192 + }, + "scriptsTree": [ + { + "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", + "version": 192 + }, + [ + { + "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", + "version": 192 + }, + { + "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", + "version": 192 + } + ] + ] + }, + "options": {}, + "expected": { + "name": "p2tr", + "output": "OP_1 75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + "pubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + "address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + "hash": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + "witness": [ + "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ], "signature": null, "input": null } diff --git a/test/payments.utils.ts b/test/payments.utils.ts index 1aa401830..d4aee8374 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -153,6 +153,11 @@ export function preform(x: any): any { if (x.redeem.network) x.redeem.network = (BNETWORKS as any)[x.redeem.network]; } + if (x.scriptLeaf) { + x.scriptLeaf = Object.assign({}, x.scriptLeaf); + if (typeof x.scriptLeaf.output === 'string') + x.scriptLeaf.output = asmToBuffer(x.scriptLeaf.output); + } if (x.scriptsTree) x.scriptsTree = convertScriptsTree(x.scriptsTree); return x; } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 8a79c10f7..476ffadbc 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,9 +1,11 @@ +import { Buffer as NBuffer } from 'buffer'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; import { typeforce as typef } from '../types'; import { - rootHashFromTree, + toHashTree, rootHashFromPath, + findScriptPath, tapLeafHash, tweakKey, liftX, @@ -59,7 +61,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return { version, prefix: result.prefix, - data: Buffer.from(data), + data: NBuffer.from(data), }; }); @@ -88,7 +90,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return rootHashFromTree(a.scriptsTree); + if (a.scriptsTree) return toHashTree(a.scriptsTree).hash; const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; @@ -105,7 +107,6 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'scriptLeaf', () => { if (!a.scriptLeaf) return a.scriptLeaf; - }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; @@ -131,13 +132,23 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; - if (!a.signature) return; - return [a.signature]; + if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + // todo: optimize/cache + const hashTree = toHashTree(a.scriptsTree) + const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version) + const path = findScriptPath(hashTree, leafHash) + const outputKey = tweakKey(a.internalPubkey, hashTree.hash); + if (!outputKey) return + const version = a.scriptLeaf.version || 0xc0 + const controlBock = NBuffer.concat([NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat(path.reverse())) + return [a.scriptLeaf.output, controlBock] + } + if (a.signature) return [a.signature]; }); // extended validation if (opts.validate) { - let pubkey: Buffer = Buffer.from([]); + let pubkey: Buffer = NBuffer.from([]); if (a.address) { if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch'); @@ -181,7 +192,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.hash && a.scriptsTree) { - const hash = rootHashFromTree(a.scriptsTree); + const hash = toHashTree(a.scriptsTree).hash; if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } @@ -237,8 +248,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (pubkey.length && !pubkey.equals(outputKey.x)) throw new TypeError('Pubkey mismatch for p2tr witness'); - const controlBlockOddParity = (controlBlock[0] & 1) === 1; - if (outputKey.isOdd !== controlBlockOddParity) + if (outputKey.parity !== (controlBlock[0] & 1)) throw new Error('Incorrect parity'); } } diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index 6c2477895..e1bb718db 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -73,7 +73,7 @@ export function tweakKey( const Q = pointAddScalar(P, tweakHash); return { - isOdd: Q[64] % 2 === 1, + parity: Q[64] % 2, x: Q.slice(1, 33), }; } @@ -96,34 +96,72 @@ export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buff return k[m]; } -export function rootHashFromTree(scripts: TaprootLeaf[]): Buffer { +export interface HashTree { + hash: Buffer + left?: HashTree + right?: HashTree +} + + +export function toHashTree(scripts: TaprootLeaf[]): HashTree { if (scripts.length === 1) { const script = scripts[0]; if (Array.isArray(script)) { - return rootHashFromTree(script); + return toHashTree(script); } script.version = script.version || LEAF_VERSION_TAPSCRIPT; if ((script.version & 1) !== 0) throw new TypeError('Invalid script version'); - return tapLeafHash(script.output, script.version); + return { + hash: tapLeafHash(script.output, script.version) + } + } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); - let leftHash = rootHashFromTree(scripts.slice(0, half)); - let rightHash = rootHashFromTree(scripts.slice(half)); + const left = toHashTree(scripts.slice(0, half)); + const right = toHashTree(scripts.slice(half)); + + let leftHash = left.hash; + let rightHash = right.hash; if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash]; - return tapBranchHash(leftHash, rightHash); + return { + hash: tapBranchHash(leftHash, rightHash), + left, + right + } +} + +export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] { + if (node.left) { + if (node.left.hash.equals(hash)) + return node.right ? [node.right.hash] : [] + const leftPath = findScriptPath(node.left, hash) + if (leftPath.length) + return node.right ? [node.right.hash].concat(leftPath) : leftPath + } + + if (node.right) { + if (node.right.hash.equals(hash)) + return node.left ? [node.left.hash] : [] + const rightPath = findScriptPath(node.right, hash) + if (rightPath.length) {} + return node.left ? [node.left.hash].concat(rightPath) : rightPath + } + + return [] + } -// todo: rename to tapLeafHash -export function tapLeafHash(script: Buffer, version: number): Buffer { +export function tapLeafHash(script: Buffer, version?: number): Buffer { + version = version || LEAF_VERSION_TAPSCRIPT return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([version]), serializeScript(script)])); } function tapBranchHash(a: Buffer, b: Buffer): Buffer { - return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b]), ); + return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b]),); } function serializeScript(s: Buffer): Buffer { diff --git a/ts_src/types.ts b/ts_src/types.ts index b22ab7261..7fd2452f1 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -72,7 +72,7 @@ export const Network = typeforce.compile({ }); export interface TweakedPublicKey { - isOdd: boolean; + parity: number; x: Buffer; } From b1fca660bebc76f708621600e240fd3b9596d04b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 19:28:22 +0200 Subject: [PATCH 26/73] chore: lint & format; fix: discovered bug in findScriptPath() after lint --- src/payments/p2tr.js | 1 + src/taproot.js | 5 ++-- test/fixtures/p2tr.json | 4 +-- ts_src/payments/p2tr.ts | 22 ++++++++------- ts_src/taproot.ts | 60 +++++++++++++++++++++++------------------ 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 8e48aea87..2de193f7c 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -120,6 +120,7 @@ function p2tr(a, opts) { lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + // todo: optimize/cache const hashTree = (0, taproot_1.toHashTree)(a.scriptsTree); const leafHash = (0, taproot_1.tapLeafHash)( a.scriptLeaf.output, diff --git a/src/taproot.js b/src/taproot.js index d11e77799..220274d61 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -116,9 +116,8 @@ function findScriptPath(node, hash) { if (node.right) { if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : []; const rightPath = findScriptPath(node.right, hash); - if (rightPath.length) { - } - return node.left ? [node.left.hash].concat(rightPath) : rightPath; + if (rightPath.length) + return node.left ? [node.left.hash].concat(rightPath) : rightPath; } return []; } diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 1ef6ed62d..da89f26f6 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -470,7 +470,7 @@ "arguments": { "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", "scriptLeaf": { - "output": "424950333431 OP_CHECKSIG", + "output": "424950333431", "version": 152 }, "scriptsTree": [ @@ -492,7 +492,7 @@ "address": "bc1ppa3u5trk8xumkjlqgewvp237u79qwcd6ta0h6mlca2e5puya54ssw9zq0y", "hash": "f3004d6c183e038105d436db1424f321613366cbb7b05939bf05d763a9ebb962", "witness": [ - "06424950333431ac", + "06424950333431", "98ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" ], "signature": null, diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 476ffadbc..0dff7713b 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -44,7 +44,6 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { pubkey: typef.maybe(typef.BufferN(32)), signature: typef.maybe(bscript.isCanonicalScriptSignature), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? scriptLeaf: typef.maybe({ version: typef.maybe(typef.Number), @@ -134,14 +133,18 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.witness) return a.witness; if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { // todo: optimize/cache - const hashTree = toHashTree(a.scriptsTree) - const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version) - const path = findScriptPath(hashTree, leafHash) + const hashTree = toHashTree(a.scriptsTree); + const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version); + const path = findScriptPath(hashTree, leafHash); const outputKey = tweakKey(a.internalPubkey, hashTree.hash); - if (!outputKey) return - const version = a.scriptLeaf.version || 0xc0 - const controlBock = NBuffer.concat([NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat(path.reverse())) - return [a.scriptLeaf.output, controlBock] + if (!outputKey) return; + const version = a.scriptLeaf.version || 0xc0; + const controlBock = NBuffer.concat( + [NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat( + path.reverse(), + ), + ); + return [a.scriptLeaf.output, controlBock]; } if (a.signature) return [a.signature]; }); @@ -211,7 +214,8 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const controlBlock = witness[witness.length - 1]; if (controlBlock.length < 33) throw new TypeError( - `The control-block length is too small. Got ${controlBlock.length + `The control-block length is too small. Got ${ + controlBlock.length }, expected min 33.`, ); diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index e1bb718db..d364670a9 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -4,7 +4,13 @@ const BN = require('bn.js'); import * as bcrypto from './crypto'; // todo: use varuint-bitcoin?? import * as varuint from 'bip174/src/lib/converter/varint'; -import { TweakedPublicKey, TaprootLeaf, ZERO32, EC_P, GROUP_ORDER } from './types'; +import { + TweakedPublicKey, + TaprootLeaf, + ZERO32, + EC_P, + GROUP_ORDER, +} from './types'; // todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. const ecc = require('tiny-secp256k1'); @@ -78,7 +84,10 @@ export function tweakKey( }; } -export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer { +export function rootHashFromPath( + controlBlock: Buffer, + tapLeafMsg: Buffer, +): Buffer { const k = [tapLeafMsg]; const e = []; @@ -97,12 +106,11 @@ export function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buff } export interface HashTree { - hash: Buffer - left?: HashTree - right?: HashTree + hash: Buffer; + left?: HashTree; + right?: HashTree; } - export function toHashTree(scripts: TaprootLeaf[]): HashTree { if (scripts.length === 1) { const script = scripts[0]; @@ -110,12 +118,12 @@ export function toHashTree(scripts: TaprootLeaf[]): HashTree { return toHashTree(script); } script.version = script.version || LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) throw new TypeError('Invalid script version'); + if ((script.version & 1) !== 0) + throw new TypeError('Invalid script version'); return { - hash: tapLeafHash(script.output, script.version) - } - + hash: tapLeafHash(script.output, script.version), + }; } // todo: this is a binary tree, use zero an one index const half = Math.trunc(scripts.length / 2); @@ -130,38 +138,38 @@ export function toHashTree(scripts: TaprootLeaf[]): HashTree { return { hash: tapBranchHash(leftHash, rightHash), left, - right - } + right, + }; } export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] { if (node.left) { - if (node.left.hash.equals(hash)) - return node.right ? [node.right.hash] : [] - const leftPath = findScriptPath(node.left, hash) + if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : []; + const leftPath = findScriptPath(node.left, hash); if (leftPath.length) - return node.right ? [node.right.hash].concat(leftPath) : leftPath + return node.right ? [node.right.hash].concat(leftPath) : leftPath; } if (node.right) { - if (node.right.hash.equals(hash)) - return node.left ? [node.left.hash] : [] - const rightPath = findScriptPath(node.right, hash) - if (rightPath.length) {} - return node.left ? [node.left.hash].concat(rightPath) : rightPath + if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : []; + const rightPath = findScriptPath(node.right, hash); + if (rightPath.length) + return node.left ? [node.left.hash].concat(rightPath) : rightPath; } - return [] - + return []; } export function tapLeafHash(script: Buffer, version?: number): Buffer { - version = version || LEAF_VERSION_TAPSCRIPT - return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([version]), serializeScript(script)])); + version = version || LEAF_VERSION_TAPSCRIPT; + return bcrypto.taggedHash( + TAP_LEAF_TAG, + NBuffer.concat([NBuffer.from([version]), serializeScript(script)]), + ); } function tapBranchHash(a: Buffer, b: Buffer): Buffer { - return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b]),); + return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b])); } function serializeScript(s: Buffer): Buffer { From ad2aec1906653d379aa6f2a83951afff93da4186 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 20:00:42 +0200 Subject: [PATCH 27/73] chore: code clean-up; fix o.scriptLeaf (needs tests) --- src/payments/p2tr.js | 5 +---- ts_src/payments/index.ts | 10 +++++----- ts_src/payments/p2tr.ts | 5 +---- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 2de193f7c..435d2f101 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -11,9 +11,6 @@ const bech32_1 = require('bech32'); const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -// witness: {signature} -// input: <> -// output: OP_1 {pubKey} function p2tr(a, opts) { if ( !a.address && @@ -93,7 +90,7 @@ function p2tr(a, opts) { return bscript.compile([OPS.OP_1, o.pubkey]); }); lazy.prop(o, 'scriptLeaf', () => { - if (!a.scriptLeaf) return a.scriptLeaf; + if (a.scriptLeaf) return a.scriptLeaf; }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index bcf09e2a6..996f292e9 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -12,18 +12,18 @@ import { p2tr } from './p2tr'; export interface Payment { name?: string; network?: Network; - output?: Buffer; // the full scriptPubKey + output?: Buffer; data?: Buffer[]; m?: number; n?: number; pubkeys?: Buffer[]; input?: Buffer; signatures?: Buffer[]; - internalPubkey?: Buffer; // taproot: output key - pubkey?: Buffer; // taproot: output key + internalPubkey?: Buffer; + pubkey?: Buffer; signature?: Buffer; - address?: string; // taproot: betch32m - hash?: Buffer; // taproot: MAST root + address?: string; + hash?: Buffer; redeem?: Payment; scriptsTree?: any; // todo: solve scriptLeaf?: TaprootLeaf; diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 0dff7713b..98ca8eb56 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -18,9 +18,6 @@ const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -// witness: {signature} -// input: <> -// output: OP_1 {pubKey} export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if ( !a.address && @@ -105,7 +102,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return bscript.compile([OPS.OP_1, o.pubkey]); }); lazy.prop(o, 'scriptLeaf', () => { - if (!a.scriptLeaf) return a.scriptLeaf; + if (a.scriptLeaf) return a.scriptLeaf; }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; From dcffed31ebb135e718aa8e0d36cc629007bf6347 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 12 Nov 2021 12:13:26 +0200 Subject: [PATCH 28/73] chore: update taggedHash() prefix after rebase --- src/taproot.js | 6 +++--- ts_src/taproot.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/taproot.js b/src/taproot.js index 220274d61..0bb77079d 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -10,9 +10,9 @@ const types_1 = require('./types'); // todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. const ecc = require('tiny-secp256k1'); const LEAF_VERSION_TAPSCRIPT = 0xc0; -const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); -const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); -const TAP_TWEAK_TAG = buffer_1.Buffer.from('TapTweak', 'utf8'); +const TAP_LEAF_TAG = 'TapLeaf'; +const TAP_BRANCH_TAG = 'TapBranch'; +const TAP_TWEAK_TAG = 'TapTweak'; const EC_P_BN = new BN(types_1.EC_P); const EC_P_REDUCTION = BN.red(EC_P_BN); const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index d364670a9..7f1a90bd4 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -16,9 +16,9 @@ import { const ecc = require('tiny-secp256k1'); const LEAF_VERSION_TAPSCRIPT = 0xc0; -const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8'); -const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8'); -const TAP_TWEAK_TAG = NBuffer.from('TapTweak', 'utf8'); +const TAP_LEAF_TAG ='TapLeaf'; +const TAP_BRANCH_TAG ='TapBranch'; +const TAP_TWEAK_TAG ='TapTweak'; const EC_P_BN = new BN(EC_P); const EC_P_REDUCTION = BN.red(EC_P_BN); From d10716146dc60b570b327c86fb5c53326dcb0908 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 12 Jan 2022 15:17:13 +0200 Subject: [PATCH 29/73] fix: rebase issues --- package-lock.json | 14 +------------- src/taproot.js | 2 +- ts_src/taproot.ts | 2 +- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a60ab056..15caf4e12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2378,19 +2378,7 @@ "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==", "dev": true, "requires": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } + "uint8array-tools": "0.0.6" } }, "to-fast-properties": { diff --git a/src/taproot.js b/src/taproot.js index 0bb77079d..f85b942a6 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -144,5 +144,5 @@ function serializeScript(s) { } // todo: do not use ecc function pointAddScalar(P, h) { - return ecc.pointAddScalar(P, h); + return buffer_1.Buffer.from(ecc.pointAddScalar(P, h)); } diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index 7f1a90bd4..ea996760b 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -181,5 +181,5 @@ function serializeScript(s: Buffer): Buffer { // todo: do not use ecc function pointAddScalar(P: Buffer, h: Buffer): Buffer { - return ecc.pointAddScalar(P, h); + return NBuffer.from(ecc.pointAddScalar(P, h)); } From 4d2af065c1bfba780a26e80c9a65a01106abd266 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 16:54:23 +0200 Subject: [PATCH 30/73] chore: remove the bn.js dependency --- package-lock.json | 5 ----- package.json | 1 - 2 files changed, 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15caf4e12..f576a482c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -606,11 +606,6 @@ "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==", "dev": true }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index 172aa18b8..13e0e68fb 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "dependencies": { "bech32": "^2.0.0", "bip174": "^2.0.1", - "bn.js": "^5.2.0", "bs58check": "^2.1.2", "create-hash": "^1.1.0", "typeforce": "^1.11.3", From 6cfbf659d24bc32568585dc9c64e6b5bfa4d2795 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 16:55:49 +0200 Subject: [PATCH 31/73] refactor: use injectable ecc lib --- src/payments/index.d.ts | 16 +- src/payments/index.js | 24 +- src/payments/p2tr.d.ts | 5 +- src/payments/p2tr.js | 440 +++++++++++++++++----------------- src/payments/testecc.d.ts | 2 + src/payments/testecc.js | 172 ++++++++++++++ src/taproot.d.ts | 5 +- src/taproot.js | 67 +----- src/types.d.ts | 10 +- test/fixtures/p2tr.json | 2 +- test/payments.spec.ts | 15 +- ts_src/payments/index.ts | 24 +- ts_src/payments/p2tr.ts | 474 +++++++++++++++++++------------------ ts_src/payments/testecc.ts | 174 ++++++++++++++ ts_src/taproot.ts | 95 +------- ts_src/types.ts | 13 +- 16 files changed, 926 insertions(+), 612 deletions(-) create mode 100644 src/payments/testecc.d.ts create mode 100644 src/payments/testecc.js create mode 100644 ts_src/payments/testecc.ts diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 0170dd093..72db5de25 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -1,6 +1,6 @@ /// import { Network } from '../networks'; -import { TaprootLeaf } from '../types'; +import { TaprootLeaf, TinySecp256k1Interface } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -8,7 +8,6 @@ import { p2pkh } from './p2pkh'; import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; -import { p2tr } from './p2tr'; export interface Payment { name?: string; network?: Network; @@ -35,7 +34,18 @@ export interface PaymentOpts { validate?: boolean; allowIncomplete?: boolean; } +export interface PaymentAPI { + embed: PaymentCreator; + p2ms: PaymentCreator; + p2pk: PaymentCreator; + p2pkh: PaymentCreator; + p2sh: PaymentCreator; + p2wpkh: PaymentCreator; + p2wsh: PaymentCreator; + p2tr: PaymentCreator; +} export declare type StackElement = Buffer | number; export declare type Stack = StackElement[]; export declare type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, PaymentFactory }; +export default function PaymentFactory(ecc: TinySecp256k1Interface): PaymentAPI; diff --git a/src/payments/index.js b/src/payments/index.js index 9ce55f859..779df03fa 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.p2tr = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; +exports.PaymentFactory = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; const embed_1 = require('./embed'); Object.defineProperty(exports, 'embed', { enumerable: true, @@ -51,11 +51,21 @@ Object.defineProperty(exports, 'p2wsh', { }, }); const p2tr_1 = require('./p2tr'); -Object.defineProperty(exports, 'p2tr', { - enumerable: true, - get: function() { - return p2tr_1.p2tr; - }, -}); +const testecc_1 = require('./testecc'); +function PaymentFactory(ecc) { + (0, testecc_1.testEcc)(ecc); + return { + embed: embed_1.p2data, + p2ms: p2ms_1.p2ms, + p2pk: p2pk_1.p2pk, + p2pkh: p2pkh_1.p2pkh, + p2sh: p2sh_1.p2sh, + p2wpkh: p2wpkh_1.p2wpkh, + p2wsh: p2wsh_1.p2wsh, + p2tr: (0, p2tr_1.p2tr)(ecc), + }; +} +exports.default = PaymentFactory; +exports.PaymentFactory = PaymentFactory; // TODO // witness commitment diff --git a/src/payments/p2tr.d.ts b/src/payments/p2tr.d.ts index 350ed0ffc..e4d4c8a9b 100644 --- a/src/payments/p2tr.d.ts +++ b/src/payments/p2tr.d.ts @@ -1,2 +1,3 @@ -import { Payment, PaymentOpts } from './index'; -export declare function p2tr(a: Payment, opts?: PaymentOpts): Payment; +import { TinySecp256k1Interface } from '../types'; +import { PaymentCreator } from './index'; +export declare function p2tr(ecc: TinySecp256k1Interface): PaymentCreator; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 435d2f101..78ef95311 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -11,227 +11,237 @@ const bech32_1 = require('bech32'); const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -function p2tr(a, opts) { - if ( - !a.address && - !a.output && - !a.pubkey && - !a.output && - !a.internalPubkey && - !(a.witness && a.witness.length > 1) - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - (0, types_1.typeforce)( - { - address: types_1.typeforce.maybe(types_1.typeforce.String), - input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), - network: types_1.typeforce.maybe(types_1.typeforce.Object), - output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)), - internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), - hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), - pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), - signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), - witness: types_1.typeforce.maybe( - types_1.typeforce.arrayOf(types_1.typeforce.Buffer), - ), - // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? - scriptLeaf: types_1.typeforce.maybe({ - version: types_1.typeforce.maybe(types_1.typeforce.Number), - output: types_1.typeforce.maybe(types_1.typeforce.Buffer), - }), - }, - a, - ); - const _address = lazy.value(() => { - const result = bech32_1.bech32m.decode(a.address); - const version = result.words.shift(); - const data = bech32_1.bech32m.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: buffer_1.Buffer.from(data), - }; - }); - const _witness = lazy.value(() => { - if (!a.witness || !a.witness.length) return; +function p2tr(ecc) { + return (a, opts) => { if ( - a.witness.length >= 2 && - a.witness[a.witness.length - 1][0] === ANNEX_PREFIX - ) { - // remove annex, ignored by taproot - return a.witness.slice(0, -1); - } - return a.witness.slice(); - }); - const network = a.network || networks_1.bitcoin; - const o = { name: 'p2tr', network }; - lazy.prop(o, 'address', () => { - if (!o.pubkey) return; - const words = bech32_1.bech32m.toWords(o.pubkey); - words.unshift(TAPROOT_VERSION); - return bech32_1.bech32m.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.hash) return a.hash; - if (a.scriptsTree) return (0, taproot_1.toHashTree)(a.scriptsTree).hash; - const w = _witness(); - if (w && w.length > 1) { - const controlBlock = w[w.length - 1]; - const leafVersion = controlBlock[0] & 0b11111110; - const script = w[w.length - 2]; - const leafHash = (0, taproot_1.tapLeafHash)(script, leafVersion); - return (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); - } - return null; - }); - lazy.prop(o, 'output', () => { - if (!o.pubkey) return; - return bscript.compile([OPS.OP_1, o.pubkey]); - }); - lazy.prop(o, 'scriptLeaf', () => { - if (a.scriptLeaf) return a.scriptLeaf; - }); - lazy.prop(o, 'pubkey', () => { - if (a.pubkey) return a.pubkey; - if (a.output) return a.output.slice(2); - if (a.address) return _address().data; - if (o.internalPubkey) { - const tweakedKey = (0, taproot_1.tweakKey)(o.internalPubkey, o.hash); - if (tweakedKey) return tweakedKey.x; - } - }); - lazy.prop(o, 'internalPubkey', () => { - if (a.internalPubkey) return a.internalPubkey; - const witness = _witness(); - if (witness && witness.length > 1) - return witness[witness.length - 1].slice(1, 33); - }); - lazy.prop(o, 'signature', () => { - if (!a.witness || a.witness.length !== 1) return; - return a.witness[0]; - }); - lazy.prop(o, 'input', () => { - // todo - }); - lazy.prop(o, 'witness', () => { - if (a.witness) return a.witness; - if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { - // todo: optimize/cache - const hashTree = (0, taproot_1.toHashTree)(a.scriptsTree); - const leafHash = (0, taproot_1.tapLeafHash)( - a.scriptLeaf.output, - a.scriptLeaf.version, - ); - const path = (0, taproot_1.findScriptPath)(hashTree, leafHash); - const outputKey = (0, taproot_1.tweakKey)( - a.internalPubkey, - hashTree.hash, - ); - if (!outputKey) return; - const version = a.scriptLeaf.version || 0xc0; - const controlBock = buffer_1.Buffer.concat( - [ - buffer_1.Buffer.from([version | outputKey.parity]), - a.internalPubkey, - ].concat(path.reverse()), - ); - return [a.scriptLeaf.output, controlBock]; - } - if (a.signature) return [a.signature]; - }); - // extended validation - if (opts.validate) { - let pubkey = buffer_1.Buffer.from([]); - if (a.address) { - if (network && network.bech32 !== _address().prefix) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== TAPROOT_VERSION) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 32) - throw new TypeError('Invalid address data'); - pubkey = _address().data; - } - if (a.pubkey) { - if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.pubkey; - } - if (a.output) { + !a.address && + !a.output && + !a.pubkey && + !a.output && + !a.internalPubkey && + !(a.witness && a.witness.length > 1) + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + (0, types_1.typeforce)( + { + address: types_1.typeforce.maybe(types_1.typeforce.String), + input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)), + internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), + // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? + scriptLeaf: types_1.typeforce.maybe({ + version: types_1.typeforce.maybe(types_1.typeforce.Number), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + }), + }, + a, + ); + const _address = lazy.value(() => { + const result = bech32_1.bech32m.decode(a.address); + const version = result.words.shift(); + const data = bech32_1.bech32m.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: buffer_1.Buffer.from(data), + }; + }); + const _witness = lazy.value(() => { + if (!a.witness || !a.witness.length) return; if ( - a.output.length !== 34 || - a.output[0] !== OPS.OP_1 || - a.output[1] !== 0x20 - ) - throw new TypeError('Output is invalid'); - if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.output.slice(2); - } - if (a.internalPubkey) { - const tweakedKey = (0, taproot_1.tweakKey)(a.internalPubkey, o.hash); - if (tweakedKey === null) - throw new TypeError('Invalid internalPubkey for p2tr'); - if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) - throw new TypeError('Pubkey mismatch'); - else pubkey = tweakedKey.x; - } - if (pubkey && pubkey.length) { - if ((0, taproot_1.liftX)(pubkey) === null) - throw new TypeError('Invalid pubkey for p2tr'); - } - if (a.hash && a.scriptsTree) { - const hash = (0, taproot_1.toHashTree)(a.scriptsTree).hash; - if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); - } - const witness = _witness(); - if (witness && witness.length) { - if (witness.length === 1) { - // key spending - if (a.signature && !a.signature.equals(witness[0])) - throw new TypeError('Signature mismatch'); - // todo: recheck - // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) - // throw new TypeError('Witness has invalid signature'); - } else { - // script path spending - const controlBlock = witness[witness.length - 1]; - if (controlBlock.length < 33) - throw new TypeError( - `The control-block length is too small. Got ${ - controlBlock.length - }, expected min 33.`, - ); - if ((controlBlock.length - 33) % 32 !== 0) - throw new TypeError( - `The control-block length of ${controlBlock.length} is incorrect!`, - ); - const m = (controlBlock.length - 33) / 32; - if (m > 128) - throw new TypeError( - `The script path is too long. Got ${m}, expected max 128.`, - ); - const internalPubkey = controlBlock.slice(1, 33); - if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) - throw new TypeError('Internal pubkey mismatch'); - const internalPubkeyPoint = (0, taproot_1.liftX)(internalPubkey); - if (!internalPubkeyPoint) - throw new TypeError('Invalid internalPubkey for p2tr witness'); + a.witness.length >= 2 && + a.witness[a.witness.length - 1][0] === ANNEX_PREFIX + ) { + // remove annex, ignored by taproot + return a.witness.slice(0, -1); + } + return a.witness.slice(); + }); + const network = a.network || networks_1.bitcoin; + const o = { name: 'p2tr', network }; + lazy.prop(o, 'address', () => { + if (!o.pubkey) return; + const words = bech32_1.bech32m.toWords(o.pubkey); + words.unshift(TAPROOT_VERSION); + return bech32_1.bech32m.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.hash) return a.hash; + if (a.scriptsTree) return (0, taproot_1.toHashTree)(a.scriptsTree).hash; + const w = _witness(); + if (w && w.length > 1) { + const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; - const script = witness[witness.length - 2]; + const script = w[w.length - 2]; const leafHash = (0, taproot_1.tapLeafHash)(script, leafVersion); - const hash = (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); - const outputKey = (0, taproot_1.tweakKey)(internalPubkey, hash); - if (!outputKey) - // todo: needs test data - throw new TypeError('Invalid outputKey for p2tr witness'); - if (pubkey.length && !pubkey.equals(outputKey.x)) - throw new TypeError('Pubkey mismatch for p2tr witness'); - if (outputKey.parity !== (controlBlock[0] & 1)) - throw new Error('Incorrect parity'); + return (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); + } + return null; + }); + lazy.prop(o, 'output', () => { + if (!o.pubkey) return; + return bscript.compile([OPS.OP_1, o.pubkey]); + }); + lazy.prop(o, 'scriptLeaf', () => { + if (a.scriptLeaf) return a.scriptLeaf; + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.internalPubkey) { + const tweakedKey = tweakKey(o.internalPubkey, o.hash); + if (tweakedKey) return tweakedKey.x; + } + }); + lazy.prop(o, 'internalPubkey', () => { + if (a.internalPubkey) return a.internalPubkey; + const witness = _witness(); + if (witness && witness.length > 1) + return witness[witness.length - 1].slice(1, 33); + }); + lazy.prop(o, 'signature', () => { + if (!a.witness || a.witness.length !== 1) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + // todo + }); + lazy.prop(o, 'witness', () => { + if (a.witness) return a.witness; + if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + // todo: optimize/cache + const hashTree = (0, taproot_1.toHashTree)(a.scriptsTree); + const leafHash = (0, taproot_1.tapLeafHash)( + a.scriptLeaf.output, + a.scriptLeaf.version, + ); + const path = (0, taproot_1.findScriptPath)(hashTree, leafHash); + const outputKey = tweakKey(a.internalPubkey, hashTree.hash); + if (!outputKey) return; + const version = a.scriptLeaf.version || 0xc0; + const controlBock = buffer_1.Buffer.concat( + [ + buffer_1.Buffer.from([version | outputKey.parity]), + a.internalPubkey, + ].concat(path.reverse()), + ); + return [a.scriptLeaf.output, controlBock]; + } + if (a.signature) return [a.signature]; + }); + // extended validation + if (opts.validate) { + let pubkey = buffer_1.Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== TAPROOT_VERSION) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + pubkey = _address().data; + } + if (a.pubkey) { + if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.pubkey; + } + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_1 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.output.slice(2); + } + if (a.internalPubkey) { + const tweakedKey = tweakKey(a.internalPubkey, o.hash); + if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) + throw new TypeError('Pubkey mismatch'); + else pubkey = tweakedKey.x; + } + if (pubkey && pubkey.length) { + if (!ecc.isXOnlyPoint(pubkey)) + throw new TypeError('Invalid pubkey for p2tr'); + } + if (a.hash && a.scriptsTree) { + const hash = (0, taproot_1.toHashTree)(a.scriptsTree).hash; + if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); + } + const witness = _witness(); + if (witness && witness.length) { + if (witness.length === 1) { + // key spending + if (a.signature && !a.signature.equals(witness[0])) + throw new TypeError('Signature mismatch'); + // todo: recheck + // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) + // throw new TypeError('Witness has invalid signature'); + } else { + // script path spending + const controlBlock = witness[witness.length - 1]; + if (controlBlock.length < 33) + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); + if ((controlBlock.length - 33) % 32 !== 0) + throw new TypeError( + `The control-block length of ${ + controlBlock.length + } is incorrect!`, + ); + const m = (controlBlock.length - 33) / 32; + if (m > 128) + throw new TypeError( + `The script path is too long. Got ${m}, expected max 128.`, + ); + const internalPubkey = controlBlock.slice(1, 33); + if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) + throw new TypeError('Internal pubkey mismatch'); + if (!ecc.isXOnlyPoint(internalPubkey)) + throw new TypeError('Invalid internalPubkey for p2tr witness'); + const leafVersion = controlBlock[0] & 0b11111110; + const script = witness[witness.length - 2]; + const leafHash = (0, taproot_1.tapLeafHash)(script, leafVersion); + const hash = (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); + const outputKey = tweakKey(internalPubkey, hash); + if (!outputKey) + // todo: needs test data + throw new TypeError('Invalid outputKey for p2tr witness'); + if (pubkey.length && !pubkey.equals(outputKey.x)) + throw new TypeError('Pubkey mismatch for p2tr witness'); + if (outputKey.parity !== (controlBlock[0] & 1)) + throw new Error('Incorrect parity'); + } } } + return Object.assign(o, a); + }; + function tweakKey(pubKey, h) { + if (!buffer_1.Buffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + const tweakHash = (0, taproot_1.tapTweakHash)(pubKey, h); + const res = ecc.xOnlyPointAddTweak(pubKey, tweakHash); + if (!res || res.xOnlyPubkey === null) return null; + return { + parity: res.parity, + x: buffer_1.Buffer.from(res.xOnlyPubkey), + }; } - return Object.assign(o, a); } exports.p2tr = p2tr; diff --git a/src/payments/testecc.d.ts b/src/payments/testecc.d.ts new file mode 100644 index 000000000..59d0de2b2 --- /dev/null +++ b/src/payments/testecc.d.ts @@ -0,0 +1,2 @@ +import { TinySecp256k1Interface } from '../types'; +export declare function testEcc(ecc: TinySecp256k1Interface): void; diff --git a/src/payments/testecc.js b/src/payments/testecc.js new file mode 100644 index 000000000..9bdc62031 --- /dev/null +++ b/src/payments/testecc.js @@ -0,0 +1,172 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.testEcc = void 0; +const h = hex => Buffer.from(hex, 'hex'); +function testEcc(ecc) { + assert( + ecc.isXOnlyPoint( + h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + ), + ); + assert( + ecc.isXOnlyPoint( + h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e'), + ), + ); + assert( + ecc.isXOnlyPoint( + h('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'), + ), + ); + assert( + ecc.isXOnlyPoint( + h('0000000000000000000000000000000000000000000000000000000000000001'), + ), + ); + assert( + !ecc.isXOnlyPoint( + h('0000000000000000000000000000000000000000000000000000000000000000'), + ), + ); + assert( + !ecc.isXOnlyPoint( + h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'), + ), + ); + tweakAddVectors.forEach(t => { + const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak)); + if (t.result === null) { + assert(r === null); + } else { + assert(r !== null); + assert(r.parity === t.parity); + assert(Buffer.from(r.xOnlyPubkey).equals(h(t.result))); + } + }); +} +exports.testEcc = testEcc; +function assert(bool) { + if (!bool) throw new Error('ecc library invalid'); +} +const tweakAddVectors = [ + { + pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', + tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', + parity: -1, + result: null, + }, + { + pubkey: '1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b', + tweak: 'a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac', + parity: 1, + result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf', + }, + { + pubkey: '2f1b310f4c065331bc0d79ba4661bb9822d67d7c4a1b0a1892e1fd0cd23aa68d', + tweak: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', + parity: 1, + result: '5786150f0ac36a2aaeb8d11aaeb7aa2d03ad63c528cc307a7cd5648c84041f34', + }, + { + pubkey: 'e7e9acacbdb43fc9fb71a8db1536c0f866caa78def49f666fa121a6f7954bb01', + tweak: '1ad613216778a70490c8a681a4901d4ca880d76916deb69979b5ac52d2760e09', + parity: 1, + result: 'ae10fa880c85848cc1faf056f56a64b7d45c68838cfb308d03ca2be9b485c130', + }, + { + pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991', + tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47', + parity: 0, + result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c', + }, + { + pubkey: '8f19ee7677713806c662078a9f2b2c7b930376d22f2d617bce50b5e444839a7c', + tweak: '7df1f4b66058f8be34b6b7d17be9bcf35ba5c98edf8d4e763b95964bad655fe4', + parity: 1, + result: '74619a5990750928d0728817b02bb0d398062dad0e568f46ea5348d35bef914f', + }, + { + pubkey: '2bda68b3aa0239d382f185ca2d8c31ce604cc26220cef3eb65223f47a0088d87', + tweak: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', + parity: 0, + result: '84479dc6bf70762721b10b89c8798e00d525507edc3fabbfc89ad915b6509379', + }, + { + pubkey: '9611ba340bce00a594f1ffb1294974af80e1301e49597378732fd77bbdedf454', + tweak: 'bbb8ec1f063522953a4a9f90ff4e849560e0f0597458529ea13b8868f255c7c7', + parity: 0, + result: '30bebfdad18b87b646f60e51d3c45c6658fbb4364c94b1b33d925a4515b66757', + }, + { + pubkey: 'd9f5792078a845303c1f1ea88aec79ed0fd9f0c49e9e7bff2765877e79b4dd52', + tweak: '531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337', + parity: 0, + result: 'fe5dd39f0491af71711454eee5b6a9c99779c422dd97f5e7f75d7ce7be7b32f0', + }, + { + pubkey: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', + tweak: '048968943184ce8a0239ab2141641e8eaead35a6dc8e6b55ad33ac1eca975a47', + parity: 1, + result: '6c8245a62201887c5e3aeb022fff06e6c110f3e58ad6d37cc20e877082b72c58', + }, + { + pubkey: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', + tweak: 'ff8adab52623bcb2717fc71d7edc6f55e98396e6c234dff01f307a12b2af1c99', + parity: 1, + result: 'd6080b5df61525fe8be31a823f3943e5fc9354d5a091b2dea195985c7c395787', + }, + { + pubkey: '24653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c', + tweak: '9e5f7dbe6d62ade5aab476b40559852ea1b5fc7bb99a61a42eab550f69ffafb4', + parity: 0, + result: '58289ee230fcf6a78cb9878cae5102cc9104490abab9d03f3eccc2f0cd07de5f', + }, + { + pubkey: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', + tweak: 'bde750d93efe821813df9c15ee676f2e9c63386336c164f5a15cf240ac653c06', + parity: 0, + result: '9a1ae919c5c78da635d94a92b3053e46b2261b81ec70db82a382f5bff474bec4', + }, + { + pubkey: 'bc14bc97e2d818ee360a9ba7782bd6a6dfc2c1e335fffc584a095fdac5fea641', + tweak: '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa', + parity: 1, + result: '19a7416f4f95f36c5e48dc7630ffea8b292e1721cecfa9cc5f794c83973e41d6', + }, + { + pubkey: '35e9d7c48e3a5254d5881b60abf004cf6eedc6ab842393caa2fdd20d6d0ad170', + tweak: '18bb586dc109adf49ffb42e0ac293d2a2965e49a0a4900c2be776b426b7cbfde', + parity: 0, + result: 'fa7cca72580bb686fbbae09ded801c7d109fa378f52e8a5f43a1922e442e44c1', + }, + { + pubkey: '67bff656551f25009ac8ed88664736c08074a15dbd2268292f5de7ca7e718338', + tweak: 'b96359049e97f49d871e856f37e54d0978bae2cc936b4484d96df984cd20daa1', + parity: 0, + result: 'dd081d737da17fb4f6686f8497cac56b16ea06e1dc05859633f735fb304e7e5a', + }, + { + pubkey: '5b0da52533a1620fe947cb658c35e1772f39ef1253753493b7dc4b8d8f31f40e', + tweak: '3d481f46056f2da27870a5d00c0c7bf484036780a83bbcc2e2da2f03bc33bff0', + parity: 1, + result: '164e13b54edc89673f94563120d87db4a47b12e49c40c195ac51ea7bc50f22e1', + }, + { + pubkey: '0612c5e8c98a9677a2ddd13770e26f5f1e771a088c88ce519a1e1b65872423f9', + tweak: 'dbcfa1c73674cba4aa1b6992ebdc6a77008d38f6c6ec068c3c862b9ff6d287f2', + parity: 0, + result: '82fc6954352b7189a156e4678d0c315c122431fa9551961b8e3c811b55a42c8b', + }, + { + pubkey: '9ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b', + tweak: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', + parity: 1, + result: 'cf9065a7e2c9f909becc1c95f9884ed9fbe19c4a8954ed17880f02d94ae96a63', + }, + { + pubkey: 'c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5', + tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f', + parity: -1, + result: null, + }, +]; diff --git a/src/taproot.d.ts b/src/taproot.d.ts index c55f10d72..37c118430 100644 --- a/src/taproot.d.ts +++ b/src/taproot.d.ts @@ -1,7 +1,5 @@ /// -import { TweakedPublicKey, TaprootLeaf } from './types'; -export declare function liftX(buffer: Buffer): Buffer | null; -export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; +import { TaprootLeaf } from './types'; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; export interface HashTree { hash: Buffer; @@ -11,3 +9,4 @@ export interface HashTree { export declare function toHashTree(scripts: TaprootLeaf[]): HashTree; export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[]; export declare function tapLeafHash(script: Buffer, version?: number): Buffer; +export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer; diff --git a/src/taproot.js b/src/taproot.js index f85b942a6..98a7db377 100644 --- a/src/taproot.js +++ b/src/taproot.js @@ -1,68 +1,16 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapLeafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.tweakKey = exports.liftX = void 0; +exports.tapTweakHash = exports.tapLeafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = void 0; const buffer_1 = require('buffer'); -const BN = require('bn.js'); const bcrypto = require('./crypto'); // todo: use varuint-bitcoin?? const varuint = require('bip174/src/lib/converter/varint'); -const types_1 = require('./types'); // todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. -const ecc = require('tiny-secp256k1'); +// const ecc = require('tiny-secp256k1'); const LEAF_VERSION_TAPSCRIPT = 0xc0; const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; const TAP_TWEAK_TAG = 'TapTweak'; -const EC_P_BN = new BN(types_1.EC_P); -const EC_P_REDUCTION = BN.red(EC_P_BN); -const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); -const BN_2 = new BN(2); -const BN_3 = new BN(3); -const BN_7 = new BN(7); -function liftX(buffer) { - if (!buffer_1.Buffer.isBuffer(buffer)) return null; - if (buffer.length !== 32) return null; - if (buffer.compare(types_1.ZERO32) === 0) return null; - if (buffer.compare(types_1.EC_P) >= 0) return null; - const x = new BN(buffer); - const x1 = x.toRed(EC_P_REDUCTION); - const ySq = x1 - .redPow(BN_3) - .add(BN_7) - .mod(EC_P_BN); - const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); - if (!ySq.eq(y.redPow(BN_2))) { - return null; - } - const y1 = y.isEven() ? y : EC_P_BN.sub(y); - return buffer_1.Buffer.concat([ - buffer_1.Buffer.from([0x04]), - buffer_1.Buffer.from(x1.toBuffer('be', 32)), - buffer_1.Buffer.from(y1.toBuffer('be', 32)), - ]); -} -exports.liftX = liftX; -function tweakKey(pubKey, h) { - if (!buffer_1.Buffer.isBuffer(pubKey)) return null; - if (pubKey.length !== 32) return null; - if (h && h.length !== 32) return null; - const tweakHash = bcrypto.taggedHash( - TAP_TWEAK_TAG, - buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), - ); - if (tweakHash.compare(types_1.GROUP_ORDER) >= 0) { - // todo: add test for this case - throw new TypeError('Tweak value over the SECP256K1 Order'); - } - const P = liftX(pubKey); - if (P === null) return null; - const Q = pointAddScalar(P, tweakHash); - return { - parity: Q[64] % 2, - x: Q.slice(1, 33), - }; -} -exports.tweakKey = tweakKey; function rootHashFromPath(controlBlock, tapLeafMsg) { const k = [tapLeafMsg]; const e = []; @@ -136,13 +84,16 @@ exports.tapLeafHash = tapLeafHash; function tapBranchHash(a, b) { return bcrypto.taggedHash(TAP_BRANCH_TAG, buffer_1.Buffer.concat([a, b])); } +function tapTweakHash(pubKey, h) { + return bcrypto.taggedHash( + TAP_TWEAK_TAG, + buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), + ); +} +exports.tapTweakHash = tapTweakHash; function serializeScript(s) { const varintLen = varuint.encodingLength(s.length); const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better varuint.encode(s.length, buffer); return buffer_1.Buffer.concat([buffer, s]); } -// todo: do not use ecc -function pointAddScalar(P, h) { - return buffer_1.Buffer.from(ecc.pointAddScalar(P, h)); -} diff --git a/src/types.d.ts b/src/types.d.ts index 1bcb12426..014c2b910 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -14,14 +14,18 @@ export declare function Signer(obj: any): boolean; export declare function Satoshi(value: number): boolean; export declare const ECPoint: any; export declare const Network: any; -export interface TweakedPublicKey { - parity: number; - x: Buffer; +export interface XOnlyPointAddTweakResult { + parity: 1 | 0; + xOnlyPubkey: Uint8Array; } export interface TaprootLeaf { output: Buffer; version?: number; } +export interface TinySecp256k1Interface { + isXOnlyPoint(p: Uint8Array): boolean; + xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; +} export declare const Buffer256bit: any; export declare const Hash160bit: any; export declare const Hash256bit: any; diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index da89f26f6..02b1cb3c0 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -898,7 +898,7 @@ } }, { - "exception": "Invalid internalPubkey for p2t", + "exception": "Expected Point", "options": {}, "arguments": { "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f8" diff --git a/test/payments.spec.ts b/test/payments.spec.ts index 9e28501ae..957396199 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -1,16 +1,15 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; -import { PaymentCreator } from '../src/payments'; +import * as ecc from 'tiny-secp256k1'; +import { PaymentCreator, PaymentFactory } from '../src/payments'; import * as u from './payments.utils'; + +const payments = PaymentFactory(ecc); ['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach(p => { describe(p, () => { - let fn: PaymentCreator; - const payment = require('../src/payments/' + p); - if (p === 'embed') { - fn = payment.p2data; - } else { - fn = payment[p]; - } + //@ts-ignore + const fn: PaymentCreator = payments[p]; + const fixtures = require('./fixtures/' + p); fixtures.valid.forEach((f: any) => { diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 996f292e9..f7c5dbadd 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,5 +1,5 @@ import { Network } from '../networks'; -import { TaprootLeaf } from '../types'; +import { TaprootLeaf, TinySecp256k1Interface } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -8,6 +8,7 @@ import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; import { p2tr } from './p2tr'; +import { testEcc } from './testecc'; export interface Payment { name?: string; @@ -39,11 +40,30 @@ export interface PaymentOpts { allowIncomplete?: boolean; } +export interface PaymentAPI { + embed: PaymentCreator; + p2ms: PaymentCreator; + p2pk: PaymentCreator; + p2pkh: PaymentCreator; + p2sh: PaymentCreator; + p2wpkh: PaymentCreator; + p2wsh: PaymentCreator; + p2tr: PaymentCreator; +} + export type StackElement = Buffer | number; export type Stack = StackElement[]; export type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, PaymentFactory }; + +export default function PaymentFactory( + ecc: TinySecp256k1Interface, +): PaymentAPI { + testEcc(ecc); + + return { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr: p2tr(ecc) }; +} // TODO // witness commitment diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 98ca8eb56..0bb68465d 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,16 +1,15 @@ import { Buffer as NBuffer } from 'buffer'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { typeforce as typef } from '../types'; +import { typeforce as typef, TinySecp256k1Interface } from '../types'; import { toHashTree, rootHashFromPath, findScriptPath, tapLeafHash, - tweakKey, - liftX, + tapTweakHash, } from '../taproot'; -import { Payment, PaymentOpts } from './index'; +import { Payment, PaymentOpts, PaymentCreator } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; const OPS = bscript.OPS; @@ -18,242 +17,267 @@ const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -export function p2tr(a: Payment, opts?: PaymentOpts): Payment { - if ( - !a.address && - !a.output && - !a.pubkey && - !a.output && - !a.internalPubkey && - !(a.witness && a.witness.length > 1) - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - - typef( - { - address: typef.maybe(typef.String), - input: typef.maybe(typef.BufferN(0)), - network: typef.maybe(typef.Object), - output: typef.maybe(typef.BufferN(34)), - internalPubkey: typef.maybe(typef.BufferN(32)), - hash: typef.maybe(typef.BufferN(32)), - pubkey: typef.maybe(typef.BufferN(32)), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), - // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? - scriptLeaf: typef.maybe({ - version: typef.maybe(typef.Number), - output: typef.maybe(typef.Buffer), - }), - }, - a, - ); - - const _address = lazy.value(() => { - const result = bech32m.decode(a.address!); - const version = result.words.shift(); - const data = bech32m.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: NBuffer.from(data), - }; - }); - - const _witness = lazy.value(() => { - if (!a.witness || !a.witness.length) return; +export function p2tr(ecc: TinySecp256k1Interface): PaymentCreator { + return (a: Payment, opts?: PaymentOpts): Payment => { if ( - a.witness.length >= 2 && - a.witness[a.witness.length - 1][0] === ANNEX_PREFIX - ) { - // remove annex, ignored by taproot - return a.witness.slice(0, -1); - } - return a.witness.slice(); - }); - - const network = a.network || BITCOIN_NETWORK; - const o: Payment = { name: 'p2tr', network }; - - lazy.prop(o, 'address', () => { - if (!o.pubkey) return; - - const words = bech32m.toWords(o.pubkey); - words.unshift(TAPROOT_VERSION); - return bech32m.encode(network.bech32, words); - }); - - lazy.prop(o, 'hash', () => { - if (a.hash) return a.hash; - if (a.scriptsTree) return toHashTree(a.scriptsTree).hash; - const w = _witness(); - if (w && w.length > 1) { - const controlBlock = w[w.length - 1]; - const leafVersion = controlBlock[0] & 0b11111110; - const script = w[w.length - 2]; - const leafHash = tapLeafHash(script, leafVersion); - return rootHashFromPath(controlBlock, leafHash); - } - return null; - }); - lazy.prop(o, 'output', () => { - if (!o.pubkey) return; - return bscript.compile([OPS.OP_1, o.pubkey]); - }); - lazy.prop(o, 'scriptLeaf', () => { - if (a.scriptLeaf) return a.scriptLeaf; - }); - lazy.prop(o, 'pubkey', () => { - if (a.pubkey) return a.pubkey; - if (a.output) return a.output.slice(2); - if (a.address) return _address().data; - if (o.internalPubkey) { - const tweakedKey = tweakKey(o.internalPubkey, o.hash); - if (tweakedKey) return tweakedKey.x; - } - }); - lazy.prop(o, 'internalPubkey', () => { - if (a.internalPubkey) return a.internalPubkey; - const witness = _witness(); - if (witness && witness.length > 1) - return witness[witness.length - 1].slice(1, 33); - }); - lazy.prop(o, 'signature', () => { - if (!a.witness || a.witness.length !== 1) return; - return a.witness[0]; - }); - lazy.prop(o, 'input', () => { - // todo - }); - lazy.prop(o, 'witness', () => { - if (a.witness) return a.witness; - if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { - // todo: optimize/cache - const hashTree = toHashTree(a.scriptsTree); - const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version); - const path = findScriptPath(hashTree, leafHash); - const outputKey = tweakKey(a.internalPubkey, hashTree.hash); - if (!outputKey) return; - const version = a.scriptLeaf.version || 0xc0; - const controlBock = NBuffer.concat( - [NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat( - path.reverse(), - ), - ); - return [a.scriptLeaf.output, controlBock]; - } - if (a.signature) return [a.signature]; - }); - - // extended validation - if (opts.validate) { - let pubkey: Buffer = NBuffer.from([]); - if (a.address) { - if (network && network.bech32 !== _address().prefix) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== TAPROOT_VERSION) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 32) - throw new TypeError('Invalid address data'); - pubkey = _address().data; - } + !a.address && + !a.output && + !a.pubkey && + !a.output && + !a.internalPubkey && + !(a.witness && a.witness.length > 1) + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); - if (a.pubkey) { - if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.pubkey; - } + typef( + { + address: typef.maybe(typef.String), + input: typef.maybe(typef.BufferN(0)), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.BufferN(34)), + internalPubkey: typef.maybe(typef.BufferN(32)), + hash: typef.maybe(typef.BufferN(32)), + pubkey: typef.maybe(typef.BufferN(32)), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? + scriptLeaf: typef.maybe({ + version: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), + }), + }, + a, + ); - if (a.output) { - if ( - a.output.length !== 34 || - a.output[0] !== OPS.OP_1 || - a.output[1] !== 0x20 - ) - throw new TypeError('Output is invalid'); - if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.output.slice(2); - } + const _address = lazy.value(() => { + const result = bech32m.decode(a.address!); + const version = result.words.shift(); + const data = bech32m.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: NBuffer.from(data), + }; + }); - if (a.internalPubkey) { - const tweakedKey = tweakKey(a.internalPubkey, o.hash); - if (tweakedKey === null) - throw new TypeError('Invalid internalPubkey for p2tr'); - if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) - throw new TypeError('Pubkey mismatch'); - else pubkey = tweakedKey.x; - } + const _witness = lazy.value(() => { + if (!a.witness || !a.witness.length) return; + if ( + a.witness.length >= 2 && + a.witness[a.witness.length - 1][0] === ANNEX_PREFIX + ) { + // remove annex, ignored by taproot + return a.witness.slice(0, -1); + } + return a.witness.slice(); + }); - if (pubkey && pubkey.length) { - if (liftX(pubkey) === null) - throw new TypeError('Invalid pubkey for p2tr'); - } + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { name: 'p2tr', network }; - if (a.hash && a.scriptsTree) { - const hash = toHashTree(a.scriptsTree).hash; - if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); - } + lazy.prop(o, 'address', () => { + if (!o.pubkey) return; - const witness = _witness(); - - if (witness && witness.length) { - if (witness.length === 1) { - // key spending - if (a.signature && !a.signature.equals(witness[0])) - throw new TypeError('Signature mismatch'); - // todo: recheck - // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) - // throw new TypeError('Witness has invalid signature'); - } else { - // script path spending - const controlBlock = witness[witness.length - 1]; - if (controlBlock.length < 33) - throw new TypeError( - `The control-block length is too small. Got ${ - controlBlock.length - }, expected min 33.`, - ); - - if ((controlBlock.length - 33) % 32 !== 0) - throw new TypeError( - `The control-block length of ${controlBlock.length} is incorrect!`, - ); - - const m = (controlBlock.length - 33) / 32; - if (m > 128) - throw new TypeError( - `The script path is too long. Got ${m}, expected max 128.`, - ); - - const internalPubkey = controlBlock.slice(1, 33); - if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) - throw new TypeError('Internal pubkey mismatch'); - - const internalPubkeyPoint = liftX(internalPubkey); - if (!internalPubkeyPoint) - throw new TypeError('Invalid internalPubkey for p2tr witness'); + const words = bech32m.toWords(o.pubkey); + words.unshift(TAPROOT_VERSION); + return bech32m.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.hash) return a.hash; + if (a.scriptsTree) return toHashTree(a.scriptsTree).hash; + const w = _witness(); + if (w && w.length > 1) { + const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; - const script = witness[witness.length - 2]; - + const script = w[w.length - 2]; const leafHash = tapLeafHash(script, leafVersion); - const hash = rootHashFromPath(controlBlock, leafHash); + return rootHashFromPath(controlBlock, leafHash); + } + return null; + }); + lazy.prop(o, 'output', () => { + if (!o.pubkey) return; + return bscript.compile([OPS.OP_1, o.pubkey]); + }); + lazy.prop(o, 'scriptLeaf', () => { + if (a.scriptLeaf) return a.scriptLeaf; + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.internalPubkey) { + const tweakedKey = tweakKey(o.internalPubkey, o.hash); + if (tweakedKey) return tweakedKey.x; + } + }); + lazy.prop(o, 'internalPubkey', () => { + if (a.internalPubkey) return a.internalPubkey; + const witness = _witness(); + if (witness && witness.length > 1) + return witness[witness.length - 1].slice(1, 33); + }); + lazy.prop(o, 'signature', () => { + if (!a.witness || a.witness.length !== 1) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + // todo + }); + lazy.prop(o, 'witness', () => { + if (a.witness) return a.witness; + if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + // todo: optimize/cache + const hashTree = toHashTree(a.scriptsTree); + const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version); + const path = findScriptPath(hashTree, leafHash); + const outputKey = tweakKey(a.internalPubkey, hashTree.hash); + if (!outputKey) return; + const version = a.scriptLeaf.version || 0xc0; + const controlBock = NBuffer.concat( + [NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat( + path.reverse(), + ), + ); + return [a.scriptLeaf.output, controlBock]; + } + if (a.signature) return [a.signature]; + }); + + // extended validation + if (opts.validate) { + let pubkey: Buffer = NBuffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== TAPROOT_VERSION) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + pubkey = _address().data; + } + + if (a.pubkey) { + if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.pubkey; + } - const outputKey = tweakKey(internalPubkey, hash); - if (!outputKey) - // todo: needs test data - throw new TypeError('Invalid outputKey for p2tr witness'); + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_1 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.output.slice(2); + } + + if (a.internalPubkey) { + const tweakedKey = tweakKey(a.internalPubkey, o.hash); + if (pubkey.length > 0 && !pubkey.equals(tweakedKey!.x)) + throw new TypeError('Pubkey mismatch'); + else pubkey = tweakedKey!.x; + } + + if (pubkey && pubkey.length) { + if (!ecc.isXOnlyPoint(pubkey)) + throw new TypeError('Invalid pubkey for p2tr'); + } + + if (a.hash && a.scriptsTree) { + const hash = toHashTree(a.scriptsTree).hash; + if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); + } + + const witness = _witness(); + + if (witness && witness.length) { + if (witness.length === 1) { + // key spending + if (a.signature && !a.signature.equals(witness[0])) + throw new TypeError('Signature mismatch'); + // todo: recheck + // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) + // throw new TypeError('Witness has invalid signature'); + } else { + // script path spending + const controlBlock = witness[witness.length - 1]; + if (controlBlock.length < 33) + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); + + if ((controlBlock.length - 33) % 32 !== 0) + throw new TypeError( + `The control-block length of ${ + controlBlock.length + } is incorrect!`, + ); + + const m = (controlBlock.length - 33) / 32; + if (m > 128) + throw new TypeError( + `The script path is too long. Got ${m}, expected max 128.`, + ); + + const internalPubkey = controlBlock.slice(1, 33); + if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) + throw new TypeError('Internal pubkey mismatch'); - if (pubkey.length && !pubkey.equals(outputKey.x)) - throw new TypeError('Pubkey mismatch for p2tr witness'); + if (!ecc.isXOnlyPoint(internalPubkey)) + throw new TypeError('Invalid internalPubkey for p2tr witness'); - if (outputKey.parity !== (controlBlock[0] & 1)) - throw new Error('Incorrect parity'); + const leafVersion = controlBlock[0] & 0b11111110; + const script = witness[witness.length - 2]; + + const leafHash = tapLeafHash(script, leafVersion); + const hash = rootHashFromPath(controlBlock, leafHash); + + const outputKey = tweakKey(internalPubkey, hash); + if (!outputKey) + // todo: needs test data + throw new TypeError('Invalid outputKey for p2tr witness'); + + if (pubkey.length && !pubkey.equals(outputKey.x)) + throw new TypeError('Pubkey mismatch for p2tr witness'); + + if (outputKey.parity !== (controlBlock[0] & 1)) + throw new Error('Incorrect parity'); + } } } + + return Object.assign(o, a); + }; + + function tweakKey( + pubKey: Buffer, + h: Buffer | undefined, + ): TweakedPublicKey | null { + if (!NBuffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + + const tweakHash = tapTweakHash(pubKey, h); + + const res = ecc.xOnlyPointAddTweak(pubKey, tweakHash); + if (!res || res.xOnlyPubkey === null) return null; + + return { + parity: res.parity, + x: NBuffer.from(res.xOnlyPubkey), + }; } +} - return Object.assign(o, a); +interface TweakedPublicKey { + parity: number; + x: Buffer; } diff --git a/ts_src/payments/testecc.ts b/ts_src/payments/testecc.ts new file mode 100644 index 000000000..773fa3eb8 --- /dev/null +++ b/ts_src/payments/testecc.ts @@ -0,0 +1,174 @@ +import { TinySecp256k1Interface } from '../types'; + +const h = (hex: string): Buffer => Buffer.from(hex, 'hex'); + +export function testEcc(ecc: TinySecp256k1Interface): void { + assert( + ecc.isXOnlyPoint( + h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + ), + ); + assert( + ecc.isXOnlyPoint( + h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e'), + ), + ); + assert( + ecc.isXOnlyPoint( + h('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'), + ), + ); + assert( + ecc.isXOnlyPoint( + h('0000000000000000000000000000000000000000000000000000000000000001'), + ), + ); + assert( + !ecc.isXOnlyPoint( + h('0000000000000000000000000000000000000000000000000000000000000000'), + ), + ); + assert( + !ecc.isXOnlyPoint( + h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'), + ), + ); + + tweakAddVectors.forEach(t => { + const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak)); + if (t.result === null) { + assert(r === null); + } else { + assert(r !== null); + assert(r!.parity === t.parity); + assert(Buffer.from(r!.xOnlyPubkey).equals(h(t.result))); + } + }); +} + +function assert(bool: boolean): void { + if (!bool) throw new Error('ecc library invalid'); +} + +const tweakAddVectors = [ + { + pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', + tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', + parity: -1, + result: null, + }, + { + pubkey: '1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b', + tweak: 'a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac', + parity: 1, + result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf', + }, + { + pubkey: '2f1b310f4c065331bc0d79ba4661bb9822d67d7c4a1b0a1892e1fd0cd23aa68d', + tweak: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', + parity: 1, + result: '5786150f0ac36a2aaeb8d11aaeb7aa2d03ad63c528cc307a7cd5648c84041f34', + }, + { + pubkey: 'e7e9acacbdb43fc9fb71a8db1536c0f866caa78def49f666fa121a6f7954bb01', + tweak: '1ad613216778a70490c8a681a4901d4ca880d76916deb69979b5ac52d2760e09', + parity: 1, + result: 'ae10fa880c85848cc1faf056f56a64b7d45c68838cfb308d03ca2be9b485c130', + }, + { + pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991', + tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47', + parity: 0, + result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c', + }, + { + pubkey: '8f19ee7677713806c662078a9f2b2c7b930376d22f2d617bce50b5e444839a7c', + tweak: '7df1f4b66058f8be34b6b7d17be9bcf35ba5c98edf8d4e763b95964bad655fe4', + parity: 1, + result: '74619a5990750928d0728817b02bb0d398062dad0e568f46ea5348d35bef914f', + }, + { + pubkey: '2bda68b3aa0239d382f185ca2d8c31ce604cc26220cef3eb65223f47a0088d87', + tweak: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', + parity: 0, + result: '84479dc6bf70762721b10b89c8798e00d525507edc3fabbfc89ad915b6509379', + }, + { + pubkey: '9611ba340bce00a594f1ffb1294974af80e1301e49597378732fd77bbdedf454', + tweak: 'bbb8ec1f063522953a4a9f90ff4e849560e0f0597458529ea13b8868f255c7c7', + parity: 0, + result: '30bebfdad18b87b646f60e51d3c45c6658fbb4364c94b1b33d925a4515b66757', + }, + { + pubkey: 'd9f5792078a845303c1f1ea88aec79ed0fd9f0c49e9e7bff2765877e79b4dd52', + tweak: '531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337', + parity: 0, + result: 'fe5dd39f0491af71711454eee5b6a9c99779c422dd97f5e7f75d7ce7be7b32f0', + }, + { + pubkey: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', + tweak: '048968943184ce8a0239ab2141641e8eaead35a6dc8e6b55ad33ac1eca975a47', + parity: 1, + result: '6c8245a62201887c5e3aeb022fff06e6c110f3e58ad6d37cc20e877082b72c58', + }, + { + pubkey: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', + tweak: 'ff8adab52623bcb2717fc71d7edc6f55e98396e6c234dff01f307a12b2af1c99', + parity: 1, + result: 'd6080b5df61525fe8be31a823f3943e5fc9354d5a091b2dea195985c7c395787', + }, + { + pubkey: '24653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c', + tweak: '9e5f7dbe6d62ade5aab476b40559852ea1b5fc7bb99a61a42eab550f69ffafb4', + parity: 0, + result: '58289ee230fcf6a78cb9878cae5102cc9104490abab9d03f3eccc2f0cd07de5f', + }, + { + pubkey: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', + tweak: 'bde750d93efe821813df9c15ee676f2e9c63386336c164f5a15cf240ac653c06', + parity: 0, + result: '9a1ae919c5c78da635d94a92b3053e46b2261b81ec70db82a382f5bff474bec4', + }, + { + pubkey: 'bc14bc97e2d818ee360a9ba7782bd6a6dfc2c1e335fffc584a095fdac5fea641', + tweak: '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa', + parity: 1, + result: '19a7416f4f95f36c5e48dc7630ffea8b292e1721cecfa9cc5f794c83973e41d6', + }, + { + pubkey: '35e9d7c48e3a5254d5881b60abf004cf6eedc6ab842393caa2fdd20d6d0ad170', + tweak: '18bb586dc109adf49ffb42e0ac293d2a2965e49a0a4900c2be776b426b7cbfde', + parity: 0, + result: 'fa7cca72580bb686fbbae09ded801c7d109fa378f52e8a5f43a1922e442e44c1', + }, + { + pubkey: '67bff656551f25009ac8ed88664736c08074a15dbd2268292f5de7ca7e718338', + tweak: 'b96359049e97f49d871e856f37e54d0978bae2cc936b4484d96df984cd20daa1', + parity: 0, + result: 'dd081d737da17fb4f6686f8497cac56b16ea06e1dc05859633f735fb304e7e5a', + }, + { + pubkey: '5b0da52533a1620fe947cb658c35e1772f39ef1253753493b7dc4b8d8f31f40e', + tweak: '3d481f46056f2da27870a5d00c0c7bf484036780a83bbcc2e2da2f03bc33bff0', + parity: 1, + result: '164e13b54edc89673f94563120d87db4a47b12e49c40c195ac51ea7bc50f22e1', + }, + { + pubkey: '0612c5e8c98a9677a2ddd13770e26f5f1e771a088c88ce519a1e1b65872423f9', + tweak: 'dbcfa1c73674cba4aa1b6992ebdc6a77008d38f6c6ec068c3c862b9ff6d287f2', + parity: 0, + result: '82fc6954352b7189a156e4678d0c315c122431fa9551961b8e3c811b55a42c8b', + }, + { + pubkey: '9ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b', + tweak: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', + parity: 1, + result: 'cf9065a7e2c9f909becc1c95f9884ed9fbe19c4a8954ed17880f02d94ae96a63', + }, + { + pubkey: 'c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5', + tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f', + parity: -1, + result: null, + }, +]; diff --git a/ts_src/taproot.ts b/ts_src/taproot.ts index ea996760b..ca8fe9fd5 100644 --- a/ts_src/taproot.ts +++ b/ts_src/taproot.ts @@ -1,88 +1,17 @@ import { Buffer as NBuffer } from 'buffer'; -const BN = require('bn.js'); - import * as bcrypto from './crypto'; + // todo: use varuint-bitcoin?? import * as varuint from 'bip174/src/lib/converter/varint'; -import { - TweakedPublicKey, - TaprootLeaf, - ZERO32, - EC_P, - GROUP_ORDER, -} from './types'; +import { TaprootLeaf } from './types'; // todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. -const ecc = require('tiny-secp256k1'); +// const ecc = require('tiny-secp256k1'); const LEAF_VERSION_TAPSCRIPT = 0xc0; -const TAP_LEAF_TAG ='TapLeaf'; -const TAP_BRANCH_TAG ='TapBranch'; -const TAP_TWEAK_TAG ='TapTweak'; - -const EC_P_BN = new BN(EC_P); -const EC_P_REDUCTION = BN.red(EC_P_BN); -const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); -const BN_2 = new BN(2); -const BN_3 = new BN(3); -const BN_7 = new BN(7); - -export function liftX(buffer: Buffer): Buffer | null { - if (!NBuffer.isBuffer(buffer)) return null; - if (buffer.length !== 32) return null; - - if (buffer.compare(ZERO32) === 0) return null; - if (buffer.compare(EC_P) >= 0) return null; - - const x = new BN(buffer); - - const x1 = x.toRed(EC_P_REDUCTION); - const ySq = x1 - .redPow(BN_3) - .add(BN_7) - .mod(EC_P_BN); - - const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); - - if (!ySq.eq(y.redPow(BN_2))) { - return null; - } - const y1 = y.isEven() ? y : EC_P_BN.sub(y); - - return NBuffer.concat([ - NBuffer.from([0x04]), - NBuffer.from(x1.toBuffer('be', 32)), - NBuffer.from(y1.toBuffer('be', 32)), - ]); -} - -export function tweakKey( - pubKey: Buffer, - h: Buffer | undefined, -): TweakedPublicKey | null { - if (!NBuffer.isBuffer(pubKey)) return null; - if (pubKey.length !== 32) return null; - if (h && h.length !== 32) return null; - - const tweakHash = bcrypto.taggedHash( - TAP_TWEAK_TAG, - NBuffer.concat(h ? [pubKey, h] : [pubKey]), - ); - - if (tweakHash.compare(GROUP_ORDER) >= 0) { - // todo: add test for this case - throw new TypeError('Tweak value over the SECP256K1 Order'); - } - - const P = liftX(pubKey); - if (P === null) return null; - - const Q = pointAddScalar(P, tweakHash); - return { - parity: Q[64] % 2, - x: Q.slice(1, 33), - }; -} +const TAP_LEAF_TAG = 'TapLeaf'; +const TAP_BRANCH_TAG = 'TapBranch'; +const TAP_TWEAK_TAG = 'TapTweak'; export function rootHashFromPath( controlBlock: Buffer, @@ -172,14 +101,16 @@ function tapBranchHash(a: Buffer, b: Buffer): Buffer { return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b])); } +export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { + return bcrypto.taggedHash( + TAP_TWEAK_TAG, + NBuffer.concat(h ? [pubKey, h] : [pubKey]), + ); +} + function serializeScript(s: Buffer): Buffer { const varintLen = varuint.encodingLength(s.length); const buffer = NBuffer.allocUnsafe(varintLen); // better varuint.encode(s.length, buffer); return NBuffer.concat([buffer, s]); } - -// todo: do not use ecc -function pointAddScalar(P: Buffer, h: Buffer): Buffer { - return NBuffer.from(ecc.pointAddScalar(P, h)); -} diff --git a/ts_src/types.ts b/ts_src/types.ts index 7fd2452f1..93446e920 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -71,15 +71,22 @@ export const Network = typeforce.compile({ wif: typeforce.UInt8, }); -export interface TweakedPublicKey { - parity: number; - x: Buffer; +export interface XOnlyPointAddTweakResult { + parity: 1 | 0; + xOnlyPubkey: Uint8Array; } export interface TaprootLeaf { output: Buffer; version?: number; } +export interface TinySecp256k1Interface { + isXOnlyPoint(p: Uint8Array): boolean; + xOnlyPointAddTweak( + p: Uint8Array, + tweak: Uint8Array, + ): XOnlyPointAddTweakResult | null; +} export const Buffer256bit = typeforce.BufferN(32); export const Hash160bit = typeforce.BufferN(20); From e64c2d87b21414b496f3d2d7dbac2aab011d9094 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 16:56:14 +0200 Subject: [PATCH 32/73] chore: code format --- test/payments.spec.ts | 178 +++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 87 deletions(-) diff --git a/test/payments.spec.ts b/test/payments.spec.ts index 957396199..b151a4d05 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -5,108 +5,112 @@ import { PaymentCreator, PaymentFactory } from '../src/payments'; import * as u from './payments.utils'; const payments = PaymentFactory(ecc); -['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach(p => { - describe(p, () => { - //@ts-ignore - const fn: PaymentCreator = payments[p]; +['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach( + p => { + describe(p, () => { + //@ts-ignore + const fn: PaymentCreator = payments[p]; - const fixtures = require('./fixtures/' + p); + const fixtures = require('./fixtures/' + p); - fixtures.valid.forEach((f: any) => { - it(f.description + ' as expected', () => { - const args = u.preform(f.arguments); - const actual = fn(args, f.options); + fixtures.valid.forEach((f: any) => { + it(f.description + ' as expected', () => { + const args = u.preform(f.arguments); + const actual = fn(args, f.options); - u.equate(actual, f.expected, f.arguments); - }); + u.equate(actual, f.expected, f.arguments); + }); - it(f.description + ' as expected (no validation)', () => { - const args = u.preform(f.arguments); - const actual = fn( - args, - Object.assign({}, f.options, { - validate: false, - }), - ); + it(f.description + ' as expected (no validation)', () => { + const args = u.preform(f.arguments); + const actual = fn( + args, + Object.assign({}, f.options, { + validate: false, + }), + ); - u.equate(actual, f.expected, f.arguments); + u.equate(actual, f.expected, f.arguments); + }); }); - }); - fixtures.invalid.forEach((f: any) => { - it( - 'throws ' + f.exception + (f.description ? 'for ' + f.description : ''), - () => { - const args = u.preform(f.arguments); + fixtures.invalid.forEach((f: any) => { + it( + 'throws ' + + f.exception + + (f.description ? 'for ' + f.description : ''), + () => { + const args = u.preform(f.arguments); - assert.throws(() => { - fn(args, f.options); - }, new RegExp(f.exception)); - }, - ); - }); + assert.throws(() => { + fn(args, f.options); + }, new RegExp(f.exception)); + }, + ); + }); - if (p === 'p2sh') { - const p2wsh = require('../src/payments/p2wsh').p2wsh; - const p2pk = require('../src/payments/p2pk').p2pk; - it('properly assembles nested p2wsh with names', () => { - const actual = fn({ - redeem: p2wsh({ - redeem: p2pk({ - pubkey: Buffer.from( - '03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058', - 'hex', - ), + if (p === 'p2sh') { + const p2wsh = require('../src/payments/p2wsh').p2wsh; + const p2pk = require('../src/payments/p2pk').p2pk; + it('properly assembles nested p2wsh with names', () => { + const actual = fn({ + redeem: p2wsh({ + redeem: p2pk({ + pubkey: Buffer.from( + '03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058', + 'hex', + ), + }), }), - }), + }); + assert.strictEqual( + actual.address, + '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw', + ); + assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk'); + assert.strictEqual(actual.redeem!.name, 'p2wsh-p2pk'); + assert.strictEqual(actual.redeem!.redeem!.name, 'p2pk'); }); - assert.strictEqual( - actual.address, - '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw', - ); - assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk'); - assert.strictEqual(actual.redeem!.name, 'p2wsh-p2pk'); - assert.strictEqual(actual.redeem!.redeem!.name, 'p2pk'); - }); - } + } - // cross-verify dynamically too - if (!fixtures.dynamic) return; - const { depends, details } = fixtures.dynamic; + // cross-verify dynamically too + if (!fixtures.dynamic) return; + const { depends, details } = fixtures.dynamic; - details.forEach((f: any) => { - const detail = u.preform(f); - const disabled: any = {}; - if (f.disabled) - f.disabled.forEach((k: string) => { - disabled[k] = true; - }); + details.forEach((f: any) => { + const detail = u.preform(f); + const disabled: any = {}; + if (f.disabled) + f.disabled.forEach((k: string) => { + disabled[k] = true; + }); - for (const key in depends) { - if (key in disabled) continue; - const dependencies = depends[key]; + for (const key in depends) { + if (key in disabled) continue; + const dependencies = depends[key]; - dependencies.forEach((dependency: any) => { - if (!Array.isArray(dependency)) dependency = [dependency]; + dependencies.forEach((dependency: any) => { + if (!Array.isArray(dependency)) dependency = [dependency]; - const args = {}; - dependency.forEach((d: any) => { - u.from(d, detail, args); - }); - const expected = u.from(key, detail); + const args = {}; + dependency.forEach((d: any) => { + u.from(d, detail, args); + }); + const expected = u.from(key, detail); - it( - f.description + - ', ' + - key + - ' derives from ' + - JSON.stringify(dependency), - () => { - u.equate(fn(args), expected); - }, - ); - }); - } + it( + f.description + + ', ' + + key + + ' derives from ' + + JSON.stringify(dependency), + () => { + u.equate(fn(args), expected); + }, + ); + }); + } + }); }); - }); -}); + }, +); From d987d8d48c5de939dfe62eebfed49d5a5019321f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 17:03:08 +0200 Subject: [PATCH 33/73] refactor: move taproot utils file --- src/payments/p2tr.js | 26 +++++++++++-------- .../taprootutils.d.ts} | 2 +- src/{taproot.js => payments/taprootutils.js} | 11 +++----- ts_src/payments/p2tr.ts | 4 +-- .../{taproot.ts => payments/taprootutils.ts} | 10 +++---- 5 files changed, 25 insertions(+), 28 deletions(-) rename src/{taproot.d.ts => payments/taprootutils.d.ts} (92%) rename src/{taproot.js => payments/taprootutils.js} (89%) rename ts_src/{taproot.ts => payments/taprootutils.ts} (90%) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 78ef95311..4c79a9b1d 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -5,7 +5,7 @@ const buffer_1 = require('buffer'); const networks_1 = require('../networks'); const bscript = require('../script'); const types_1 = require('../types'); -const taproot_1 = require('../taproot'); +const taprootutils_1 = require('./taprootutils'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); const OPS = bscript.OPS; @@ -75,14 +75,15 @@ function p2tr(ecc) { }); lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return (0, taproot_1.toHashTree)(a.scriptsTree).hash; + if (a.scriptsTree) + return (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & 0b11111110; const script = w[w.length - 2]; - const leafHash = (0, taproot_1.tapLeafHash)(script, leafVersion); - return (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); + const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); + return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); } return null; }); @@ -119,12 +120,12 @@ function p2tr(ecc) { if (a.witness) return a.witness; if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { // todo: optimize/cache - const hashTree = (0, taproot_1.toHashTree)(a.scriptsTree); - const leafHash = (0, taproot_1.tapLeafHash)( + const hashTree = (0, taprootutils_1.toHashTree)(a.scriptsTree); + const leafHash = (0, taprootutils_1.tapLeafHash)( a.scriptLeaf.output, a.scriptLeaf.version, ); - const path = (0, taproot_1.findScriptPath)(hashTree, leafHash); + const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); const outputKey = tweakKey(a.internalPubkey, hashTree.hash); if (!outputKey) return; const version = a.scriptLeaf.version || 0xc0; @@ -177,7 +178,7 @@ function p2tr(ecc) { throw new TypeError('Invalid pubkey for p2tr'); } if (a.hash && a.scriptsTree) { - const hash = (0, taproot_1.toHashTree)(a.scriptsTree).hash; + const hash = (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } const witness = _witness(); @@ -216,8 +217,11 @@ function p2tr(ecc) { throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & 0b11111110; const script = witness[witness.length - 2]; - const leafHash = (0, taproot_1.tapLeafHash)(script, leafVersion); - const hash = (0, taproot_1.rootHashFromPath)(controlBlock, leafHash); + const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); + const hash = (0, taprootutils_1.rootHashFromPath)( + controlBlock, + leafHash, + ); const outputKey = tweakKey(internalPubkey, hash); if (!outputKey) // todo: needs test data @@ -235,7 +239,7 @@ function p2tr(ecc) { if (!buffer_1.Buffer.isBuffer(pubKey)) return null; if (pubKey.length !== 32) return null; if (h && h.length !== 32) return null; - const tweakHash = (0, taproot_1.tapTweakHash)(pubKey, h); + const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h); const res = ecc.xOnlyPointAddTweak(pubKey, tweakHash); if (!res || res.xOnlyPubkey === null) return null; return { diff --git a/src/taproot.d.ts b/src/payments/taprootutils.d.ts similarity index 92% rename from src/taproot.d.ts rename to src/payments/taprootutils.d.ts index 37c118430..185ee79de 100644 --- a/src/taproot.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,5 +1,5 @@ /// -import { TaprootLeaf } from './types'; +import { TaprootLeaf } from '../types'; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; export interface HashTree { hash: Buffer; diff --git a/src/taproot.js b/src/payments/taprootutils.js similarity index 89% rename from src/taproot.js rename to src/payments/taprootutils.js index 98a7db377..d97169b1b 100644 --- a/src/taproot.js +++ b/src/payments/taprootutils.js @@ -2,11 +2,8 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.tapTweakHash = exports.tapLeafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = void 0; const buffer_1 = require('buffer'); -const bcrypto = require('./crypto'); -// todo: use varuint-bitcoin?? -const varuint = require('bip174/src/lib/converter/varint'); -// todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. -// const ecc = require('tiny-secp256k1'); +const bcrypto = require('../crypto'); +const bufferutils_1 = require('../bufferutils'); const LEAF_VERSION_TAPSCRIPT = 0xc0; const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; @@ -92,8 +89,8 @@ function tapTweakHash(pubKey, h) { } exports.tapTweakHash = tapTweakHash; function serializeScript(s) { - const varintLen = varuint.encodingLength(s.length); + const varintLen = bufferutils_1.varuint.encodingLength(s.length); const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better - varuint.encode(s.length, buffer); + bufferutils_1.varuint.encode(s.length, buffer); return buffer_1.Buffer.concat([buffer, s]); } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 0bb68465d..4520e93b5 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -8,12 +8,12 @@ import { findScriptPath, tapLeafHash, tapTweakHash, -} from '../taproot'; +} from './taprootutils'; import { Payment, PaymentOpts, PaymentCreator } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; -const OPS = bscript.OPS; +const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; diff --git a/ts_src/taproot.ts b/ts_src/payments/taprootutils.ts similarity index 90% rename from ts_src/taproot.ts rename to ts_src/payments/taprootutils.ts index ca8fe9fd5..94567b37c 100644 --- a/ts_src/taproot.ts +++ b/ts_src/payments/taprootutils.ts @@ -1,12 +1,8 @@ import { Buffer as NBuffer } from 'buffer'; -import * as bcrypto from './crypto'; +import * as bcrypto from '../crypto'; -// todo: use varuint-bitcoin?? -import * as varuint from 'bip174/src/lib/converter/varint'; -import { TaprootLeaf } from './types'; - -// todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. -// const ecc = require('tiny-secp256k1'); +import { varuint } from '../bufferutils'; +import { TaprootLeaf } from '../types'; const LEAF_VERSION_TAPSCRIPT = 0xc0; const TAP_LEAF_TAG = 'TapLeaf'; From 9f51a1a540512009c2f8134cc71ad539a40343ee Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 17:04:06 +0200 Subject: [PATCH 34/73] refactor: move non-exported function to the bottom --- ts_src/payments/taprootutils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 94567b37c..cbfc3bfac 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -93,10 +93,6 @@ export function tapLeafHash(script: Buffer, version?: number): Buffer { ); } -function tapBranchHash(a: Buffer, b: Buffer): Buffer { - return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b])); -} - export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { return bcrypto.taggedHash( TAP_TWEAK_TAG, @@ -104,6 +100,10 @@ export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { ); } +function tapBranchHash(a: Buffer, b: Buffer): Buffer { + return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b])); +} + function serializeScript(s: Buffer): Buffer { const varintLen = varuint.encodingLength(s.length); const buffer = NBuffer.allocUnsafe(varintLen); // better From bc6358f1e9cb60ca49c171edf78520aca40a90c4 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 17:14:33 +0200 Subject: [PATCH 35/73] fix: lint & gitdiff issues --- src/payments/taprootutils.js | 6 +++--- test/payments.spec.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index d97169b1b..fbcf90a7a 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -78,9 +78,6 @@ function tapLeafHash(script, version) { ); } exports.tapLeafHash = tapLeafHash; -function tapBranchHash(a, b) { - return bcrypto.taggedHash(TAP_BRANCH_TAG, buffer_1.Buffer.concat([a, b])); -} function tapTweakHash(pubKey, h) { return bcrypto.taggedHash( TAP_TWEAK_TAG, @@ -88,6 +85,9 @@ function tapTweakHash(pubKey, h) { ); } exports.tapTweakHash = tapTweakHash; +function tapBranchHash(a, b) { + return bcrypto.taggedHash(TAP_BRANCH_TAG, buffer_1.Buffer.concat([a, b])); +} function serializeScript(s) { const varintLen = bufferutils_1.varuint.encodingLength(s.length); const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better diff --git a/test/payments.spec.ts b/test/payments.spec.ts index b151a4d05..139594af2 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -8,7 +8,7 @@ const payments = PaymentFactory(ecc); ['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach( p => { describe(p, () => { - //@ts-ignore + // @ts-ignore const fn: PaymentCreator = payments[p]; const fixtures = require('./fixtures/' + p); From f61371c7d573f0c0b659a4fb9e502f42a7583ef7 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 18:39:06 +0200 Subject: [PATCH 36/73] chore: removed un-used exports --- src/types.d.ts | 4 ---- src/types.js | 18 +++++++----------- ts_src/types.ts | 8 ++------ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/types.d.ts b/src/types.d.ts index 014c2b910..aefc6bed7 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,9 +1,5 @@ /// -import { Buffer as NBuffer } from 'buffer'; export declare const typeforce: any; -export declare const ZERO32: NBuffer; -export declare const EC_P: NBuffer; -export declare const GROUP_ORDER: NBuffer; export declare function isPoint(p: Buffer | number | undefined | null): boolean; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; diff --git a/src/types.js b/src/types.js index a8acbef86..a6d1efa16 100644 --- a/src/types.js +++ b/src/types.js @@ -1,30 +1,26 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.GROUP_ORDER = exports.EC_P = exports.ZERO32 = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0; const buffer_1 = require('buffer'); exports.typeforce = require('typeforce'); -exports.ZERO32 = buffer_1.Buffer.alloc(32, 0); -exports.EC_P = buffer_1.Buffer.from( +const ZERO32 = buffer_1.Buffer.alloc(32, 0); +const EC_P = buffer_1.Buffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); -exports.GROUP_ORDER = buffer_1.Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', -); function isPoint(p) { if (!buffer_1.Buffer.isBuffer(p)) return false; if (p.length < 33) return false; const t = p[0]; const x = p.slice(1, 33); - if (x.compare(exports.ZERO32) === 0) return false; - if (x.compare(exports.EC_P) >= 0) return false; + if (x.compare(ZERO32) === 0) return false; + if (x.compare(EC_P) >= 0) return false; if ((t === 0x02 || t === 0x03) && p.length === 33) { return true; } const y = p.slice(33); - if (y.compare(exports.ZERO32) === 0) return false; - if (y.compare(exports.EC_P) >= 0) return false; + if (y.compare(ZERO32) === 0) return false; + if (y.compare(EC_P) >= 0) return false; if (t === 0x04 && p.length === 65) return true; return false; } diff --git a/ts_src/types.ts b/ts_src/types.ts index 93446e920..840ab9be2 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -2,15 +2,11 @@ import { Buffer as NBuffer } from 'buffer'; export const typeforce = require('typeforce'); -export const ZERO32 = NBuffer.alloc(32, 0); -export const EC_P = NBuffer.from( +const ZERO32 = NBuffer.alloc(32, 0); +const EC_P = NBuffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); -export const GROUP_ORDER = NBuffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', -); export function isPoint(p: Buffer | number | undefined | null): boolean { if (!NBuffer.isBuffer(p)) return false; From 4573e6c0ec82a845fd2cfc48653ea8b62c6d47e2 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 18:40:55 +0200 Subject: [PATCH 37/73] chore: add docs, simplify code --- src/payments/taprootutils.d.ts | 16 +++++++++++++++- src/payments/taprootutils.js | 26 +++++++++++++++++++------- ts_src/payments/taprootutils.ts | 27 ++++++++++++++++++++------- 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index 185ee79de..f68a37495 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -6,7 +6,21 @@ export interface HashTree { left?: HashTree; right?: HashTree; } -export declare function toHashTree(scripts: TaprootLeaf[]): HashTree; +/** + * Build the hash tree from the scripts binary tree. + * The binary tree can be balanced or not. + * @param scriptsTree - is a list representing a binary tree where an element can be: + * - a taproot leaf [(output, version)], or + * - a pair of two taproot leafs [(output, version), (output, version)], or + * - one taproot leaf and a list of elements + */ +export declare function toHashTree(scriptsTree: TaprootLeaf[]): HashTree; +/** + * Given a MAST tree, it finds the path of a particular hash. + * @param node - the root of the tree + * @param hash - the hash to search for + * @returns - and array of hashes representing the path, or an empty array if no pat is found + */ export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[]; export declare function tapLeafHash(script: Buffer, version?: number): Buffer; export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer; diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index fbcf90a7a..2da6a4b7d 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -23,9 +23,17 @@ function rootHashFromPath(controlBlock, tapLeafMsg) { return k[m]; } exports.rootHashFromPath = rootHashFromPath; -function toHashTree(scripts) { - if (scripts.length === 1) { - const script = scripts[0]; +/** + * Build the hash tree from the scripts binary tree. + * The binary tree can be balanced or not. + * @param scriptsTree - is a list representing a binary tree where an element can be: + * - a taproot leaf [(output, version)], or + * - a pair of two taproot leafs [(output, version), (output, version)], or + * - one taproot leaf and a list of elements + */ +function toHashTree(scriptsTree) { + if (scriptsTree.length === 1) { + const script = scriptsTree[0]; if (Array.isArray(script)) { return toHashTree(script); } @@ -36,10 +44,8 @@ function toHashTree(scripts) { hash: tapLeafHash(script.output, script.version), }; } - // todo: this is a binary tree, use zero an one index - const half = Math.trunc(scripts.length / 2); - const left = toHashTree(scripts.slice(0, half)); - const right = toHashTree(scripts.slice(half)); + const left = toHashTree([scriptsTree[0]]); + const right = toHashTree([scriptsTree[1]]); let leftHash = left.hash; let rightHash = right.hash; if (leftHash.compare(rightHash) === 1) @@ -51,6 +57,12 @@ function toHashTree(scripts) { }; } exports.toHashTree = toHashTree; +/** + * Given a MAST tree, it finds the path of a particular hash. + * @param node - the root of the tree + * @param hash - the hash to search for + * @returns - and array of hashes representing the path, or an empty array if no pat is found + */ function findScriptPath(node, hash) { if (node.left) { if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : []; diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index cbfc3bfac..83ed9b14c 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -36,9 +36,17 @@ export interface HashTree { right?: HashTree; } -export function toHashTree(scripts: TaprootLeaf[]): HashTree { - if (scripts.length === 1) { - const script = scripts[0]; +/** + * Build the hash tree from the scripts binary tree. + * The binary tree can be balanced or not. + * @param scriptsTree - is a list representing a binary tree where an element can be: + * - a taproot leaf [(output, version)], or + * - a pair of two taproot leafs [(output, version), (output, version)], or + * - one taproot leaf and a list of elements + */ +export function toHashTree(scriptsTree: TaprootLeaf[]): HashTree { + if (scriptsTree.length === 1) { + const script = scriptsTree[0]; if (Array.isArray(script)) { return toHashTree(script); } @@ -50,10 +58,9 @@ export function toHashTree(scripts: TaprootLeaf[]): HashTree { hash: tapLeafHash(script.output, script.version), }; } - // todo: this is a binary tree, use zero an one index - const half = Math.trunc(scripts.length / 2); - const left = toHashTree(scripts.slice(0, half)); - const right = toHashTree(scripts.slice(half)); + + const left = toHashTree([scriptsTree[0]]); + const right = toHashTree([scriptsTree[1]]); let leftHash = left.hash; let rightHash = right.hash; @@ -67,6 +74,12 @@ export function toHashTree(scripts: TaprootLeaf[]): HashTree { }; } +/** + * Given a MAST tree, it finds the path of a particular hash. + * @param node - the root of the tree + * @param hash - the hash to search for + * @returns - and array of hashes representing the path, or an empty array if no pat is found + */ export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] { if (node.left) { if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : []; From ddc1e8dc7bf33d41089336f2a2f6f665912f5125 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 14 Jan 2022 16:18:13 +0200 Subject: [PATCH 38/73] feat: pass the ECC library as an optional parameter to p2tr --- src/payments/index.d.ts | 16 +- src/payments/index.js | 24 +- src/payments/p2tr.d.ts | 4 +- src/payments/p2tr.js | 458 ++++++++++++++++----------------- src/payments/testecc.js | 2 + test/payments.spec.ts | 22 +- ts_src/payments/index.ts | 28 +-- ts_src/payments/p2tr.ts | 502 +++++++++++++++++++------------------ ts_src/payments/testecc.ts | 2 + 9 files changed, 523 insertions(+), 535 deletions(-) diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 72db5de25..a72a8ea41 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -8,6 +8,7 @@ import { p2pkh } from './p2pkh'; import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; +import { p2tr } from './p2tr'; export interface Payment { name?: string; network?: Network; @@ -28,24 +29,13 @@ export interface Payment { scriptLeaf?: TaprootLeaf; witness?: Buffer[]; } -export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; +export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts, eccLib?: TinySecp256k1Interface) => Payment; export declare type PaymentFunction = () => Payment; export interface PaymentOpts { validate?: boolean; allowIncomplete?: boolean; } -export interface PaymentAPI { - embed: PaymentCreator; - p2ms: PaymentCreator; - p2pk: PaymentCreator; - p2pkh: PaymentCreator; - p2sh: PaymentCreator; - p2wpkh: PaymentCreator; - p2wsh: PaymentCreator; - p2tr: PaymentCreator; -} export declare type StackElement = Buffer | number; export declare type Stack = StackElement[]; export declare type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, PaymentFactory }; -export default function PaymentFactory(ecc: TinySecp256k1Interface): PaymentAPI; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; diff --git a/src/payments/index.js b/src/payments/index.js index 779df03fa..9ce55f859 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.PaymentFactory = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; +exports.p2tr = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; const embed_1 = require('./embed'); Object.defineProperty(exports, 'embed', { enumerable: true, @@ -51,21 +51,11 @@ Object.defineProperty(exports, 'p2wsh', { }, }); const p2tr_1 = require('./p2tr'); -const testecc_1 = require('./testecc'); -function PaymentFactory(ecc) { - (0, testecc_1.testEcc)(ecc); - return { - embed: embed_1.p2data, - p2ms: p2ms_1.p2ms, - p2pk: p2pk_1.p2pk, - p2pkh: p2pkh_1.p2pkh, - p2sh: p2sh_1.p2sh, - p2wpkh: p2wpkh_1.p2wpkh, - p2wsh: p2wsh_1.p2wsh, - p2tr: (0, p2tr_1.p2tr)(ecc), - }; -} -exports.default = PaymentFactory; -exports.PaymentFactory = PaymentFactory; +Object.defineProperty(exports, 'p2tr', { + enumerable: true, + get: function() { + return p2tr_1.p2tr; + }, +}); // TODO // witness commitment diff --git a/src/payments/p2tr.d.ts b/src/payments/p2tr.d.ts index e4d4c8a9b..3268bf450 100644 --- a/src/payments/p2tr.d.ts +++ b/src/payments/p2tr.d.ts @@ -1,3 +1,3 @@ import { TinySecp256k1Interface } from '../types'; -import { PaymentCreator } from './index'; -export declare function p2tr(ecc: TinySecp256k1Interface): PaymentCreator; +import { Payment, PaymentOpts } from './index'; +export declare function p2tr(a: Payment, opts?: PaymentOpts, eccLib?: TinySecp256k1Interface): Payment; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 4c79a9b1d..1517ef0c6 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -8,244 +8,246 @@ const types_1 = require('../types'); const taprootutils_1 = require('./taprootutils'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); +const testecc_1 = require('./testecc'); const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -function p2tr(ecc) { - return (a, opts) => { +function p2tr(a, opts, eccLib) { + if ( + !a.address && + !a.output && + !a.pubkey && + !a.output && + !a.internalPubkey && + !(a.witness && a.witness.length > 1) + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + const _ecc = lazy.value(() => { + if (!eccLib) throw new Error('ECC Library is missing for p2tr.'); + (0, testecc_1.testEcc)(eccLib); + return eccLib; + }); + (0, types_1.typeforce)( + { + address: types_1.typeforce.maybe(types_1.typeforce.String), + input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)), + internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), + // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? + scriptLeaf: types_1.typeforce.maybe({ + version: types_1.typeforce.maybe(types_1.typeforce.Number), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + }), + }, + a, + ); + const _address = lazy.value(() => { + const result = bech32_1.bech32m.decode(a.address); + const version = result.words.shift(); + const data = bech32_1.bech32m.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: buffer_1.Buffer.from(data), + }; + }); + const _witness = lazy.value(() => { + if (!a.witness || !a.witness.length) return; if ( - !a.address && - !a.output && - !a.pubkey && - !a.output && - !a.internalPubkey && - !(a.witness && a.witness.length > 1) - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - (0, types_1.typeforce)( - { - address: types_1.typeforce.maybe(types_1.typeforce.String), - input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), - network: types_1.typeforce.maybe(types_1.typeforce.Object), - output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)), - internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), - hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), - pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), - signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), - witness: types_1.typeforce.maybe( - types_1.typeforce.arrayOf(types_1.typeforce.Buffer), - ), - // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? - scriptLeaf: types_1.typeforce.maybe({ - version: types_1.typeforce.maybe(types_1.typeforce.Number), - output: types_1.typeforce.maybe(types_1.typeforce.Buffer), - }), - }, - a, - ); - const _address = lazy.value(() => { - const result = bech32_1.bech32m.decode(a.address); - const version = result.words.shift(); - const data = bech32_1.bech32m.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: buffer_1.Buffer.from(data), - }; - }); - const _witness = lazy.value(() => { - if (!a.witness || !a.witness.length) return; + a.witness.length >= 2 && + a.witness[a.witness.length - 1][0] === ANNEX_PREFIX + ) { + // remove annex, ignored by taproot + return a.witness.slice(0, -1); + } + return a.witness.slice(); + }); + const network = a.network || networks_1.bitcoin; + const o = { name: 'p2tr', network }; + lazy.prop(o, 'address', () => { + if (!o.pubkey) return; + const words = bech32_1.bech32m.toWords(o.pubkey); + words.unshift(TAPROOT_VERSION); + return bech32_1.bech32m.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.hash) return a.hash; + if (a.scriptsTree) + return (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; + const w = _witness(); + if (w && w.length > 1) { + const controlBlock = w[w.length - 1]; + const leafVersion = controlBlock[0] & 0b11111110; + const script = w[w.length - 2]; + const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); + return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); + } + return null; + }); + lazy.prop(o, 'output', () => { + if (!o.pubkey) return; + return bscript.compile([OPS.OP_1, o.pubkey]); + }); + lazy.prop(o, 'scriptLeaf', () => { + if (a.scriptLeaf) return a.scriptLeaf; + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.internalPubkey) { + const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc()); + if (tweakedKey) return tweakedKey.x; + } + }); + lazy.prop(o, 'internalPubkey', () => { + if (a.internalPubkey) return a.internalPubkey; + const witness = _witness(); + if (witness && witness.length > 1) + return witness[witness.length - 1].slice(1, 33); + }); + lazy.prop(o, 'signature', () => { + if (!a.witness || a.witness.length !== 1) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + // todo + }); + lazy.prop(o, 'witness', () => { + if (a.witness) return a.witness; + if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + // todo: optimize/cache + const hashTree = (0, taprootutils_1.toHashTree)(a.scriptsTree); + const leafHash = (0, taprootutils_1.tapLeafHash)( + a.scriptLeaf.output, + a.scriptLeaf.version, + ); + const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); + const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); + if (!outputKey) return; + const version = a.scriptLeaf.version || 0xc0; + const controlBock = buffer_1.Buffer.concat( + [ + buffer_1.Buffer.from([version | outputKey.parity]), + a.internalPubkey, + ].concat(path.reverse()), + ); + return [a.scriptLeaf.output, controlBock]; + } + if (a.signature) return [a.signature]; + }); + // extended validation + if (opts.validate) { + let pubkey = buffer_1.Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== TAPROOT_VERSION) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + pubkey = _address().data; + } + if (a.pubkey) { + if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.pubkey; + } + if (a.output) { if ( - a.witness.length >= 2 && - a.witness[a.witness.length - 1][0] === ANNEX_PREFIX - ) { - // remove annex, ignored by taproot - return a.witness.slice(0, -1); - } - return a.witness.slice(); - }); - const network = a.network || networks_1.bitcoin; - const o = { name: 'p2tr', network }; - lazy.prop(o, 'address', () => { - if (!o.pubkey) return; - const words = bech32_1.bech32m.toWords(o.pubkey); - words.unshift(TAPROOT_VERSION); - return bech32_1.bech32m.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.hash) return a.hash; - if (a.scriptsTree) - return (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; - const w = _witness(); - if (w && w.length > 1) { - const controlBlock = w[w.length - 1]; + a.output.length !== 34 || + a.output[0] !== OPS.OP_1 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.output.slice(2); + } + if (a.internalPubkey) { + const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc()); + if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) + throw new TypeError('Pubkey mismatch'); + else pubkey = tweakedKey.x; + } + if (pubkey && pubkey.length) { + if (!_ecc().isXOnlyPoint(pubkey)) + throw new TypeError('Invalid pubkey for p2tr'); + } + if (a.hash && a.scriptsTree) { + const hash = (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; + if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); + } + const witness = _witness(); + if (witness && witness.length) { + if (witness.length === 1) { + // key spending + if (a.signature && !a.signature.equals(witness[0])) + throw new TypeError('Signature mismatch'); + // todo: recheck + // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) + // throw new TypeError('Witness has invalid signature'); + } else { + // script path spending + const controlBlock = witness[witness.length - 1]; + if (controlBlock.length < 33) + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); + if ((controlBlock.length - 33) % 32 !== 0) + throw new TypeError( + `The control-block length of ${controlBlock.length} is incorrect!`, + ); + const m = (controlBlock.length - 33) / 32; + if (m > 128) + throw new TypeError( + `The script path is too long. Got ${m}, expected max 128.`, + ); + const internalPubkey = controlBlock.slice(1, 33); + if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) + throw new TypeError('Internal pubkey mismatch'); + if (!_ecc().isXOnlyPoint(internalPubkey)) + throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & 0b11111110; - const script = w[w.length - 2]; + const script = witness[witness.length - 2]; const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); - return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); - } - return null; - }); - lazy.prop(o, 'output', () => { - if (!o.pubkey) return; - return bscript.compile([OPS.OP_1, o.pubkey]); - }); - lazy.prop(o, 'scriptLeaf', () => { - if (a.scriptLeaf) return a.scriptLeaf; - }); - lazy.prop(o, 'pubkey', () => { - if (a.pubkey) return a.pubkey; - if (a.output) return a.output.slice(2); - if (a.address) return _address().data; - if (o.internalPubkey) { - const tweakedKey = tweakKey(o.internalPubkey, o.hash); - if (tweakedKey) return tweakedKey.x; - } - }); - lazy.prop(o, 'internalPubkey', () => { - if (a.internalPubkey) return a.internalPubkey; - const witness = _witness(); - if (witness && witness.length > 1) - return witness[witness.length - 1].slice(1, 33); - }); - lazy.prop(o, 'signature', () => { - if (!a.witness || a.witness.length !== 1) return; - return a.witness[0]; - }); - lazy.prop(o, 'input', () => { - // todo - }); - lazy.prop(o, 'witness', () => { - if (a.witness) return a.witness; - if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { - // todo: optimize/cache - const hashTree = (0, taprootutils_1.toHashTree)(a.scriptsTree); - const leafHash = (0, taprootutils_1.tapLeafHash)( - a.scriptLeaf.output, - a.scriptLeaf.version, + const hash = (0, taprootutils_1.rootHashFromPath)( + controlBlock, + leafHash, ); - const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); - const outputKey = tweakKey(a.internalPubkey, hashTree.hash); - if (!outputKey) return; - const version = a.scriptLeaf.version || 0xc0; - const controlBock = buffer_1.Buffer.concat( - [ - buffer_1.Buffer.from([version | outputKey.parity]), - a.internalPubkey, - ].concat(path.reverse()), - ); - return [a.scriptLeaf.output, controlBock]; - } - if (a.signature) return [a.signature]; - }); - // extended validation - if (opts.validate) { - let pubkey = buffer_1.Buffer.from([]); - if (a.address) { - if (network && network.bech32 !== _address().prefix) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== TAPROOT_VERSION) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 32) - throw new TypeError('Invalid address data'); - pubkey = _address().data; - } - if (a.pubkey) { - if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.pubkey; - } - if (a.output) { - if ( - a.output.length !== 34 || - a.output[0] !== OPS.OP_1 || - a.output[1] !== 0x20 - ) - throw new TypeError('Output is invalid'); - if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.output.slice(2); - } - if (a.internalPubkey) { - const tweakedKey = tweakKey(a.internalPubkey, o.hash); - if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) - throw new TypeError('Pubkey mismatch'); - else pubkey = tweakedKey.x; - } - if (pubkey && pubkey.length) { - if (!ecc.isXOnlyPoint(pubkey)) - throw new TypeError('Invalid pubkey for p2tr'); - } - if (a.hash && a.scriptsTree) { - const hash = (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; - if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); - } - const witness = _witness(); - if (witness && witness.length) { - if (witness.length === 1) { - // key spending - if (a.signature && !a.signature.equals(witness[0])) - throw new TypeError('Signature mismatch'); - // todo: recheck - // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) - // throw new TypeError('Witness has invalid signature'); - } else { - // script path spending - const controlBlock = witness[witness.length - 1]; - if (controlBlock.length < 33) - throw new TypeError( - `The control-block length is too small. Got ${ - controlBlock.length - }, expected min 33.`, - ); - if ((controlBlock.length - 33) % 32 !== 0) - throw new TypeError( - `The control-block length of ${ - controlBlock.length - } is incorrect!`, - ); - const m = (controlBlock.length - 33) / 32; - if (m > 128) - throw new TypeError( - `The script path is too long. Got ${m}, expected max 128.`, - ); - const internalPubkey = controlBlock.slice(1, 33); - if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) - throw new TypeError('Internal pubkey mismatch'); - if (!ecc.isXOnlyPoint(internalPubkey)) - throw new TypeError('Invalid internalPubkey for p2tr witness'); - const leafVersion = controlBlock[0] & 0b11111110; - const script = witness[witness.length - 2]; - const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); - const hash = (0, taprootutils_1.rootHashFromPath)( - controlBlock, - leafHash, - ); - const outputKey = tweakKey(internalPubkey, hash); - if (!outputKey) - // todo: needs test data - throw new TypeError('Invalid outputKey for p2tr witness'); - if (pubkey.length && !pubkey.equals(outputKey.x)) - throw new TypeError('Pubkey mismatch for p2tr witness'); - if (outputKey.parity !== (controlBlock[0] & 1)) - throw new Error('Incorrect parity'); - } + const outputKey = tweakKey(internalPubkey, hash, _ecc()); + if (!outputKey) + // todo: needs test data + throw new TypeError('Invalid outputKey for p2tr witness'); + if (pubkey.length && !pubkey.equals(outputKey.x)) + throw new TypeError('Pubkey mismatch for p2tr witness'); + if (outputKey.parity !== (controlBlock[0] & 1)) + throw new Error('Incorrect parity'); } } - return Object.assign(o, a); - }; - function tweakKey(pubKey, h) { - if (!buffer_1.Buffer.isBuffer(pubKey)) return null; - if (pubKey.length !== 32) return null; - if (h && h.length !== 32) return null; - const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h); - const res = ecc.xOnlyPointAddTweak(pubKey, tweakHash); - if (!res || res.xOnlyPubkey === null) return null; - return { - parity: res.parity, - x: buffer_1.Buffer.from(res.xOnlyPubkey), - }; } + return Object.assign(o, a); } exports.p2tr = p2tr; +function tweakKey(pubKey, h, eccLib) { + if (!buffer_1.Buffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h); + const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash); + if (!res || res.xOnlyPubkey === null) return null; + return { + parity: res.parity, + x: buffer_1.Buffer.from(res.xOnlyPubkey), + }; +} diff --git a/src/payments/testecc.js b/src/payments/testecc.js index 9bdc62031..77e2b3e15 100644 --- a/src/payments/testecc.js +++ b/src/payments/testecc.js @@ -3,6 +3,7 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.testEcc = void 0; const h = hex => Buffer.from(hex, 'hex'); function testEcc(ecc) { + assert(typeof ecc.isXOnlyPoint === 'function'); assert( ecc.isXOnlyPoint( h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), @@ -33,6 +34,7 @@ function testEcc(ecc) { h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'), ), ); + assert(typeof ecc.xOnlyPointAddTweak === 'function'); tweakAddVectors.forEach(t => { const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak)); if (t.result === null) { diff --git a/test/payments.spec.ts b/test/payments.spec.ts index 139594af2..e5227124e 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -1,22 +1,29 @@ import * as assert from 'assert'; -import { describe, it } from 'mocha'; import * as ecc from 'tiny-secp256k1'; -import { PaymentCreator, PaymentFactory } from '../src/payments'; +import { describe, it } from 'mocha'; +import { PaymentCreator } from '../src/payments'; import * as u from './payments.utils'; +import { TinySecp256k1Interface } from '../src/types'; -const payments = PaymentFactory(ecc); ['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach( p => { describe(p, () => { - // @ts-ignore - const fn: PaymentCreator = payments[p]; + let fn: PaymentCreator; + const eccLib: TinySecp256k1Interface | undefined = + p === 'p2tr' ? ecc : undefined; + const payment = require('../src/payments/' + p); + if (p === 'embed') { + fn = payment.p2data; + } else { + fn = payment[p]; + } const fixtures = require('./fixtures/' + p); fixtures.valid.forEach((f: any) => { it(f.description + ' as expected', () => { const args = u.preform(f.arguments); - const actual = fn(args, f.options); + const actual = fn(args, f.options, eccLib); u.equate(actual, f.expected, f.arguments); }); @@ -28,6 +35,7 @@ const payments = PaymentFactory(ecc); Object.assign({}, f.options, { validate: false, }), + eccLib, ); u.equate(actual, f.expected, f.arguments); @@ -43,7 +51,7 @@ const payments = PaymentFactory(ecc); const args = u.preform(f.arguments); assert.throws(() => { - fn(args, f.options); + fn(args, f.options, eccLib); }, new RegExp(f.exception)); }, ); diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index f7c5dbadd..f2dcfb939 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -8,7 +8,6 @@ import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; import { p2tr } from './p2tr'; -import { testEcc } from './testecc'; export interface Payment { name?: string; @@ -31,7 +30,11 @@ export interface Payment { witness?: Buffer[]; } -export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; +export type PaymentCreator = ( + a: Payment, + opts?: PaymentOpts, + eccLib?: TinySecp256k1Interface, +) => Payment; export type PaymentFunction = () => Payment; @@ -40,30 +43,11 @@ export interface PaymentOpts { allowIncomplete?: boolean; } -export interface PaymentAPI { - embed: PaymentCreator; - p2ms: PaymentCreator; - p2pk: PaymentCreator; - p2pkh: PaymentCreator; - p2sh: PaymentCreator; - p2wpkh: PaymentCreator; - p2wsh: PaymentCreator; - p2tr: PaymentCreator; -} - export type StackElement = Buffer | number; export type Stack = StackElement[]; export type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, PaymentFactory }; - -export default function PaymentFactory( - ecc: TinySecp256k1Interface, -): PaymentAPI { - testEcc(ecc); - - return { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr: p2tr(ecc) }; -} +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; // TODO // witness commitment diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 4520e93b5..1ecce1432 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -9,275 +9,285 @@ import { tapLeafHash, tapTweakHash, } from './taprootutils'; -import { Payment, PaymentOpts, PaymentCreator } from './index'; +import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; +import { testEcc } from './testecc'; const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -export function p2tr(ecc: TinySecp256k1Interface): PaymentCreator { - return (a: Payment, opts?: PaymentOpts): Payment => { - if ( - !a.address && - !a.output && - !a.pubkey && - !a.output && - !a.internalPubkey && - !(a.witness && a.witness.length > 1) - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - - typef( - { - address: typef.maybe(typef.String), - input: typef.maybe(typef.BufferN(0)), - network: typef.maybe(typef.Object), - output: typef.maybe(typef.BufferN(34)), - internalPubkey: typef.maybe(typef.BufferN(32)), - hash: typef.maybe(typef.BufferN(32)), - pubkey: typef.maybe(typef.BufferN(32)), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), - // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? - scriptLeaf: typef.maybe({ - version: typef.maybe(typef.Number), - output: typef.maybe(typef.Buffer), - }), - }, - a, - ); - - const _address = lazy.value(() => { - const result = bech32m.decode(a.address!); - const version = result.words.shift(); - const data = bech32m.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: NBuffer.from(data), - }; - }); - - const _witness = lazy.value(() => { - if (!a.witness || !a.witness.length) return; - if ( - a.witness.length >= 2 && - a.witness[a.witness.length - 1][0] === ANNEX_PREFIX - ) { - // remove annex, ignored by taproot - return a.witness.slice(0, -1); - } - return a.witness.slice(); - }); - - const network = a.network || BITCOIN_NETWORK; - const o: Payment = { name: 'p2tr', network }; - - lazy.prop(o, 'address', () => { - if (!o.pubkey) return; - - const words = bech32m.toWords(o.pubkey); - words.unshift(TAPROOT_VERSION); - return bech32m.encode(network.bech32, words); - }); - - lazy.prop(o, 'hash', () => { - if (a.hash) return a.hash; - if (a.scriptsTree) return toHashTree(a.scriptsTree).hash; - const w = _witness(); - if (w && w.length > 1) { - const controlBlock = w[w.length - 1]; - const leafVersion = controlBlock[0] & 0b11111110; - const script = w[w.length - 2]; - const leafHash = tapLeafHash(script, leafVersion); - return rootHashFromPath(controlBlock, leafHash); - } - return null; - }); - lazy.prop(o, 'output', () => { - if (!o.pubkey) return; - return bscript.compile([OPS.OP_1, o.pubkey]); - }); - lazy.prop(o, 'scriptLeaf', () => { - if (a.scriptLeaf) return a.scriptLeaf; - }); - lazy.prop(o, 'pubkey', () => { - if (a.pubkey) return a.pubkey; - if (a.output) return a.output.slice(2); - if (a.address) return _address().data; - if (o.internalPubkey) { - const tweakedKey = tweakKey(o.internalPubkey, o.hash); - if (tweakedKey) return tweakedKey.x; - } - }); - lazy.prop(o, 'internalPubkey', () => { - if (a.internalPubkey) return a.internalPubkey; - const witness = _witness(); - if (witness && witness.length > 1) - return witness[witness.length - 1].slice(1, 33); - }); - lazy.prop(o, 'signature', () => { - if (!a.witness || a.witness.length !== 1) return; - return a.witness[0]; - }); - lazy.prop(o, 'input', () => { - // todo - }); - lazy.prop(o, 'witness', () => { - if (a.witness) return a.witness; - if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { - // todo: optimize/cache - const hashTree = toHashTree(a.scriptsTree); - const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version); - const path = findScriptPath(hashTree, leafHash); - const outputKey = tweakKey(a.internalPubkey, hashTree.hash); - if (!outputKey) return; - const version = a.scriptLeaf.version || 0xc0; - const controlBock = NBuffer.concat( - [NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat( - path.reverse(), - ), - ); - return [a.scriptLeaf.output, controlBock]; - } - if (a.signature) return [a.signature]; - }); - - // extended validation - if (opts.validate) { - let pubkey: Buffer = NBuffer.from([]); - if (a.address) { - if (network && network.bech32 !== _address().prefix) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== TAPROOT_VERSION) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 32) - throw new TypeError('Invalid address data'); - pubkey = _address().data; - } +export function p2tr( + a: Payment, + opts?: PaymentOpts, + eccLib?: TinySecp256k1Interface, +): Payment { + if ( + !a.address && + !a.output && + !a.pubkey && + !a.output && + !a.internalPubkey && + !(a.witness && a.witness.length > 1) + ) + throw new TypeError('Not enough data'); + + opts = Object.assign({ validate: true }, opts || {}); + + const _ecc = lazy.value(() => { + if (!eccLib) throw new Error('ECC Library is missing for p2tr.'); + + testEcc(eccLib); + return eccLib; + }); + + typef( + { + address: typef.maybe(typef.String), + input: typef.maybe(typef.BufferN(0)), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.BufferN(34)), + internalPubkey: typef.maybe(typef.BufferN(32)), + hash: typef.maybe(typef.BufferN(32)), + pubkey: typef.maybe(typef.BufferN(32)), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? + scriptLeaf: typef.maybe({ + version: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), + }), + }, + a, + ); + + const _address = lazy.value(() => { + const result = bech32m.decode(a.address!); + const version = result.words.shift(); + const data = bech32m.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: NBuffer.from(data), + }; + }); - if (a.pubkey) { - if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.pubkey; - } + const _witness = lazy.value(() => { + if (!a.witness || !a.witness.length) return; + if ( + a.witness.length >= 2 && + a.witness[a.witness.length - 1][0] === ANNEX_PREFIX + ) { + // remove annex, ignored by taproot + return a.witness.slice(0, -1); + } + return a.witness.slice(); + }); + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { name: 'p2tr', network }; + + lazy.prop(o, 'address', () => { + if (!o.pubkey) return; + + const words = bech32m.toWords(o.pubkey); + words.unshift(TAPROOT_VERSION); + return bech32m.encode(network.bech32, words); + }); + + lazy.prop(o, 'hash', () => { + if (a.hash) return a.hash; + if (a.scriptsTree) return toHashTree(a.scriptsTree).hash; + const w = _witness(); + if (w && w.length > 1) { + const controlBlock = w[w.length - 1]; + const leafVersion = controlBlock[0] & 0b11111110; + const script = w[w.length - 2]; + const leafHash = tapLeafHash(script, leafVersion); + return rootHashFromPath(controlBlock, leafHash); + } + return null; + }); + lazy.prop(o, 'output', () => { + if (!o.pubkey) return; + return bscript.compile([OPS.OP_1, o.pubkey]); + }); + lazy.prop(o, 'scriptLeaf', () => { + if (a.scriptLeaf) return a.scriptLeaf; + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.internalPubkey) { + const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc()); + if (tweakedKey) return tweakedKey.x; + } + }); + lazy.prop(o, 'internalPubkey', () => { + if (a.internalPubkey) return a.internalPubkey; + const witness = _witness(); + if (witness && witness.length > 1) + return witness[witness.length - 1].slice(1, 33); + }); + lazy.prop(o, 'signature', () => { + if (!a.witness || a.witness.length !== 1) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + // todo + }); + lazy.prop(o, 'witness', () => { + if (a.witness) return a.witness; + if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + // todo: optimize/cache + const hashTree = toHashTree(a.scriptsTree); + const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version); + const path = findScriptPath(hashTree, leafHash); + const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); + if (!outputKey) return; + const version = a.scriptLeaf.version || 0xc0; + const controlBock = NBuffer.concat( + [NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat( + path.reverse(), + ), + ); + return [a.scriptLeaf.output, controlBock]; + } + if (a.signature) return [a.signature]; + }); + + // extended validation + if (opts.validate) { + let pubkey: Buffer = NBuffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== TAPROOT_VERSION) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + pubkey = _address().data; + } - if (a.output) { - if ( - a.output.length !== 34 || - a.output[0] !== OPS.OP_1 || - a.output[1] !== 0x20 - ) - throw new TypeError('Output is invalid'); - if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) - throw new TypeError('Pubkey mismatch'); - else pubkey = a.output.slice(2); - } + if (a.pubkey) { + if (pubkey.length > 0 && !pubkey.equals(a.pubkey)) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.pubkey; + } - if (a.internalPubkey) { - const tweakedKey = tweakKey(a.internalPubkey, o.hash); - if (pubkey.length > 0 && !pubkey.equals(tweakedKey!.x)) - throw new TypeError('Pubkey mismatch'); - else pubkey = tweakedKey!.x; - } + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_1 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2))) + throw new TypeError('Pubkey mismatch'); + else pubkey = a.output.slice(2); + } - if (pubkey && pubkey.length) { - if (!ecc.isXOnlyPoint(pubkey)) - throw new TypeError('Invalid pubkey for p2tr'); - } + if (a.internalPubkey) { + const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc()); + if (pubkey.length > 0 && !pubkey.equals(tweakedKey!.x)) + throw new TypeError('Pubkey mismatch'); + else pubkey = tweakedKey!.x; + } - if (a.hash && a.scriptsTree) { - const hash = toHashTree(a.scriptsTree).hash; - if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); - } + if (pubkey && pubkey.length) { + if (!_ecc().isXOnlyPoint(pubkey)) + throw new TypeError('Invalid pubkey for p2tr'); + } - const witness = _witness(); - - if (witness && witness.length) { - if (witness.length === 1) { - // key spending - if (a.signature && !a.signature.equals(witness[0])) - throw new TypeError('Signature mismatch'); - // todo: recheck - // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) - // throw new TypeError('Witness has invalid signature'); - } else { - // script path spending - const controlBlock = witness[witness.length - 1]; - if (controlBlock.length < 33) - throw new TypeError( - `The control-block length is too small. Got ${ - controlBlock.length - }, expected min 33.`, - ); - - if ((controlBlock.length - 33) % 32 !== 0) - throw new TypeError( - `The control-block length of ${ - controlBlock.length - } is incorrect!`, - ); - - const m = (controlBlock.length - 33) / 32; - if (m > 128) - throw new TypeError( - `The script path is too long. Got ${m}, expected max 128.`, - ); - - const internalPubkey = controlBlock.slice(1, 33); - if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) - throw new TypeError('Internal pubkey mismatch'); - - if (!ecc.isXOnlyPoint(internalPubkey)) - throw new TypeError('Invalid internalPubkey for p2tr witness'); - - const leafVersion = controlBlock[0] & 0b11111110; - const script = witness[witness.length - 2]; - - const leafHash = tapLeafHash(script, leafVersion); - const hash = rootHashFromPath(controlBlock, leafHash); - - const outputKey = tweakKey(internalPubkey, hash); - if (!outputKey) - // todo: needs test data - throw new TypeError('Invalid outputKey for p2tr witness'); - - if (pubkey.length && !pubkey.equals(outputKey.x)) - throw new TypeError('Pubkey mismatch for p2tr witness'); - - if (outputKey.parity !== (controlBlock[0] & 1)) - throw new Error('Incorrect parity'); - } - } + if (a.hash && a.scriptsTree) { + const hash = toHashTree(a.scriptsTree).hash; + if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } - return Object.assign(o, a); - }; + const witness = _witness(); + + if (witness && witness.length) { + if (witness.length === 1) { + // key spending + if (a.signature && !a.signature.equals(witness[0])) + throw new TypeError('Signature mismatch'); + // todo: recheck + // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) + // throw new TypeError('Witness has invalid signature'); + } else { + // script path spending + const controlBlock = witness[witness.length - 1]; + if (controlBlock.length < 33) + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); + + if ((controlBlock.length - 33) % 32 !== 0) + throw new TypeError( + `The control-block length of ${controlBlock.length} is incorrect!`, + ); + + const m = (controlBlock.length - 33) / 32; + if (m > 128) + throw new TypeError( + `The script path is too long. Got ${m}, expected max 128.`, + ); + + const internalPubkey = controlBlock.slice(1, 33); + if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) + throw new TypeError('Internal pubkey mismatch'); + + if (!_ecc().isXOnlyPoint(internalPubkey)) + throw new TypeError('Invalid internalPubkey for p2tr witness'); - function tweakKey( - pubKey: Buffer, - h: Buffer | undefined, - ): TweakedPublicKey | null { - if (!NBuffer.isBuffer(pubKey)) return null; - if (pubKey.length !== 32) return null; - if (h && h.length !== 32) return null; + const leafVersion = controlBlock[0] & 0b11111110; + const script = witness[witness.length - 2]; - const tweakHash = tapTweakHash(pubKey, h); + const leafHash = tapLeafHash(script, leafVersion); + const hash = rootHashFromPath(controlBlock, leafHash); - const res = ecc.xOnlyPointAddTweak(pubKey, tweakHash); - if (!res || res.xOnlyPubkey === null) return null; + const outputKey = tweakKey(internalPubkey, hash, _ecc()); + if (!outputKey) + // todo: needs test data + throw new TypeError('Invalid outputKey for p2tr witness'); - return { - parity: res.parity, - x: NBuffer.from(res.xOnlyPubkey), - }; + if (pubkey.length && !pubkey.equals(outputKey.x)) + throw new TypeError('Pubkey mismatch for p2tr witness'); + + if (outputKey.parity !== (controlBlock[0] & 1)) + throw new Error('Incorrect parity'); + } + } } + + return Object.assign(o, a); } interface TweakedPublicKey { parity: number; x: Buffer; } + +function tweakKey( + pubKey: Buffer, + h: Buffer | undefined, + eccLib: TinySecp256k1Interface, +): TweakedPublicKey | null { + if (!NBuffer.isBuffer(pubKey)) return null; + if (pubKey.length !== 32) return null; + if (h && h.length !== 32) return null; + + const tweakHash = tapTweakHash(pubKey, h); + + const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash); + if (!res || res.xOnlyPubkey === null) return null; + + return { + parity: res.parity, + x: NBuffer.from(res.xOnlyPubkey), + }; +} diff --git a/ts_src/payments/testecc.ts b/ts_src/payments/testecc.ts index 773fa3eb8..5e8643d67 100644 --- a/ts_src/payments/testecc.ts +++ b/ts_src/payments/testecc.ts @@ -3,6 +3,7 @@ import { TinySecp256k1Interface } from '../types'; const h = (hex: string): Buffer => Buffer.from(hex, 'hex'); export function testEcc(ecc: TinySecp256k1Interface): void { + assert(typeof ecc.isXOnlyPoint === 'function'); assert( ecc.isXOnlyPoint( h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), @@ -34,6 +35,7 @@ export function testEcc(ecc: TinySecp256k1Interface): void { ), ); + assert(typeof ecc.xOnlyPointAddTweak === 'function'); tweakAddVectors.forEach(t => { const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak)); if (t.result === null) { From 4cad59ce2d32c01ff33d2bd69f2903f6967ecfd5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 18 Jan 2022 09:10:37 +0200 Subject: [PATCH 39/73] refactor: move eccLib to PaymentOptions --- src/payments/index.d.ts | 1 + src/payments/p2tr.d.ts | 3 +-- src/payments/p2tr.js | 8 ++++---- test/payments.spec.ts | 8 +++++--- ts_src/payments/index.ts | 1 + ts_src/payments/p2tr.ts | 12 ++++-------- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index a72a8ea41..6c3e09c1a 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -34,6 +34,7 @@ export declare type PaymentFunction = () => Payment; export interface PaymentOpts { validate?: boolean; allowIncomplete?: boolean; + eccLib?: TinySecp256k1Interface; } export declare type StackElement = Buffer | number; export declare type Stack = StackElement[]; diff --git a/src/payments/p2tr.d.ts b/src/payments/p2tr.d.ts index 3268bf450..350ed0ffc 100644 --- a/src/payments/p2tr.d.ts +++ b/src/payments/p2tr.d.ts @@ -1,3 +1,2 @@ -import { TinySecp256k1Interface } from '../types'; import { Payment, PaymentOpts } from './index'; -export declare function p2tr(a: Payment, opts?: PaymentOpts, eccLib?: TinySecp256k1Interface): Payment; +export declare function p2tr(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 1517ef0c6..7e944e68b 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -12,7 +12,7 @@ const testecc_1 = require('./testecc'); const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -function p2tr(a, opts, eccLib) { +function p2tr(a, opts) { if ( !a.address && !a.output && @@ -24,9 +24,9 @@ function p2tr(a, opts, eccLib) { throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); const _ecc = lazy.value(() => { - if (!eccLib) throw new Error('ECC Library is missing for p2tr.'); - (0, testecc_1.testEcc)(eccLib); - return eccLib; + if (!opts.eccLib) throw new Error('ECC Library is missing for p2tr.'); + (0, testecc_1.testEcc)(opts.eccLib); + return opts.eccLib; }); (0, types_1.typeforce)( { diff --git a/test/payments.spec.ts b/test/payments.spec.ts index e5227124e..6726e96d8 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -21,9 +21,10 @@ import { TinySecp256k1Interface } from '../src/types'; const fixtures = require('./fixtures/' + p); fixtures.valid.forEach((f: any) => { + const options = Object.assign({ eccLib }, f.options || {}); it(f.description + ' as expected', () => { const args = u.preform(f.arguments); - const actual = fn(args, f.options, eccLib); + const actual = fn(args, options, eccLib); u.equate(actual, f.expected, f.arguments); }); @@ -32,7 +33,7 @@ import { TinySecp256k1Interface } from '../src/types'; const args = u.preform(f.arguments); const actual = fn( args, - Object.assign({}, f.options, { + Object.assign({}, options, { validate: false, }), eccLib, @@ -43,6 +44,7 @@ import { TinySecp256k1Interface } from '../src/types'; }); fixtures.invalid.forEach((f: any) => { + const options = Object.assign({ eccLib }, f.options || {}); it( 'throws ' + f.exception + @@ -51,7 +53,7 @@ import { TinySecp256k1Interface } from '../src/types'; const args = u.preform(f.arguments); assert.throws(() => { - fn(args, f.options, eccLib); + fn(args, options, eccLib); }, new RegExp(f.exception)); }, ); diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index f2dcfb939..577bdcb22 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -41,6 +41,7 @@ export type PaymentFunction = () => Payment; export interface PaymentOpts { validate?: boolean; allowIncomplete?: boolean; + eccLib?: TinySecp256k1Interface; } export type StackElement = Buffer | number; diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 1ecce1432..66fa4946d 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -18,11 +18,7 @@ const OPS = bscript.OPS; const TAPROOT_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -export function p2tr( - a: Payment, - opts?: PaymentOpts, - eccLib?: TinySecp256k1Interface, -): Payment { +export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if ( !a.address && !a.output && @@ -36,10 +32,10 @@ export function p2tr( opts = Object.assign({ validate: true }, opts || {}); const _ecc = lazy.value(() => { - if (!eccLib) throw new Error('ECC Library is missing for p2tr.'); + if (!opts!.eccLib) throw new Error('ECC Library is missing for p2tr.'); - testEcc(eccLib); - return eccLib; + testEcc(opts!.eccLib); + return opts!.eccLib; }); typef( From c6706989324a7f2a92558515b3b99002c352da2a Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 20 Jan 2022 08:51:44 +0200 Subject: [PATCH 40/73] fix: remove `TinySecp256k1Interface` from `PaymentCreator` --- src/payments/index.d.ts | 2 +- test/payments.spec.ts | 5 ++--- ts_src/payments/index.ts | 6 +----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 6c3e09c1a..6b186c4d1 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -29,7 +29,7 @@ export interface Payment { scriptLeaf?: TaprootLeaf; witness?: Buffer[]; } -export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts, eccLib?: TinySecp256k1Interface) => Payment; +export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; export declare type PaymentFunction = () => Payment; export interface PaymentOpts { validate?: boolean; diff --git a/test/payments.spec.ts b/test/payments.spec.ts index 6726e96d8..e89834d3b 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -24,7 +24,7 @@ import { TinySecp256k1Interface } from '../src/types'; const options = Object.assign({ eccLib }, f.options || {}); it(f.description + ' as expected', () => { const args = u.preform(f.arguments); - const actual = fn(args, options, eccLib); + const actual = fn(args, options); u.equate(actual, f.expected, f.arguments); }); @@ -36,7 +36,6 @@ import { TinySecp256k1Interface } from '../src/types'; Object.assign({}, options, { validate: false, }), - eccLib, ); u.equate(actual, f.expected, f.arguments); @@ -53,7 +52,7 @@ import { TinySecp256k1Interface } from '../src/types'; const args = u.preform(f.arguments); assert.throws(() => { - fn(args, options, eccLib); + fn(args, options); }, new RegExp(f.exception)); }, ); diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 577bdcb22..7bb77b6ac 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -30,11 +30,7 @@ export interface Payment { witness?: Buffer[]; } -export type PaymentCreator = ( - a: Payment, - opts?: PaymentOpts, - eccLib?: TinySecp256k1Interface, -) => Payment; +export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; export type PaymentFunction = () => Payment; From b566dd76bd3e917eb4eea03e1f58425fed7bbf0d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 20 Jan 2022 08:52:43 +0200 Subject: [PATCH 41/73] chore: keep ecc test vectors to a minimum --- src/payments/testecc.js | 102 ------------------------------------- ts_src/payments/testecc.ts | 102 ------------------------------------- 2 files changed, 204 deletions(-) diff --git a/src/payments/testecc.js b/src/payments/testecc.js index 77e2b3e15..44e19c887 100644 --- a/src/payments/testecc.js +++ b/src/payments/testecc.js @@ -63,112 +63,10 @@ const tweakAddVectors = [ parity: 1, result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf', }, - { - pubkey: '2f1b310f4c065331bc0d79ba4661bb9822d67d7c4a1b0a1892e1fd0cd23aa68d', - tweak: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', - parity: 1, - result: '5786150f0ac36a2aaeb8d11aaeb7aa2d03ad63c528cc307a7cd5648c84041f34', - }, - { - pubkey: 'e7e9acacbdb43fc9fb71a8db1536c0f866caa78def49f666fa121a6f7954bb01', - tweak: '1ad613216778a70490c8a681a4901d4ca880d76916deb69979b5ac52d2760e09', - parity: 1, - result: 'ae10fa880c85848cc1faf056f56a64b7d45c68838cfb308d03ca2be9b485c130', - }, { pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991', tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47', parity: 0, result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c', }, - { - pubkey: '8f19ee7677713806c662078a9f2b2c7b930376d22f2d617bce50b5e444839a7c', - tweak: '7df1f4b66058f8be34b6b7d17be9bcf35ba5c98edf8d4e763b95964bad655fe4', - parity: 1, - result: '74619a5990750928d0728817b02bb0d398062dad0e568f46ea5348d35bef914f', - }, - { - pubkey: '2bda68b3aa0239d382f185ca2d8c31ce604cc26220cef3eb65223f47a0088d87', - tweak: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', - parity: 0, - result: '84479dc6bf70762721b10b89c8798e00d525507edc3fabbfc89ad915b6509379', - }, - { - pubkey: '9611ba340bce00a594f1ffb1294974af80e1301e49597378732fd77bbdedf454', - tweak: 'bbb8ec1f063522953a4a9f90ff4e849560e0f0597458529ea13b8868f255c7c7', - parity: 0, - result: '30bebfdad18b87b646f60e51d3c45c6658fbb4364c94b1b33d925a4515b66757', - }, - { - pubkey: 'd9f5792078a845303c1f1ea88aec79ed0fd9f0c49e9e7bff2765877e79b4dd52', - tweak: '531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337', - parity: 0, - result: 'fe5dd39f0491af71711454eee5b6a9c99779c422dd97f5e7f75d7ce7be7b32f0', - }, - { - pubkey: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', - tweak: '048968943184ce8a0239ab2141641e8eaead35a6dc8e6b55ad33ac1eca975a47', - parity: 1, - result: '6c8245a62201887c5e3aeb022fff06e6c110f3e58ad6d37cc20e877082b72c58', - }, - { - pubkey: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', - tweak: 'ff8adab52623bcb2717fc71d7edc6f55e98396e6c234dff01f307a12b2af1c99', - parity: 1, - result: 'd6080b5df61525fe8be31a823f3943e5fc9354d5a091b2dea195985c7c395787', - }, - { - pubkey: '24653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c', - tweak: '9e5f7dbe6d62ade5aab476b40559852ea1b5fc7bb99a61a42eab550f69ffafb4', - parity: 0, - result: '58289ee230fcf6a78cb9878cae5102cc9104490abab9d03f3eccc2f0cd07de5f', - }, - { - pubkey: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', - tweak: 'bde750d93efe821813df9c15ee676f2e9c63386336c164f5a15cf240ac653c06', - parity: 0, - result: '9a1ae919c5c78da635d94a92b3053e46b2261b81ec70db82a382f5bff474bec4', - }, - { - pubkey: 'bc14bc97e2d818ee360a9ba7782bd6a6dfc2c1e335fffc584a095fdac5fea641', - tweak: '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa', - parity: 1, - result: '19a7416f4f95f36c5e48dc7630ffea8b292e1721cecfa9cc5f794c83973e41d6', - }, - { - pubkey: '35e9d7c48e3a5254d5881b60abf004cf6eedc6ab842393caa2fdd20d6d0ad170', - tweak: '18bb586dc109adf49ffb42e0ac293d2a2965e49a0a4900c2be776b426b7cbfde', - parity: 0, - result: 'fa7cca72580bb686fbbae09ded801c7d109fa378f52e8a5f43a1922e442e44c1', - }, - { - pubkey: '67bff656551f25009ac8ed88664736c08074a15dbd2268292f5de7ca7e718338', - tweak: 'b96359049e97f49d871e856f37e54d0978bae2cc936b4484d96df984cd20daa1', - parity: 0, - result: 'dd081d737da17fb4f6686f8497cac56b16ea06e1dc05859633f735fb304e7e5a', - }, - { - pubkey: '5b0da52533a1620fe947cb658c35e1772f39ef1253753493b7dc4b8d8f31f40e', - tweak: '3d481f46056f2da27870a5d00c0c7bf484036780a83bbcc2e2da2f03bc33bff0', - parity: 1, - result: '164e13b54edc89673f94563120d87db4a47b12e49c40c195ac51ea7bc50f22e1', - }, - { - pubkey: '0612c5e8c98a9677a2ddd13770e26f5f1e771a088c88ce519a1e1b65872423f9', - tweak: 'dbcfa1c73674cba4aa1b6992ebdc6a77008d38f6c6ec068c3c862b9ff6d287f2', - parity: 0, - result: '82fc6954352b7189a156e4678d0c315c122431fa9551961b8e3c811b55a42c8b', - }, - { - pubkey: '9ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b', - tweak: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', - parity: 1, - result: 'cf9065a7e2c9f909becc1c95f9884ed9fbe19c4a8954ed17880f02d94ae96a63', - }, - { - pubkey: 'c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5', - tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f', - parity: -1, - result: null, - }, ]; diff --git a/ts_src/payments/testecc.ts b/ts_src/payments/testecc.ts index 5e8643d67..382d6149a 100644 --- a/ts_src/payments/testecc.ts +++ b/ts_src/payments/testecc.ts @@ -65,112 +65,10 @@ const tweakAddVectors = [ parity: 1, result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf', }, - { - pubkey: '2f1b310f4c065331bc0d79ba4661bb9822d67d7c4a1b0a1892e1fd0cd23aa68d', - tweak: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', - parity: 1, - result: '5786150f0ac36a2aaeb8d11aaeb7aa2d03ad63c528cc307a7cd5648c84041f34', - }, - { - pubkey: 'e7e9acacbdb43fc9fb71a8db1536c0f866caa78def49f666fa121a6f7954bb01', - tweak: '1ad613216778a70490c8a681a4901d4ca880d76916deb69979b5ac52d2760e09', - parity: 1, - result: 'ae10fa880c85848cc1faf056f56a64b7d45c68838cfb308d03ca2be9b485c130', - }, { pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991', tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47', parity: 0, result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c', }, - { - pubkey: '8f19ee7677713806c662078a9f2b2c7b930376d22f2d617bce50b5e444839a7c', - tweak: '7df1f4b66058f8be34b6b7d17be9bcf35ba5c98edf8d4e763b95964bad655fe4', - parity: 1, - result: '74619a5990750928d0728817b02bb0d398062dad0e568f46ea5348d35bef914f', - }, - { - pubkey: '2bda68b3aa0239d382f185ca2d8c31ce604cc26220cef3eb65223f47a0088d87', - tweak: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', - parity: 0, - result: '84479dc6bf70762721b10b89c8798e00d525507edc3fabbfc89ad915b6509379', - }, - { - pubkey: '9611ba340bce00a594f1ffb1294974af80e1301e49597378732fd77bbdedf454', - tweak: 'bbb8ec1f063522953a4a9f90ff4e849560e0f0597458529ea13b8868f255c7c7', - parity: 0, - result: '30bebfdad18b87b646f60e51d3c45c6658fbb4364c94b1b33d925a4515b66757', - }, - { - pubkey: 'd9f5792078a845303c1f1ea88aec79ed0fd9f0c49e9e7bff2765877e79b4dd52', - tweak: '531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337', - parity: 0, - result: 'fe5dd39f0491af71711454eee5b6a9c99779c422dd97f5e7f75d7ce7be7b32f0', - }, - { - pubkey: '40ab636041bb695843ac7f220344565bed3b5dca919e256b7e31e19d69b36fad', - tweak: '048968943184ce8a0239ab2141641e8eaead35a6dc8e6b55ad33ac1eca975a47', - parity: 1, - result: '6c8245a62201887c5e3aeb022fff06e6c110f3e58ad6d37cc20e877082b72c58', - }, - { - pubkey: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', - tweak: 'ff8adab52623bcb2717fc71d7edc6f55e98396e6c234dff01f307a12b2af1c99', - parity: 1, - result: 'd6080b5df61525fe8be31a823f3943e5fc9354d5a091b2dea195985c7c395787', - }, - { - pubkey: '24653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c', - tweak: '9e5f7dbe6d62ade5aab476b40559852ea1b5fc7bb99a61a42eab550f69ffafb4', - parity: 0, - result: '58289ee230fcf6a78cb9878cae5102cc9104490abab9d03f3eccc2f0cd07de5f', - }, - { - pubkey: 'e5a018b3a2e155316109d9cdc5eab739759c0e07e0c00bf9fccb8237fe4d7f02', - tweak: 'bde750d93efe821813df9c15ee676f2e9c63386336c164f5a15cf240ac653c06', - parity: 0, - result: '9a1ae919c5c78da635d94a92b3053e46b2261b81ec70db82a382f5bff474bec4', - }, - { - pubkey: 'bc14bc97e2d818ee360a9ba7782bd6a6dfc2c1e335fffc584a095fdac5fea641', - tweak: '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa', - parity: 1, - result: '19a7416f4f95f36c5e48dc7630ffea8b292e1721cecfa9cc5f794c83973e41d6', - }, - { - pubkey: '35e9d7c48e3a5254d5881b60abf004cf6eedc6ab842393caa2fdd20d6d0ad170', - tweak: '18bb586dc109adf49ffb42e0ac293d2a2965e49a0a4900c2be776b426b7cbfde', - parity: 0, - result: 'fa7cca72580bb686fbbae09ded801c7d109fa378f52e8a5f43a1922e442e44c1', - }, - { - pubkey: '67bff656551f25009ac8ed88664736c08074a15dbd2268292f5de7ca7e718338', - tweak: 'b96359049e97f49d871e856f37e54d0978bae2cc936b4484d96df984cd20daa1', - parity: 0, - result: 'dd081d737da17fb4f6686f8497cac56b16ea06e1dc05859633f735fb304e7e5a', - }, - { - pubkey: '5b0da52533a1620fe947cb658c35e1772f39ef1253753493b7dc4b8d8f31f40e', - tweak: '3d481f46056f2da27870a5d00c0c7bf484036780a83bbcc2e2da2f03bc33bff0', - parity: 1, - result: '164e13b54edc89673f94563120d87db4a47b12e49c40c195ac51ea7bc50f22e1', - }, - { - pubkey: '0612c5e8c98a9677a2ddd13770e26f5f1e771a088c88ce519a1e1b65872423f9', - tweak: 'dbcfa1c73674cba4aa1b6992ebdc6a77008d38f6c6ec068c3c862b9ff6d287f2', - parity: 0, - result: '82fc6954352b7189a156e4678d0c315c122431fa9551961b8e3c811b55a42c8b', - }, - { - pubkey: '9ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b', - tweak: '6aa3da9b5c1d61956076cb3014ffdaa0996bacdae29ba4b89e39b4088f86ec78', - parity: 1, - result: 'cf9065a7e2c9f909becc1c95f9884ed9fbe19c4a8954ed17880f02d94ae96a63', - }, - { - pubkey: 'c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5', - tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f', - parity: -1, - result: null, - }, ]; From 0eb9961b52599e72aaa69a13af9956d413ac61d1 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 27 Jan 2022 14:40:11 +0200 Subject: [PATCH 42/73] fix: taproot signature is 64 bytes --- src/payments/p2tr.js | 2 +- test/fixtures/p2tr.json | 8 ++++---- ts_src/payments/p2tr.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 7e944e68b..d03d7980d 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -37,7 +37,7 @@ function p2tr(a, opts) { internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), - signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), + signature: types_1.typeforce.maybe(types_1.typeforce.BufferN(64)), witness: types_1.typeforce.maybe( types_1.typeforce.arrayOf(types_1.typeforce.Buffer), ), diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 02b1cb3c0..4c7174fc0 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -47,7 +47,7 @@ "description": "address, output and witness from pubkey and signature", "arguments": { "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", - "signature": "300602010002010001" + "signature": "a251221c339a7129dd0b769698aca40d8d9da9570ab796a1820b91ab7dbf5acbea21c88ba8f1e9308a21729baf080734beaf97023882d972f75e380d480fd704" }, "expected": { "name": "p2tr", @@ -55,7 +55,7 @@ "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", "input": null, "witness": [ - "300602010002010001" + "a251221c339a7129dd0b769698aca40d8d9da9570ab796a1820b91ab7dbf5acbea21c88ba8f1e9308a21729baf080734beaf97023882d972f75e380d480fd704" ] } }, @@ -908,9 +908,9 @@ "exception": "Signature mismatch", "arguments": { "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5", - "signature": "300602010002010002", + "signature": "a251221c339a7129dd0b769698aca40d8d9da9570ab796a1820b91ab7dbf5acbea21c88ba8f1e9308a21729baf080734beaf97023882d972f75e380d480fd704", "witness": [ - "300602010002010001" + "607b8b5b5c8614757736e3d5811790636d2a8e2ea14418f8cff66b2e898b3b7536a49b7c4bc8b3227953194bf5d0548e13e3526fdb36beeefadda1ec834a0db2" ] } }, diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 66fa4946d..fa6598dc3 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -47,7 +47,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { internalPubkey: typef.maybe(typef.BufferN(32)), hash: typef.maybe(typef.BufferN(32)), pubkey: typef.maybe(typef.BufferN(32)), - signature: typef.maybe(bscript.isCanonicalScriptSignature), + signature: typef.maybe(typef.BufferN(64)), witness: typef.maybe(typef.arrayOf(typef.Buffer)), // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? scriptLeaf: typef.maybe({ From 5aa934581a09e9eda5dd5681a939decc00ff75d7 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 27 Jan 2022 15:09:42 +0200 Subject: [PATCH 43/73] feat: add Key Spend support for taproot to PSBT (more in commit description) - `signInput()` creates and serialises the Schnorr signature for the taproot inptuts - only `SIGHASH_DEFAULT` supported at the moment - `validateSignaturesOfInput()` validates taproot input Key Spend signatures - add `tweakSigner()` as static method - the `Signer` interface has an optional `privateKey` field (used for tweaking) - direct dependency to `tiny-secp256k1` introduced - it is awkward to pass an `ecc` lib from outside <- must be revisited - added 'tiny-secp256k1' to package.json deps <- must be revisited --- package-lock.json | 10 +-- package.json | 2 +- src/psbt.d.ts | 31 +++++++- src/psbt.js | 142 ++++++++++++++++++++++++++++----- test/fixtures/psbt.json | 30 +++++++ test/psbt.spec.ts | 65 +++++++++++++++ ts_src/psbt.ts | 171 +++++++++++++++++++++++++++++++++++----- 7 files changed, 406 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index f576a482c..c9bf48576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2368,10 +2368,9 @@ } }, "tiny-secp256k1": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.2.tgz", - "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.0.tgz", + "integrity": "sha512-2hPuUGCroLrxh6xxwoe+1RgPpOOK1w2uTnhgiHBpvoutBR+krNuT4hOXQyOaaYnZgoXBB6hBYkuAJHxyeBOPzQ==", "requires": { "uint8array-tools": "0.0.6" } @@ -2485,8 +2484,7 @@ "uint8array-tools": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.6.tgz", - "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", - "dev": true + "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==" }, "uuid": { "version": "3.4.0", diff --git a/package.json b/package.json index 13e0e68fb..57c1b4d6e 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "bip174": "^2.0.1", "bs58check": "^2.1.2", "create-hash": "^1.1.0", + "tiny-secp256k1": "^2.2.0", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" @@ -83,7 +84,6 @@ "randombytes": "^2.1.0", "regtest-client": "0.2.0", "rimraf": "^2.6.3", - "tiny-secp256k1": "^2.1.2", "ts-node": "^8.3.0", "tslint": "^6.1.3", "typescript": "^4.4.4" diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 8603a6955..4ce55ce0c 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -56,6 +56,17 @@ export declare class Psbt { static fromBase64(data: string, opts?: PsbtOptsOptional): Psbt; static fromHex(data: string, opts?: PsbtOptsOptional): Psbt; static fromBuffer(buffer: Buffer, opts?: PsbtOptsOptional): Psbt; + /** + * Helper method for converting a normal Signer into a Taproot Signer. + * Note that this helper method requires the Private Key of the Signer to be present. + * Steps: + * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key + * - tweak the private key with the provided hash (should be empty for key-path spending) + * @param signer - a taproot signer object, the Private Key must be present + * @param opts - tweak options + * @returns a Signer having the Private and Public keys tweaked + */ + static tweakSigner(signer: Signer, opts?: TaprootSignerOpts): Signer; private __CACHE; private opts; constructor(opts?: PsbtOptsOptional, data?: PsbtBase); @@ -143,6 +154,7 @@ export interface HDSigner extends HDSignerBase { * Return a 64 byte signature (32 byte r and 32 byte s in that order) */ sign(hash: Buffer): Buffer; + signSchnorr?(hash: Buffer): Buffer; } /** * Same as above but with async sign method @@ -150,17 +162,34 @@ export interface HDSigner extends HDSignerBase { export interface HDSignerAsync extends HDSignerBase { derivePath(path: string): HDSignerAsync; sign(hash: Buffer): Promise; + signSchnorr?(hash: Buffer): Promise; } export interface Signer { publicKey: Buffer; + /** + * Private Key is optional, it is required only if the signer must be tweaked. + * See the `tweakSigner()` method. + */ + privateKey?: Buffer; network?: any; sign(hash: Buffer, lowR?: boolean): Buffer; + signSchnorr?(hash: Buffer): Buffer; getPublicKey?(): Buffer; } +/** + * Options for tweaking a Signer into a valid Taproot Signer + */ +export interface TaprootSignerOpts { + network?: Network; + eccLib?: any; + /** The hash used to tweak the Signer */ + tweakHash?: Buffer; +} export interface SignerAsync { publicKey: Buffer; network?: any; sign(hash: Buffer, lowR?: boolean): Promise; + signSchnorr?(hash: Buffer): Promise; getPublicKey?(): Buffer; } /** @@ -178,5 +207,5 @@ isP2WSH: boolean) => { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | undefined; }; -declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard'; +declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'taproot' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard'; export {}; diff --git a/src/psbt.js b/src/psbt.js index 616219580..6ce9ab82b 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1,6 +1,8 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.Psbt = void 0; +const ecc = require('tiny-secp256k1'); // TODO: extract +const ecpair_1 = require('ecpair'); const bip174_1 = require('bip174'); const varuint = require('bip174/src/lib/converter/varint'); const utils_1 = require('bip174/src/lib/utils'); @@ -11,6 +13,7 @@ const networks_1 = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); const transaction_1 = require('./transaction'); +const taprootutils_1 = require('./payments/taprootutils'); /** * These are the default arguments for a Psbt instance. */ @@ -103,6 +106,39 @@ class Psbt { checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); return psbt; } + /** + * Helper method for converting a normal Signer into a Taproot Signer. + * Note that this helper method requires the Private Key of the Signer to be present. + * Steps: + * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key + * - tweak the private key with the provided hash (should be empty for key-path spending) + * @param signer - a taproot signer object, the Private Key must be present + * @param opts - tweak options + * @returns a Signer having the Private and Public keys tweaked + */ + static tweakSigner(signer, opts = {}) { + let privateKey = signer.privateKey; + if (!privateKey) { + throw new Error('Private key is required for tweaking signer!'); + } + if (signer.publicKey[0] === 3) { + privateKey = ecc.privateNegate(privateKey); + } + const tweakedPrivateKey = ecc.privateAdd( + privateKey, + (0, taprootutils_1.tapTweakHash)( + signer.publicKey.slice(1, 33), + opts.tweakHash, + ), + ); + if (!tweakedPrivateKey) { + throw new Error('Invalid tweaked private key!'); + } + const ECPair = (0, ecpair_1.ECPairFactory)(ecc); + return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: opts.network, + }); + } get inputCount() { return this.data.inputs.length; } @@ -298,7 +334,11 @@ class Psbt { } getInputType(inputIndex) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); - const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); + const { script } = getScriptAndAmountFromUtxo( + inputIndex, + input, + this.__CACHE, + ); const result = getMeaningfulScript( script, inputIndex, @@ -355,13 +395,21 @@ class Psbt { let hashCache; let scriptCache; let sighashCache; + const scriptType = this.getInputType(inputIndex); for (const pSig of mySigs) { - const sig = bscript.signature.decode(pSig.signature); + const sig = + scriptType === 'taproot' + ? { + signature: pSig.signature, + hashType: transaction_1.Transaction.SIGHASH_DEFAULT, + } + : bscript.signature.decode(pSig.signature); const { hash, script } = sighashCache !== sig.hashType ? getHashForSig( inputIndex, Object.assign({}, input, { sighashType: sig.hashType }), + this.data.inputs, this.__CACHE, true, ) @@ -526,13 +574,30 @@ class Psbt { this.__CACHE, sighashTypes, ); - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(keyPair.sign(hash), sighashType), - }, - ]; - this.data.updateInput(inputIndex, { partialSig }); + const scriptType = this.getInputType(inputIndex); + if (scriptType === 'taproot') { + if (!keyPair.signSchnorr) { + throw new Error( + `Need Schnorr Signer to sign taproot input #${inputIndex}.`, + ); + } + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: keyPair.signSchnorr(hash), + }, + ]; + // must be changed to use the `updateInput()` public API + this.data.inputs[inputIndex].partialSig = partialSig; + } else { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); + } return this; } signInputAsync( @@ -671,6 +736,7 @@ function canFinalize(input, script, scriptType) { case 'pubkey': case 'pubkeyhash': case 'witnesspubkeyhash': + case 'taproot': return hasSigs(1, input.partialSig); case 'multisig': const p2ms = payments.p2ms({ output: script }); @@ -704,9 +770,9 @@ function isFinalized(input) { return !!input.finalScriptSig || !!input.finalScriptWitness; } function isPaymentFactory(payment) { - return script => { + return (script, eccLib) => { try { - payment({ output: script }); + payment({ output: script }, { eccLib }); return true; } catch (err) { return false; @@ -719,6 +785,7 @@ const isP2PKH = isPaymentFactory(payments.p2pkh); const isP2WPKH = isPaymentFactory(payments.p2wpkh); const isP2WSHScript = isPaymentFactory(payments.p2wsh); const isP2SHScript = isPaymentFactory(payments.p2sh); +const isP2TR = isPaymentFactory(payments.p2tr); function bip32DerivationIsMine(root) { return d => { if (!d.masterFingerprint.equals(root.fingerprint)) return false; @@ -920,6 +987,7 @@ function getHashAndSighashType( const { hash, sighashType, script } = getHashForSig( inputIndex, input, + inputs, cache, false, sighashTypes, @@ -930,7 +998,14 @@ function getHashAndSighashType( sighashType, }; } -function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { +function getHashForSig( + inputIndex, + input, + inputs, + cache, + forValidate, + sighashTypes, +) { const unsignedTx = cache.__TX; const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; @@ -988,6 +1063,18 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { prevout.value, sighashType, ); + } else if (isP2TR(meaningfulScript, ecc)) { + const prevOuts = inputs.map((i, index) => + getScriptAndAmountFromUtxo(index, i, cache), + ); + const signingScripts = prevOuts.map(o => o.script); + const values = prevOuts.map(o => o.value); + hash = unsignedTx.hashForWitnessV1( + inputIndex, + signingScripts, + values, + transaction_1.Transaction.SIGHASH_DEFAULT, + ); } else { // non-segwit if ( @@ -1050,6 +1137,15 @@ function getPayment(script, scriptType, partialSig) { signature: partialSig[0].signature, }); break; + case 'taproot': + payment = payments.p2tr( + { + output: script, + signature: partialSig[0].signature, + }, + { eccLib: ecc }, + ); + break; } return payment; } @@ -1094,7 +1190,7 @@ function getScriptFromInput(inputIndex, input, cache) { res.script = input.witnessUtxo.script; } } - if (input.witnessScript || isP2WPKH(res.script)) { + if (input.witnessScript || isP2WPKH(res.script) || isP2TR(res.script, ecc)) { res.isSegwit = true; } return res; @@ -1267,22 +1363,26 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) { } return c[inputIndex]; } -function getScriptFromUtxo(inputIndex, input, cache) { +function getScriptAndAmountFromUtxo(inputIndex, input, cache) { if (input.witnessUtxo !== undefined) { - return input.witnessUtxo.script; + return { + script: input.witnessUtxo.script, + value: input.witnessUtxo.value, + }; } else if (input.nonWitnessUtxo !== undefined) { const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( cache, input, inputIndex, ); - return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script; + const o = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index]; + return { script: o.script, value: o.value }; } else { throw new Error("Can't find pubkey in input without Utxo data"); } } function pubkeyInInput(pubkey, input, inputIndex, cache) { - const script = getScriptFromUtxo(inputIndex, input, cache); + const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); const { meaningfulScript } = getMeaningfulScript( script, inputIndex, @@ -1392,11 +1492,16 @@ function checkInvalidP2WSH(script) { } function pubkeyInScript(pubkey, script) { const pubkeyHash = (0, crypto_1.hash160)(pubkey); + const pubkeyXOnly = pubkey.slice(1, 33); const decompiled = bscript.decompile(script); if (decompiled === null) throw new Error('Unknown script error'); return decompiled.some(element => { if (typeof element === 'number') return false; - return element.equals(pubkey) || element.equals(pubkeyHash); + return ( + element.equals(pubkey) || + element.equals(pubkeyHash) || + element.equals(pubkeyXOnly) + ); }); } function classifyScript(script) { @@ -1404,6 +1509,7 @@ function classifyScript(script) { if (isP2PKH(script)) return 'pubkeyhash'; if (isP2MS(script)) return 'multisig'; if (isP2PK(script)) return 'pubkey'; + if (isP2TR(script, ecc)) return 'taproot'; return 'nonstandard'; } function range(n) { diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index 0e51d57cf..b113d75b6 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -116,6 +116,10 @@ { "description": "PSBT with unknown types in the inputs.", "psbt": "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=" + }, + { + "description": "PSBT with one P2TR input and one P2TR output.", + "psbt": "cHNidP8BAF4CAAAAAWbQAKi9hNXynJhqPu8bqkvp0kHihVShkWGh3yLy15+LAAAAAAD/////Aej9AAAAAAAAIlEgRvZJfLLxnVDD6emCqVDcyGIUsB/M5DekIGHHvbEjDTMAAAAAAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAA==" } ], "failSignChecks": [ @@ -273,6 +277,25 @@ } ], "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + }, + { + "description": "Sign PSBT with 3 inputs [P2PKH, P2TR, P2WPKH] and two outputs [P2TR, P2WPKH]", + "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAABASu4BQEAAAAAACJRIJQh5zSw+dLEZ+p90ZfGGstEZ83LyfTLDFcfi2OlxAyuAAEBHxAnAAAAAAAAFgAUT6KsoSi2+d7lMJxPcAUeScZf1zIAAAA=", + "keys": [ + { + "inputToSign": 0, + "WIF": "cRyKzLXVgTReWe7wgfEiXktTa9tf4e5DK1STha274d7BBbnucTaR" + }, + { + "inputToSign": 1, + "WIF": "cNPzVNoVCAfNEadTExqN2HzfC4dX42RtduE39D2i7cxuVEKY3DM3" + }, + { + "inputToSign": 2, + "WIF": "cPPRdCmAMZMjPdHfRmTCmzYVruZHJ8GbM1FqN2W6DnmEPWDg29aL" + } + ], + "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4iAgKUIec0sPnSxGfqfdGXxhrLRGfNy8n0ywxXH4tjpcQMrkADaUubfpFFrzbU+vL8qCzZE/FO+9unzylfpIgQZ4HTy2qPUtLvbyH59GApdz0SiUZGl8K6Crvt9YIfI/5FxbOLAAEBHxAnAAAAAAAAFgAUT6KsoSi2+d7lMJxPcAUeScZf1zIiAgOlTqRAWzyTP8WLKjtnrrbWBaYHnPb3MYIMk8qJJSuutEgwRQIhAKAiJLYIS+eYrjAJpM8GCc2/ofqpjXsGV8QMf9Ojm8SEAiBCwrAc/8HdsD5ZyW9uzpbsTJEz5wshwNgvksR4l/xbzwEAAAA=" } ], "combiner": [ @@ -551,6 +574,13 @@ "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')", "nonExistantIndex": 42 }, + "validateSignaturesOfTaprootInput": { + "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAABASu4BQEAAAAAACJRIJQh5zSw+dLEZ+p90ZfGGstEZ83LyfTLDFcfi2OlxAyuIgIClCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK5AA2lLm36RRa821Pry/Kgs2RPxTvvbp88pX6SIEGeB08tqj1LS728h+fRgKXc9EolGRpfCugq77fWCHyP+RcWziwABAR8QJwAAAAAAABYAFE+irKEotvne5TCcT3AFHknGX9cyAAAA", + "index": 1, + "pubkey": "Buffer.from('029421e734b0f9d2c467ea7dd197c61acb4467cdcbc9f4cb0c571f8b63a5c40cae', 'hex')", + "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')", + "nonExistantIndex": 42 + }, "getFeeRate": { "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "fee": 21 diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index f583e8068..3f4cf93b8 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -18,6 +18,12 @@ const validator = ( signature: Buffer, ): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature); +const schnorrValidator = ( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer, +): boolean => ECPair.fromPublicKey(pubkey).verifySchnorr(msghash, signature); + const initBuffers = (object: any): typeof preFixtures => JSON.parse(JSON.stringify(object), (_, value) => { const regex = new RegExp(/^Buffer.from\(['"](.*)['"], ['"](.*)['"]\)$/); @@ -952,6 +958,36 @@ describe(`Psbt`, () => { }); }); + describe('validateSignaturesOfTaprootInput', () => { + const f = fixtures.validateSignaturesOfTaprootInput; + it('Correctly validates a signature', () => { + const psbt = Psbt.fromBase64(f.psbt); + assert.strictEqual( + psbt.validateSignaturesOfInput(f.index, schnorrValidator), + true, + ); + }); + + it('Correctly validates a signature against a pubkey', () => { + const psbt = Psbt.fromBase64(f.psbt); + assert.strictEqual( + psbt.validateSignaturesOfInput( + f.index, + schnorrValidator, + f.pubkey as any, + ), + true, + ); + assert.throws(() => { + psbt.validateSignaturesOfInput( + f.index, + schnorrValidator, + f.incorrectPubkey as any, + ); + }, new RegExp('No signatures for this pubkey')); + }); + }); + describe('getFeeRate', () => { it('Throws error if called before inputs are finalized', () => { const f = fixtures.getFeeRate; @@ -969,6 +1005,35 @@ describe(`Psbt`, () => { }); }); + describe('tweakSigner', () => { + it('Throws error if signer is missing private key', () => { + const keyPair = Object.assign({}, ECPair.makeRandom(), { + privateKey: null, + }); + assert.throws(() => { + Psbt.tweakSigner(keyPair); + }, new RegExp('Private key is required for tweaking signer!')); + }); + + it('Correctly creates tweaked signer', () => { + const keyPair = ECPair.fromPrivateKey( + Buffer.from( + 'accaf12e04e11b08fc28f5fe75b47ea663843b698981e31f1cafa2224d6e28c0', + 'hex', + ), + ); + const tweakedSigner: Signer = Psbt.tweakSigner(keyPair); + assert.strictEqual( + '029421e734b0f9d2c467ea7dd197c61acb4467cdcbc9f4cb0c571f8b63a5c40cae', + tweakedSigner.publicKey.toString('hex'), + ); + assert.strictEqual( + '1853f5034982ec659e015873a0a958a73eac785850f425fd3444b12430d58692', + tweakedSigner.privateKey!.toString('hex'), + ); + }); + }); + describe('create 1-to-1 transaction', () => { const alice = ECPair.fromWIF( 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr', diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index b9af10fcf..230d23210 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1,3 +1,6 @@ +import * as ecc from 'tiny-secp256k1'; // TODO: extract +import { ECPairFactory } from 'ecpair'; + import { Psbt as PsbtBase } from 'bip174'; import * as varuint from 'bip174/src/lib/converter/varint'; import { @@ -20,6 +23,7 @@ import { bitcoin as btcNetwork, Network } from './networks'; import * as payments from './payments'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; +import { tapTweakHash } from './payments/taprootutils'; export interface TransactionInput { hash: string | Buffer; @@ -114,6 +118,39 @@ export class Psbt { return psbt; } + /** + * Helper method for converting a normal Signer into a Taproot Signer. + * Note that this helper method requires the Private Key of the Signer to be present. + * Steps: + * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key + * - tweak the private key with the provided hash (should be empty for key-path spending) + * @param signer - a taproot signer object, the Private Key must be present + * @param opts - tweak options + * @returns a Signer having the Private and Public keys tweaked + */ + static tweakSigner(signer: Signer, opts: TaprootSignerOpts = {}): Signer { + let privateKey: Uint8Array | undefined = signer.privateKey; + if (!privateKey) { + throw new Error('Private key is required for tweaking signer!'); + } + if (signer.publicKey[0] === 3) { + privateKey = ecc.privateNegate(privateKey!); + } + + const tweakedPrivateKey = ecc.privateAdd( + privateKey, + tapTweakHash(signer.publicKey.slice(1, 33), opts.tweakHash), + ); + if (!tweakedPrivateKey) { + throw new Error('Invalid tweaked private key!'); + } + + const ECPair = ECPairFactory(ecc); + return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: opts.network, + }); + } + private __CACHE: PsbtCache; private opts: PsbtOpts; @@ -378,7 +415,11 @@ export class Psbt { getInputType(inputIndex: number): AllScriptType { const input = checkForInput(this.data.inputs, inputIndex); - const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); + const { script } = getScriptAndAmountFromUtxo( + inputIndex, + input, + this.__CACHE, + ); const result = getMeaningfulScript( script, inputIndex, @@ -445,13 +486,23 @@ export class Psbt { let hashCache: Buffer; let scriptCache: Buffer; let sighashCache: number; + const scriptType = this.getInputType(inputIndex); + for (const pSig of mySigs) { - const sig = bscript.signature.decode(pSig.signature); + const sig = + scriptType === 'taproot' + ? { + signature: pSig.signature, + hashType: Transaction.SIGHASH_DEFAULT, + } + : bscript.signature.decode(pSig.signature); + const { hash, script } = sighashCache! !== sig.hashType ? getHashForSig( inputIndex, Object.assign({}, input, { sighashType: sig.hashType }), + this.data.inputs, this.__CACHE, true, ) @@ -642,14 +693,32 @@ export class Psbt { sighashTypes, ); - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(keyPair.sign(hash), sighashType), - }, - ]; + const scriptType = this.getInputType(inputIndex); + + if (scriptType === 'taproot') { + if (!keyPair.signSchnorr) { + throw new Error( + `Need Schnorr Signer to sign taproot input #${inputIndex}.`, + ); + } + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: keyPair.signSchnorr!(hash), + }, + ]; + // must be changed to use the `updateInput()` public API + this.data.inputs[inputIndex].partialSig = partialSig; + } else { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); + } - this.data.updateInput(inputIndex, { partialSig }); return this; } @@ -798,6 +867,7 @@ export interface HDSigner extends HDSignerBase { * Return a 64 byte signature (32 byte r and 32 byte s in that order) */ sign(hash: Buffer): Buffer; + signSchnorr?(hash: Buffer): Buffer; } /** @@ -806,19 +876,37 @@ export interface HDSigner extends HDSignerBase { export interface HDSignerAsync extends HDSignerBase { derivePath(path: string): HDSignerAsync; sign(hash: Buffer): Promise; + signSchnorr?(hash: Buffer): Promise; } export interface Signer { publicKey: Buffer; + /** + * Private Key is optional, it is required only if the signer must be tweaked. + * See the `tweakSigner()` method. + */ + privateKey?: Buffer; network?: any; sign(hash: Buffer, lowR?: boolean): Buffer; + signSchnorr?(hash: Buffer): Buffer; getPublicKey?(): Buffer; } +/** + * Options for tweaking a Signer into a valid Taproot Signer + */ +export interface TaprootSignerOpts { + network?: Network; + // TODO: revisit. + eccLib?: any; + /** The hash used to tweak the Signer */ + tweakHash?: Buffer; +} export interface SignerAsync { publicKey: Buffer; network?: any; sign(hash: Buffer, lowR?: boolean): Promise; + signSchnorr?(hash: Buffer): Promise; getPublicKey?(): Buffer; } @@ -899,6 +987,7 @@ function canFinalize( case 'pubkey': case 'pubkeyhash': case 'witnesspubkeyhash': + case 'taproot': return hasSigs(1, input.partialSig); case 'multisig': const p2ms = payments.p2ms({ output: script }); @@ -939,10 +1028,12 @@ function isFinalized(input: PsbtInput): boolean { return !!input.finalScriptSig || !!input.finalScriptWitness; } -function isPaymentFactory(payment: any): (script: Buffer) => boolean { - return (script: Buffer): boolean => { +function isPaymentFactory( + payment: any, +): (script: Buffer, eccLib?: any) => boolean { + return (script: Buffer, eccLib?: any): boolean => { try { - payment({ output: script }); + payment({ output: script }, { eccLib }); return true; } catch (err) { return false; @@ -955,6 +1046,7 @@ const isP2PKH = isPaymentFactory(payments.p2pkh); const isP2WPKH = isPaymentFactory(payments.p2wpkh); const isP2WSHScript = isPaymentFactory(payments.p2wsh); const isP2SHScript = isPaymentFactory(payments.p2sh); +const isP2TR = isPaymentFactory(payments.p2tr); function bip32DerivationIsMine( root: HDSigner, @@ -1227,6 +1319,7 @@ function getHashAndSighashType( const { hash, sighashType, script } = getHashForSig( inputIndex, input, + inputs, cache, false, sighashTypes, @@ -1241,6 +1334,7 @@ function getHashAndSighashType( function getHashForSig( inputIndex: number, input: PsbtInput, + inputs: PsbtInput[], cache: PsbtCache, forValidate: boolean, sighashTypes?: number[], @@ -1311,6 +1405,19 @@ function getHashForSig( prevout.value, sighashType, ); + } else if (isP2TR(meaningfulScript, ecc)) { + const prevOuts: Output[] = inputs.map((i, index) => + getScriptAndAmountFromUtxo(index, i, cache), + ); + const signingScripts: any = prevOuts.map(o => o.script); + const values: any = prevOuts.map(o => o.value); + + hash = unsignedTx.hashForWitnessV1( + inputIndex, + signingScripts, + values, + Transaction.SIGHASH_DEFAULT, + ); } else { // non-segwit if ( @@ -1379,6 +1486,15 @@ function getPayment( signature: partialSig[0].signature, }); break; + case 'taproot': + payment = payments.p2tr( + { + output: script, + signature: partialSig[0].signature, + }, + { eccLib: ecc }, + ); + break; } return payment!; } @@ -1435,7 +1551,12 @@ function getScriptFromInput( res.script = input.witnessUtxo.script; } } - if (input.witnessScript || isP2WPKH(res.script!)) { + + if ( + input.witnessScript || + isP2WPKH(res.script!) || + isP2TR(res.script!, ecc) + ) { res.isSegwit = true; } return res; @@ -1651,20 +1772,24 @@ function nonWitnessUtxoTxFromCache( return c[inputIndex]; } -function getScriptFromUtxo( +function getScriptAndAmountFromUtxo( inputIndex: number, input: PsbtInput, cache: PsbtCache, -): Buffer { +): { script: Buffer; value: number } { if (input.witnessUtxo !== undefined) { - return input.witnessUtxo.script; + return { + script: input.witnessUtxo.script, + value: input.witnessUtxo.value, + }; } else if (input.nonWitnessUtxo !== undefined) { const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( cache, input, inputIndex, ); - return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script; + const o = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index]; + return { script: o.script, value: o.value }; } else { throw new Error("Can't find pubkey in input without Utxo data"); } @@ -1676,7 +1801,7 @@ function pubkeyInInput( inputIndex: number, cache: PsbtCache, ): boolean { - const script = getScriptFromUtxo(inputIndex, input, cache); + const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); const { meaningfulScript } = getMeaningfulScript( script, inputIndex, @@ -1810,13 +1935,18 @@ function checkInvalidP2WSH(script: Buffer): void { function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean { const pubkeyHash = hash160(pubkey); + const pubkeyXOnly = pubkey.slice(1, 33); const decompiled = bscript.decompile(script); if (decompiled === null) throw new Error('Unknown script error'); return decompiled.some(element => { if (typeof element === 'number') return false; - return element.equals(pubkey) || element.equals(pubkeyHash); + return ( + element.equals(pubkey) || + element.equals(pubkeyHash) || + element.equals(pubkeyXOnly) + ); }); } @@ -1825,6 +1955,7 @@ type AllScriptType = | 'pubkeyhash' | 'multisig' | 'pubkey' + | 'taproot' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' @@ -1844,12 +1975,14 @@ type ScriptType = | 'pubkeyhash' | 'multisig' | 'pubkey' + | 'taproot' | 'nonstandard'; function classifyScript(script: Buffer): ScriptType { if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash'; if (isP2MS(script)) return 'multisig'; if (isP2PK(script)) return 'pubkey'; + if (isP2TR(script, ecc)) return 'taproot'; return 'nonstandard'; } From 3ee59b73536b78d1271ae4c1328d3e5b3c807f02 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 27 Jan 2022 15:11:03 +0200 Subject: [PATCH 44/73] chore: version bump --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9bf48576..4668dfc5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.1", + "version": "6.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 57c1b4d6e..53d123e36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.1", + "version": "6.0.2", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From fdb8698e273bbe70ea59269f28beac4e40dff0d0 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 28 Jan 2022 15:18:16 +0200 Subject: [PATCH 45/73] feat: extract `tiny-secp256k1` out of the Psbt module - the Psbt() constructor options accept an eccLib - `tweakSigner()` is a public helper method - `tiny-secp256k1` is only a dev dependency now --- package-lock.json | 4 +- package.json | 2 +- src/index.d.ts | 2 +- src/index.js | 8 ++- src/payments/p2tr.js | 5 +- src/psbt.d.ts | 27 +++++----- src/psbt.js | 109 +++++++++++++++++++++++----------------- src/types.d.ts | 2 + test/fixtures/psbt.json | 1 + test/psbt.spec.ts | 20 +++++--- ts_src/index.ts | 1 + ts_src/payments/p2tr.ts | 5 +- ts_src/psbt.ts | 107 +++++++++++++++++++++------------------ ts_src/types.ts | 2 + 14 files changed, 172 insertions(+), 123 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4668dfc5f..bf519f3f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2371,6 +2371,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.0.tgz", "integrity": "sha512-2hPuUGCroLrxh6xxwoe+1RgPpOOK1w2uTnhgiHBpvoutBR+krNuT4hOXQyOaaYnZgoXBB6hBYkuAJHxyeBOPzQ==", + "dev": true, "requires": { "uint8array-tools": "0.0.6" } @@ -2484,7 +2485,8 @@ "uint8array-tools": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.6.tgz", - "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==" + "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", + "dev": true }, "uuid": { "version": "3.4.0", diff --git a/package.json b/package.json index 53d123e36..8666bb478 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "bip174": "^2.0.1", "bs58check": "^2.1.2", "create-hash": "^1.1.0", - "tiny-secp256k1": "^2.2.0", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" @@ -84,6 +83,7 @@ "randombytes": "^2.1.0", "regtest-client": "0.2.0", "rimraf": "^2.6.3", + "tiny-secp256k1": "^2.2.0", "ts-node": "^8.3.0", "tslint": "^6.1.3", "typescript": "^4.4.4" diff --git a/src/index.d.ts b/src/index.d.ts index b93c2aa40..13259bed6 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -6,7 +6,7 @@ import * as script from './script'; export { address, crypto, networks, payments, script }; export { Block } from './block'; export { TaggedHashPrefix } from './crypto'; -export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt'; +export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, tweakSigner, } from './psbt'; export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; export { Network } from './networks'; diff --git a/src/index.js b/src/index.js index 983b0cc76..55d2b0068 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0; +exports.Transaction = exports.opcodes = exports.tweakSigner = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0; const address = require('./address'); exports.address = address; const crypto = require('./crypto'); @@ -25,6 +25,12 @@ Object.defineProperty(exports, 'Psbt', { return psbt_1.Psbt; }, }); +Object.defineProperty(exports, 'tweakSigner', { + enumerable: true, + get: function() { + return psbt_1.tweakSigner; + }, +}); var ops_1 = require('./ops'); Object.defineProperty(exports, 'opcodes', { enumerable: true, diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index d03d7980d..79fe62a6f 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -17,7 +17,6 @@ function p2tr(a, opts) { !a.address && !a.output && !a.pubkey && - !a.output && !a.internalPubkey && !(a.witness && a.witness.length > 1) ) @@ -115,6 +114,7 @@ function p2tr(a, opts) { return witness[witness.length - 1].slice(1, 33); }); lazy.prop(o, 'signature', () => { + if (a.signature) return a.signature; if (!a.witness || a.witness.length !== 1) return; return a.witness[0]; }); @@ -192,9 +192,6 @@ function p2tr(a, opts) { // key spending if (a.signature && !a.signature.equals(witness[0])) throw new TypeError('Signature mismatch'); - // todo: recheck - // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) - // throw new TypeError('Witness has invalid signature'); } else { // script path spending const controlBlock = witness[witness.length - 1]; diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 4ce55ce0c..57e3f4d35 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -1,8 +1,10 @@ /// import { Psbt as PsbtBase } from 'bip174'; import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces'; +import { TinySecp256k1Interface as ECPairTinySecp256k1Interface } from 'ecpair'; import { Network } from './networks'; import { Transaction } from './transaction'; +import { TinySecp256k1Interface } from './types'; export interface TransactionInput { hash: string | Buffer; index: number; @@ -56,17 +58,6 @@ export declare class Psbt { static fromBase64(data: string, opts?: PsbtOptsOptional): Psbt; static fromHex(data: string, opts?: PsbtOptsOptional): Psbt; static fromBuffer(buffer: Buffer, opts?: PsbtOptsOptional): Psbt; - /** - * Helper method for converting a normal Signer into a Taproot Signer. - * Note that this helper method requires the Private Key of the Signer to be present. - * Steps: - * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key - * - tweak the private key with the provided hash (should be empty for key-path spending) - * @param signer - a taproot signer object, the Private Key must be present - * @param opts - tweak options - * @returns a Signer having the Private and Public keys tweaked - */ - static tweakSigner(signer: Signer, opts?: TaprootSignerOpts): Signer; private __CACHE; private opts; constructor(opts?: PsbtOptsOptional, data?: PsbtBase); @@ -118,9 +109,21 @@ export declare class Psbt { addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this; clearFinalizedInput(inputIndex: number): this; } +/** + * Helper method for converting a normal Signer into a Taproot Signer. + * Note that this helper method requires the Private Key of the Signer to be present. + * Steps: + * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key + * - tweak the private key with the provided hash (should be empty for key-path spending) + * @param signer - a taproot signer object, the Private Key must be present + * @param opts - tweak options + * @returns a Signer having the Private and Public keys tweaked + */ +export declare function tweakSigner(signer: Signer, opts: TaprootSignerOpts): Signer; interface PsbtOptsOptional { network?: Network; maximumFeeRate?: number; + eccLib?: TinySecp256k1Interface; } interface PsbtInputExtended extends PsbtInput, TransactionInput { } @@ -181,7 +184,7 @@ export interface Signer { */ export interface TaprootSignerOpts { network?: Network; - eccLib?: any; + eccLib: TinySecp256k1Interface & ECPairTinySecp256k1Interface; /** The hash used to tweak the Signer */ tweakHash?: Buffer; } diff --git a/src/psbt.js b/src/psbt.js index 6ce9ab82b..70594153a 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1,7 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.Psbt = void 0; -const ecc = require('tiny-secp256k1'); // TODO: extract +exports.tweakSigner = exports.Psbt = void 0; const ecpair_1 = require('ecpair'); const bip174_1 = require('bip174'); const varuint = require('bip174/src/lib/converter/varint'); @@ -81,6 +80,7 @@ class Psbt { // We will disable exporting the Psbt when unsafe sign is active. // because it is not BIP174 compliant. __UNSAFE_SIGN_NONSEGWIT: false, + __EC_LIB: opts.eccLib, }; if (this.data.inputs.length === 0) this.setVersion(2); // Make data hidden when enumerating @@ -106,39 +106,6 @@ class Psbt { checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); return psbt; } - /** - * Helper method for converting a normal Signer into a Taproot Signer. - * Note that this helper method requires the Private Key of the Signer to be present. - * Steps: - * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key - * - tweak the private key with the provided hash (should be empty for key-path spending) - * @param signer - a taproot signer object, the Private Key must be present - * @param opts - tweak options - * @returns a Signer having the Private and Public keys tweaked - */ - static tweakSigner(signer, opts = {}) { - let privateKey = signer.privateKey; - if (!privateKey) { - throw new Error('Private key is required for tweaking signer!'); - } - if (signer.publicKey[0] === 3) { - privateKey = ecc.privateNegate(privateKey); - } - const tweakedPrivateKey = ecc.privateAdd( - privateKey, - (0, taprootutils_1.tapTweakHash)( - signer.publicKey.slice(1, 33), - opts.tweakHash, - ), - ); - if (!tweakedPrivateKey) { - throw new Error('Invalid tweaked private key!'); - } - const ECPair = (0, ecpair_1.ECPairFactory)(ecc); - return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { - network: opts.network, - }); - } get inputCount() { return this.data.inputs.length; } @@ -307,7 +274,7 @@ class Psbt { range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); return this; } - finalizeInput(inputIndex, finalScriptsFunc = getFinalScripts) { + finalizeInput(inputIndex, finalScriptsFunc) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( inputIndex, @@ -316,13 +283,15 @@ class Psbt { ); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); - const { finalScriptSig, finalScriptWitness } = finalScriptsFunc( + const fn = finalScriptsFunc || getFinalScripts; + const { finalScriptSig, finalScriptWitness } = fn( inputIndex, input, script, isSegwit, isP2SH, isP2WSH, + this.__CACHE.__EC_LIB, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); if (finalScriptWitness) @@ -348,7 +317,10 @@ class Psbt { redeemFromFinalWitnessScript(input.finalScriptWitness), ); const type = result.type === 'raw' ? '' : result.type + '-'; - const mainType = classifyScript(result.meaningfulScript); + const mainType = classifyScript( + result.meaningfulScript, + this.__CACHE.__EC_LIB, + ); return type + mainType; } inputHasPubkey(inputIndex, pubkey) { @@ -676,6 +648,41 @@ class Psbt { } } exports.Psbt = Psbt; +/** + * Helper method for converting a normal Signer into a Taproot Signer. + * Note that this helper method requires the Private Key of the Signer to be present. + * Steps: + * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key + * - tweak the private key with the provided hash (should be empty for key-path spending) + * @param signer - a taproot signer object, the Private Key must be present + * @param opts - tweak options + * @returns a Signer having the Private and Public keys tweaked + */ +function tweakSigner(signer, opts) { + // todo: test ecc?? + let privateKey = signer.privateKey; + if (!privateKey) { + throw new Error('Private key is required for tweaking signer!'); + } + if (signer.publicKey[0] === 3) { + privateKey = opts.eccLib.privateNegate(privateKey); + } + const tweakedPrivateKey = opts.eccLib.privateAdd( + privateKey, + (0, taprootutils_1.tapTweakHash)( + signer.publicKey.slice(1, 33), + opts.tweakHash, + ), + ); + if (!tweakedPrivateKey) { + throw new Error('Invalid tweaked private key!'); + } + const ECPair = (0, ecpair_1.ECPairFactory)(opts.eccLib); + return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: opts.network, + }); +} +exports.tweakSigner = tweakSigner; /** * This function is needed to pass to the bip174 base class's fromBuffer. * It takes the "transaction buffer" portion of the psbt buffer and returns a @@ -928,8 +935,16 @@ function getTxCacheValue(key, name, inputs, c) { if (key === '__FEE_RATE') return c.__FEE_RATE; else if (key === '__FEE') return c.__FEE; } -function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) { - const scriptType = classifyScript(script); +function getFinalScripts( + inputIndex, + input, + script, + isSegwit, + isP2SH, + isP2WSH, + eccLib, +) { + const scriptType = classifyScript(script, eccLib); if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -1063,7 +1078,7 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(meaningfulScript, ecc)) { + } else if (isP2TR(meaningfulScript, cache.__EC_LIB)) { const prevOuts = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache), ); @@ -1143,7 +1158,7 @@ function getPayment(script, scriptType, partialSig) { output: script, signature: partialSig[0].signature, }, - { eccLib: ecc }, + { validate: false }, ); break; } @@ -1190,7 +1205,11 @@ function getScriptFromInput(inputIndex, input, cache) { res.script = input.witnessUtxo.script; } } - if (input.witnessScript || isP2WPKH(res.script) || isP2TR(res.script, ecc)) { + if ( + input.witnessScript || + isP2WPKH(res.script) || + isP2TR(res.script, cache.__EC_LIB) + ) { res.isSegwit = true; } return res; @@ -1504,12 +1523,12 @@ function pubkeyInScript(pubkey, script) { ); }); } -function classifyScript(script) { +function classifyScript(script, eccLib) { if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash'; if (isP2MS(script)) return 'multisig'; if (isP2PK(script)) return 'pubkey'; - if (isP2TR(script, ecc)) return 'taproot'; + if (isP2TR(script, eccLib)) return 'taproot'; return 'nonstandard'; } function range(n) { diff --git a/src/types.d.ts b/src/types.d.ts index aefc6bed7..5ddded9a2 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -21,6 +21,8 @@ export interface TaprootLeaf { export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; + privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null; + privateNegate(d: Uint8Array): Uint8Array; } export declare const Buffer256bit: any; export declare const Hash160bit: any; diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index b113d75b6..2e7d9cf76 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -281,6 +281,7 @@ { "description": "Sign PSBT with 3 inputs [P2PKH, P2TR, P2WPKH] and two outputs [P2TR, P2WPKH]", "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAABASu4BQEAAAAAACJRIJQh5zSw+dLEZ+p90ZfGGstEZ83LyfTLDFcfi2OlxAyuAAEBHxAnAAAAAAAAFgAUT6KsoSi2+d7lMJxPcAUeScZf1zIAAAA=", + "isTaproot": true, "keys": [ { "inputToSign": 0, diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 3f4cf93b8..d5693c7ed 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -8,7 +8,14 @@ import { describe, it } from 'mocha'; const bip32 = BIP32Factory(ecc); const ECPair = ECPairFactory(ecc); -import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; +import { + networks as NETWORKS, + payments, + Psbt, + Signer, + SignerAsync, + tweakSigner, +} from '..'; import * as preFixtures from './fixtures/psbt.json'; @@ -139,7 +146,8 @@ describe(`Psbt`, () => { fixtures.bip174.signer.forEach(f => { it('Signs PSBT to the expected result', () => { - const psbt = Psbt.fromBase64(f.psbt); + const opts = f.isTaproot ? { eccLib: ecc } : {}; + const psbt = Psbt.fromBase64(f.psbt, opts); f.keys.forEach(({ inputToSign, WIF }) => { const keyPair = ECPair.fromWIF(WIF, NETWORKS.testnet); @@ -961,7 +969,7 @@ describe(`Psbt`, () => { describe('validateSignaturesOfTaprootInput', () => { const f = fixtures.validateSignaturesOfTaprootInput; it('Correctly validates a signature', () => { - const psbt = Psbt.fromBase64(f.psbt); + const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); assert.strictEqual( psbt.validateSignaturesOfInput(f.index, schnorrValidator), true, @@ -969,7 +977,7 @@ describe(`Psbt`, () => { }); it('Correctly validates a signature against a pubkey', () => { - const psbt = Psbt.fromBase64(f.psbt); + const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); assert.strictEqual( psbt.validateSignaturesOfInput( f.index, @@ -1011,7 +1019,7 @@ describe(`Psbt`, () => { privateKey: null, }); assert.throws(() => { - Psbt.tweakSigner(keyPair); + tweakSigner(keyPair, { eccLib: ecc }); }, new RegExp('Private key is required for tweaking signer!')); }); @@ -1022,7 +1030,7 @@ describe(`Psbt`, () => { 'hex', ), ); - const tweakedSigner: Signer = Psbt.tweakSigner(keyPair); + const tweakedSigner: Signer = tweakSigner(keyPair, { eccLib: ecc }); assert.strictEqual( '029421e734b0f9d2c467ea7dd197c61acb4467cdcbc9f4cb0c571f8b63a5c40cae', tweakedSigner.publicKey.toString('hex'), diff --git a/ts_src/index.ts b/ts_src/index.ts index d8b8619d1..6a0f00a1e 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -16,6 +16,7 @@ export { SignerAsync, HDSigner, HDSignerAsync, + tweakSigner, } from './psbt'; export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index fa6598dc3..c79bb772e 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -23,7 +23,6 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { !a.address && !a.output && !a.pubkey && - !a.output && !a.internalPubkey && !(a.witness && a.witness.length > 1) ) @@ -128,6 +127,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return witness[witness.length - 1].slice(1, 33); }); lazy.prop(o, 'signature', () => { + if (a.signature) return a.signature; if (!a.witness || a.witness.length !== 1) return; return a.witness[0]; }); @@ -209,9 +209,6 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { // key spending if (a.signature && !a.signature.equals(witness[0])) throw new TypeError('Signature mismatch'); - // todo: recheck - // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0])) - // throw new TypeError('Witness has invalid signature'); } else { // script path spending const controlBlock = witness[witness.length - 1]; diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 230d23210..c74278b78 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1,4 +1,3 @@ -import * as ecc from 'tiny-secp256k1'; // TODO: extract import { ECPairFactory } from 'ecpair'; import { Psbt as PsbtBase } from 'bip174'; @@ -16,6 +15,8 @@ import { TransactionFromBuffer, } from 'bip174/src/lib/interfaces'; import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; +import { TinySecp256k1Interface as ECPairTinySecp256k1Interface } from 'ecpair'; + import { fromOutputScript, toOutputScript } from './address'; import { cloneBuffer, reverseBuffer } from './bufferutils'; import { hash160 } from './crypto'; @@ -24,6 +25,7 @@ import * as payments from './payments'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; import { tapTweakHash } from './payments/taprootutils'; +import { TinySecp256k1Interface } from './types'; export interface TransactionInput { hash: string | Buffer; @@ -118,39 +120,6 @@ export class Psbt { return psbt; } - /** - * Helper method for converting a normal Signer into a Taproot Signer. - * Note that this helper method requires the Private Key of the Signer to be present. - * Steps: - * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key - * - tweak the private key with the provided hash (should be empty for key-path spending) - * @param signer - a taproot signer object, the Private Key must be present - * @param opts - tweak options - * @returns a Signer having the Private and Public keys tweaked - */ - static tweakSigner(signer: Signer, opts: TaprootSignerOpts = {}): Signer { - let privateKey: Uint8Array | undefined = signer.privateKey; - if (!privateKey) { - throw new Error('Private key is required for tweaking signer!'); - } - if (signer.publicKey[0] === 3) { - privateKey = ecc.privateNegate(privateKey!); - } - - const tweakedPrivateKey = ecc.privateAdd( - privateKey, - tapTweakHash(signer.publicKey.slice(1, 33), opts.tweakHash), - ); - if (!tweakedPrivateKey) { - throw new Error('Invalid tweaked private key!'); - } - - const ECPair = ECPairFactory(ecc); - return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { - network: opts.network, - }); - } - private __CACHE: PsbtCache; private opts: PsbtOpts; @@ -174,6 +143,7 @@ export class Psbt { // We will disable exporting the Psbt when unsafe sign is active. // because it is not BIP174 compliant. __UNSAFE_SIGN_NONSEGWIT: false, + __EC_LIB: opts.eccLib, }; if (this.data.inputs.length === 0) this.setVersion(2); @@ -380,10 +350,7 @@ export class Psbt { return this; } - finalizeInput( - inputIndex: number, - finalScriptsFunc: FinalScriptsFunc = getFinalScripts, - ): this { + finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this { const input = checkForInput(this.data.inputs, inputIndex); const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( inputIndex, @@ -394,13 +361,15 @@ export class Psbt { checkPartialSigSighashes(input); - const { finalScriptSig, finalScriptWitness } = finalScriptsFunc( + const fn = finalScriptsFunc || getFinalScripts; + const { finalScriptSig, finalScriptWitness } = fn( inputIndex, input, script, isSegwit, isP2SH, isP2WSH, + this.__CACHE.__EC_LIB, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); @@ -429,7 +398,10 @@ export class Psbt { redeemFromFinalWitnessScript(input.finalScriptWitness), ); const type = result.type === 'raw' ? '' : result.type + '-'; - const mainType = classifyScript(result.meaningfulScript); + const mainType = classifyScript( + result.meaningfulScript, + this.__CACHE.__EC_LIB, + ); return (type + mainType) as AllScriptType; } @@ -810,6 +782,40 @@ export class Psbt { } } +/** + * Helper method for converting a normal Signer into a Taproot Signer. + * Note that this helper method requires the Private Key of the Signer to be present. + * Steps: + * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key + * - tweak the private key with the provided hash (should be empty for key-path spending) + * @param signer - a taproot signer object, the Private Key must be present + * @param opts - tweak options + * @returns a Signer having the Private and Public keys tweaked + */ +export function tweakSigner(signer: Signer, opts: TaprootSignerOpts): Signer { + // todo: test ecc?? + let privateKey: Uint8Array | undefined = signer.privateKey; + if (!privateKey) { + throw new Error('Private key is required for tweaking signer!'); + } + if (signer.publicKey[0] === 3) { + privateKey = opts.eccLib.privateNegate(privateKey!); + } + + const tweakedPrivateKey = opts.eccLib.privateAdd( + privateKey, + tapTweakHash(signer.publicKey.slice(1, 33), opts.tweakHash), + ); + if (!tweakedPrivateKey) { + throw new Error('Invalid tweaked private key!'); + } + + const ECPair = ECPairFactory(opts.eccLib); + return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: opts.network, + }); +} + interface PsbtCache { __NON_WITNESS_UTXO_TX_CACHE: Transaction[]; __NON_WITNESS_UTXO_BUF_CACHE: Buffer[]; @@ -819,11 +825,13 @@ interface PsbtCache { __FEE?: number; __EXTRACTED_TX?: Transaction; __UNSAFE_SIGN_NONSEGWIT: boolean; + __EC_LIB?: TinySecp256k1Interface; } interface PsbtOptsOptional { network?: Network; maximumFeeRate?: number; + eccLib?: TinySecp256k1Interface; } interface PsbtOpts { @@ -896,8 +904,7 @@ export interface Signer { */ export interface TaprootSignerOpts { network?: Network; - // TODO: revisit. - eccLib?: any; + eccLib: TinySecp256k1Interface & ECPairTinySecp256k1Interface; /** The hash used to tweak the Signer */ tweakHash?: Buffer; } @@ -1247,11 +1254,12 @@ function getFinalScripts( isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, + eccLib?: TinySecp256k1Interface, ): { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | undefined; } { - const scriptType = classifyScript(script); + const scriptType = classifyScript(script, eccLib); if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -1405,7 +1413,7 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(meaningfulScript, ecc)) { + } else if (isP2TR(meaningfulScript, cache.__EC_LIB)) { const prevOuts: Output[] = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache), ); @@ -1492,7 +1500,7 @@ function getPayment( output: script, signature: partialSig[0].signature, }, - { eccLib: ecc }, + { validate: false }, // skip validation (for now) ); break; } @@ -1555,7 +1563,7 @@ function getScriptFromInput( if ( input.witnessScript || isP2WPKH(res.script!) || - isP2TR(res.script!, ecc) + isP2TR(res.script!, cache.__EC_LIB) ) { res.isSegwit = true; } @@ -1977,12 +1985,15 @@ type ScriptType = | 'pubkey' | 'taproot' | 'nonstandard'; -function classifyScript(script: Buffer): ScriptType { +function classifyScript( + script: Buffer, + eccLib?: TinySecp256k1Interface, +): ScriptType { if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash'; if (isP2MS(script)) return 'multisig'; if (isP2PK(script)) return 'pubkey'; - if (isP2TR(script, ecc)) return 'taproot'; + if (isP2TR(script, eccLib)) return 'taproot'; return 'nonstandard'; } diff --git a/ts_src/types.ts b/ts_src/types.ts index 840ab9be2..487f29757 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -82,6 +82,8 @@ export interface TinySecp256k1Interface { p: Uint8Array, tweak: Uint8Array, ): XOnlyPointAddTweakResult | null; + privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null; + privateNegate(d: Uint8Array): Uint8Array; } export const Buffer256bit = typeforce.BufferN(32); From b4d7760795da81ef4e883329c26238dc1a1badd4 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 10 Feb 2022 13:52:47 +0200 Subject: [PATCH 46/73] chore: take the garbage out. Remove `tweakSigner()` and ecpair dep from PSBT --- src/index.d.ts | 2 +- src/index.js | 8 +------- src/psbt.d.ts | 26 ------------------------ src/psbt.js | 39 +---------------------------------- test/psbt.spec.ts | 38 +--------------------------------- ts_src/index.ts | 1 - ts_src/psbt.ts | 52 ----------------------------------------------- 7 files changed, 4 insertions(+), 162 deletions(-) diff --git a/src/index.d.ts b/src/index.d.ts index 13259bed6..b93c2aa40 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -6,7 +6,7 @@ import * as script from './script'; export { address, crypto, networks, payments, script }; export { Block } from './block'; export { TaggedHashPrefix } from './crypto'; -export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, tweakSigner, } from './psbt'; +export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt'; export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; export { Network } from './networks'; diff --git a/src/index.js b/src/index.js index 55d2b0068..983b0cc76 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.Transaction = exports.opcodes = exports.tweakSigner = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0; +exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0; const address = require('./address'); exports.address = address; const crypto = require('./crypto'); @@ -25,12 +25,6 @@ Object.defineProperty(exports, 'Psbt', { return psbt_1.Psbt; }, }); -Object.defineProperty(exports, 'tweakSigner', { - enumerable: true, - get: function() { - return psbt_1.tweakSigner; - }, -}); var ops_1 = require('./ops'); Object.defineProperty(exports, 'opcodes', { enumerable: true, diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 57e3f4d35..0aad55d37 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -1,7 +1,6 @@ /// import { Psbt as PsbtBase } from 'bip174'; import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces'; -import { TinySecp256k1Interface as ECPairTinySecp256k1Interface } from 'ecpair'; import { Network } from './networks'; import { Transaction } from './transaction'; import { TinySecp256k1Interface } from './types'; @@ -109,17 +108,6 @@ export declare class Psbt { addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this; clearFinalizedInput(inputIndex: number): this; } -/** - * Helper method for converting a normal Signer into a Taproot Signer. - * Note that this helper method requires the Private Key of the Signer to be present. - * Steps: - * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key - * - tweak the private key with the provided hash (should be empty for key-path spending) - * @param signer - a taproot signer object, the Private Key must be present - * @param opts - tweak options - * @returns a Signer having the Private and Public keys tweaked - */ -export declare function tweakSigner(signer: Signer, opts: TaprootSignerOpts): Signer; interface PsbtOptsOptional { network?: Network; maximumFeeRate?: number; @@ -169,25 +157,11 @@ export interface HDSignerAsync extends HDSignerBase { } export interface Signer { publicKey: Buffer; - /** - * Private Key is optional, it is required only if the signer must be tweaked. - * See the `tweakSigner()` method. - */ - privateKey?: Buffer; network?: any; sign(hash: Buffer, lowR?: boolean): Buffer; signSchnorr?(hash: Buffer): Buffer; getPublicKey?(): Buffer; } -/** - * Options for tweaking a Signer into a valid Taproot Signer - */ -export interface TaprootSignerOpts { - network?: Network; - eccLib: TinySecp256k1Interface & ECPairTinySecp256k1Interface; - /** The hash used to tweak the Signer */ - tweakHash?: Buffer; -} export interface SignerAsync { publicKey: Buffer; network?: any; diff --git a/src/psbt.js b/src/psbt.js index 70594153a..4250f0b4d 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1,7 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tweakSigner = exports.Psbt = void 0; -const ecpair_1 = require('ecpair'); +exports.Psbt = void 0; const bip174_1 = require('bip174'); const varuint = require('bip174/src/lib/converter/varint'); const utils_1 = require('bip174/src/lib/utils'); @@ -12,7 +11,6 @@ const networks_1 = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); const transaction_1 = require('./transaction'); -const taprootutils_1 = require('./payments/taprootutils'); /** * These are the default arguments for a Psbt instance. */ @@ -648,41 +646,6 @@ class Psbt { } } exports.Psbt = Psbt; -/** - * Helper method for converting a normal Signer into a Taproot Signer. - * Note that this helper method requires the Private Key of the Signer to be present. - * Steps: - * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key - * - tweak the private key with the provided hash (should be empty for key-path spending) - * @param signer - a taproot signer object, the Private Key must be present - * @param opts - tweak options - * @returns a Signer having the Private and Public keys tweaked - */ -function tweakSigner(signer, opts) { - // todo: test ecc?? - let privateKey = signer.privateKey; - if (!privateKey) { - throw new Error('Private key is required for tweaking signer!'); - } - if (signer.publicKey[0] === 3) { - privateKey = opts.eccLib.privateNegate(privateKey); - } - const tweakedPrivateKey = opts.eccLib.privateAdd( - privateKey, - (0, taprootutils_1.tapTweakHash)( - signer.publicKey.slice(1, 33), - opts.tweakHash, - ), - ); - if (!tweakedPrivateKey) { - throw new Error('Invalid tweaked private key!'); - } - const ECPair = (0, ecpair_1.ECPairFactory)(opts.eccLib); - return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { - network: opts.network, - }); -} -exports.tweakSigner = tweakSigner; /** * This function is needed to pass to the bip174 base class's fromBuffer. * It takes the "transaction buffer" portion of the psbt buffer and returns a diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index d5693c7ed..e5198ea78 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -8,14 +8,7 @@ import { describe, it } from 'mocha'; const bip32 = BIP32Factory(ecc); const ECPair = ECPairFactory(ecc); -import { - networks as NETWORKS, - payments, - Psbt, - Signer, - SignerAsync, - tweakSigner, -} from '..'; +import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; import * as preFixtures from './fixtures/psbt.json'; @@ -1013,35 +1006,6 @@ describe(`Psbt`, () => { }); }); - describe('tweakSigner', () => { - it('Throws error if signer is missing private key', () => { - const keyPair = Object.assign({}, ECPair.makeRandom(), { - privateKey: null, - }); - assert.throws(() => { - tweakSigner(keyPair, { eccLib: ecc }); - }, new RegExp('Private key is required for tweaking signer!')); - }); - - it('Correctly creates tweaked signer', () => { - const keyPair = ECPair.fromPrivateKey( - Buffer.from( - 'accaf12e04e11b08fc28f5fe75b47ea663843b698981e31f1cafa2224d6e28c0', - 'hex', - ), - ); - const tweakedSigner: Signer = tweakSigner(keyPair, { eccLib: ecc }); - assert.strictEqual( - '029421e734b0f9d2c467ea7dd197c61acb4467cdcbc9f4cb0c571f8b63a5c40cae', - tweakedSigner.publicKey.toString('hex'), - ); - assert.strictEqual( - '1853f5034982ec659e015873a0a958a73eac785850f425fd3444b12430d58692', - tweakedSigner.privateKey!.toString('hex'), - ); - }); - }); - describe('create 1-to-1 transaction', () => { const alice = ECPair.fromWIF( 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr', diff --git a/ts_src/index.ts b/ts_src/index.ts index 6a0f00a1e..d8b8619d1 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -16,7 +16,6 @@ export { SignerAsync, HDSigner, HDSignerAsync, - tweakSigner, } from './psbt'; export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index c74278b78..504b48fe2 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1,5 +1,3 @@ -import { ECPairFactory } from 'ecpair'; - import { Psbt as PsbtBase } from 'bip174'; import * as varuint from 'bip174/src/lib/converter/varint'; import { @@ -15,7 +13,6 @@ import { TransactionFromBuffer, } from 'bip174/src/lib/interfaces'; import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; -import { TinySecp256k1Interface as ECPairTinySecp256k1Interface } from 'ecpair'; import { fromOutputScript, toOutputScript } from './address'; import { cloneBuffer, reverseBuffer } from './bufferutils'; @@ -24,7 +21,6 @@ import { bitcoin as btcNetwork, Network } from './networks'; import * as payments from './payments'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; -import { tapTweakHash } from './payments/taprootutils'; import { TinySecp256k1Interface } from './types'; export interface TransactionInput { @@ -782,40 +778,6 @@ export class Psbt { } } -/** - * Helper method for converting a normal Signer into a Taproot Signer. - * Note that this helper method requires the Private Key of the Signer to be present. - * Steps: - * - if the Y coordinate of the Signer Public Key is odd then negate the Private Key - * - tweak the private key with the provided hash (should be empty for key-path spending) - * @param signer - a taproot signer object, the Private Key must be present - * @param opts - tweak options - * @returns a Signer having the Private and Public keys tweaked - */ -export function tweakSigner(signer: Signer, opts: TaprootSignerOpts): Signer { - // todo: test ecc?? - let privateKey: Uint8Array | undefined = signer.privateKey; - if (!privateKey) { - throw new Error('Private key is required for tweaking signer!'); - } - if (signer.publicKey[0] === 3) { - privateKey = opts.eccLib.privateNegate(privateKey!); - } - - const tweakedPrivateKey = opts.eccLib.privateAdd( - privateKey, - tapTweakHash(signer.publicKey.slice(1, 33), opts.tweakHash), - ); - if (!tweakedPrivateKey) { - throw new Error('Invalid tweaked private key!'); - } - - const ECPair = ECPairFactory(opts.eccLib); - return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { - network: opts.network, - }); -} - interface PsbtCache { __NON_WITNESS_UTXO_TX_CACHE: Transaction[]; __NON_WITNESS_UTXO_BUF_CACHE: Buffer[]; @@ -889,25 +851,11 @@ export interface HDSignerAsync extends HDSignerBase { export interface Signer { publicKey: Buffer; - /** - * Private Key is optional, it is required only if the signer must be tweaked. - * See the `tweakSigner()` method. - */ - privateKey?: Buffer; network?: any; sign(hash: Buffer, lowR?: boolean): Buffer; signSchnorr?(hash: Buffer): Buffer; getPublicKey?(): Buffer; } -/** - * Options for tweaking a Signer into a valid Taproot Signer - */ -export interface TaprootSignerOpts { - network?: Network; - eccLib: TinySecp256k1Interface & ECPairTinySecp256k1Interface; - /** The hash used to tweak the Signer */ - tweakHash?: Buffer; -} export interface SignerAsync { publicKey: Buffer; From 6f1e7ea3f7d148b89f101f0a607f217437047b93 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Feb 2022 11:42:23 +0200 Subject: [PATCH 47/73] feat: add taproot check for signInputAsync() --- src/psbt.js | 18 ++++++++++++++++++ ts_src/psbt.ts | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/psbt.js b/src/psbt.js index 4250f0b4d..fc7bfe15f 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -585,6 +585,24 @@ class Psbt { this.__CACHE, sighashTypes, ); + const scriptType = this.getInputType(inputIndex); + if (scriptType === 'taproot') { + if (!keyPair.signSchnorr) { + throw new Error( + `Need Schnorr Signer to sign taproot input #${inputIndex}.`, + ); + } + return Promise.resolve(keyPair.signSchnorr(hash)).then(signature => { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature, + }, + ]; + // must be changed to use the `updateInput()` public API + this.data.inputs[inputIndex].partialSig = partialSig; + }); + } return Promise.resolve(keyPair.sign(hash)).then(signature => { const partialSig = [ { diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 504b48fe2..9497081e2 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -706,6 +706,26 @@ export class Psbt { sighashTypes, ); + const scriptType = this.getInputType(inputIndex); + + if (scriptType === 'taproot') { + if (!keyPair.signSchnorr) { + throw new Error( + `Need Schnorr Signer to sign taproot input #${inputIndex}.`, + ); + } + return Promise.resolve(keyPair.signSchnorr(hash)).then(signature => { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature, + }, + ]; + // must be changed to use the `updateInput()` public API + this.data.inputs[inputIndex].partialSig = partialSig; + }); + } + return Promise.resolve(keyPair.sign(hash)).then(signature => { const partialSig = [ { From 9ef904c5825a40130ce236055b8618fd3e7aeeb7 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Feb 2022 16:29:32 +0200 Subject: [PATCH 48/73] feat: add stricter validation for taproot addresses --- src/address.d.ts | 5 +++-- src/address.js | 21 +++++++++++++++------ test/address.spec.ts | 17 ++++++++++++----- test/fixtures/address.json | 37 ++++++++++++++++++++++++++++++++----- ts_src/address.ts | 34 +++++++++++++++++++++++++++------- 5 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/address.d.ts b/src/address.d.ts index be0e00a61..13922dab3 100644 --- a/src/address.d.ts +++ b/src/address.d.ts @@ -1,5 +1,6 @@ /// import { Network } from './networks'; +import { TinySecp256k1Interface } from './types'; export interface Base58CheckResult { hash: Buffer; version: number; @@ -13,5 +14,5 @@ export declare function fromBase58Check(address: string): Base58CheckResult; export declare function fromBech32(address: string): Bech32Result; export declare function toBase58Check(hash: Buffer, version: number): string; export declare function toBech32(data: Buffer, version: number, prefix: string): string; -export declare function fromOutputScript(output: Buffer, network?: Network): string; -export declare function toOutputScript(address: string, network?: Network): Buffer; +export declare function fromOutputScript(output: Buffer, network?: Network, eccLib?: TinySecp256k1Interface): string; +export declare function toOutputScript(address: string, network?: Network, eccLib?: TinySecp256k1Interface): Buffer; diff --git a/src/address.js b/src/address.js index 164bf7ef1..2c7bc4857 100644 --- a/src/address.js +++ b/src/address.js @@ -4,14 +4,13 @@ exports.toOutputScript = exports.fromOutputScript = exports.toBech32 = exports.t const networks = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); -const types = require('./types'); +const types_1 = require('./types'); const bech32_1 = require('bech32'); const bs58check = require('bs58check'); -const { typeforce } = types; const FUTURE_SEGWIT_MAX_SIZE = 40; const FUTURE_SEGWIT_MIN_SIZE = 2; const FUTURE_SEGWIT_MAX_VERSION = 16; -const FUTURE_SEGWIT_MIN_VERSION = 1; +const FUTURE_SEGWIT_MIN_VERSION = 2; const FUTURE_SEGWIT_VERSION_DIFF = 0x50; const FUTURE_SEGWIT_VERSION_WARNING = 'WARNING: Sending to a future segwit version address can lead to loss of funds. ' + @@ -69,7 +68,10 @@ function fromBech32(address) { } exports.fromBech32 = fromBech32; function toBase58Check(hash, version) { - typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); + (0, types_1.typeforce)( + (0, types_1.tuple)(types_1.Hash160bit, types_1.UInt8), + arguments, + ); const payload = Buffer.allocUnsafe(21); payload.writeUInt8(version, 0); hash.copy(payload, 1); @@ -84,7 +86,7 @@ function toBech32(data, version, prefix) { : bech32_1.bech32m.encode(prefix, words); } exports.toBech32 = toBech32; -function fromOutputScript(output, network) { +function fromOutputScript(output, network, eccLib) { // TODO: Network network = network || networks.bitcoin; try { @@ -99,13 +101,16 @@ function fromOutputScript(output, network) { try { return payments.p2wsh({ output, network }).address; } catch (e) {} + try { + if (eccLib) return payments.p2tr({ output, network }, { eccLib }).address; + } catch (e) {} try { return _toFutureSegwitAddress(output, network); } catch (e) {} throw new Error(bscript.toASM(output) + ' has no matching Address'); } exports.fromOutputScript = fromOutputScript; -function toOutputScript(address, network) { +function toOutputScript(address, network, eccLib) { network = network || networks.bitcoin; let decodeBase58; let decodeBech32; @@ -129,6 +134,10 @@ function toOutputScript(address, network) { return payments.p2wpkh({ hash: decodeBech32.data }).output; if (decodeBech32.data.length === 32) return payments.p2wsh({ hash: decodeBech32.data }).output; + } else if (decodeBech32.version === 1) { + if (decodeBech32.data.length === 32 && eccLib) + return payments.p2tr({ pubkey: decodeBech32.data }, { eccLib }) + .output; } else if ( decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION && decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && diff --git a/test/address.spec.ts b/test/address.spec.ts index b1304c323..be08cf803 100644 --- a/test/address.spec.ts +++ b/test/address.spec.ts @@ -1,5 +1,6 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; +import * as ecc from 'tiny-secp256k1'; import * as baddress from '../src/address'; import * as bscript from '../src/script'; import * as fixtures from './fixtures/address.json'; @@ -68,7 +69,11 @@ describe('address', () => { fixtures.standard.forEach(f => { it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => { const script = bscript.fromASM(f.script); - const address = baddress.fromOutputScript(script, NETWORKS[f.network]); + const address = baddress.fromOutputScript( + script, + NETWORKS[f.network], + ecc, + ); assert.strictEqual(address, f.base58check || f.bech32!.toLowerCase()); }); @@ -79,7 +84,7 @@ describe('address', () => { const script = bscript.fromASM(f.script); assert.throws(() => { - baddress.fromOutputScript(script); + baddress.fromOutputScript(script, undefined, ecc); }, new RegExp(f.exception)); }); }); @@ -131,6 +136,7 @@ describe('address', () => { const script = baddress.toOutputScript( (f.base58check || f.bech32)!, NETWORKS[f.network], + ecc, ); assert.strictEqual(bscript.toASM(script), f.script); @@ -138,10 +144,11 @@ describe('address', () => { }); fixtures.invalid.toOutputScript.forEach(f => { - it('throws when ' + f.exception, () => { + it('throws when ' + (f.exception || f.paymentException), () => { + const exception = f.paymentException || `${f.address} ${f.exception}`; assert.throws(() => { - baddress.toOutputScript(f.address, f.network as any); - }, new RegExp(f.address + ' ' + f.exception)); + baddress.toOutputScript(f.address, f.network as any, ecc); + }, new RegExp(exception)); }); }); }); diff --git a/test/fixtures/address.json b/test/fixtures/address.json index 765ea8aca..0430b7887 100644 --- a/test/fixtures/address.json +++ b/test/fixtures/address.json @@ -80,10 +80,10 @@ }, { "network": "bitcoin", - "bech32": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", + "bech32": "bc1p3efq8ujsj0qr5xvms7mv89p8cz0crqdtuxe9ms6grqgxc9sgsntslthf6w", "version": 1, - "data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", - "script": "OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6" + "data": "8e5203f25093c03a199b87b6c39427c09f8181abe1b25dc34818106c160884d7", + "script": "OP_1 8e5203f25093c03a199b87b6c39427c09f8181abe1b25dc34818106c160884d7" }, { "network": "bitcoin", @@ -116,10 +116,16 @@ ], "bech32": [ { - "address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", + "address": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + "version": 0, + "prefix": "tb", + "data": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433" + }, + { + "address": "bc1p3efq8ujsj0qr5xvms7mv89p8cz0crqdtuxe9ms6grqgxc9sgsntslthf6w", "version": 1, "prefix": "bc", - "data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6" + "data": "8e5203f25093c03a199b87b6c39427c09f8181abe1b25dc34818106c160884d7" }, { "address": "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", @@ -195,6 +201,19 @@ { "exception": "has no matching Address", "script": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675" + }, + { + "exception": "has no matching Address", + "script": "OP_1 75" + }, + { + "exception": "has no matching Address", + "script": "OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675" + }, + { + "description": "pubkey is not valid x coordinate", + "exception": "has no matching Address", + "script": "OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" } ], "toOutputScript": [ @@ -297,6 +316,14 @@ { "address": "bc1gmk9yu", "exception": "has no matching Script" + }, + { + "address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw55h884v", + "exception": "has no matching Script" + }, + { + "address": "bc1pllllllllllllllllllllllllllllllllllllllllllllallllshsdfvw2y", + "paymentException": "TypeError: Invalid pubkey for p2tr" } ] } diff --git a/ts_src/address.ts b/ts_src/address.ts index 753589d46..62bcf2ef7 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -2,11 +2,15 @@ import { Network } from './networks'; import * as networks from './networks'; import * as payments from './payments'; import * as bscript from './script'; -import * as types from './types'; +import { + typeforce, + tuple, + Hash160bit, + UInt8, + TinySecp256k1Interface, +} from './types'; import { bech32, bech32m } from 'bech32'; import * as bs58check from 'bs58check'; -const { typeforce } = types; - export interface Base58CheckResult { hash: Buffer; version: number; @@ -21,7 +25,7 @@ export interface Bech32Result { const FUTURE_SEGWIT_MAX_SIZE: number = 40; const FUTURE_SEGWIT_MIN_SIZE: number = 2; const FUTURE_SEGWIT_MAX_VERSION: number = 16; -const FUTURE_SEGWIT_MIN_VERSION: number = 1; +const FUTURE_SEGWIT_MIN_VERSION: number = 2; const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50; const FUTURE_SEGWIT_VERSION_WARNING: string = 'WARNING: Sending to a future segwit version address can lead to loss of funds. ' + @@ -93,7 +97,7 @@ export function fromBech32(address: string): Bech32Result { } export function toBase58Check(hash: Buffer, version: number): string { - typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); + typeforce(tuple(Hash160bit, UInt8), arguments); const payload = Buffer.allocUnsafe(21); payload.writeUInt8(version, 0); @@ -115,7 +119,11 @@ export function toBech32( : bech32m.encode(prefix, words); } -export function fromOutputScript(output: Buffer, network?: Network): string { +export function fromOutputScript( + output: Buffer, + network?: Network, + eccLib?: TinySecp256k1Interface, +): string { // TODO: Network network = network || networks.bitcoin; @@ -131,6 +139,10 @@ export function fromOutputScript(output: Buffer, network?: Network): string { try { return payments.p2wsh({ output, network }).address as string; } catch (e) {} + try { + if (eccLib) + return payments.p2tr({ output, network }, { eccLib }).address as string; + } catch (e) {} try { return _toFutureSegwitAddress(output, network); } catch (e) {} @@ -138,7 +150,11 @@ export function fromOutputScript(output: Buffer, network?: Network): string { throw new Error(bscript.toASM(output) + ' has no matching Address'); } -export function toOutputScript(address: string, network?: Network): Buffer { +export function toOutputScript( + address: string, + network?: Network, + eccLib?: TinySecp256k1Interface, +): Buffer { network = network || networks.bitcoin; let decodeBase58: Base58CheckResult | undefined; @@ -165,6 +181,10 @@ export function toOutputScript(address: string, network?: Network): Buffer { return payments.p2wpkh({ hash: decodeBech32.data }).output as Buffer; if (decodeBech32.data.length === 32) return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer; + } else if (decodeBech32.version === 1) { + if (decodeBech32.data.length === 32 && eccLib) + return payments.p2tr({ pubkey: decodeBech32.data }, { eccLib }) + .output as Buffer; } else if ( decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION && decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && From d3e9aa62d6a1e502ee589a6557bd4a6a6b67ce7f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Feb 2022 16:41:56 +0200 Subject: [PATCH 49/73] fix: fix integration test --- test/integration/taproot.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index f7b3733fa..b3ff997b0 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -12,7 +12,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { const myKey = bip32.fromSeed(rng(64), regtest); const output = createKeySpendOutput(myKey.publicKey); - const address = bitcoin.address.fromOutputScript(output, regtest); + const address = bitcoin.address.fromOutputScript(output, regtest, ecc); // amount from faucet const amount = 42e4; // amount to send From 203974d40614b48c4baac71e9bac4f6cbb181149 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 22 Feb 2022 13:57:34 +0200 Subject: [PATCH 50/73] feat: correctly identify P2TR (pass `eccLib` to `toOutputScript` and `fromOutputScript`) --- src/psbt.js | 7 ++++++- ts_src/psbt.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index fc7bfe15f..03c2cd048 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -133,6 +133,7 @@ class Psbt { address = (0, address_1.fromOutputScript)( output.script, this.opts.network, + this.__CACHE.__EC_LIB, ); } catch (_) {} return { @@ -235,7 +236,11 @@ class Psbt { const { address } = outputData; if (typeof address === 'string') { const { network } = this.opts; - const script = (0, address_1.toOutputScript)(address, network); + const script = (0, address_1.toOutputScript)( + address, + network, + this.__CACHE.__EC_LIB, + ); outputData = Object.assign(outputData, { script }); } const c = this.__CACHE; diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 9497081e2..163da5b2c 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -190,7 +190,11 @@ export class Psbt { return this.__CACHE.__TX.outs.map(output => { let address; try { - address = fromOutputScript(output.script, this.opts.network); + address = fromOutputScript( + output.script, + this.opts.network, + this.__CACHE.__EC_LIB, + ); } catch (_) {} return { script: cloneBuffer(output.script), @@ -304,7 +308,7 @@ export class Psbt { const { address } = outputData as any; if (typeof address === 'string') { const { network } = this.opts; - const script = toOutputScript(address, network); + const script = toOutputScript(address, network, this.__CACHE.__EC_LIB); outputData = Object.assign(outputData, { script }); } const c = this.__CACHE; From 719e4a4d85f923625f720dc4b52fef5b9e6c1d20 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 17:58:30 +0200 Subject: [PATCH 51/73] add integration tests for taproot (#3) * test: add PSBT example for taproot * refactor: variable renaming * refactor: use constant for tapscript leaf version * feat: reuse redeem field for taproot spend * test: make sure it defaults to tapscript version 192 * chore: remove `scriptLeaf` logic * feat: add tapscript sign() a finalize() logic * test: spend taproot script-path * test: add tapscript for OP_CHECKSEQUENCEVERIFY * feat: add multisig integration test * refactor: rename scriptsTree to scriptTree (as per BP341) * feat: check that the scriptTree is a binary tree * feat: compute the redeem from witness; add unit tests * test: add test for invalid redeem script * feat: check the redeemVersion on the input data first * test: add tests for taproot script-path sign, and key-path finalize --- src/ops.js | 1 + src/payments/index.d.ts | 6 +- src/payments/p2tr.js | 94 +++++-- src/payments/taprootutils.d.ts | 9 +- src/payments/taprootutils.js | 40 ++- src/psbt.d.ts | 7 +- src/psbt.js | 127 +++++---- test/fixtures/p2tr.json | 230 +++++++++++++---- test/fixtures/psbt.json | 38 +++ test/integration/taproot.md | 3 +- test/integration/taproot.spec.ts | 426 ++++++++++++++++++++++++++++++- test/payments.utils.ts | 25 +- test/psbt.spec.ts | 18 +- test/psbt.utils.ts | 53 ++++ ts_src/ops.ts | 2 + ts_src/payments/index.ts | 6 +- ts_src/payments/p2tr.ts | 104 ++++++-- ts_src/payments/taprootutils.ts | 37 ++- ts_src/psbt.ts | 147 +++++++---- ts_src/types.ts | 1 + 20 files changed, 1136 insertions(+), 238 deletions(-) create mode 100644 test/psbt.utils.ts diff --git a/src/ops.js b/src/ops.js index 9d629cd00..7853ad0f0 100644 --- a/src/ops.js +++ b/src/ops.js @@ -117,6 +117,7 @@ const OPS = { OP_NOP8: 183, OP_NOP9: 184, OP_NOP10: 185, + OP_CHECKSIGADD: 186, OP_PUBKEYHASH: 253, OP_PUBKEY: 254, OP_INVALIDOPCODE: 255, diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 6b186c4d1..386ea3f68 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -1,6 +1,6 @@ /// import { Network } from '../networks'; -import { TaprootLeaf, TinySecp256k1Interface } from '../types'; +import { TinySecp256k1Interface, TaprootLeaf } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -25,8 +25,8 @@ export interface Payment { address?: string; hash?: Buffer; redeem?: Payment; - scriptsTree?: any; - scriptLeaf?: TaprootLeaf; + redeemVersion?: number; + scriptTree?: TaprootLeaf[]; witness?: Buffer[]; } export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 79fe62a6f..d4406c2b2 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -10,8 +10,9 @@ const lazy = require('./lazy'); const bech32_1 = require('bech32'); const testecc_1 = require('./testecc'); const OPS = bscript.OPS; -const TAPROOT_VERSION = 0x01; +const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; +const LEAF_VERSION_MASK = 0b11111110; function p2tr(a, opts) { if ( !a.address && @@ -40,11 +41,15 @@ function p2tr(a, opts) { witness: types_1.typeforce.maybe( types_1.typeforce.arrayOf(types_1.typeforce.Buffer), ), - // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? - scriptLeaf: types_1.typeforce.maybe({ - version: types_1.typeforce.maybe(types_1.typeforce.Number), + scriptTree: types_1.typeforce.maybe(taprootutils_1.isTapTree), + redeem: types_1.typeforce.maybe({ output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), }), + redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number), }, a, ); @@ -58,13 +63,13 @@ function p2tr(a, opts) { data: buffer_1.Buffer.from(data), }; }); + // remove annex if present, ignored by taproot const _witness = lazy.value(() => { if (!a.witness || !a.witness.length) return; if ( a.witness.length >= 2 && a.witness[a.witness.length - 1][0] === ANNEX_PREFIX ) { - // remove annex, ignored by taproot return a.witness.slice(0, -1); } return a.witness.slice(); @@ -74,17 +79,16 @@ function p2tr(a, opts) { lazy.prop(o, 'address', () => { if (!o.pubkey) return; const words = bech32_1.bech32m.toWords(o.pubkey); - words.unshift(TAPROOT_VERSION); + words.unshift(TAPROOT_WITNESS_VERSION); return bech32_1.bech32m.encode(network.bech32, words); }); lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) - return (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; + if (a.scriptTree) return (0, taprootutils_1.toHashTree)(a.scriptTree).hash; const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; - const leafVersion = controlBlock[0] & 0b11111110; + const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; const script = w[w.length - 2]; const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); @@ -95,8 +99,25 @@ function p2tr(a, opts) { if (!o.pubkey) return; return bscript.compile([OPS.OP_1, o.pubkey]); }); - lazy.prop(o, 'scriptLeaf', () => { - if (a.scriptLeaf) return a.scriptLeaf; + lazy.prop(o, 'redeemVersion', () => { + if (a.redeemVersion) return a.redeemVersion; + if ( + a.redeem && + a.redeem.redeemVersion !== undefined && + a.redeem.redeemVersion !== null + ) { + return a.redeem.redeemVersion; + } + return taprootutils_1.LEAF_VERSION_TAPSCRIPT; + }); + lazy.prop(o, 'redeem', () => { + const witness = _witness(); // witness without annex + if (!witness || witness.length < 2) return; + return { + output: witness[witness.length - 2], + witness: witness.slice(0, -2), + redeemVersion: witness[witness.length - 1][0] & LEAF_VERSION_MASK, + }; }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; @@ -118,29 +139,25 @@ function p2tr(a, opts) { if (!a.witness || a.witness.length !== 1) return; return a.witness[0]; }); - lazy.prop(o, 'input', () => { - // todo - }); lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; - if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { // todo: optimize/cache - const hashTree = (0, taprootutils_1.toHashTree)(a.scriptsTree); + const hashTree = (0, taprootutils_1.toHashTree)(a.scriptTree); const leafHash = (0, taprootutils_1.tapLeafHash)( - a.scriptLeaf.output, - a.scriptLeaf.version, + a.redeem.output, + o.redeemVersion, ); const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); if (!outputKey) return; - const version = a.scriptLeaf.version || 0xc0; const controlBock = buffer_1.Buffer.concat( [ - buffer_1.Buffer.from([version | outputKey.parity]), + buffer_1.Buffer.from([o.redeemVersion | outputKey.parity]), a.internalPubkey, ].concat(path.reverse()), ); - return [a.scriptLeaf.output, controlBock]; + return [a.redeem.output, controlBock]; } if (a.signature) return [a.signature]; }); @@ -150,7 +167,7 @@ function p2tr(a, opts) { if (a.address) { if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== TAPROOT_VERSION) + if (_address().version !== TAPROOT_WITNESS_VERSION) throw new TypeError('Invalid address version'); if (_address().data.length !== 32) throw new TypeError('Invalid address data'); @@ -182,11 +199,32 @@ function p2tr(a, opts) { if (!_ecc().isXOnlyPoint(pubkey)) throw new TypeError('Invalid pubkey for p2tr'); } - if (a.hash && a.scriptsTree) { - const hash = (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; + if (a.hash && a.scriptTree) { + const hash = (0, taprootutils_1.toHashTree)(a.scriptTree).hash; if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } const witness = _witness(); + // compare the provided redeem data with the one computed from witness + if (a.redeem && o.redeem) { + if (a.redeem.redeemVersion) { + if (a.redeem.redeemVersion !== o.redeem.redeemVersion) + throw new TypeError('Redeem.redeemVersion and witness mismatch'); + } + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output).length === 0) + throw new TypeError('Redeem.output is invalid'); + // output redeem is constructed from the witness + if (o.redeem.output && !a.redeem.output.equals(o.redeem.output)) + throw new TypeError('Redeem.output and witness mismatch'); + } + if (a.redeem.witness) { + if ( + o.redeem.witness && + !stacksEqual(a.redeem.witness, o.redeem.witness) + ) + throw new TypeError('Redeem.witness and witness mismatch'); + } + } if (witness && witness.length) { if (witness.length === 1) { // key spending @@ -215,7 +253,7 @@ function p2tr(a, opts) { throw new TypeError('Internal pubkey mismatch'); if (!_ecc().isXOnlyPoint(internalPubkey)) throw new TypeError('Invalid internalPubkey for p2tr witness'); - const leafVersion = controlBlock[0] & 0b11111110; + const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; const script = witness[witness.length - 2]; const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); const hash = (0, taprootutils_1.rootHashFromPath)( @@ -248,3 +286,9 @@ function tweakKey(pubKey, h, eccLib) { x: buffer_1.Buffer.from(res.xOnlyPubkey), }; } +function stacksEqual(a, b) { + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); +} diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index f68a37495..82a488530 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,5 +1,6 @@ /// import { TaprootLeaf } from '../types'; +export declare const LEAF_VERSION_TAPSCRIPT = 192; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; export interface HashTree { hash: Buffer; @@ -9,12 +10,16 @@ export interface HashTree { /** * Build the hash tree from the scripts binary tree. * The binary tree can be balanced or not. - * @param scriptsTree - is a list representing a binary tree where an element can be: + * @param scriptTree - is a list representing a binary tree where an element can be: * - a taproot leaf [(output, version)], or * - a pair of two taproot leafs [(output, version), (output, version)], or * - one taproot leaf and a list of elements */ -export declare function toHashTree(scriptsTree: TaprootLeaf[]): HashTree; +export declare function toHashTree(scriptTree: TaprootLeaf[]): HashTree; +/** + * Check if the tree is a binary tree with leafs of type TaprootLeaf + */ +export declare function isTapTree(scriptTree: TaprootLeaf[]): boolean; /** * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 2da6a4b7d..2a807214d 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -1,13 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapTweakHash = exports.tapLeafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = void 0; +exports.tapTweakHash = exports.tapLeafHash = exports.findScriptPath = exports.isTapTree = exports.toHashTree = exports.rootHashFromPath = exports.LEAF_VERSION_TAPSCRIPT = void 0; const buffer_1 = require('buffer'); const bcrypto = require('../crypto'); const bufferutils_1 = require('../bufferutils'); -const LEAF_VERSION_TAPSCRIPT = 0xc0; const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; const TAP_TWEAK_TAG = 'TapTweak'; +exports.LEAF_VERSION_TAPSCRIPT = 0xc0; function rootHashFromPath(controlBlock, tapLeafMsg) { const k = [tapLeafMsg]; const e = []; @@ -26,26 +26,26 @@ exports.rootHashFromPath = rootHashFromPath; /** * Build the hash tree from the scripts binary tree. * The binary tree can be balanced or not. - * @param scriptsTree - is a list representing a binary tree where an element can be: + * @param scriptTree - is a list representing a binary tree where an element can be: * - a taproot leaf [(output, version)], or * - a pair of two taproot leafs [(output, version), (output, version)], or * - one taproot leaf and a list of elements */ -function toHashTree(scriptsTree) { - if (scriptsTree.length === 1) { - const script = scriptsTree[0]; +function toHashTree(scriptTree) { + if (scriptTree.length === 1) { + const script = scriptTree[0]; if (Array.isArray(script)) { return toHashTree(script); } - script.version = script.version || LEAF_VERSION_TAPSCRIPT; + script.version = script.version || exports.LEAF_VERSION_TAPSCRIPT; if ((script.version & 1) !== 0) throw new TypeError('Invalid script version'); return { hash: tapLeafHash(script.output, script.version), }; } - const left = toHashTree([scriptsTree[0]]); - const right = toHashTree([scriptsTree[1]]); + const left = toHashTree([scriptTree[0]]); + const right = toHashTree([scriptTree[1]]); let leftHash = left.hash; let rightHash = right.hash; if (leftHash.compare(rightHash) === 1) @@ -57,6 +57,26 @@ function toHashTree(scriptsTree) { }; } exports.toHashTree = toHashTree; +/** + * Check if the tree is a binary tree with leafs of type TaprootLeaf + */ +function isTapTree(scriptTree) { + if (scriptTree.length > 2) return false; + if (scriptTree.length === 1) { + const script = scriptTree[0]; + if (Array.isArray(script)) { + return isTapTree(script); + } + if (!script.output) return false; + script.version = script.version || exports.LEAF_VERSION_TAPSCRIPT; + if ((script.version & 1) !== 0) return false; + return true; + } + if (!isTapTree([scriptTree[0]])) return false; + if (!isTapTree([scriptTree[1]])) return false; + return true; +} +exports.isTapTree = isTapTree; /** * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree @@ -80,7 +100,7 @@ function findScriptPath(node, hash) { } exports.findScriptPath = findScriptPath; function tapLeafHash(script, version) { - version = version || LEAF_VERSION_TAPSCRIPT; + version = version || exports.LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( TAP_LEAF_TAG, buffer_1.Buffer.concat([ diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 0aad55d37..cfc64a6e7 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -180,9 +180,10 @@ input: PsbtInput, // The PSBT input contents script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.) isSegwit: boolean, // Is it segwit? isP2SH: boolean, // Is it P2SH? -isP2WSH: boolean) => { +isP2WSH: boolean, // Is it P2WSH? +eccLib?: TinySecp256k1Interface) => { finalScriptSig: Buffer | undefined; - finalScriptWitness: Buffer | undefined; + finalScriptWitness: Buffer | Buffer[] | undefined; }; -declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'taproot' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard'; +declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'taproot' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard' | 'p2tr-pubkey' | 'p2tr-nonstandard'; export {}; diff --git a/src/psbt.js b/src/psbt.js index 03c2cd048..ec5614a07 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -11,6 +11,7 @@ const networks_1 = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); const transaction_1 = require('./transaction'); +const taprootutils_1 = require('./payments/taprootutils'); /** * These are the default arguments for a Psbt instance. */ @@ -279,13 +280,19 @@ class Psbt { } finalizeInput(inputIndex, finalScriptsFunc) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); - const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( - inputIndex, - input, - this.__CACHE, - ); + const { + script, + isP2SH, + isP2WSH, + isSegwit, + isTapscript, + } = getScriptFromInput(inputIndex, input, this.__CACHE); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); + if (isTapscript && !finalScriptsFunc) + throw new Error( + `Taproot script-path finalizer required for input #${inputIndex}`, + ); const fn = finalScriptsFunc || getFinalScripts; const { finalScriptSig, finalScriptWitness } = fn( inputIndex, @@ -297,8 +304,13 @@ class Psbt { this.__CACHE.__EC_LIB, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); - if (finalScriptWitness) - this.data.updateInput(inputIndex, { finalScriptWitness }); + if (finalScriptWitness) { + // allow custom finalizers to build the witness as an array + const witness = Array.isArray(finalScriptWitness) + ? witnessStackToScriptWitness(finalScriptWitness) + : finalScriptWitness; + this.data.updateInput(inputIndex, { finalScriptWitness: witness }); + } if (!finalScriptSig && !finalScriptWitness) throw new Error(`Unknown error finalizing input #${inputIndex}`); this.data.clearFinalizedInput(inputIndex); @@ -318,6 +330,7 @@ class Psbt { input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness), + this.__CACHE, ); const type = result.type === 'raw' ? '' : result.type + '-'; const mainType = classifyScript( @@ -372,13 +385,12 @@ class Psbt { let sighashCache; const scriptType = this.getInputType(inputIndex); for (const pSig of mySigs) { - const sig = - scriptType === 'taproot' - ? { - signature: pSig.signature, - hashType: transaction_1.Transaction.SIGHASH_DEFAULT, - } - : bscript.signature.decode(pSig.signature); + const sig = isTaprootSpend(scriptType) + ? { + signature: pSig.signature, + hashType: transaction_1.Transaction.SIGHASH_DEFAULT, + } + : bscript.signature.decode(pSig.signature); const { hash, script } = sighashCache !== sig.hashType ? getHashForSig( @@ -550,18 +562,17 @@ class Psbt { sighashTypes, ); const scriptType = this.getInputType(inputIndex); - if (scriptType === 'taproot') { + if (isTaprootSpend(scriptType)) { if (!keyPair.signSchnorr) { throw new Error( `Need Schnorr Signer to sign taproot input #${inputIndex}.`, ); } - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: keyPair.signSchnorr(hash), - }, - ]; + const partialSig = this.data.inputs[inputIndex].partialSig || []; + partialSig.push({ + pubkey: keyPair.publicKey, + signature: keyPair.signSchnorr(hash), + }); // must be changed to use the `updateInput()` public API this.data.inputs[inputIndex].partialSig = partialSig; } else { @@ -591,19 +602,18 @@ class Psbt { sighashTypes, ); const scriptType = this.getInputType(inputIndex); - if (scriptType === 'taproot') { + if (isTaprootSpend(scriptType)) { if (!keyPair.signSchnorr) { throw new Error( `Need Schnorr Signer to sign taproot input #${inputIndex}.`, ); } return Promise.resolve(keyPair.signSchnorr(hash)).then(signature => { - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature, - }, - ]; + const partialSig = this.data.inputs[inputIndex].partialSig || []; + partialSig.push({ + pubkey: keyPair.publicKey, + signature, + }); // must be changed to use the `updateInput()` public API this.data.inputs[inputIndex].partialSig = partialSig; }); @@ -1046,6 +1056,7 @@ function getHashForSig( 'input', input.redeemScript, input.witnessScript, + cache, ); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { hash = unsignedTx.hashForWitnessV0( @@ -1064,17 +1075,21 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(meaningfulScript, cache.__EC_LIB)) { + } else if (isP2TR(prevout.script, cache.__EC_LIB)) { const prevOuts = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache), ); const signingScripts = prevOuts.map(o => o.script); const values = prevOuts.map(o => o.value); + const leafHash = input.witnessScript + ? (0, taprootutils_1.tapLeafHash)(input.witnessScript) + : undefined; hash = unsignedTx.hashForWitnessV1( inputIndex, signingScripts, values, transaction_1.Transaction.SIGHASH_DEFAULT, + leafHash, ); } else { // non-segwit @@ -1169,35 +1184,39 @@ function getScriptFromInput(inputIndex, input, cache) { const res = { script: null, isSegwit: false, + isTapscript: false, isP2SH: false, isP2WSH: false, }; - res.isP2SH = !!input.redeemScript; - res.isP2WSH = !!input.witnessScript; + let utxoScript = null; + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + const prevoutIndex = unsignedTx.ins[inputIndex].index; + utxoScript = nonWitnessUtxoTx.outs[prevoutIndex].script; + } else if (input.witnessUtxo) { + utxoScript = input.witnessUtxo.script; + } if (input.witnessScript) { res.script = input.witnessScript; } else if (input.redeemScript) { res.script = input.redeemScript; } else { - if (input.nonWitnessUtxo) { - const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( - cache, - input, - inputIndex, - ); - const prevoutIndex = unsignedTx.ins[inputIndex].index; - res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; - } else if (input.witnessUtxo) { - res.script = input.witnessUtxo.script; - } + res.script = utxoScript; } - if ( - input.witnessScript || - isP2WPKH(res.script) || - isP2TR(res.script, cache.__EC_LIB) - ) { + const isTaproot = utxoScript && isP2TR(utxoScript, cache.__EC_LIB); + // Segregated Witness versions 0 or 1 + if (input.witnessScript || isP2WPKH(res.script) || isTaproot) { res.isSegwit = true; } + if (isTaproot && input.witnessScript) { + res.isTapscript = true; + } + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript && !res.isTapscript; return res; } function getSignersFromHD(inputIndex, inputs, hdKeyPair) { @@ -1394,6 +1413,7 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) { 'input', input.redeemScript, input.witnessScript, + cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1405,6 +1425,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { 'output', output.redeemScript, output.witnessScript, + cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1453,10 +1474,12 @@ function getMeaningfulScript( ioType, redeemScript, witnessScript, + cache, ) { const isP2SH = isP2SHScript(script); const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); const isP2WSH = isP2WSHScript(script); + const isP2TRScript = isP2TR(script, cache && cache.__EC_LIB); if (isP2SH && redeemScript === undefined) throw new Error('scriptPubkey is P2SH but redeemScript missing'); if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) @@ -1476,6 +1499,9 @@ function getMeaningfulScript( } else if (isP2SH) { meaningfulScript = redeemScript; checkRedeemScript(index, script, redeemScript, ioType); + } else if (isP2TRScript && !!witnessScript) { + meaningfulScript = witnessScript; + // TODO: check here something? } else { meaningfulScript = script; } @@ -1487,6 +1513,8 @@ function getMeaningfulScript( ? 'p2sh' : isP2WSH ? 'p2wsh' + : isP2TRScript + ? 'p2tr' : 'raw', }; } @@ -1509,6 +1537,11 @@ function pubkeyInScript(pubkey, script) { ); }); } +function isTaprootSpend(scriptType) { + return ( + !!scriptType && (scriptType === 'taproot' || scriptType.startsWith('p2tr-')) + ); +} function classifyScript(script, eccLib) { if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash'; diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 4c7174fc0..b38bb1776 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -94,7 +94,7 @@ } }, { - "description": "address, pubkey, internalPubkey and output from witness", + "description": "address, pubkey, internalPubkey, redeeem and output from witness", "arguments": { "witness": [ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", @@ -112,6 +112,14 @@ "output": "OP_1 1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc", "signature": null, "input": null, + "redeem" : { + "output": "OP_DROP c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0 OP_CHECKSIG", + "redeemVersion": 192, + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba" + ] + }, "witness": [ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", @@ -153,7 +161,7 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with one leaf", "arguments": { "internalPubkey": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", - "scriptsTree": [ + "scriptTree": [ { "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" } @@ -174,7 +182,7 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with two leafs", "arguments": { "internalPubkey": "2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7", - "scriptsTree": [ + "scriptTree": [ { "output": "d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b OP_CHECKSIG" }, @@ -198,7 +206,7 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with three leafs", "arguments": { "internalPubkey": "7631cacec3343052d87ef4d0065f61dde82d7d2db0c1cc02ef61ef3c982ea763", - "scriptsTree": [ + "scriptTree": [ { "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" }, @@ -227,7 +235,7 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with four leafs", "arguments": { "internalPubkey": "d0c19def28bb1b39451c1a814737615983967780d223b79969ba692182c6006b", - "scriptsTree": [ + "scriptTree": [ [ { "output": "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744 OP_CHECKSIG" @@ -261,7 +269,7 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs", "arguments": { "internalPubkey": "f95886b02a84928c5c15bdca32784993105f73de27fa6ad8c1a60389b999267c", - "scriptsTree": [ + "scriptTree": [ [ { "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" @@ -310,7 +318,7 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs (2)", "arguments": { "internalPubkey": "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247", - "scriptsTree": [ + "scriptTree": [ { "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG" }, @@ -375,11 +383,11 @@ "description": "BIP341 Test case 2", "arguments": { "internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", - "scriptLeaf": { + "redeem": { "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", "version": 192 @@ -397,6 +405,10 @@ "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" ], + "redeem": { + "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", + "redeemVersion": 192 + }, "signature": null, "input": null } @@ -405,11 +417,11 @@ "description": "BIP341 Test case 3", "arguments": { "internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", - "scriptLeaf": { + "redeem": { "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", "version": 192 @@ -435,11 +447,11 @@ "description": "BIP341 Test case 4 - spend leaf 0", "arguments": { "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", - "scriptLeaf": { + "redeem": { "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", "version": 192 @@ -469,11 +481,11 @@ "description": "BIP341 Test case 4 - spend leaf 1", "arguments": { "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", - "scriptLeaf": { + "redeem": { "output": "424950333431", - "version": 152 + "redeemVersion": 152 }, - "scriptsTree": [ + "scriptTree": [ { "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", "version": 192 @@ -503,11 +515,11 @@ "description": "BIP341 Test case 5 - spend leaf 0", "arguments": { "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", - "scriptLeaf": { + "redeem": { "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", "version": 192 @@ -537,11 +549,11 @@ "description": "BIP341 Test case 5 - spend leaf 1", "arguments": { "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", - "scriptLeaf": { + "redeem": { "output": "546170726f6f74", - "version": 82 + "redeemVersion": 82 }, - "scriptsTree": [ + "scriptTree": [ { "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", "version": 192 @@ -571,11 +583,11 @@ "description": "BIP341 Test case 6 - spend leaf 0", "arguments": { "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", - "scriptLeaf": { + "redeem": { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", "version": 192 @@ -611,11 +623,11 @@ "description": "BIP341 Test case 6 - spend leaf 1", "arguments": { "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", - "scriptLeaf": { + "redeem": { "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", "version": 192 @@ -651,11 +663,11 @@ "description": "BIP341 Test case 6 - spend leaf 2", "arguments": { "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", - "scriptLeaf": { + "redeem": { "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", "version": 192 @@ -691,11 +703,10 @@ "description": "BIP341 Test case 7 - spend leaf 0", "arguments": { "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", - "scriptLeaf": { - "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", - "version": 192 + "redeem": { + "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG" }, - "scriptsTree": [ + "scriptTree": [ { "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", "version": 192 @@ -731,11 +742,11 @@ "description": "BIP341 Test case 7 - spend leaf 1", "arguments": { "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", - "scriptLeaf": { + "redeem": { "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", "version": 192 @@ -771,11 +782,11 @@ "description": "BIP341 Test case 7 - spend leaf 2", "arguments": { "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", - "scriptLeaf": { + "redeem": { "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, - "scriptsTree": [ + "scriptTree": [ { "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", "version": 192 @@ -884,12 +895,12 @@ } }, { - "description": "Hash mismatch between scriptsTree and hash", + "description": "Hash mismatch between scriptTree and hash", "exception": "Hash mismatch", "options": {}, "arguments": { "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", - "scriptsTree": [ + "scriptTree": [ { "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" } @@ -1017,6 +1028,133 @@ "c0a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" ] } + }, + { + "description": "Script Tree is not a binary tree (has tree leafs)", + "exception": "property \"scriptTree\" of type \\?isTapTree, got Array", + "options": {}, + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", + "scriptTree": [ + { + "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", + "version": 192 + }, + [ + { + "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", + "version": 192 + }, + { + "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", + "version": 192 + }, + { + "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", + "version": 192 + } + ] + ], + "hash": "b76077013c8e303085e300000000000000000000000000000000000000000000" + } + }, + { + "description": "Script Tree is not a TapTree tree (leaf has no script)", + "exception": "property \"scriptTree\" of type \\?isTapTree, got Array", + "options": {}, + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", + "scriptTree": [ + { + "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", + "version": 192 + }, + [ + [ + [ + [ + { + "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", + "version": 192 + }, + { + "version": 192 + } + ] + ] + ] + ] + ], + "hash": "b76077013c8e303085e300000000000000000000000000000000000000000000" + } + }, + { + "description": "Incorrect redeem version", + "exception": "Redeem.redeemVersion and witness mismatch", + "arguments": { + "witness": [ + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ], + "redeem": { + "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", + "redeemVersion": 111 + } + } + }, + { + "description": "Incorrect redeem output", + "exception": "Redeem.output and witness mismatch", + "arguments": { + "witness": [ + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ], + "redeem": { + "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e0000000000 OP_CHECKSIG", + "redeemVersion": 192 + } + } + }, + { + "description": "Incorrect redeem witness", + "exception": "Redeem.witness and witness mismatch", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ], + "redeem" : { + "output": "OP_DROP c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0 OP_CHECKSIG", + "redeemVersion": 192, + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e0100000000000000000000", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba" + ] + } + } + }, + { + "description": "Incorrect redeem output ASM", + "exception": "Redeem.output is invalid", + "arguments": { + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", + "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", + "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c" + ], + "redeem" : { + "output": "", + "redeemVersion": 192, + "witness": [ + "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e0100000000000000000000", + "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba" + ] + } + } } ], "dynamic": { diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index 2e7d9cf76..5a78e3527 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -297,6 +297,26 @@ } ], "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4iAgKUIec0sPnSxGfqfdGXxhrLRGfNy8n0ywxXH4tjpcQMrkADaUubfpFFrzbU+vL8qCzZE/FO+9unzylfpIgQZ4HTy2qPUtLvbyH59GApdz0SiUZGl8K6Crvt9YIfI/5FxbOLAAEBHxAnAAAAAAAAFgAUT6KsoSi2+d7lMJxPcAUeScZf1zIiAgOlTqRAWzyTP8WLKjtnrrbWBaYHnPb3MYIMk8qJJSuutEgwRQIhAKAiJLYIS+eYrjAJpM8GCc2/ofqpjXsGV8QMf9Ojm8SEAiBCwrAc/8HdsD5ZyW9uzpbsTJEz5wshwNgvksR4l/xbzwEAAAA=" + }, + { + "description": "Sign PSBT with 1 input [P2TR] (script-path, 3-of-3) and one output [P2TR]", + "psbt": "cHNidP8BAF4CAAAAAcPe80j90ChJ8zbpzcG0ZVC7LEvKwW5HRFLFTyc7y4bHAAAAAAD/////AZBBBgAAAAAAIlEgMqacf47eQ4lDRqNGRo3DRZ07bl/zz75tVIEa7xr7H0IAAAAAAAEBK6BoBgAAAAAAIlEgMqacf47eQ4lDRqNGRo3DRZ07bl/zz75tVIEa7xr7H0IBBWggj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WsIDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nruiCoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641crpTnAAA", + "isTaproot": true, + "keys": [ + { + "inputToSign": 0, + "WIF": "cRXSy63fXDve59e8cvqozVFfqXJB6YL6cPzoRewmEsux81SgPrfj" + }, + { + "inputToSign": 0, + "WIF": "cQQXUJocNBS6oZCCtyhCsdN5ammK6WoJWpx44ANKxZSN2A3WDDEN" + }, + { + "inputToSign": 0, + "WIF": "cTrPNrN2EQo4ppBHcFNxyLBFq2WLjZoNKY5nQbPwAGdhQqqsRKSu" + } + ], + "result": "cHNidP8BAF4CAAAAAcPe80j90ChJ8zbpzcG0ZVC7LEvKwW5HRFLFTyc7y4bHAAAAAAD/////AZBBBgAAAAAAIlEgMqacf47eQ4lDRqNGRo3DRZ07bl/zz75tVIEa7xr7H0IAAAAAAAEBK6BoBgAAAAAAIlEgMqacf47eQ4lDRqNGRo3DRZ07bl/zz75tVIEa7xr7H0IiAgI5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf560BOGsYvM/Ot27p7l86wu+8NyTgqenZPSYN3g88W0NWF1C6O2P7k8vMntZ7qXQJwahuxKQPjlHAhb+wCOp+sHi1cIgIDj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83VAygBYJBfi/tFJV/2WNjeuh+rBnU17ZtihdMICzGGN7OLURnBRB5oARqqvcGwQF4ta2sarDwZd+mg6DMaHUqOQ4CICA6irN7wWCdg0w5E7NTja0chNf5tqg1/8F1d3kVo3rjVyQGNFuQCECKRcz9CR7vuYOQ5p9dwxty0rMxt33MPpUh+RoihShEgazosa20pguB1lg/TF1RTY25OYfjl9CUYTQdgBBWggj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WsIDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nruiCoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641crpTnAAA" } ], "combiner": [ @@ -319,6 +339,11 @@ { "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" + }, + { + "psbt": "cHNidP8BAF4CAAAAAWbQAKi9hNXynJhqPu8bqkvp0kHihVShkWGh3yLy15+LAAAAAAD/////AbgFAQAAAAAAIlEgRvZJfLLxnVDD6emCqVDcyGIUsB/M5DekIGHHvbEjDTMAAAAAAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4iAgKUIec0sPnSxGfqfdGXxhrLRGfNy8n0ywxXH4tjpcQMrkDYKEk9EBnhyC92Y/sV2r8U7uushyGLzWj/UcNmsym5qFYJUq2Fjhh2mUeMRu1yad248jY5EC8LQ7bb4vNqFVuMAAA=", + "result": "cHNidP8BAF4CAAAAAWbQAKi9hNXynJhqPu8bqkvp0kHihVShkWGh3yLy15+LAAAAAAD/////AbgFAQAAAAAAIlEgRvZJfLLxnVDD6emCqVDcyGIUsB/M5DekIGHHvbEjDTMAAAAAAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BCEIBQNgoST0QGeHIL3Zj+xXavxTu66yHIYvNaP9Rw2azKbmoVglSrYWOGHaZR4xG7XJp3bjyNjkQLwtDttvi82oVW4wAAA==", + "isTaproot": true } ], "extractor": [ @@ -582,6 +607,19 @@ "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')", "nonExistantIndex": 42 }, + "finalizeTaprootScriptPathSpendInput": { + "psbt": "cHNidP8BAF4CAAAAAWbQAKi9hNXynJhqPu8bqkvp0kHihVShkWGh3yLy15+LAAAAAAD/////AbgFAQAAAAAAIlEgRvZJfLLxnVDD6emCqVDcyGIUsB/M5DekIGHHvbEjDTMAAAAAAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4iAgIuaLwR9cS6BsT6rRYePMFBKZzPkrdUmz80cDu5ATSjkEC5NO2PsYVquVp/60QIc7eTYcr16eABzDpFibWxBgfXoEDrH0oCzDH5HQ8lu7S9VWJwKvJ7GJIMGLDCX/n13qSsAQUiIC5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQrAAA", + "internalPublicKey": "Buffer.from('02982a2876765bb37b53a12418b9e72b8afa8d54e344a1bd585299a211fbe625f3', 'hex')", + "scriptTree": [ + { + "output": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ac', 'hex')" + }, + { + "output": "Buffer.from('202e68bc11f5c4ba06c4faad161e3cc141299ccf92b7549b3f34703bb90134a390ac', 'hex')" + } + ], + "result": "cHNidP8BAF4CAAAAAWbQAKi9hNXynJhqPu8bqkvp0kHihVShkWGh3yLy15+LAAAAAAD/////AbgFAQAAAAAAIlEgRvZJfLLxnVDD6emCqVDcyGIUsB/M5DekIGHHvbEjDTMAAAAAAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BCKcDQLk07Y+xhWq5Wn/rRAhzt5NhyvXp4AHMOkWJtbEGB9egQOsfSgLMMfkdDyW7tL1VYnAq8nsYkgwYsMJf+fXepKwiIC5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQrEHAmCoodnZbs3tToSQYuecrivqNVONEob1YUpmiEfvmJfMaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1gAA" + }, "getFeeRate": { "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "fee": 21 diff --git a/test/integration/taproot.md b/test/integration/taproot.md index 401034061..7627450e2 100644 --- a/test/integration/taproot.md +++ b/test/integration/taproot.md @@ -9,8 +9,9 @@ A simple keyspend example that is possible with the current API is below. ## TODO -- [ ] p2tr payment API to make script spends easier +- [x] p2tr payment API to make script spends easier - [ ] Support within the Psbt class + - partial support added ## Example diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index b3ff997b0..90dacb63d 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -1,11 +1,15 @@ import BIP32Factory from 'bip32'; +import ECPairFactory from 'ecpair'; import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; -import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; +import * as bitcoin from '../..'; +import { buildTapscriptFinalizer, toXOnly } from '../psbt.utils'; + const rng = require('randombytes'); const regtest = regtestUtils.network; const bip32 = BIP32Factory(ecc); +const ECPair = ECPairFactory(ecc); describe('bitcoinjs-lib (transaction with taproot)', () => { it('can create (and broadcast via 3PBP) a taproot keyspend Transaction', async () => { @@ -42,6 +46,391 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { value: sendAmount, }); }); + + it('can create (and broadcast via 3PBP) a taproot key-path spend Transaction', async () => { + const internalKey = bip32.fromSeed(rng(64), regtest); + + const { output, address } = bitcoin.payments.p2tr( + { internalPubkey: toXOnly(internalKey.publicKey), network: regtest }, + { eccLib: ecc }, + ); + + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output!, amount); + + const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + psbt.addInput({ + hash: unspent.txId, + index: 0, + witnessUtxo: { value: amount, script: output! }, + }); + psbt.addOutput({ value: sendAmount, address: address! }); + + const tweakedSigher = tweakSigner(internalKey!, { network: regtest }); + psbt.signInput(0, tweakedSigher); + + psbt.finalizeAllInputs(); + const tx = psbt.extractTransaction(); + const rawTx = tx.toBuffer(); + + const hex = rawTx.toString('hex'); + + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address: address!, + vout: 0, + value: sendAmount, + }); + }); + + it('can create (and broadcast via 3PBP) a taproot key-path spend Transaction (with unused scriptTree)', async () => { + const internalKey = bip32.fromSeed(rng(64), regtest); + const leafKey = bip32.fromSeed(rng(64), regtest); + + const leafScriptAsm = `${toXOnly(leafKey.publicKey).toString( + 'hex', + )} OP_CHECKSIG`; + const leafScript = bitcoin.script.fromASM(leafScriptAsm); + + const scriptTree = [ + { + output: leafScript, + }, + ]; + + const { output, address, hash } = bitcoin.payments.p2tr( + { + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + network: regtest, + }, + { eccLib: ecc }, + ); + + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output!, amount); + + const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + psbt.addInput({ + hash: unspent.txId, + index: 0, + witnessUtxo: { value: amount, script: output! }, + }); + psbt.addOutput({ value: sendAmount, address: address! }); + + const tweakedSigher = tweakSigner(internalKey!, { + tweakHash: hash, + network: regtest, + }); + psbt.signInput(0, tweakedSigher); + + psbt.finalizeAllInputs(); + const tx = psbt.extractTransaction(); + const rawTx = tx.toBuffer(); + + const hex = rawTx.toString('hex'); + + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address: address!, + vout: 0, + value: sendAmount, + }); + }); + + it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIG', async () => { + const internalKey = bip32.fromSeed(rng(64), regtest); + const leafKey = bip32.fromSeed(rng(64), regtest); + + const leafScriptAsm = `${toXOnly(leafKey.publicKey).toString( + 'hex', + )} OP_CHECKSIG`; + const leafScript = bitcoin.script.fromASM(leafScriptAsm); + + const scriptTree: any[] = [ + [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG', + ), + }, + [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac1 OP_CHECKSIG', + ), + }, + { + output: bitcoin.script.fromASM( + '2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7 OP_CHECKSIG', + ), + }, + ], + ], + [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac2 OP_CHECKSIG', + ), + }, + [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac3 OP_CHECKSIG', + ), + }, + [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG', + ), + }, + { + output: leafScript, + }, + ], + ], + ], + ]; + const redeem = { + output: leafScript, + redeemVersion: 192, + }; + + const { output, address } = bitcoin.payments.p2tr( + { + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + redeem, + network: regtest, + }, + { eccLib: ecc }, + ); + + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output!, amount); + + const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + psbt.addInput({ + hash: unspent.txId, + index: 0, + witnessUtxo: { value: amount, script: output! }, + witnessScript: redeem.output, + }); + psbt.addOutput({ value: sendAmount, address: address! }); + + psbt.signInput(0, leafKey); + + const tapscriptFinalizer = buildTapscriptFinalizer( + internalKey.publicKey, + scriptTree, + regtest, + ); + psbt.finalizeInput(0, tapscriptFinalizer); + const tx = psbt.extractTransaction(); + const rawTx = tx.toBuffer(); + const hex = rawTx.toString('hex'); + + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address: address!, + vout: 0, + value: sendAmount, + }); + }); + + it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSEQUENCEVERIFY', async () => { + const internalKey = bip32.fromSeed(rng(64), regtest); + const leafKey = bip32.fromSeed(rng(64), regtest); + const leafPubkey = toXOnly(leafKey.publicKey).toString('hex'); + + const leafScriptAsm = `OP_10 OP_CHECKSEQUENCEVERIFY OP_DROP ${leafPubkey} OP_CHECKSIG`; + const leafScript = bitcoin.script.fromASM(leafScriptAsm); + + const scriptTree: any[] = [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG', + ), + }, + [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG', + ), + }, + { + output: leafScript, + }, + ], + ]; + const redeem = { + output: leafScript, + redeemVersion: 192, + }; + + const { output, address } = bitcoin.payments.p2tr( + { + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + redeem, + network: regtest, + }, + { eccLib: ecc }, + ); + + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output!, amount); + + const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + psbt.addInput({ + hash: unspent.txId, + index: 0, + sequence: 10, + witnessUtxo: { value: amount, script: output! }, + witnessScript: redeem.output, + }); + psbt.addOutput({ value: sendAmount, address: address! }); + + psbt.signInput(0, leafKey); + + const tapscriptFinalizer = buildTapscriptFinalizer( + internalKey.publicKey, + scriptTree, + regtest, + ); + psbt.finalizeInput(0, tapscriptFinalizer); + const tx = psbt.extractTransaction(); + const rawTx = tx.toBuffer(); + const hex = rawTx.toString('hex'); + + try { + // broadcast before the confirmation period has expired + await regtestUtils.broadcast(hex); + throw new Error('Broadcast should fail.'); + } catch (err) { + if ((err as any).message !== 'non-BIP68-final') + throw new Error( + 'Expected OP_CHECKSEQUENCEVERIFY validation to fail. But it faild with: ' + + err, + ); + } + await regtestUtils.mine(10); + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address: address!, + vout: 0, + value: sendAmount, + }); + }); + + it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIGADD (3-of-3)', async () => { + const internalKey = bip32.fromSeed(rng(64), regtest); + + const leafKeys = []; + const leafPubkeys = []; + for (let i = 0; i < 3; i++) { + const leafKey = bip32.fromSeed(rng(64), regtest); + leafKeys.push(leafKey); + leafPubkeys.push(toXOnly(leafKey.publicKey).toString('hex')); + } + + const leafScriptAsm = `${leafPubkeys[2]} OP_CHECKSIG ${ + leafPubkeys[1] + } OP_CHECKSIGADD ${leafPubkeys[0]} OP_CHECKSIGADD OP_3 OP_NUMEQUAL`; + + const leafScript = bitcoin.script.fromASM(leafScriptAsm); + + const scriptTree: any[] = [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG', + ), + }, + [ + { + output: bitcoin.script.fromASM( + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG', + ), + }, + { + output: leafScript, + }, + ], + ]; + const redeem = { + output: leafScript, + redeemVersion: 192, + }; + + const { output, address } = bitcoin.payments.p2tr( + { + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + redeem, + network: regtest, + }, + { eccLib: ecc }, + ); + + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output!, amount); + + const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + psbt.addInput({ + hash: unspent.txId, + index: 0, + witnessUtxo: { value: amount, script: output! }, + witnessScript: redeem.output, + }); + psbt.addOutput({ value: sendAmount, address: address! }); + + psbt.signInput(0, leafKeys[0]); + psbt.signInput(0, leafKeys[1]); + psbt.signInput(0, leafKeys[2]); + + const tapscriptFinalizer = buildTapscriptFinalizer( + internalKey.publicKey, + scriptTree, + regtest, + ); + psbt.finalizeInput(0, tapscriptFinalizer); + const tx = psbt.extractTransaction(); + const rawTx = tx.toBuffer(); + const hex = rawTx.toString('hex'); + + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address: address!, + vout: 0, + value: sendAmount, + }); + }); }); // Order of the curve (N) - 1 @@ -59,7 +448,7 @@ const ONE = Buffer.from( // (This is recommended by BIP341) function createKeySpendOutput(publicKey: Buffer): Buffer { // x-only pubkey (remove 1 byte y parity) - const myXOnlyPubkey = publicKey.slice(1, 33); + const myXOnlyPubkey = toXOnly(publicKey); const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey); const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash); if (tweakResult === null) throw new Error('Invalid Tweak'); @@ -86,7 +475,7 @@ function signTweaked(messageHash: Buffer, key: KeyPair): Uint8Array { : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey!)!, ONE)!; const tweakHash = bitcoin.crypto.taggedHash( 'TapTweak', - key.publicKey.slice(1, 33), + toXOnly(key.publicKey), ); const newPrivateKey = ecc.privateAdd(privateKey!, tweakHash); if (newPrivateKey === null) throw new Error('Invalid Tweak'); @@ -120,3 +509,34 @@ function createSigned( tx.ins[0].witness = [signature]; return tx; } + +// This logic will be extracted to ecpair +function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer { + // @ts-ignore + let privateKey: Uint8Array | undefined = signer.privateKey!; + if (!privateKey) { + throw new Error('Private key is required for tweaking signer!'); + } + if (signer.publicKey[0] === 3) { + privateKey = ecc.privateNegate(privateKey); + } + + const tweakedPrivateKey = ecc.privateAdd( + privateKey, + tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash), + ); + if (!tweakedPrivateKey) { + throw new Error('Invalid tweaked private key!'); + } + + return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: opts.network, + }); +} + +function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { + return bitcoin.crypto.taggedHash( + 'TapTweak', + Buffer.concat(h ? [pubKey, h] : [pubKey]), + ); +} diff --git a/test/payments.utils.ts b/test/payments.utils.ts index d4aee8374..12fc20712 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -52,6 +52,12 @@ function equateBase(a: any, b: any, context: string): void { tryHex(b.witness), `Inequal ${context}witness`, ); + if ('redeemVersion' in b) + t.strictEqual( + a.redeemVersion, + b.redeemVersion, + `Inequal ${context}redeemVersion`, + ); } export function equate(a: any, b: any, args?: any): void { @@ -62,10 +68,12 @@ export function equate(a: any, b: any, args?: any): void { if (b.input === null) b.input = undefined; if (b.output === null) b.output = undefined; if (b.witness === null) b.witness = undefined; + if (b.redeemVersion === null) b.redeemVersion = undefined; if (b.redeem) { if (b.redeem.input === null) b.redeem.input = undefined; if (b.redeem.output === null) b.redeem.output = undefined; if (b.redeem.witness === null) b.redeem.witness = undefined; + if (b.redeem.redeemVersion === null) b.redeem.redeemVersion = undefined; } equateBase(a, b, ''); @@ -153,12 +161,8 @@ export function preform(x: any): any { if (x.redeem.network) x.redeem.network = (BNETWORKS as any)[x.redeem.network]; } - if (x.scriptLeaf) { - x.scriptLeaf = Object.assign({}, x.scriptLeaf); - if (typeof x.scriptLeaf.output === 'string') - x.scriptLeaf.output = asmToBuffer(x.scriptLeaf.output); - } - if (x.scriptsTree) x.scriptsTree = convertScriptsTree(x.scriptsTree); + + if (x.scriptTree) x.scriptTree = convertScriptTree(x.scriptTree); return x; } @@ -182,12 +186,11 @@ export function from(path: string, object: any, result?: any): any { return result; } -// todo: solve any type -function convertScriptsTree(scriptsTree: any): any { - if (Array.isArray(scriptsTree)) return scriptsTree.map(convertScriptsTree); +function convertScriptTree(scriptTree: any): any { + if (Array.isArray(scriptTree)) return scriptTree.map(convertScriptTree); - const script = Object.assign({}, scriptsTree); + const script = Object.assign({}, scriptTree); if (typeof script.output === 'string') - script.output = asmToBuffer(scriptsTree.output); + script.output = asmToBuffer(scriptTree.output); return script; } diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index e5198ea78..6d5e1db6f 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -9,6 +9,7 @@ const bip32 = BIP32Factory(ecc); const ECPair = ECPairFactory(ecc); import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; +import { buildTapscriptFinalizer } from './psbt.utils'; import * as preFixtures from './fixtures/psbt.json'; @@ -167,7 +168,8 @@ describe(`Psbt`, () => { fixtures.bip174.finalizer.forEach(f => { it('Finalizes inputs and gives the expected PSBT', () => { - const psbt = Psbt.fromBase64(f.psbt); + const opts = f.isTaproot ? { eccLib: ecc } : {}; + const psbt = Psbt.fromBase64(f.psbt, opts); psbt.finalizeAllInputs(); @@ -989,6 +991,20 @@ describe(`Psbt`, () => { }); }); + describe('finalizeTaprootInput', () => { + it('Correctly finalizes a taproot script-path spend', () => { + const f = fixtures.finalizeTaprootScriptPathSpendInput; + const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); + const tapscriptFinalizer = buildTapscriptFinalizer( + f.internalPublicKey as any, + f.scriptTree, + NETWORKS.testnet, + ); + psbt.finalizeInput(0, tapscriptFinalizer); + assert.strictEqual(psbt.toBase64(), f.result); + }); + }); + describe('getFeeRate', () => { it('Throws error if called before inputs are finalized', () => { const f = fixtures.getFeeRate; diff --git a/test/psbt.utils.ts b/test/psbt.utils.ts new file mode 100644 index 000000000..b85d3989e --- /dev/null +++ b/test/psbt.utils.ts @@ -0,0 +1,53 @@ +import { PsbtInput } from 'bip174/src/lib/interfaces'; +import * as bitcoin from './..'; +import { TinySecp256k1Interface } from '../src/types'; + +/** + * Build finalizer function for Tapscript. + * Usees the default Tapscript version (0xc0). + * @returns finalizer function + */ +const buildTapscriptFinalizer = ( + internalPubkey: Buffer, + scriptTree: any, + network: bitcoin.networks.Network, +) => { + return ( + inputIndex: number, + input: PsbtInput, + script: Buffer, + _isSegwit: boolean, + _isP2SH: boolean, + _isP2WSH: boolean, + eccLib?: TinySecp256k1Interface, + ): { + finalScriptSig: Buffer | undefined; + finalScriptWitness: Buffer | Buffer[] | undefined; + } => { + if (!internalPubkey || !scriptTree || !script) + throw new Error(`Can not finalize taproot input #${inputIndex}`); + + try { + const tapscriptSpend = bitcoin.payments.p2tr( + { + internalPubkey: toXOnly(internalPubkey), + scriptTree, + redeem: { output: script }, + network, + }, + { eccLib }, + ); + const sigs = (input.partialSig || []).map(ps => ps.signature) as Buffer[]; + const finalScriptWitness = sigs.concat( + tapscriptSpend.witness as Buffer[], + ); + return { finalScriptWitness, finalScriptSig: undefined }; + } catch (err) { + throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`); + } + }; +}; + +const toXOnly = (pubKey: Buffer) => pubKey.slice(1, 33); + +export { buildTapscriptFinalizer, toXOnly }; diff --git a/ts_src/ops.ts b/ts_src/ops.ts index 8e2c41c11..dd8b1e6da 100644 --- a/ts_src/ops.ts +++ b/ts_src/ops.ts @@ -127,6 +127,8 @@ const OPS: { [key: string]: number } = { OP_NOP9: 184, OP_NOP10: 185, + OP_CHECKSIGADD: 186, + OP_PUBKEYHASH: 253, OP_PUBKEY: 254, OP_INVALIDOPCODE: 255, diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 7bb77b6ac..90f5403c5 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,5 +1,5 @@ import { Network } from '../networks'; -import { TaprootLeaf, TinySecp256k1Interface } from '../types'; +import { TinySecp256k1Interface, TaprootLeaf } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -25,8 +25,8 @@ export interface Payment { address?: string; hash?: Buffer; redeem?: Payment; - scriptsTree?: any; // todo: solve - scriptLeaf?: TaprootLeaf; + redeemVersion?: number; + scriptTree?: TaprootLeaf[]; witness?: Buffer[]; } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index c79bb772e..268266e50 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -8,6 +8,8 @@ import { findScriptPath, tapLeafHash, tapTweakHash, + isTapTree, + LEAF_VERSION_TAPSCRIPT, } from './taprootutils'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; @@ -15,8 +17,9 @@ import { bech32m } from 'bech32'; import { testEcc } from './testecc'; const OPS = bscript.OPS; -const TAPROOT_VERSION = 0x01; +const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; +const LEAF_VERSION_MASK = 0b11111110; export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if ( @@ -48,11 +51,13 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { pubkey: typef.maybe(typef.BufferN(32)), signature: typef.maybe(typef.BufferN(64)), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? - scriptLeaf: typef.maybe({ - version: typef.maybe(typef.Number), + scriptTree: typef.maybe(isTapTree), + redeem: typef.maybe({ output: typef.maybe(typef.Buffer), + redeemVersion: typef.maybe(typef.Number), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), }), + redeemVersion: typef.maybe(typef.Number), }, a, ); @@ -68,13 +73,13 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }; }); + // remove annex if present, ignored by taproot const _witness = lazy.value(() => { if (!a.witness || !a.witness.length) return; if ( a.witness.length >= 2 && a.witness[a.witness.length - 1][0] === ANNEX_PREFIX ) { - // remove annex, ignored by taproot return a.witness.slice(0, -1); } return a.witness.slice(); @@ -87,17 +92,17 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (!o.pubkey) return; const words = bech32m.toWords(o.pubkey); - words.unshift(TAPROOT_VERSION); + words.unshift(TAPROOT_WITNESS_VERSION); return bech32m.encode(network.bech32, words); }); lazy.prop(o, 'hash', () => { if (a.hash) return a.hash; - if (a.scriptsTree) return toHashTree(a.scriptsTree).hash; + if (a.scriptTree) return toHashTree(a.scriptTree).hash; const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; - const leafVersion = controlBlock[0] & 0b11111110; + const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; const script = w[w.length - 2]; const leafHash = tapLeafHash(script, leafVersion); return rootHashFromPath(controlBlock, leafHash); @@ -108,8 +113,27 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (!o.pubkey) return; return bscript.compile([OPS.OP_1, o.pubkey]); }); - lazy.prop(o, 'scriptLeaf', () => { - if (a.scriptLeaf) return a.scriptLeaf; + lazy.prop(o, 'redeemVersion', () => { + if (a.redeemVersion) return a.redeemVersion; + if ( + a.redeem && + a.redeem.redeemVersion !== undefined && + a.redeem.redeemVersion !== null + ) { + return a.redeem.redeemVersion; + } + + return LEAF_VERSION_TAPSCRIPT; + }); + lazy.prop(o, 'redeem', () => { + const witness = _witness(); // witness without annex + if (!witness || witness.length < 2) return; + + return { + output: witness[witness.length - 2], + witness: witness.slice(0, -2), + redeemVersion: witness[witness.length - 1][0] & LEAF_VERSION_MASK, + }; }); lazy.prop(o, 'pubkey', () => { if (a.pubkey) return a.pubkey; @@ -131,25 +155,23 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (!a.witness || a.witness.length !== 1) return; return a.witness[0]; }); - lazy.prop(o, 'input', () => { - // todo - }); + lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; - if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { + if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { // todo: optimize/cache - const hashTree = toHashTree(a.scriptsTree); - const leafHash = tapLeafHash(a.scriptLeaf.output, a.scriptLeaf.version); + const hashTree = toHashTree(a.scriptTree); + const leafHash = tapLeafHash(a.redeem.output, o.redeemVersion); const path = findScriptPath(hashTree, leafHash); const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); if (!outputKey) return; - const version = a.scriptLeaf.version || 0xc0; const controlBock = NBuffer.concat( - [NBuffer.from([version | outputKey.parity]), a.internalPubkey].concat( - path.reverse(), - ), + [ + NBuffer.from([o.redeemVersion! | outputKey.parity]), + a.internalPubkey, + ].concat(path.reverse()), ); - return [a.scriptLeaf.output, controlBock]; + return [a.redeem.output, controlBock]; } if (a.signature) return [a.signature]; }); @@ -160,7 +182,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.address) { if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== TAPROOT_VERSION) + if (_address().version !== TAPROOT_WITNESS_VERSION) throw new TypeError('Invalid address version'); if (_address().data.length !== 32) throw new TypeError('Invalid address data'); @@ -197,13 +219,37 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Invalid pubkey for p2tr'); } - if (a.hash && a.scriptsTree) { - const hash = toHashTree(a.scriptsTree).hash; + if (a.hash && a.scriptTree) { + const hash = toHashTree(a.scriptTree).hash; if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } const witness = _witness(); + // compare the provided redeem data with the one computed from witness + if (a.redeem && o.redeem) { + if (a.redeem.redeemVersion) { + if (a.redeem.redeemVersion !== o.redeem.redeemVersion) + throw new TypeError('Redeem.redeemVersion and witness mismatch'); + } + + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output)!.length === 0) + throw new TypeError('Redeem.output is invalid'); + + // output redeem is constructed from the witness + if (o.redeem.output && !a.redeem.output.equals(o.redeem.output)) + throw new TypeError('Redeem.output and witness mismatch'); + } + if (a.redeem.witness) { + if ( + o.redeem.witness && + !stacksEqual(a.redeem.witness, o.redeem.witness) + ) + throw new TypeError('Redeem.witness and witness mismatch'); + } + } + if (witness && witness.length) { if (witness.length === 1) { // key spending @@ -237,7 +283,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (!_ecc().isXOnlyPoint(internalPubkey)) throw new TypeError('Invalid internalPubkey for p2tr witness'); - const leafVersion = controlBlock[0] & 0b11111110; + const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; const script = witness[witness.length - 2]; const leafHash = tapLeafHash(script, leafVersion); @@ -284,3 +330,11 @@ function tweakKey( x: NBuffer.from(res.xOnlyPubkey), }; } + +function stacksEqual(a: Buffer[], b: Buffer[]): boolean { + if (a.length !== b.length) return false; + + return a.every((x, i) => { + return x.equals(b[i]); + }); +} diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 83ed9b14c..c71a38b9d 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -4,11 +4,12 @@ import * as bcrypto from '../crypto'; import { varuint } from '../bufferutils'; import { TaprootLeaf } from '../types'; -const LEAF_VERSION_TAPSCRIPT = 0xc0; const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; const TAP_TWEAK_TAG = 'TapTweak'; +export const LEAF_VERSION_TAPSCRIPT = 0xc0; + export function rootHashFromPath( controlBlock: Buffer, tapLeafMsg: Buffer, @@ -39,14 +40,14 @@ export interface HashTree { /** * Build the hash tree from the scripts binary tree. * The binary tree can be balanced or not. - * @param scriptsTree - is a list representing a binary tree where an element can be: + * @param scriptTree - is a list representing a binary tree where an element can be: * - a taproot leaf [(output, version)], or * - a pair of two taproot leafs [(output, version), (output, version)], or * - one taproot leaf and a list of elements */ -export function toHashTree(scriptsTree: TaprootLeaf[]): HashTree { - if (scriptsTree.length === 1) { - const script = scriptsTree[0]; +export function toHashTree(scriptTree: TaprootLeaf[]): HashTree { + if (scriptTree.length === 1) { + const script = scriptTree[0]; if (Array.isArray(script)) { return toHashTree(script); } @@ -59,8 +60,8 @@ export function toHashTree(scriptsTree: TaprootLeaf[]): HashTree { }; } - const left = toHashTree([scriptsTree[0]]); - const right = toHashTree([scriptsTree[1]]); + const left = toHashTree([scriptTree[0]]); + const right = toHashTree([scriptTree[1]]); let leftHash = left.hash; let rightHash = right.hash; @@ -73,6 +74,28 @@ export function toHashTree(scriptsTree: TaprootLeaf[]): HashTree { right, }; } +/** + * Check if the tree is a binary tree with leafs of type TaprootLeaf + */ +export function isTapTree(scriptTree: TaprootLeaf[]): boolean { + if (scriptTree.length > 2) return false; + if (scriptTree.length === 1) { + const script = scriptTree[0]; + if (Array.isArray(script)) { + return isTapTree(script); + } + if (!script.output) return false; + script.version = script.version || LEAF_VERSION_TAPSCRIPT; + if ((script.version & 1) !== 0) return false; + + return true; + } + + if (!isTapTree([scriptTree[0]])) return false; + if (!isTapTree([scriptTree[1]])) return false; + + return true; +} /** * Given a MAST tree, it finds the path of a particular hash. diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 163da5b2c..eec6d2da8 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -21,6 +21,7 @@ import { bitcoin as btcNetwork, Network } from './networks'; import * as payments from './payments'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; +import { tapLeafHash } from './payments/taprootutils'; import { TinySecp256k1Interface } from './types'; export interface TransactionInput { @@ -352,15 +353,22 @@ export class Psbt { finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this { const input = checkForInput(this.data.inputs, inputIndex); - const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( - inputIndex, - input, - this.__CACHE, - ); + const { + script, + isP2SH, + isP2WSH, + isSegwit, + isTapscript, + } = getScriptFromInput(inputIndex, input, this.__CACHE); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); + if (isTapscript && !finalScriptsFunc) + throw new Error( + `Taproot script-path finalizer required for input #${inputIndex}`, + ); + const fn = finalScriptsFunc || getFinalScripts; const { finalScriptSig, finalScriptWitness } = fn( inputIndex, @@ -373,8 +381,13 @@ export class Psbt { ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); - if (finalScriptWitness) - this.data.updateInput(inputIndex, { finalScriptWitness }); + if (finalScriptWitness) { + // allow custom finalizers to build the witness as an array + const witness = Array.isArray(finalScriptWitness) + ? witnessStackToScriptWitness(finalScriptWitness) + : finalScriptWitness; + this.data.updateInput(inputIndex, { finalScriptWitness: witness }); + } if (!finalScriptSig && !finalScriptWitness) throw new Error(`Unknown error finalizing input #${inputIndex}`); @@ -396,6 +409,7 @@ export class Psbt { input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness), + this.__CACHE, ); const type = result.type === 'raw' ? '' : result.type + '-'; const mainType = classifyScript( @@ -461,13 +475,12 @@ export class Psbt { const scriptType = this.getInputType(inputIndex); for (const pSig of mySigs) { - const sig = - scriptType === 'taproot' - ? { - signature: pSig.signature, - hashType: Transaction.SIGHASH_DEFAULT, - } - : bscript.signature.decode(pSig.signature); + const sig = isTaprootSpend(scriptType) + ? { + signature: pSig.signature, + hashType: Transaction.SIGHASH_DEFAULT, + } + : bscript.signature.decode(pSig.signature); const { hash, script } = sighashCache! !== sig.hashType @@ -667,18 +680,17 @@ export class Psbt { const scriptType = this.getInputType(inputIndex); - if (scriptType === 'taproot') { + if (isTaprootSpend(scriptType)) { if (!keyPair.signSchnorr) { throw new Error( `Need Schnorr Signer to sign taproot input #${inputIndex}.`, ); } - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: keyPair.signSchnorr!(hash), - }, - ]; + const partialSig = this.data.inputs[inputIndex].partialSig || []; + partialSig.push({ + pubkey: keyPair.publicKey, + signature: keyPair.signSchnorr!(hash), + }); // must be changed to use the `updateInput()` public API this.data.inputs[inputIndex].partialSig = partialSig; } else { @@ -712,19 +724,19 @@ export class Psbt { const scriptType = this.getInputType(inputIndex); - if (scriptType === 'taproot') { + if (isTaprootSpend(scriptType)) { if (!keyPair.signSchnorr) { throw new Error( `Need Schnorr Signer to sign taproot input #${inputIndex}.`, ); } return Promise.resolve(keyPair.signSchnorr(hash)).then(signature => { - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature, - }, - ]; + const partialSig = this.data.inputs[inputIndex].partialSig || []; + partialSig.push({ + pubkey: keyPair.publicKey, + signature, + }); + // must be changed to use the `updateInput()` public API this.data.inputs[inputIndex].partialSig = partialSig; }); @@ -1214,9 +1226,10 @@ type FinalScriptsFunc = ( isSegwit: boolean, // Is it segwit? isP2SH: boolean, // Is it P2SH? isP2WSH: boolean, // Is it P2WSH? + eccLib?: TinySecp256k1Interface, // optional lib for checking taproot validity ) => { finalScriptSig: Buffer | undefined; - finalScriptWitness: Buffer | undefined; + finalScriptWitness: Buffer | Buffer[] | undefined; }; function getFinalScripts( @@ -1366,6 +1379,7 @@ function getHashForSig( 'input', input.redeemScript, input.witnessScript, + cache, ); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { @@ -1385,18 +1399,22 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(meaningfulScript, cache.__EC_LIB)) { + } else if (isP2TR(prevout.script, cache.__EC_LIB)) { const prevOuts: Output[] = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache), ); const signingScripts: any = prevOuts.map(o => o.script); const values: any = prevOuts.map(o => o.value); + const leafHash = input.witnessScript + ? tapLeafHash(input.witnessScript) + : undefined; hash = unsignedTx.hashForWitnessV1( inputIndex, signingScripts, values, Transaction.SIGHASH_DEFAULT, + leafHash, ); } else { // non-segwit @@ -1472,7 +1490,7 @@ function getPayment( output: script, signature: partialSig[0].signature, }, - { validate: false }, // skip validation (for now) + { validate: false }, // skip validation ); break; } @@ -1497,6 +1515,7 @@ function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { interface GetScriptReturn { script: Buffer | null; isSegwit: boolean; + isTapscript: boolean; isP2SH: boolean; isP2WSH: boolean; } @@ -1509,36 +1528,45 @@ function getScriptFromInput( const res: GetScriptReturn = { script: null, isSegwit: false, + isTapscript: false, isP2SH: false, isP2WSH: false, }; - res.isP2SH = !!input.redeemScript; - res.isP2WSH = !!input.witnessScript; + let utxoScript = null; + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + const prevoutIndex = unsignedTx.ins[inputIndex].index; + utxoScript = nonWitnessUtxoTx.outs[prevoutIndex].script; + } else if (input.witnessUtxo) { + utxoScript = input.witnessUtxo.script; + } + if (input.witnessScript) { res.script = input.witnessScript; } else if (input.redeemScript) { res.script = input.redeemScript; } else { - if (input.nonWitnessUtxo) { - const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( - cache, - input, - inputIndex, - ); - const prevoutIndex = unsignedTx.ins[inputIndex].index; - res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; - } else if (input.witnessUtxo) { - res.script = input.witnessUtxo.script; - } + res.script = utxoScript; } - if ( - input.witnessScript || - isP2WPKH(res.script!) || - isP2TR(res.script!, cache.__EC_LIB) - ) { + const isTaproot = utxoScript && isP2TR(utxoScript, cache.__EC_LIB); + + // Segregated Witness versions 0 or 1 + if (input.witnessScript || isP2WPKH(res.script!) || isTaproot) { res.isSegwit = true; } + + if (isTaproot && input.witnessScript) { + res.isTapscript = true; + } + + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript && !res.isTapscript; + return res; } @@ -1788,6 +1816,7 @@ function pubkeyInInput( 'input', input.redeemScript, input.witnessScript, + cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1805,6 +1834,7 @@ function pubkeyInOutput( 'output', output.redeemScript, output.witnessScript, + cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1863,13 +1893,15 @@ function getMeaningfulScript( ioType: 'input' | 'output', redeemScript?: Buffer, witnessScript?: Buffer, + cache?: PsbtCache, ): { meaningfulScript: Buffer; - type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw'; + type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'p2tr' | 'raw'; } { const isP2SH = isP2SHScript(script); const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); const isP2WSH = isP2WSHScript(script); + const isP2TRScript = isP2TR(script, cache && cache.__EC_LIB); if (isP2SH && redeemScript === undefined) throw new Error('scriptPubkey is P2SH but redeemScript missing'); @@ -1892,6 +1924,9 @@ function getMeaningfulScript( } else if (isP2SH) { meaningfulScript = redeemScript!; checkRedeemScript(index, script, redeemScript!, ioType); + } else if (isP2TRScript && !!witnessScript) { + meaningfulScript = witnessScript; + // TODO: check here something? } else { meaningfulScript = script; } @@ -1903,6 +1938,8 @@ function getMeaningfulScript( ? 'p2sh' : isP2WSH ? 'p2wsh' + : isP2TRScript + ? 'p2tr' : 'raw', }; } @@ -1930,6 +1967,12 @@ function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean { }); } +function isTaprootSpend(scriptType: string): boolean { + return ( + !!scriptType && (scriptType === 'taproot' || scriptType.startsWith('p2tr-')) + ); +} + type AllScriptType = | 'witnesspubkeyhash' | 'pubkeyhash' @@ -1949,7 +1992,9 @@ type AllScriptType = | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' - | 'p2sh-p2wsh-nonstandard'; + | 'p2sh-p2wsh-nonstandard' + | 'p2tr-pubkey' + | 'p2tr-nonstandard'; type ScriptType = | 'witnesspubkeyhash' | 'pubkeyhash' diff --git a/ts_src/types.ts b/ts_src/types.ts index 487f29757..fad2bc29c 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -76,6 +76,7 @@ export interface TaprootLeaf { output: Buffer; version?: number; } + export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak( From 72a0b8f21bc462992bd1dbd8bf02b0386648d5b8 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 18:36:48 +0200 Subject: [PATCH 52/73] refactor: move tapscript finalizer check; add unit test --- src/psbt.d.ts | 1 + src/psbt.js | 8 +++----- test/psbt.spec.ts | 9 +++++++++ test/psbt.utils.ts | 1 + ts_src/psbt.ts | 10 ++++------ 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/psbt.d.ts b/src/psbt.d.ts index cfc64a6e7..8b21ce7bb 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -179,6 +179,7 @@ declare type FinalScriptsFunc = (inputIndex: number, // Which input is it? input: PsbtInput, // The PSBT input contents script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.) isSegwit: boolean, // Is it segwit? +isTapscript: boolean, // Is taproot script path? isP2SH: boolean, // Is it P2SH? isP2WSH: boolean, // Is it P2WSH? eccLib?: TinySecp256k1Interface) => { diff --git a/src/psbt.js b/src/psbt.js index ec5614a07..1d6cad4fd 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -289,16 +289,13 @@ class Psbt { } = getScriptFromInput(inputIndex, input, this.__CACHE); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); - if (isTapscript && !finalScriptsFunc) - throw new Error( - `Taproot script-path finalizer required for input #${inputIndex}`, - ); const fn = finalScriptsFunc || getFinalScripts; const { finalScriptSig, finalScriptWitness } = fn( inputIndex, input, script, isSegwit, + isTapscript, isP2SH, isP2WSH, this.__CACHE.__EC_LIB, @@ -936,12 +933,13 @@ function getFinalScripts( input, script, isSegwit, + isTapscript, isP2SH, isP2WSH, eccLib, ) { const scriptType = classifyScript(script, eccLib); - if (!canFinalize(input, script, scriptType)) + if (isTapscript || !canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( script, diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 6d5e1db6f..871142194 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -1003,6 +1003,15 @@ describe(`Psbt`, () => { psbt.finalizeInput(0, tapscriptFinalizer); assert.strictEqual(psbt.toBase64(), f.result); }); + + it('Failes to finalize a taproot script-path spend when a finalizer is not provided', () => { + const f = fixtures.finalizeTaprootScriptPathSpendInput; + const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); + + assert.throws(() => { + psbt.finalizeInput(0); + }, new RegExp('Can not finalize input #0')); + }); }); describe('getFeeRate', () => { diff --git a/test/psbt.utils.ts b/test/psbt.utils.ts index b85d3989e..aec6ca42d 100644 --- a/test/psbt.utils.ts +++ b/test/psbt.utils.ts @@ -17,6 +17,7 @@ const buildTapscriptFinalizer = ( input: PsbtInput, script: Buffer, _isSegwit: boolean, + _isTapscript: boolean, _isP2SH: boolean, _isP2WSH: boolean, eccLib?: TinySecp256k1Interface, diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index eec6d2da8..a906a21c5 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -364,17 +364,13 @@ export class Psbt { checkPartialSigSighashes(input); - if (isTapscript && !finalScriptsFunc) - throw new Error( - `Taproot script-path finalizer required for input #${inputIndex}`, - ); - const fn = finalScriptsFunc || getFinalScripts; const { finalScriptSig, finalScriptWitness } = fn( inputIndex, input, script, isSegwit, + isTapscript, isP2SH, isP2WSH, this.__CACHE.__EC_LIB, @@ -1224,6 +1220,7 @@ type FinalScriptsFunc = ( input: PsbtInput, // The PSBT input contents script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.) isSegwit: boolean, // Is it segwit? + isTapscript: boolean, // Is taproot script path? isP2SH: boolean, // Is it P2SH? isP2WSH: boolean, // Is it P2WSH? eccLib?: TinySecp256k1Interface, // optional lib for checking taproot validity @@ -1237,6 +1234,7 @@ function getFinalScripts( input: PsbtInput, script: Buffer, isSegwit: boolean, + isTapscript: boolean, isP2SH: boolean, isP2WSH: boolean, eccLib?: TinySecp256k1Interface, @@ -1245,7 +1243,7 @@ function getFinalScripts( finalScriptWitness: Buffer | undefined; } { const scriptType = classifyScript(script, eccLib); - if (!canFinalize(input, script, scriptType)) + if (isTapscript || !canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( script, From ceea6293e969f78fe3074631d0122a233f3d281c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:27:12 +0200 Subject: [PATCH 53/73] fix: fix integration test --- package-lock.json | 3396 +++++++++++++++++++++++++++++++++- test/integration/csv.spec.ts | 1 + 2 files changed, 3396 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index bf519f3f0..1642f0026 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,3402 @@ { "name": "bitcoinjs-lib", "version": "6.0.2", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "bitcoinjs-lib", + "version": "6.0.2", + "license": "MIT", + "dependencies": { + "bech32": "^2.0.0", + "bip174": "^2.0.1", + "bs58check": "^2.1.2", + "create-hash": "^1.1.0", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2", + "wif": "^2.0.1" + }, + "devDependencies": { + "@types/bs58": "^4.0.0", + "@types/bs58check": "^2.1.0", + "@types/create-hash": "^1.2.2", + "@types/mocha": "^5.2.7", + "@types/node": "^16.11.7", + "@types/proxyquire": "^1.3.28", + "@types/randombytes": "^2.0.0", + "@types/wif": "^2.0.2", + "bip32": "^3.0.1", + "bip39": "^3.0.2", + "bip65": "^1.0.1", + "bip68": "^1.0.3", + "bs58": "^4.0.0", + "dhttp": "^3.0.0", + "ecpair": "^2.0.1", + "hoodwink": "^2.0.0", + "minimaldata": "^1.0.2", + "mocha": "^7.1.1", + "npm-audit-whitelister": "^1.0.2", + "nyc": "^15.1.0", + "prettier": "1.16.4", + "proxyquire": "^2.0.1", + "randombytes": "^2.1.0", + "regtest-client": "0.2.0", + "rimraf": "^2.6.3", + "tiny-secp256k1": "^2.2.0", + "ts-node": "^8.3.0", + "tslint": "^6.1.3", + "typescript": "^4.4.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.13.tgz", + "integrity": "sha512-BQKE9kXkPlXHPeqissfxo0lySWJcYdEP0hdtJOH/iJfDdhOCcgtNCjftCJg3qqauB4h+lz2N6ixM++b9DN1Tcw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.12.13", + "@babel/helper-module-transforms": "^7.12.13", + "@babel/helpers": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.12.13" + } + }, + "node_modules/@babel/core/node_modules/@babel/highlight": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", + "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", + "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", + "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz", + "integrity": "sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13", + "@babel/helper-simple-access": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", + "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", + "integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "node_modules/@babel/helpers": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.13.tgz", + "integrity": "sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", + "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/template/node_modules/@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.12.13" + } + }, + "node_modules/@babel/template/node_modules/@babel/highlight": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", + "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.12.13" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/highlight": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", + "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/base-x": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/base-x/-/base-x-3.0.0.tgz", + "integrity": "sha512-vnqSlpsv9uFX5/z8GyKWAfWHhLGJDBkrgRRsnxlsX23DHOlNyqP/eHQiv4TwnYcZULzQIxaWA/xRWU9Dyy4qzw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bs58": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.0.tgz", + "integrity": "sha512-gYX+MHD4G/R+YGYwdhG5gbJj4LsEQGr3Vg6gVDAbe7xC5Bn8dNNG2Lpo6uDX/rT5dE7VBj0rGEFuV8L0AEx4Rg==", + "dev": true, + "dependencies": { + "@types/base-x": "*" + } + }, + "node_modules/@types/bs58check": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/bs58check/-/bs58check-2.1.0.tgz", + "integrity": "sha512-OxsysnJQh82vy9DRbOcw9m2j/WiyqZLn0YBhKxdQ+aCwoHj+tWzyCgpwAkr79IfDXZKxc6h7k89T9pwS78CqTQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/create-hash": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.2.tgz", + "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", + "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==", + "dev": true + }, + "node_modules/@types/proxyquire": { + "version": "1.3.28", + "resolved": "https://registry.npmjs.org/@types/proxyquire/-/proxyquire-1.3.28.tgz", + "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", + "dev": true + }, + "node_modules/@types/randombytes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.0.tgz", + "integrity": "sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/wif": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", + "integrity": "sha512-IiIuBeJzlh4LWJ7kVTrX0nwB60OG0vvGTaWC/SgSbVFw7uYUTF6gEuvDZ1goWkeirekJDD58Y8g7NljQh2fNkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", + "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base-x": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz", + "integrity": "sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bip174": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz", + "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bip32": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz", + "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", + "dev": true, + "dependencies": { + "@types/node": "10.12.18", + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bip32/node_modules/@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", + "dev": true + }, + "node_modules/bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "dev": true, + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/bip39/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "dev": true + }, + "node_modules/bip65": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bip65/-/bip65-1.0.3.tgz", + "integrity": "sha512-RQ1nc7xtnLa5XltnCqkoR2zmhuz498RjMJwrLKQzOE049D1HUqnYfon7cVSbwS5UGm0/EQlC2CH+NY3MyITA4Q==", + "dev": true, + "engines": { + "node": ">=4.5.0" + } + }, + "node_modules/bip68": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bip68/-/bip68-1.0.4.tgz", + "integrity": "sha512-O1htyufFTYy3EO0JkHg2CLykdXEtV2ssqw47Gq9A0WByp662xpJnMEB9m43LZjsSDjIAOozWRExlFQk2hlV1XQ==", + "dev": true, + "engines": { + "node": ">=4.5.0" + } + }, + "node_modules/bitcoin-ops": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", + "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.1" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dhttp": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dhttp/-/dhttp-3.0.3.tgz", + "integrity": "sha512-map1b8iyvxSv0uEw3DUDDK5XvH3aYA7QU9DcXy8e3FBIXSwHPHTZWVrOot7Iu9mieWq5XcrZemEJlob6IdCBmg==", + "deprecated": "Not maintained, don't use this", + "dev": true, + "dependencies": { + "statuses": "^1.5.0" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecpair": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", + "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "dev": true, + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "deprecated": "Fixed a prototype pollution security issue in 4.1.0, please upgrade to ^4.1.1 or ^5.0.1.", + "dev": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hoodwink": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz", + "integrity": "sha512-j1jog3tDfhpWlqbVbh29qc7FG7w+NT4ed+QQFGqvww83+50AzzretB7wykZGOe28mBdvCYH3GdHaVWJQ2lJ/4w==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "node_modules/minimaldata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minimaldata/-/minimaldata-1.0.2.tgz", + "integrity": "sha1-AfOywB2LJzmEP9hT1AY2xaLrdms=", + "dev": true, + "dependencies": { + "bitcoin-ops": "^1.3.0", + "pushdata-bitcoin": "^1.0.1" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "dev": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.3", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-audit-whitelister": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/npm-audit-whitelister/-/npm-audit-whitelister-1.0.2.tgz", + "integrity": "sha512-MNaYMUPI4P1cGcnLNvMv0XW4F5NkVEJv2aAfLqXXKY4cgo5lXCHl1h9eUIQnWLKM3WHVOqKzUipMzfunzQZXUg==", + "dev": true, + "bin": { + "npm-audit-whitelister": "bin/npm-audit-whitelister.js" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/nyc/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/nyc/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "1.16.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", + "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/proxyquire": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.0.tgz", + "integrity": "sha512-kptdFArCfGRtQFv3Qwjr10lwbEV0TBJYvfqzhwucyfEXqVgmnAkyEw/S3FYzR5HI9i5QOq4rcqQjZ6AlknlCDQ==", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.0", + "resolve": "~1.8.1" + } + }, + "node_modules/pushdata-bitcoin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", + "integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=", + "dev": true, + "dependencies": { + "bitcoin-ops": "^1.3.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/regtest-client": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regtest-client/-/regtest-client-0.2.0.tgz", + "integrity": "sha512-eIcC8Kle/wjS47pRlw7nJpstrJDWp0bkvVPl2KJpJcK3JDNW0fMxJgE/CGpMEUSjhhFXW1rtJMN6kyKw5NIzqg==", + "dev": true, + "dependencies": { + "bs58check": "^2.1.2", + "dhttp": "^3.0.3", + "randombytes": "^2.1.0" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.5" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spawn-wrap/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tiny-secp256k1": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.0.tgz", + "integrity": "sha512-2hPuUGCroLrxh6xxwoe+1RgPpOOK1w2uTnhgiHBpvoutBR+krNuT4hOXQyOaaYnZgoXBB6hBYkuAJHxyeBOPzQ==", + "dev": true, + "dependencies": { + "uint8array-tools": "0.0.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", + "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + }, + "peerDependencies": { + "typescript": ">=2.0" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "node_modules/typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uint8array-tools": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.6.tgz", + "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.15.8", diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 742d68fa1..5e66804d4 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -439,6 +439,7 @@ function csvGetFinalScripts( input: PsbtInput, script: Buffer, isSegwit: boolean, + _isTapscript: boolean, isP2SH: boolean, isP2WSH: boolean, ): { From 11681b31405e9a44cbfe2428802fe4d5efbb1537 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:28:35 +0200 Subject: [PATCH 54/73] fix: revert package lock version upgrade --- package-lock.json | 3402 +-------------------------------------------- 1 file changed, 4 insertions(+), 3398 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1642f0026..956740594 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3402 +1,8 @@ { "name": "bitcoinjs-lib", "version": "6.0.2", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "bitcoinjs-lib", - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "bech32": "^2.0.0", - "bip174": "^2.0.1", - "bs58check": "^2.1.2", - "create-hash": "^1.1.0", - "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.1.2", - "wif": "^2.0.1" - }, - "devDependencies": { - "@types/bs58": "^4.0.0", - "@types/bs58check": "^2.1.0", - "@types/create-hash": "^1.2.2", - "@types/mocha": "^5.2.7", - "@types/node": "^16.11.7", - "@types/proxyquire": "^1.3.28", - "@types/randombytes": "^2.0.0", - "@types/wif": "^2.0.2", - "bip32": "^3.0.1", - "bip39": "^3.0.2", - "bip65": "^1.0.1", - "bip68": "^1.0.3", - "bs58": "^4.0.0", - "dhttp": "^3.0.0", - "ecpair": "^2.0.1", - "hoodwink": "^2.0.0", - "minimaldata": "^1.0.2", - "mocha": "^7.1.1", - "npm-audit-whitelister": "^1.0.2", - "nyc": "^15.1.0", - "prettier": "1.16.4", - "proxyquire": "^2.0.1", - "randombytes": "^2.1.0", - "regtest-client": "0.2.0", - "rimraf": "^2.6.3", - "tiny-secp256k1": "^2.2.0", - "ts-node": "^8.3.0", - "tslint": "^6.1.3", - "typescript": "^4.4.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.13.tgz", - "integrity": "sha512-BQKE9kXkPlXHPeqissfxo0lySWJcYdEP0hdtJOH/iJfDdhOCcgtNCjftCJg3qqauB4h+lz2N6ixM++b9DN1Tcw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-module-transforms": "^7.12.13", - "@babel/helpers": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@babel/core/node_modules/@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/generator": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", - "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", - "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", - "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz", - "integrity": "sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-simple-access": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", - "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", - "integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "node_modules/@babel/helpers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.13.tgz", - "integrity": "sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", - "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@babel/template/node_modules/@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/types": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", - "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@types/base-x": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/base-x/-/base-x-3.0.0.tgz", - "integrity": "sha512-vnqSlpsv9uFX5/z8GyKWAfWHhLGJDBkrgRRsnxlsX23DHOlNyqP/eHQiv4TwnYcZULzQIxaWA/xRWU9Dyy4qzw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/bs58": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.0.tgz", - "integrity": "sha512-gYX+MHD4G/R+YGYwdhG5gbJj4LsEQGr3Vg6gVDAbe7xC5Bn8dNNG2Lpo6uDX/rT5dE7VBj0rGEFuV8L0AEx4Rg==", - "dev": true, - "dependencies": { - "@types/base-x": "*" - } - }, - "node_modules/@types/bs58check": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/bs58check/-/bs58check-2.1.0.tgz", - "integrity": "sha512-OxsysnJQh82vy9DRbOcw9m2j/WiyqZLn0YBhKxdQ+aCwoHj+tWzyCgpwAkr79IfDXZKxc6h7k89T9pwS78CqTQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/create-hash": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.2.tgz", - "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "16.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", - "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==", - "dev": true - }, - "node_modules/@types/proxyquire": { - "version": "1.3.28", - "resolved": "https://registry.npmjs.org/@types/proxyquire/-/proxyquire-1.3.28.tgz", - "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", - "dev": true - }, - "node_modules/@types/randombytes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.0.tgz", - "integrity": "sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/wif": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", - "integrity": "sha512-IiIuBeJzlh4LWJ7kVTrX0nwB60OG0vvGTaWC/SgSbVFw7uYUTF6gEuvDZ1goWkeirekJDD58Y8g7NljQh2fNkA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "node_modules/arg": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", - "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/base-x": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz", - "integrity": "sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bech32": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" - }, - "node_modules/binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bip174": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz", - "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/bip32": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz", - "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", - "dev": true, - "dependencies": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "typeforce": "^1.11.5", - "wif": "^2.0.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/bip32/node_modules/@types/node": { - "version": "10.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", - "dev": true - }, - "node_modules/bip39": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", - "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", - "dev": true, - "dependencies": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - } - }, - "node_modules/bip39/node_modules/@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", - "dev": true - }, - "node_modules/bip65": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bip65/-/bip65-1.0.3.tgz", - "integrity": "sha512-RQ1nc7xtnLa5XltnCqkoR2zmhuz498RjMJwrLKQzOE049D1HUqnYfon7cVSbwS5UGm0/EQlC2CH+NY3MyITA4Q==", - "dev": true, - "engines": { - "node": ">=4.5.0" - } - }, - "node_modules/bip68": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bip68/-/bip68-1.0.4.tgz", - "integrity": "sha512-O1htyufFTYy3EO0JkHg2CLykdXEtV2ssqw47Gq9A0WByp662xpJnMEB9m43LZjsSDjIAOozWRExlFQk2hlV1XQ==", - "dev": true, - "engines": { - "node": ">=4.5.0" - } - }, - "node_modules/bitcoin-ops": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", - "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.1" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/dhttp": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dhttp/-/dhttp-3.0.3.tgz", - "integrity": "sha512-map1b8iyvxSv0uEw3DUDDK5XvH3aYA7QU9DcXy8e3FBIXSwHPHTZWVrOot7Iu9mieWq5XcrZemEJlob6IdCBmg==", - "deprecated": "Not maintained, don't use this", - "dev": true, - "dependencies": { - "statuses": "^1.5.0" - } - }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ecpair": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", - "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/es-abstract": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", - "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", - "dev": true, - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fill-keys": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "dependencies": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "deprecated": "Fixed a prototype pollution security issue in 4.1.0, please upgrade to ^4.1.1 or ^5.0.1.", - "dev": true, - "dependencies": { - "is-buffer": "~2.0.3" - }, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hoodwink": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz", - "integrity": "sha512-j1jog3tDfhpWlqbVbh29qc7FG7w+NT4ed+QQFGqvww83+50AzzretB7wykZGOe28mBdvCYH3GdHaVWJQ2lJ/4w==", - "dev": true - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "node_modules/minimaldata": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minimaldata/-/minimaldata-1.0.2.tgz", - "integrity": "sha1-AfOywB2LJzmEP9hT1AY2xaLrdms=", - "dev": true, - "dependencies": { - "bitcoin-ops": "^1.3.0", - "pushdata-bitcoin": "^1.0.1" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", - "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", - "dev": true, - "dependencies": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.3", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "node_modules/node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-audit-whitelister": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/npm-audit-whitelister/-/npm-audit-whitelister-1.0.2.tgz", - "integrity": "sha512-MNaYMUPI4P1cGcnLNvMv0XW4F5NkVEJv2aAfLqXXKY4cgo5lXCHl1h9eUIQnWLKM3WHVOqKzUipMzfunzQZXUg==", - "dev": true, - "bin": { - "npm-audit-whitelister": "bin/npm-audit-whitelister.js" - } - }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/nyc/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/nyc/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nyc/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nyc/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picomatch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", - "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/prettier": { - "version": "1.16.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", - "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/proxyquire": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.0.tgz", - "integrity": "sha512-kptdFArCfGRtQFv3Qwjr10lwbEV0TBJYvfqzhwucyfEXqVgmnAkyEw/S3FYzR5HI9i5QOq4rcqQjZ6AlknlCDQ==", - "dev": true, - "dependencies": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.0", - "resolve": "~1.8.1" - } - }, - "node_modules/pushdata-bitcoin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", - "integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=", - "dev": true, - "dependencies": { - "bitcoin-ops": "^1.3.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/regtest-client": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regtest-client/-/regtest-client-0.2.0.tgz", - "integrity": "sha512-eIcC8Kle/wjS47pRlw7nJpstrJDWp0bkvVPl2KJpJcK3JDNW0fMxJgE/CGpMEUSjhhFXW1rtJMN6kyKw5NIzqg==", - "dev": true, - "dependencies": { - "bs58check": "^2.1.2", - "dhttp": "^3.0.3", - "randombytes": "^2.1.0" - } - }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.5" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/spawn-wrap/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/spawn-wrap/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tiny-secp256k1": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.0.tgz", - "integrity": "sha512-2hPuUGCroLrxh6xxwoe+1RgPpOOK1w2uTnhgiHBpvoutBR+krNuT4hOXQyOaaYnZgoXBB6hBYkuAJHxyeBOPzQ==", - "dev": true, - "dependencies": { - "uint8array-tools": "0.0.6" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" - }, - "bin": { - "ts-node": "dist/bin.js" - }, - "engines": { - "node": ">=4.2.0" - }, - "peerDependencies": { - "typescript": ">=2.0" - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.13.0", - "tsutils": "^2.29.0" - }, - "bin": { - "tslint": "bin/tslint" - }, - "engines": { - "node": ">=4.8.0" - }, - "peerDependencies": { - "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" - } - }, - "node_modules/tslint/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "peerDependencies": { - "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typeforce": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", - "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" - }, - "node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uint8array-tools": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.6.tgz", - "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/varuint-bitcoin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", - "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", - "dependencies": { - "safe-buffer": "^5.1.1" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/wif": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", - "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", - "dependencies": { - "bs58check": "<3.0.0" - } - }, - "node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - } - }, "dependencies": { "@babel/code-frame": { "version": "7.15.8", @@ -5360,9 +1966,9 @@ "dev": true }, "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dev": true, "requires": { "create-hash": "^1.1.2", From b8c0ba56146c3976b99f406508a766fb7534d66b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:29:32 +0200 Subject: [PATCH 55/73] refactor: change code to original version --- src/psbt.js | 5 ++--- ts_src/psbt.ts | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 1d6cad4fd..1307e30f8 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -278,7 +278,7 @@ class Psbt { range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); return this; } - finalizeInput(inputIndex, finalScriptsFunc) { + finalizeInput(inputIndex, finalScriptsFunc = getFinalScripts) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); const { script, @@ -289,8 +289,7 @@ class Psbt { } = getScriptFromInput(inputIndex, input, this.__CACHE); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); - const fn = finalScriptsFunc || getFinalScripts; - const { finalScriptSig, finalScriptWitness } = fn( + const { finalScriptSig, finalScriptWitness } = finalScriptsFunc( inputIndex, input, script, diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index a906a21c5..cae46aa8c 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -351,7 +351,10 @@ export class Psbt { return this; } - finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this { + finalizeInput( + inputIndex: number, + finalScriptsFunc: FinalScriptsFunc = getFinalScripts, + ): this { const input = checkForInput(this.data.inputs, inputIndex); const { script, @@ -364,8 +367,7 @@ export class Psbt { checkPartialSigSighashes(input); - const fn = finalScriptsFunc || getFinalScripts; - const { finalScriptSig, finalScriptWitness } = fn( + const { finalScriptSig, finalScriptWitness } = finalScriptsFunc( inputIndex, input, script, From de06357d4d5c5bffe4ad7e3c31dec37e705fe4df Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:46:29 +0200 Subject: [PATCH 56/73] fix: make `FinalScriptsFunc` backwards compatible by moving `isTapscript` param to the end --- src/psbt.js | 4 ++-- test/integration/csv.spec.ts | 1 - test/psbt.utils.ts | 2 +- ts_src/psbt.ts | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 1307e30f8..6747af981 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -294,9 +294,9 @@ class Psbt { input, script, isSegwit, - isTapscript, isP2SH, isP2WSH, + isTapscript, this.__CACHE.__EC_LIB, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); @@ -932,9 +932,9 @@ function getFinalScripts( input, script, isSegwit, - isTapscript, isP2SH, isP2WSH, + isTapscript = false, eccLib, ) { const scriptType = classifyScript(script, eccLib); diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 5e66804d4..742d68fa1 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -439,7 +439,6 @@ function csvGetFinalScripts( input: PsbtInput, script: Buffer, isSegwit: boolean, - _isTapscript: boolean, isP2SH: boolean, isP2WSH: boolean, ): { diff --git a/test/psbt.utils.ts b/test/psbt.utils.ts index aec6ca42d..59cccb323 100644 --- a/test/psbt.utils.ts +++ b/test/psbt.utils.ts @@ -17,9 +17,9 @@ const buildTapscriptFinalizer = ( input: PsbtInput, script: Buffer, _isSegwit: boolean, - _isTapscript: boolean, _isP2SH: boolean, _isP2WSH: boolean, + _isTapscript: boolean, eccLib?: TinySecp256k1Interface, ): { finalScriptSig: Buffer | undefined; diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index cae46aa8c..f135173bd 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -372,9 +372,9 @@ export class Psbt { input, script, isSegwit, - isTapscript, isP2SH, isP2WSH, + isTapscript, this.__CACHE.__EC_LIB, ); @@ -1236,9 +1236,9 @@ function getFinalScripts( input: PsbtInput, script: Buffer, isSegwit: boolean, - isTapscript: boolean, isP2SH: boolean, isP2WSH: boolean, + isTapscript: boolean = false, eccLib?: TinySecp256k1Interface, ): { finalScriptSig: Buffer | undefined; From 408e073199dae61287bd849dbea00700e0829920 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 10 Mar 2022 13:57:43 +0200 Subject: [PATCH 57/73] fix: tap tree branch sorting; improve unit test --- src/payments/taprootutils.js | 11 ++++------- test/fixtures/p2tr.json | 28 +++++++++++++++++----------- ts_src/payments/p2tr.ts | 8 ++++---- ts_src/payments/taprootutils.ts | 12 ++++-------- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 2a807214d..4d261bcd8 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -44,14 +44,11 @@ function toHashTree(scriptTree) { hash: tapLeafHash(script.output, script.version), }; } - const left = toHashTree([scriptTree[0]]); - const right = toHashTree([scriptTree[1]]); - let leftHash = left.hash; - let rightHash = right.hash; - if (leftHash.compare(rightHash) === 1) - [leftHash, rightHash] = [rightHash, leftHash]; + let left = toHashTree([scriptTree[0]]); + let right = toHashTree([scriptTree[1]]); + if (left.hash.compare(right.hash) === 1) [left, right] = [right, left]; return { - hash: tapBranchHash(leftHash, rightHash), + hash: tapBranchHash(left.hash, right.hash), left, right, }; diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index b38bb1776..9bcf1f7c4 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -318,6 +318,9 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs (2)", "arguments": { "internalPubkey": "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247", + "redeem": { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG" + }, "scriptTree": [ { "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG" @@ -325,28 +328,28 @@ [ [ { - "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG" + "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d4 OP_CHECKSIG" }, [ { - "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac1 OP_CHECKSIG" }, { - "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac2 OP_CHECKSIG" } ] ], [ [ { - "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac3 OP_CHECKSIG" }, { - "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG" + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG" } ], { - "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG" + "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d5 OP_CHECKSIG" } ] ] @@ -354,13 +357,16 @@ }, "expected": { "name": "p2tr", - "address": "bc1pmu8qwr9zljs9anger0d6q3uyr43yzjetmjmzf8p93ltycrwj28lsee3e0n", - "pubkey": "df0e070ca2fca05ecd191bdba047841d62414b2bdcb6249c258fd64c0dd251ff", - "output": "OP_1 df0e070ca2fca05ecd191bdba047841d62414b2bdcb6249c258fd64c0dd251ff", - "hash": "027391d0aac8d94725e4fcec4b07214d7c8a14bcdca2b1c08e4bc786308bdae5", + "address": "bc1pd2llmtym6c5hyecf5zqsyjz9q0jlxaaksw9j0atx8lc8a0e0vrmsw9ewly", + "pubkey": "6abffdac9bd629726709a08102484503e5f377b6838b27f5663ff07ebf2f60f7", + "output": "OP_1 6abffdac9bd629726709a08102484503e5f377b6838b27f5663ff07ebf2f60f7", + "hash": "88b7e3b495a84aa2bc12780b1773f130ce5eb747b0c28dc4840b7c9280f7326d", "signature": null, "input": null, - "witness": null + "witness": [ + "2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac", + "c0aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0a315fb34a7a93dcaed5e26cf7468be5bd377dda7a4d29128f7dd98db6da9bf04325fff3aa86365bac7534dcb6495867109941ec444dd35294e0706e29e051066d73e0d427bd3249bb921fa78c04fb76511f583ff48c97210d17c2d9dcfbb95023" + ] } }, { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 268266e50..0e00417be 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -47,14 +47,14 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { network: typef.maybe(typef.Object), output: typef.maybe(typef.BufferN(34)), internalPubkey: typef.maybe(typef.BufferN(32)), - hash: typef.maybe(typef.BufferN(32)), - pubkey: typef.maybe(typef.BufferN(32)), + hash: typef.maybe(typef.BufferN(32)), // merkle root hash, the tweak + pubkey: typef.maybe(typef.BufferN(32)), // tweaked with `hash` from `internalPubkey` signature: typef.maybe(typef.BufferN(64)), witness: typef.maybe(typef.arrayOf(typef.Buffer)), scriptTree: typef.maybe(isTapTree), redeem: typef.maybe({ - output: typef.maybe(typef.Buffer), - redeemVersion: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), // tapleaf script + redeemVersion: typef.maybe(typef.Number), // tapleaf version witness: typef.maybe(typef.arrayOf(typef.Buffer)), }), redeemVersion: typef.maybe(typef.Number), diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index c71a38b9d..b3636a9c4 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -60,16 +60,12 @@ export function toHashTree(scriptTree: TaprootLeaf[]): HashTree { }; } - const left = toHashTree([scriptTree[0]]); - const right = toHashTree([scriptTree[1]]); + let left = toHashTree([scriptTree[0]]); + let right = toHashTree([scriptTree[1]]); - let leftHash = left.hash; - let rightHash = right.hash; - - if (leftHash.compare(rightHash) === 1) - [leftHash, rightHash] = [rightHash, leftHash]; + if (left.hash.compare(right.hash) === 1) [left, right] = [right, left]; return { - hash: tapBranchHash(leftHash, rightHash), + hash: tapBranchHash(left.hash, right.hash), left, right, }; From f5ee927967bbb07e0463df3cb3668f3d08f82cfa Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Mar 2022 17:41:06 +0200 Subject: [PATCH 58/73] refactor: add Taptree interface --- src/payments/index.d.ts | 4 ++-- src/payments/taprootutils.d.ts | 8 ++++---- src/payments/taprootutils.js | 2 +- src/types.d.ts | 3 ++- ts_src/payments/index.ts | 4 ++-- ts_src/payments/taprootutils.ts | 8 ++++---- ts_src/types.ts | 4 +++- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 386ea3f68..5a71f8cc1 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -1,6 +1,6 @@ /// import { Network } from '../networks'; -import { TinySecp256k1Interface, TaprootLeaf } from '../types'; +import { TinySecp256k1Interface, Taptree } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -26,7 +26,7 @@ export interface Payment { hash?: Buffer; redeem?: Payment; redeemVersion?: number; - scriptTree?: TaprootLeaf[]; + scriptTree?: Taptree; witness?: Buffer[]; } export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index 82a488530..2bb998c84 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,5 +1,5 @@ /// -import { TaprootLeaf } from '../types'; +import { Taptree } from '../types'; export declare const LEAF_VERSION_TAPSCRIPT = 192; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; export interface HashTree { @@ -15,11 +15,11 @@ export interface HashTree { * - a pair of two taproot leafs [(output, version), (output, version)], or * - one taproot leaf and a list of elements */ -export declare function toHashTree(scriptTree: TaprootLeaf[]): HashTree; +export declare function toHashTree(scriptTree: Taptree): HashTree; /** - * Check if the tree is a binary tree with leafs of type TaprootLeaf + * Check if the tree is a binary tree with leafs of type Tapleaf */ -export declare function isTapTree(scriptTree: TaprootLeaf[]): boolean; +export declare function isTapTree(scriptTree: Taptree): boolean; /** * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 4d261bcd8..d9221fc33 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -55,7 +55,7 @@ function toHashTree(scriptTree) { } exports.toHashTree = toHashTree; /** - * Check if the tree is a binary tree with leafs of type TaprootLeaf + * Check if the tree is a binary tree with leafs of type Tapleaf */ function isTapTree(scriptTree) { if (scriptTree.length > 2) return false; diff --git a/src/types.d.ts b/src/types.d.ts index 5ddded9a2..9b62b4933 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -14,10 +14,11 @@ export interface XOnlyPointAddTweakResult { parity: 1 | 0; xOnlyPubkey: Uint8Array; } -export interface TaprootLeaf { +export interface Tapleaf { output: Buffer; version?: number; } +export declare type Taptree = Array<[Tapleaf, Tapleaf] | Tapleaf>; export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 90f5403c5..70d7614b7 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,5 +1,5 @@ import { Network } from '../networks'; -import { TinySecp256k1Interface, TaprootLeaf } from '../types'; +import { TinySecp256k1Interface, Taptree } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -26,7 +26,7 @@ export interface Payment { hash?: Buffer; redeem?: Payment; redeemVersion?: number; - scriptTree?: TaprootLeaf[]; + scriptTree?: Taptree; witness?: Buffer[]; } diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index b3636a9c4..cfa7a6dd2 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -2,7 +2,7 @@ import { Buffer as NBuffer } from 'buffer'; import * as bcrypto from '../crypto'; import { varuint } from '../bufferutils'; -import { TaprootLeaf } from '../types'; +import { Taptree } from '../types'; const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; @@ -45,7 +45,7 @@ export interface HashTree { * - a pair of two taproot leafs [(output, version), (output, version)], or * - one taproot leaf and a list of elements */ -export function toHashTree(scriptTree: TaprootLeaf[]): HashTree { +export function toHashTree(scriptTree: Taptree): HashTree { if (scriptTree.length === 1) { const script = scriptTree[0]; if (Array.isArray(script)) { @@ -71,9 +71,9 @@ export function toHashTree(scriptTree: TaprootLeaf[]): HashTree { }; } /** - * Check if the tree is a binary tree with leafs of type TaprootLeaf + * Check if the tree is a binary tree with leafs of type Tapleaf */ -export function isTapTree(scriptTree: TaprootLeaf[]): boolean { +export function isTapTree(scriptTree: Taptree): boolean { if (scriptTree.length > 2) return false; if (scriptTree.length === 1) { const script = scriptTree[0]; diff --git a/ts_src/types.ts b/ts_src/types.ts index fad2bc29c..59e4e1929 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -72,11 +72,13 @@ export interface XOnlyPointAddTweakResult { xOnlyPubkey: Uint8Array; } -export interface TaprootLeaf { +export interface Tapleaf { output: Buffer; version?: number; } +export type Taptree = Array<[Tapleaf, Tapleaf] | Tapleaf>; + export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak( From a04b9d503d59e0f3c3aaff4b2e29afbc8c5e44d0 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Mar 2022 17:43:17 +0200 Subject: [PATCH 59/73] refactor: rename `testecc` to `verifyecc` (avoid confusing it with unit tests) --- src/payments/p2tr.js | 4 ++-- src/payments/testecc.d.ts | 2 -- src/payments/verifyecc.d.ts | 2 ++ src/payments/{testecc.js => verifyecc.js} | 6 +++--- ts_src/payments/p2tr.ts | 4 ++-- ts_src/payments/{testecc.ts => verifyecc.ts} | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 src/payments/testecc.d.ts create mode 100644 src/payments/verifyecc.d.ts rename src/payments/{testecc.js => verifyecc.js} (96%) rename ts_src/payments/{testecc.ts => verifyecc.ts} (97%) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index d4406c2b2..13f283ed8 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -8,7 +8,7 @@ const types_1 = require('../types'); const taprootutils_1 = require('./taprootutils'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); -const testecc_1 = require('./testecc'); +const verifyecc_1 = require('./verifyecc'); const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; @@ -25,7 +25,7 @@ function p2tr(a, opts) { opts = Object.assign({ validate: true }, opts || {}); const _ecc = lazy.value(() => { if (!opts.eccLib) throw new Error('ECC Library is missing for p2tr.'); - (0, testecc_1.testEcc)(opts.eccLib); + (0, verifyecc_1.verifyEcc)(opts.eccLib); return opts.eccLib; }); (0, types_1.typeforce)( diff --git a/src/payments/testecc.d.ts b/src/payments/testecc.d.ts deleted file mode 100644 index 59d0de2b2..000000000 --- a/src/payments/testecc.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { TinySecp256k1Interface } from '../types'; -export declare function testEcc(ecc: TinySecp256k1Interface): void; diff --git a/src/payments/verifyecc.d.ts b/src/payments/verifyecc.d.ts new file mode 100644 index 000000000..0f23affa7 --- /dev/null +++ b/src/payments/verifyecc.d.ts @@ -0,0 +1,2 @@ +import { TinySecp256k1Interface } from '../types'; +export declare function verifyEcc(ecc: TinySecp256k1Interface): void; diff --git a/src/payments/testecc.js b/src/payments/verifyecc.js similarity index 96% rename from src/payments/testecc.js rename to src/payments/verifyecc.js index 44e19c887..9a1eebd64 100644 --- a/src/payments/testecc.js +++ b/src/payments/verifyecc.js @@ -1,8 +1,8 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.testEcc = void 0; +exports.verifyEcc = void 0; const h = hex => Buffer.from(hex, 'hex'); -function testEcc(ecc) { +function verifyEcc(ecc) { assert(typeof ecc.isXOnlyPoint === 'function'); assert( ecc.isXOnlyPoint( @@ -46,7 +46,7 @@ function testEcc(ecc) { } }); } -exports.testEcc = testEcc; +exports.verifyEcc = verifyEcc; function assert(bool) { if (!bool) throw new Error('ecc library invalid'); } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 0e00417be..2c7d8557a 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -14,7 +14,7 @@ import { import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; -import { testEcc } from './testecc'; +import { verifyEcc } from './verifyecc'; const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; @@ -36,7 +36,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const _ecc = lazy.value(() => { if (!opts!.eccLib) throw new Error('ECC Library is missing for p2tr.'); - testEcc(opts!.eccLib); + verifyEcc(opts!.eccLib); return opts!.eccLib; }); diff --git a/ts_src/payments/testecc.ts b/ts_src/payments/verifyecc.ts similarity index 97% rename from ts_src/payments/testecc.ts rename to ts_src/payments/verifyecc.ts index 382d6149a..75c2c5062 100644 --- a/ts_src/payments/testecc.ts +++ b/ts_src/payments/verifyecc.ts @@ -2,7 +2,7 @@ import { TinySecp256k1Interface } from '../types'; const h = (hex: string): Buffer => Buffer.from(hex, 'hex'); -export function testEcc(ecc: TinySecp256k1Interface): void { +export function verifyEcc(ecc: TinySecp256k1Interface): void { assert(typeof ecc.isXOnlyPoint === 'function'); assert( ecc.isXOnlyPoint( From 643e8eb5c703cf8cc92540fdee9b107195ab3f6f Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:11:14 -0700 Subject: [PATCH 60/73] Declare tapscript version mask like the BIP --- src/payments/p2tr.js | 2 +- ts_src/payments/p2tr.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 13f283ed8..bab9fcd4e 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -12,7 +12,7 @@ const verifyecc_1 = require('./verifyecc'); const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -const LEAF_VERSION_MASK = 0b11111110; +const LEAF_VERSION_MASK = 0xfe; function p2tr(a, opts) { if ( !a.address && diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 2c7d8557a..04d0f1d11 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -19,7 +19,7 @@ import { verifyEcc } from './verifyecc'; const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -const LEAF_VERSION_MASK = 0b11111110; +const LEAF_VERSION_MASK = 0xfe; export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if ( From a80a9b66b56e0c81a26b00ff8319f458971fa8f2 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 12:54:52 -0700 Subject: [PATCH 61/73] Correct Taptree type * Move the (much simplified) type check function to types.ts * Use `Tapleaf` type a bit more (this might be a bad idea) * Be more consistent in the capitalization of `Taptree` --- src/payments/p2tr.js | 28 +++++++++++------- src/payments/taprootutils.d.ts | 8 ++--- src/payments/taprootutils.js | 49 +++++++------------------------ src/psbt.js | 2 +- src/types.d.ts | 5 +++- src/types.js | 17 ++++++++++- test/fixtures/p2tr.json | 42 +++++++++++---------------- test/integration/taproot.spec.ts | 15 +++++----- ts_src/payments/p2tr.ts | 26 ++++++++++------- ts_src/payments/taprootutils.ts | 50 +++++--------------------------- ts_src/psbt.ts | 2 +- ts_src/types.ts | 17 ++++++++++- 12 files changed, 115 insertions(+), 146 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index bab9fcd4e..7f60e21ff 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -12,7 +12,6 @@ const verifyecc_1 = require('./verifyecc'); const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -const LEAF_VERSION_MASK = 0xfe; function p2tr(a, opts) { if ( !a.address && @@ -41,7 +40,7 @@ function p2tr(a, opts) { witness: types_1.typeforce.maybe( types_1.typeforce.arrayOf(types_1.typeforce.Buffer), ), - scriptTree: types_1.typeforce.maybe(taprootutils_1.isTapTree), + scriptTree: types_1.typeforce.maybe(types_1.isTaptree), redeem: types_1.typeforce.maybe({ output: types_1.typeforce.maybe(types_1.typeforce.Buffer), redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number), @@ -88,9 +87,12 @@ function p2tr(a, opts) { const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; - const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; + const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK; const script = w[w.length - 2]; - const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); + const leafHash = (0, taprootutils_1.tapLeafHash)({ + output: script, + version: leafVersion, + }); return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); } return null; @@ -116,7 +118,8 @@ function p2tr(a, opts) { return { output: witness[witness.length - 2], witness: witness.slice(0, -2), - redeemVersion: witness[witness.length - 1][0] & LEAF_VERSION_MASK, + redeemVersion: + witness[witness.length - 1][0] & types_1.TAPLEAF_VERSION_MASK, }; }); lazy.prop(o, 'pubkey', () => { @@ -144,10 +147,10 @@ function p2tr(a, opts) { if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { // todo: optimize/cache const hashTree = (0, taprootutils_1.toHashTree)(a.scriptTree); - const leafHash = (0, taprootutils_1.tapLeafHash)( - a.redeem.output, - o.redeemVersion, - ); + const leafHash = (0, taprootutils_1.tapLeafHash)({ + output: a.redeem.output, + version: o.redeemVersion, + }); const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); if (!outputKey) return; @@ -253,9 +256,12 @@ function p2tr(a, opts) { throw new TypeError('Internal pubkey mismatch'); if (!_ecc().isXOnlyPoint(internalPubkey)) throw new TypeError('Invalid internalPubkey for p2tr witness'); - const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; + const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK; const script = witness[witness.length - 2]; - const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); + const leafHash = (0, taprootutils_1.tapLeafHash)({ + output: script, + version: leafVersion, + }); const hash = (0, taprootutils_1.rootHashFromPath)( controlBlock, leafHash, diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index 2bb998c84..c1181743c 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,5 +1,5 @@ /// -import { Taptree } from '../types'; +import { Tapleaf, Taptree } from '../types'; export declare const LEAF_VERSION_TAPSCRIPT = 192; export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; export interface HashTree { @@ -16,10 +16,6 @@ export interface HashTree { * - one taproot leaf and a list of elements */ export declare function toHashTree(scriptTree: Taptree): HashTree; -/** - * Check if the tree is a binary tree with leafs of type Tapleaf - */ -export declare function isTapTree(scriptTree: Taptree): boolean; /** * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree @@ -27,5 +23,5 @@ export declare function isTapTree(scriptTree: Taptree): boolean; * @returns - and array of hashes representing the path, or an empty array if no pat is found */ export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[]; -export declare function tapLeafHash(script: Buffer, version?: number): Buffer; +export declare function tapLeafHash(leaf: Tapleaf): Buffer; export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer; diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index d9221fc33..9cb88ace4 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -1,9 +1,10 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapTweakHash = exports.tapLeafHash = exports.findScriptPath = exports.isTapTree = exports.toHashTree = exports.rootHashFromPath = exports.LEAF_VERSION_TAPSCRIPT = void 0; +exports.tapTweakHash = exports.tapLeafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.LEAF_VERSION_TAPSCRIPT = void 0; const buffer_1 = require('buffer'); const bcrypto = require('../crypto'); const bufferutils_1 = require('../bufferutils'); +const types_1 = require('../types'); const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; const TAP_TWEAK_TAG = 'TapTweak'; @@ -32,21 +33,11 @@ exports.rootHashFromPath = rootHashFromPath; * - one taproot leaf and a list of elements */ function toHashTree(scriptTree) { - if (scriptTree.length === 1) { - const script = scriptTree[0]; - if (Array.isArray(script)) { - return toHashTree(script); - } - script.version = script.version || exports.LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) - throw new TypeError('Invalid script version'); - return { - hash: tapLeafHash(script.output, script.version), - }; - } - let left = toHashTree([scriptTree[0]]); - let right = toHashTree([scriptTree[1]]); - if (left.hash.compare(right.hash) === 1) [left, right] = [right, left]; + if ((0, types_1.isTapleaf)(scriptTree)) + return { hash: tapLeafHash(scriptTree) }; + const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])]; + hashes.sort((a, b) => a.hash.compare(b.hash)); + const [left, right] = hashes; return { hash: tapBranchHash(left.hash, right.hash), left, @@ -54,26 +45,6 @@ function toHashTree(scriptTree) { }; } exports.toHashTree = toHashTree; -/** - * Check if the tree is a binary tree with leafs of type Tapleaf - */ -function isTapTree(scriptTree) { - if (scriptTree.length > 2) return false; - if (scriptTree.length === 1) { - const script = scriptTree[0]; - if (Array.isArray(script)) { - return isTapTree(script); - } - if (!script.output) return false; - script.version = script.version || exports.LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) return false; - return true; - } - if (!isTapTree([scriptTree[0]])) return false; - if (!isTapTree([scriptTree[1]])) return false; - return true; -} -exports.isTapTree = isTapTree; /** * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree @@ -96,13 +67,13 @@ function findScriptPath(node, hash) { return []; } exports.findScriptPath = findScriptPath; -function tapLeafHash(script, version) { - version = version || exports.LEAF_VERSION_TAPSCRIPT; +function tapLeafHash(leaf) { + const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( TAP_LEAF_TAG, buffer_1.Buffer.concat([ buffer_1.Buffer.from([version]), - serializeScript(script), + serializeScript(leaf.output), ]), ); } diff --git a/src/psbt.js b/src/psbt.js index 6747af981..8f46a2718 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1079,7 +1079,7 @@ function getHashForSig( const signingScripts = prevOuts.map(o => o.script); const values = prevOuts.map(o => o.value); const leafHash = input.witnessScript - ? (0, taprootutils_1.tapLeafHash)(input.witnessScript) + ? (0, taprootutils_1.tapLeafHash)({ output: input.witnessScript }) : undefined; hash = unsignedTx.hashForWitnessV1( inputIndex, diff --git a/src/types.d.ts b/src/types.d.ts index 9b62b4933..c8048c29a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -18,7 +18,10 @@ export interface Tapleaf { output: Buffer; version?: number; } -export declare type Taptree = Array<[Tapleaf, Tapleaf] | Tapleaf>; +export declare const TAPLEAF_VERSION_MASK = 254; +export declare function isTapleaf(o: any): o is Tapleaf; +export declare type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf; +export declare function isTaptree(scriptTree: any): scriptTree is Taptree; export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; diff --git a/src/types.js b/src/types.js index a6d1efa16..e1d0a528d 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0; +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.isTaptree = exports.isTapleaf = exports.TAPLEAF_VERSION_MASK = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0; const buffer_1 = require('buffer'); exports.typeforce = require('typeforce'); const ZERO32 = buffer_1.Buffer.alloc(32, 0); @@ -68,6 +68,21 @@ exports.Network = exports.typeforce.compile({ scriptHash: exports.typeforce.UInt8, wif: exports.typeforce.UInt8, }); +exports.TAPLEAF_VERSION_MASK = 0xfe; +function isTapleaf(o) { + if (!('output' in o)) return false; + if (!buffer_1.Buffer.isBuffer(o.output)) return false; + if (o.version !== undefined) + return (o.version & exports.TAPLEAF_VERSION_MASK) === o.version; + return true; +} +exports.isTapleaf = isTapleaf; +function isTaptree(scriptTree) { + if (!(0, exports.Array)(scriptTree)) return isTapleaf(scriptTree); + if (scriptTree.length !== 2) return false; + return scriptTree.every(t => isTaptree(t)); +} +exports.isTaptree = isTaptree; exports.Buffer256bit = exports.typeforce.BufferN(32); exports.Hash160bit = exports.typeforce.BufferN(20); exports.Hash256bit = exports.typeforce.BufferN(32); diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 9bcf1f7c4..75e0456ab 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -161,11 +161,9 @@ "description": "address, pubkey, output and hash from internalPubkey and a script tree with one leaf", "arguments": { "internalPubkey": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", - "scriptTree": [ - { - "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" - } - ] + "scriptTree": { + "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" + } }, "expected": { "name": "p2tr", @@ -393,12 +391,10 @@ "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", "redeemVersion": 192 }, - "scriptTree": [ - { - "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", - "version": 192 - } - ] + "scriptTree": { + "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", + "version": 192 + } }, "options": {}, "expected": { @@ -427,12 +423,10 @@ "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", "redeemVersion": 192 }, - "scriptTree": [ - { - "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", - "version": 192 - } - ] + "scriptTree": { + "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", + "version": 192 + } }, "options": {}, "expected": { @@ -906,11 +900,9 @@ "options": {}, "arguments": { "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", - "scriptTree": [ - { - "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" - } - ], + "scriptTree": { + "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" + }, "hash": "b76077013c8e303085e300000000000000000000000000000000000000000000" } }, @@ -1037,7 +1029,7 @@ }, { "description": "Script Tree is not a binary tree (has tree leafs)", - "exception": "property \"scriptTree\" of type \\?isTapTree, got Array", + "exception": "property \"scriptTree\" of type \\?isTaptree, got Array", "options": {}, "arguments": { "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", @@ -1066,7 +1058,7 @@ }, { "description": "Script Tree is not a TapTree tree (leaf has no script)", - "exception": "property \"scriptTree\" of type \\?isTapTree, got Array", + "exception": "property \"scriptTree\" of type \\?isTaptree, got Array", "options": {}, "arguments": { "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", @@ -1167,4 +1159,4 @@ "depends": {}, "details": [] } -} \ No newline at end of file +} diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 90dacb63d..05d7d154d 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -4,6 +4,7 @@ import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; import { regtestUtils } from './_regtest'; import * as bitcoin from '../..'; +import { Taptree } from '../../src/types'; import { buildTapscriptFinalizer, toXOnly } from '../psbt.utils'; const rng = require('randombytes'); @@ -97,11 +98,9 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { )} OP_CHECKSIG`; const leafScript = bitcoin.script.fromASM(leafScriptAsm); - const scriptTree = [ - { - output: leafScript, - }, - ]; + const scriptTree = { + output: leafScript, + }; const { output, address, hash } = bitcoin.payments.p2tr( { @@ -157,7 +156,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { )} OP_CHECKSIG`; const leafScript = bitcoin.script.fromASM(leafScriptAsm); - const scriptTree: any[] = [ + const scriptTree: Taptree = [ [ { output: bitcoin.script.fromASM( @@ -262,7 +261,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { const leafScriptAsm = `OP_10 OP_CHECKSEQUENCEVERIFY OP_DROP ${leafPubkey} OP_CHECKSIG`; const leafScript = bitcoin.script.fromASM(leafScriptAsm); - const scriptTree: any[] = [ + const scriptTree: Taptree = [ { output: bitcoin.script.fromASM( '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG', @@ -361,7 +360,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { const leafScript = bitcoin.script.fromASM(leafScriptAsm); - const scriptTree: any[] = [ + const scriptTree: Taptree = [ { output: bitcoin.script.fromASM( '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG', diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 04d0f1d11..b298e2ba2 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,14 +1,18 @@ import { Buffer as NBuffer } from 'buffer'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { typeforce as typef, TinySecp256k1Interface } from '../types'; +import { + typeforce as typef, + isTaptree, + TinySecp256k1Interface, + TAPLEAF_VERSION_MASK, +} from '../types'; import { toHashTree, rootHashFromPath, findScriptPath, tapLeafHash, tapTweakHash, - isTapTree, LEAF_VERSION_TAPSCRIPT, } from './taprootutils'; import { Payment, PaymentOpts } from './index'; @@ -19,7 +23,6 @@ import { verifyEcc } from './verifyecc'; const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; -const LEAF_VERSION_MASK = 0xfe; export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if ( @@ -51,7 +54,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { pubkey: typef.maybe(typef.BufferN(32)), // tweaked with `hash` from `internalPubkey` signature: typef.maybe(typef.BufferN(64)), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - scriptTree: typef.maybe(isTapTree), + scriptTree: typef.maybe(isTaptree), redeem: typef.maybe({ output: typef.maybe(typef.Buffer), // tapleaf script redeemVersion: typef.maybe(typef.Number), // tapleaf version @@ -102,9 +105,9 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; - const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; + const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK; const script = w[w.length - 2]; - const leafHash = tapLeafHash(script, leafVersion); + const leafHash = tapLeafHash({ output: script, version: leafVersion }); return rootHashFromPath(controlBlock, leafHash); } return null; @@ -132,7 +135,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return { output: witness[witness.length - 2], witness: witness.slice(0, -2), - redeemVersion: witness[witness.length - 1][0] & LEAF_VERSION_MASK, + redeemVersion: witness[witness.length - 1][0] & TAPLEAF_VERSION_MASK, }; }); lazy.prop(o, 'pubkey', () => { @@ -161,7 +164,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { // todo: optimize/cache const hashTree = toHashTree(a.scriptTree); - const leafHash = tapLeafHash(a.redeem.output, o.redeemVersion); + const leafHash = tapLeafHash({ + output: a.redeem.output, + version: o.redeemVersion, + }); const path = findScriptPath(hashTree, leafHash); const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); if (!outputKey) return; @@ -283,10 +289,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (!_ecc().isXOnlyPoint(internalPubkey)) throw new TypeError('Invalid internalPubkey for p2tr witness'); - const leafVersion = controlBlock[0] & LEAF_VERSION_MASK; + const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK; const script = witness[witness.length - 2]; - const leafHash = tapLeafHash(script, leafVersion); + const leafHash = tapLeafHash({ output: script, version: leafVersion }); const hash = rootHashFromPath(controlBlock, leafHash); const outputKey = tweakKey(internalPubkey, hash, _ecc()); diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index cfa7a6dd2..48d08e617 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -2,7 +2,7 @@ import { Buffer as NBuffer } from 'buffer'; import * as bcrypto from '../crypto'; import { varuint } from '../bufferutils'; -import { Taptree } from '../types'; +import { Tapleaf, Taptree, isTapleaf } from '../types'; const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; @@ -46,52 +46,18 @@ export interface HashTree { * - one taproot leaf and a list of elements */ export function toHashTree(scriptTree: Taptree): HashTree { - if (scriptTree.length === 1) { - const script = scriptTree[0]; - if (Array.isArray(script)) { - return toHashTree(script); - } - script.version = script.version || LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) - throw new TypeError('Invalid script version'); - - return { - hash: tapLeafHash(script.output, script.version), - }; - } + if (isTapleaf(scriptTree)) return { hash: tapLeafHash(scriptTree) }; - let left = toHashTree([scriptTree[0]]); - let right = toHashTree([scriptTree[1]]); + const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])]; + hashes.sort((a, b) => a.hash.compare(b.hash)); + const [left, right] = hashes; - if (left.hash.compare(right.hash) === 1) [left, right] = [right, left]; return { hash: tapBranchHash(left.hash, right.hash), left, right, }; } -/** - * Check if the tree is a binary tree with leafs of type Tapleaf - */ -export function isTapTree(scriptTree: Taptree): boolean { - if (scriptTree.length > 2) return false; - if (scriptTree.length === 1) { - const script = scriptTree[0]; - if (Array.isArray(script)) { - return isTapTree(script); - } - if (!script.output) return false; - script.version = script.version || LEAF_VERSION_TAPSCRIPT; - if ((script.version & 1) !== 0) return false; - - return true; - } - - if (!isTapTree([scriptTree[0]])) return false; - if (!isTapTree([scriptTree[1]])) return false; - - return true; -} /** * Given a MAST tree, it finds the path of a particular hash. @@ -117,11 +83,11 @@ export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] { return []; } -export function tapLeafHash(script: Buffer, version?: number): Buffer { - version = version || LEAF_VERSION_TAPSCRIPT; +export function tapLeafHash(leaf: Tapleaf): Buffer { + const version = leaf.version || LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( TAP_LEAF_TAG, - NBuffer.concat([NBuffer.from([version]), serializeScript(script)]), + NBuffer.concat([NBuffer.from([version]), serializeScript(leaf.output)]), ); } diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index f135173bd..7a64c3e02 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1406,7 +1406,7 @@ function getHashForSig( const signingScripts: any = prevOuts.map(o => o.script); const values: any = prevOuts.map(o => o.value); const leafHash = input.witnessScript - ? tapLeafHash(input.witnessScript) + ? tapLeafHash({ output: input.witnessScript }) : undefined; hash = unsignedTx.hashForWitnessV1( diff --git a/ts_src/types.ts b/ts_src/types.ts index 59e4e1929..1e49361b6 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -77,7 +77,22 @@ export interface Tapleaf { version?: number; } -export type Taptree = Array<[Tapleaf, Tapleaf] | Tapleaf>; +export const TAPLEAF_VERSION_MASK = 0xfe; +export function isTapleaf(o: any): o is Tapleaf { + if (!('output' in o)) return false; + if (!NBuffer.isBuffer(o.output)) return false; + if (o.version !== undefined) + return (o.version & TAPLEAF_VERSION_MASK) === o.version; + return true; +} + +export type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf; + +export function isTaptree(scriptTree: any): scriptTree is Taptree { + if (!Array(scriptTree)) return isTapleaf(scriptTree); + if (scriptTree.length !== 2) return false; + return scriptTree.every((t: any) => isTaptree(t)); +} export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; From 1a248a2cb8421b20ff1f296f496d4073030543e1 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 13:59:18 -0700 Subject: [PATCH 62/73] Consistent capitalization of tapleaf --- src/payments/p2tr.js | 6 +++--- src/payments/taprootutils.d.ts | 4 ++-- src/payments/taprootutils.js | 12 ++++++------ src/psbt.js | 2 +- ts_src/payments/p2tr.ts | 8 ++++---- ts_src/payments/taprootutils.ts | 8 ++++---- ts_src/psbt.ts | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 7f60e21ff..af31dc418 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -89,7 +89,7 @@ function p2tr(a, opts) { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK; const script = w[w.length - 2]; - const leafHash = (0, taprootutils_1.tapLeafHash)({ + const leafHash = (0, taprootutils_1.tapleafHash)({ output: script, version: leafVersion, }); @@ -147,7 +147,7 @@ function p2tr(a, opts) { if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { // todo: optimize/cache const hashTree = (0, taprootutils_1.toHashTree)(a.scriptTree); - const leafHash = (0, taprootutils_1.tapLeafHash)({ + const leafHash = (0, taprootutils_1.tapleafHash)({ output: a.redeem.output, version: o.redeemVersion, }); @@ -258,7 +258,7 @@ function p2tr(a, opts) { throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK; const script = witness[witness.length - 2]; - const leafHash = (0, taprootutils_1.tapLeafHash)({ + const leafHash = (0, taprootutils_1.tapleafHash)({ output: script, version: leafVersion, }); diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index c1181743c..bc32e8523 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,7 +1,7 @@ /// import { Tapleaf, Taptree } from '../types'; export declare const LEAF_VERSION_TAPSCRIPT = 192; -export declare function rootHashFromPath(controlBlock: Buffer, tapLeafMsg: Buffer): Buffer; +export declare function rootHashFromPath(controlBlock: Buffer, tapleafMsg: Buffer): Buffer; export interface HashTree { hash: Buffer; left?: HashTree; @@ -23,5 +23,5 @@ export declare function toHashTree(scriptTree: Taptree): HashTree; * @returns - and array of hashes representing the path, or an empty array if no pat is found */ export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[]; -export declare function tapLeafHash(leaf: Tapleaf): Buffer; +export declare function tapleafHash(leaf: Tapleaf): Buffer; export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer; diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 9cb88ace4..d946216cc 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapTweakHash = exports.tapLeafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.LEAF_VERSION_TAPSCRIPT = void 0; +exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.LEAF_VERSION_TAPSCRIPT = void 0; const buffer_1 = require('buffer'); const bcrypto = require('../crypto'); const bufferutils_1 = require('../bufferutils'); @@ -9,8 +9,8 @@ const TAP_LEAF_TAG = 'TapLeaf'; const TAP_BRANCH_TAG = 'TapBranch'; const TAP_TWEAK_TAG = 'TapTweak'; exports.LEAF_VERSION_TAPSCRIPT = 0xc0; -function rootHashFromPath(controlBlock, tapLeafMsg) { - const k = [tapLeafMsg]; +function rootHashFromPath(controlBlock, tapleafMsg) { + const k = [tapleafMsg]; const e = []; const m = (controlBlock.length - 33) / 32; for (let j = 0; j < m; j++) { @@ -34,7 +34,7 @@ exports.rootHashFromPath = rootHashFromPath; */ function toHashTree(scriptTree) { if ((0, types_1.isTapleaf)(scriptTree)) - return { hash: tapLeafHash(scriptTree) }; + return { hash: tapleafHash(scriptTree) }; const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])]; hashes.sort((a, b) => a.hash.compare(b.hash)); const [left, right] = hashes; @@ -67,7 +67,7 @@ function findScriptPath(node, hash) { return []; } exports.findScriptPath = findScriptPath; -function tapLeafHash(leaf) { +function tapleafHash(leaf) { const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( TAP_LEAF_TAG, @@ -77,7 +77,7 @@ function tapLeafHash(leaf) { ]), ); } -exports.tapLeafHash = tapLeafHash; +exports.tapleafHash = tapleafHash; function tapTweakHash(pubKey, h) { return bcrypto.taggedHash( TAP_TWEAK_TAG, diff --git a/src/psbt.js b/src/psbt.js index 8f46a2718..694a6cc1b 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1079,7 +1079,7 @@ function getHashForSig( const signingScripts = prevOuts.map(o => o.script); const values = prevOuts.map(o => o.value); const leafHash = input.witnessScript - ? (0, taprootutils_1.tapLeafHash)({ output: input.witnessScript }) + ? (0, taprootutils_1.tapleafHash)({ output: input.witnessScript }) : undefined; hash = unsignedTx.hashForWitnessV1( inputIndex, diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index b298e2ba2..40fd49e03 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -11,7 +11,7 @@ import { toHashTree, rootHashFromPath, findScriptPath, - tapLeafHash, + tapleafHash, tapTweakHash, LEAF_VERSION_TAPSCRIPT, } from './taprootutils'; @@ -107,7 +107,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK; const script = w[w.length - 2]; - const leafHash = tapLeafHash({ output: script, version: leafVersion }); + const leafHash = tapleafHash({ output: script, version: leafVersion }); return rootHashFromPath(controlBlock, leafHash); } return null; @@ -164,7 +164,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { // todo: optimize/cache const hashTree = toHashTree(a.scriptTree); - const leafHash = tapLeafHash({ + const leafHash = tapleafHash({ output: a.redeem.output, version: o.redeemVersion, }); @@ -292,7 +292,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK; const script = witness[witness.length - 2]; - const leafHash = tapLeafHash({ output: script, version: leafVersion }); + const leafHash = tapleafHash({ output: script, version: leafVersion }); const hash = rootHashFromPath(controlBlock, leafHash); const outputKey = tweakKey(internalPubkey, hash, _ecc()); diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 48d08e617..12026ace3 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -12,9 +12,9 @@ export const LEAF_VERSION_TAPSCRIPT = 0xc0; export function rootHashFromPath( controlBlock: Buffer, - tapLeafMsg: Buffer, + tapleafMsg: Buffer, ): Buffer { - const k = [tapLeafMsg]; + const k = [tapleafMsg]; const e = []; const m = (controlBlock.length - 33) / 32; @@ -46,7 +46,7 @@ export interface HashTree { * - one taproot leaf and a list of elements */ export function toHashTree(scriptTree: Taptree): HashTree { - if (isTapleaf(scriptTree)) return { hash: tapLeafHash(scriptTree) }; + if (isTapleaf(scriptTree)) return { hash: tapleafHash(scriptTree) }; const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])]; hashes.sort((a, b) => a.hash.compare(b.hash)); @@ -83,7 +83,7 @@ export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] { return []; } -export function tapLeafHash(leaf: Tapleaf): Buffer { +export function tapleafHash(leaf: Tapleaf): Buffer { const version = leaf.version || LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( TAP_LEAF_TAG, diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 7a64c3e02..479421636 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -21,7 +21,7 @@ import { bitcoin as btcNetwork, Network } from './networks'; import * as payments from './payments'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; -import { tapLeafHash } from './payments/taprootutils'; +import { tapleafHash } from './payments/taprootutils'; import { TinySecp256k1Interface } from './types'; export interface TransactionInput { @@ -1406,7 +1406,7 @@ function getHashForSig( const signingScripts: any = prevOuts.map(o => o.script); const values: any = prevOuts.map(o => o.value); const leafHash = input.witnessScript - ? tapLeafHash({ output: input.witnessScript }) + ? tapleafHash({ output: input.witnessScript }) : undefined; hash = unsignedTx.hashForWitnessV1( From 431e38c317a70ef269aebdb64f1a280ef9998a0c Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 14:00:26 -0700 Subject: [PATCH 63/73] Don't use constants for tag prefixes Because the taggedHash API is typed, these are compile-time checked and it's more clear w/o the constants. --- src/payments/taprootutils.js | 9 +++------ ts_src/payments/taprootutils.ts | 10 +++------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index d946216cc..37cea1b20 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -5,9 +5,6 @@ const buffer_1 = require('buffer'); const bcrypto = require('../crypto'); const bufferutils_1 = require('../bufferutils'); const types_1 = require('../types'); -const TAP_LEAF_TAG = 'TapLeaf'; -const TAP_BRANCH_TAG = 'TapBranch'; -const TAP_TWEAK_TAG = 'TapTweak'; exports.LEAF_VERSION_TAPSCRIPT = 0xc0; function rootHashFromPath(controlBlock, tapleafMsg) { const k = [tapleafMsg]; @@ -70,7 +67,7 @@ exports.findScriptPath = findScriptPath; function tapleafHash(leaf) { const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( - TAP_LEAF_TAG, + 'TapLeaf', buffer_1.Buffer.concat([ buffer_1.Buffer.from([version]), serializeScript(leaf.output), @@ -80,13 +77,13 @@ function tapleafHash(leaf) { exports.tapleafHash = tapleafHash; function tapTweakHash(pubKey, h) { return bcrypto.taggedHash( - TAP_TWEAK_TAG, + 'TapTweak', buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), ); } exports.tapTweakHash = tapTweakHash; function tapBranchHash(a, b) { - return bcrypto.taggedHash(TAP_BRANCH_TAG, buffer_1.Buffer.concat([a, b])); + return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b])); } function serializeScript(s) { const varintLen = bufferutils_1.varuint.encodingLength(s.length); diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 12026ace3..090abadd5 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -4,10 +4,6 @@ import * as bcrypto from '../crypto'; import { varuint } from '../bufferutils'; import { Tapleaf, Taptree, isTapleaf } from '../types'; -const TAP_LEAF_TAG = 'TapLeaf'; -const TAP_BRANCH_TAG = 'TapBranch'; -const TAP_TWEAK_TAG = 'TapTweak'; - export const LEAF_VERSION_TAPSCRIPT = 0xc0; export function rootHashFromPath( @@ -86,20 +82,20 @@ export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] { export function tapleafHash(leaf: Tapleaf): Buffer { const version = leaf.version || LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( - TAP_LEAF_TAG, + 'TapLeaf', NBuffer.concat([NBuffer.from([version]), serializeScript(leaf.output)]), ); } export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { return bcrypto.taggedHash( - TAP_TWEAK_TAG, + 'TapTweak', NBuffer.concat(h ? [pubKey, h] : [pubKey]), ); } function tapBranchHash(a: Buffer, b: Buffer): Buffer { - return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([a, b])); + return bcrypto.taggedHash('TapBranch', NBuffer.concat([a, b])); } function serializeScript(s: Buffer): Buffer { From 748e0dca696a4815ac45c5b5f457a0b7ce90d027 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 14:49:26 -0700 Subject: [PATCH 64/73] Simplify HashTree processing, remove footgun * More clearly show the continuation and base cases in findScriptPath * Return undefined not empty path when no path is found * This would lead to generating an invalid witness * Tighten the type for HashTree to not allow 1-sided branch nodes --- src/payments/p2tr.js | 1 + src/payments/taprootutils.d.ts | 15 +++++++---- src/payments/taprootutils.js | 26 +++++++++---------- ts_src/payments/p2tr.ts | 1 + ts_src/payments/taprootutils.ts | 46 +++++++++++++++++++++------------ 5 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index af31dc418..d9fa7ceca 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -152,6 +152,7 @@ function p2tr(a, opts) { version: o.redeemVersion, }); const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); + if (!path) return; const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); if (!outputKey) return; const controlBock = buffer_1.Buffer.concat( diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index bc32e8523..3c4f800c6 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -2,11 +2,15 @@ import { Tapleaf, Taptree } from '../types'; export declare const LEAF_VERSION_TAPSCRIPT = 192; export declare function rootHashFromPath(controlBlock: Buffer, tapleafMsg: Buffer): Buffer; -export interface HashTree { +interface HashLeaf { hash: Buffer; - left?: HashTree; - right?: HashTree; } +interface HashBranch { + hash: Buffer; + left: HashTree; + right: HashTree; +} +export declare type HashTree = HashLeaf | HashBranch; /** * Build the hash tree from the scripts binary tree. * The binary tree can be balanced or not. @@ -20,8 +24,9 @@ export declare function toHashTree(scriptTree: Taptree): HashTree; * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree * @param hash - the hash to search for - * @returns - and array of hashes representing the path, or an empty array if no pat is found + * @returns - and array of hashes representing the path, undefined if no path is found */ -export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[]; +export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[] | undefined; export declare function tapleafHash(leaf: Tapleaf): Buffer; export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer; +export {}; diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 37cea1b20..d9ebc7bcb 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -21,6 +21,7 @@ function rootHashFromPath(controlBlock, tapleafMsg) { return k[m]; } exports.rootHashFromPath = rootHashFromPath; +const isHashBranch = ht => 'left' in ht && 'right' in ht; /** * Build the hash tree from the scripts binary tree. * The binary tree can be balanced or not. @@ -46,22 +47,21 @@ exports.toHashTree = toHashTree; * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree * @param hash - the hash to search for - * @returns - and array of hashes representing the path, or an empty array if no pat is found + * @returns - and array of hashes representing the path, undefined if no path is found */ function findScriptPath(node, hash) { - if (node.left) { - if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : []; - const leftPath = findScriptPath(node.left, hash); - if (leftPath.length) - return node.right ? [node.right.hash].concat(leftPath) : leftPath; - } - if (node.right) { - if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : []; - const rightPath = findScriptPath(node.right, hash); - if (rightPath.length) - return node.left ? [node.left.hash].concat(rightPath) : rightPath; + if (!isHashBranch(node)) { + if (node.hash.equals(hash)) { + return []; + } else { + return undefined; + } } - return []; + const leftPath = findScriptPath(node.left, hash); + if (leftPath !== undefined) return [node.right.hash, ...leftPath]; + const rightPath = findScriptPath(node.right, hash); + if (rightPath !== undefined) return [node.left.hash, ...rightPath]; + return undefined; } exports.findScriptPath = findScriptPath; function tapleafHash(leaf) { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 40fd49e03..a254d6ab8 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -169,6 +169,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { version: o.redeemVersion, }); const path = findScriptPath(hashTree, leafHash); + if (!path) return; const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); if (!outputKey) return; const controlBock = NBuffer.concat( diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 090abadd5..7c10e2306 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -27,12 +27,21 @@ export function rootHashFromPath( return k[m]; } -export interface HashTree { +interface HashLeaf { hash: Buffer; - left?: HashTree; - right?: HashTree; } +interface HashBranch { + hash: Buffer; + left: HashTree; + right: HashTree; +} + +const isHashBranch = (ht: HashTree): ht is HashBranch => + 'left' in ht && 'right' in ht; + +export type HashTree = HashLeaf | HashBranch; + /** * Build the hash tree from the scripts binary tree. * The binary tree can be balanced or not. @@ -59,24 +68,27 @@ export function toHashTree(scriptTree: Taptree): HashTree { * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree * @param hash - the hash to search for - * @returns - and array of hashes representing the path, or an empty array if no pat is found + * @returns - and array of hashes representing the path, undefined if no path is found */ -export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] { - if (node.left) { - if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : []; - const leftPath = findScriptPath(node.left, hash); - if (leftPath.length) - return node.right ? [node.right.hash].concat(leftPath) : leftPath; +export function findScriptPath( + node: HashTree, + hash: Buffer, +): Buffer[] | undefined { + if (!isHashBranch(node)) { + if (node.hash.equals(hash)) { + return []; + } else { + return undefined; + } } - if (node.right) { - if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : []; - const rightPath = findScriptPath(node.right, hash); - if (rightPath.length) - return node.left ? [node.left.hash].concat(rightPath) : rightPath; - } + const leftPath = findScriptPath(node.left, hash); + if (leftPath !== undefined) return [node.right.hash, ...leftPath]; + + const rightPath = findScriptPath(node.right, hash); + if (rightPath !== undefined) return [node.left.hash, ...rightPath]; - return []; + return undefined; } export function tapleafHash(leaf: Tapleaf): Buffer { From db23603f7964202d1539567f2d8af8a4f004f22d Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:46:47 -0700 Subject: [PATCH 65/73] Support p2tr with 1 script and no tree * Also added caching of `hashTree`, per todo. * Added a test for this functionality --- src/payments/p2tr.js | 16 ++++++++++------ test/fixtures/p2tr.json | 22 ++++++++++++++++++++++ ts_src/payments/p2tr.ts | 17 +++++++++++------ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index d9fa7ceca..57087092f 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -73,6 +73,11 @@ function p2tr(a, opts) { } return a.witness.slice(); }); + const _hashTree = lazy.value(() => { + if (a.scriptTree) return (0, taprootutils_1.toHashTree)(a.scriptTree); + if (a.hash) return { hash: a.hash }; + return; + }); const network = a.network || networks_1.bitcoin; const o = { name: 'p2tr', network }; lazy.prop(o, 'address', () => { @@ -82,8 +87,8 @@ function p2tr(a, opts) { return bech32_1.bech32m.encode(network.bech32, words); }); lazy.prop(o, 'hash', () => { - if (a.hash) return a.hash; - if (a.scriptTree) return (0, taprootutils_1.toHashTree)(a.scriptTree).hash; + const hashTree = _hashTree(); + if (hashTree) return hashTree.hash; const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; @@ -144,9 +149,8 @@ function p2tr(a, opts) { }); lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; - if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { - // todo: optimize/cache - const hashTree = (0, taprootutils_1.toHashTree)(a.scriptTree); + const hashTree = _hashTree(); + if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) { const leafHash = (0, taprootutils_1.tapleafHash)({ output: a.redeem.output, version: o.redeemVersion, @@ -204,7 +208,7 @@ function p2tr(a, opts) { throw new TypeError('Invalid pubkey for p2tr'); } if (a.hash && a.scriptTree) { - const hash = (0, taprootutils_1.toHashTree)(a.scriptTree).hash; + const hash = _hashTree().hash; if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } const witness = _witness(); diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 75e0456ab..cf9482c36 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -312,6 +312,28 @@ "witness": null } }, + { + "description": "address, pubkey, and output from internalPubkey redeem, and hash (one leaf, no tree)", + "arguments": { + "internalPubkey": "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247", + "redeem": { + "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG" + }, + "hash": "b424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26" + }, + "expected": { + "name": "p2tr", + "address": "bc1pnxyp0ahcg53jzgrzj57hnlgdtqtzn7qqhmgjgczk8hzhcltq974qazepzf", + "pubkey": "998817f6f84523212062953d79fd0d581629f800bed12460563dc57c7d602faa", + "output": "OP_1 998817f6f84523212062953d79fd0d581629f800bed12460563dc57c7d602faa", + "signature": null, + "input": null, + "witness": [ + "2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac", + "c0aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247" + ] + } + }, { "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs (2)", "arguments": { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index a254d6ab8..0da70c11e 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -88,6 +88,12 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return a.witness.slice(); }); + const _hashTree = lazy.value(() => { + if (a.scriptTree) return toHashTree(a.scriptTree); + if (a.hash) return { hash: a.hash }; + return; + }); + const network = a.network || BITCOIN_NETWORK; const o: Payment = { name: 'p2tr', network }; @@ -100,8 +106,8 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'hash', () => { - if (a.hash) return a.hash; - if (a.scriptTree) return toHashTree(a.scriptTree).hash; + const hashTree = _hashTree(); + if (hashTree) return hashTree.hash; const w = _witness(); if (w && w.length > 1) { const controlBlock = w[w.length - 1]; @@ -161,9 +167,8 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; - if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) { - // todo: optimize/cache - const hashTree = toHashTree(a.scriptTree); + const hashTree = _hashTree(); + if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) { const leafHash = tapleafHash({ output: a.redeem.output, version: o.redeemVersion, @@ -227,7 +232,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.hash && a.scriptTree) { - const hash = toHashTree(a.scriptTree).hash; + const hash = _hashTree()!.hash; if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); } From e28b964f2854ac4aa3a9e6f6e2e04205efab84ca Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:54:24 -0700 Subject: [PATCH 66/73] Remove unnecessary arrays of values The spec uses this notation because in a spec there's no such thing as reassigning a value. In real code it is appropriate to us accumulators or such. --- src/payments/taprootutils.js | 13 ++++++------- ts_src/payments/taprootutils.ts | 14 ++++++-------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index d9ebc7bcb..445faf1e8 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -7,18 +7,17 @@ const bufferutils_1 = require('../bufferutils'); const types_1 = require('../types'); exports.LEAF_VERSION_TAPSCRIPT = 0xc0; function rootHashFromPath(controlBlock, tapleafMsg) { - const k = [tapleafMsg]; - const e = []; const m = (controlBlock.length - 33) / 32; + let kj = tapleafMsg; for (let j = 0; j < m; j++) { - e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); - if (k[j].compare(e[j]) < 0) { - k[j + 1] = tapBranchHash(k[j], e[j]); + const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j); + if (kj.compare(ej) < 0) { + kj = tapBranchHash(kj, ej); } else { - k[j + 1] = tapBranchHash(e[j], k[j]); + kj = tapBranchHash(ej, kj); } } - return k[m]; + return kj; } exports.rootHashFromPath = rootHashFromPath; const isHashBranch = ht => 'left' in ht && 'right' in ht; diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 7c10e2306..53fc2a6e6 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -10,21 +10,19 @@ export function rootHashFromPath( controlBlock: Buffer, tapleafMsg: Buffer, ): Buffer { - const k = [tapleafMsg]; - const e = []; - const m = (controlBlock.length - 33) / 32; + let kj = tapleafMsg; for (let j = 0; j < m; j++) { - e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); - if (k[j].compare(e[j]) < 0) { - k[j + 1] = tapBranchHash(k[j], e[j]); + const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j); + if (kj.compare(ej) < 0) { + kj = tapBranchHash(kj, ej); } else { - k[j + 1] = tapBranchHash(e[j], k[j]); + kj = tapBranchHash(ej, kj); } } - return k[m]; + return kj; } interface HashLeaf { From c8387c466890e33982002db06a8579e8cf5e07fc Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:57:05 -0700 Subject: [PATCH 67/73] Improve tapleah hash parameter name --- src/payments/taprootutils.d.ts | 2 +- src/payments/taprootutils.js | 4 ++-- ts_src/payments/taprootutils.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index 3c4f800c6..1635168c3 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,7 +1,7 @@ /// import { Tapleaf, Taptree } from '../types'; export declare const LEAF_VERSION_TAPSCRIPT = 192; -export declare function rootHashFromPath(controlBlock: Buffer, tapleafMsg: Buffer): Buffer; +export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer; interface HashLeaf { hash: Buffer; } diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 445faf1e8..0cf1b1657 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -6,9 +6,9 @@ const bcrypto = require('../crypto'); const bufferutils_1 = require('../bufferutils'); const types_1 = require('../types'); exports.LEAF_VERSION_TAPSCRIPT = 0xc0; -function rootHashFromPath(controlBlock, tapleafMsg) { +function rootHashFromPath(controlBlock, leafHash) { const m = (controlBlock.length - 33) / 32; - let kj = tapleafMsg; + let kj = leafHash; for (let j = 0; j < m; j++) { const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j); if (kj.compare(ej) < 0) { diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 53fc2a6e6..fc043f696 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -8,11 +8,11 @@ export const LEAF_VERSION_TAPSCRIPT = 0xc0; export function rootHashFromPath( controlBlock: Buffer, - tapleafMsg: Buffer, + leafHash: Buffer, ): Buffer { const m = (controlBlock.length - 33) / 32; - let kj = tapleafMsg; + let kj = leafHash; for (let j = 0; j < m; j++) { const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j); if (kj.compare(ej) < 0) { From 984649d27ea6c5e79929826fa4008b362c3dca83 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 16:05:56 -0700 Subject: [PATCH 68/73] Fix indentation --- test/fixtures/p2tr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index cf9482c36..3da6103fd 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -385,8 +385,8 @@ "input": null, "witness": [ "2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac", - "c0aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0a315fb34a7a93dcaed5e26cf7468be5bd377dda7a4d29128f7dd98db6da9bf04325fff3aa86365bac7534dcb6495867109941ec444dd35294e0706e29e051066d73e0d427bd3249bb921fa78c04fb76511f583ff48c97210d17c2d9dcfbb95023" - ] + "c0aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0a315fb34a7a93dcaed5e26cf7468be5bd377dda7a4d29128f7dd98db6da9bf04325fff3aa86365bac7534dcb6495867109941ec444dd35294e0706e29e051066d73e0d427bd3249bb921fa78c04fb76511f583ff48c97210d17c2d9dcfbb95023" + ] } }, { From 6bf4ffc4d053405b0c52c4bf52e29d46f5049ce9 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Mon, 21 Mar 2022 07:55:57 -0700 Subject: [PATCH 69/73] Add validation for redeem in scriptTree --- src/payments/p2tr.js | 14 +++++++++++--- test/fixtures/p2tr.json | 14 ++++++++++++++ ts_src/payments/p2tr.ts | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 57087092f..ce30f4662 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -207,9 +207,17 @@ function p2tr(a, opts) { if (!_ecc().isXOnlyPoint(pubkey)) throw new TypeError('Invalid pubkey for p2tr'); } - if (a.hash && a.scriptTree) { - const hash = _hashTree().hash; - if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); + const hashTree = _hashTree(); + if (a.hash && hashTree) { + if (!a.hash.equals(hashTree.hash)) throw new TypeError('Hash mismatch'); + } + if (a.redeem && a.redeem.output && hashTree) { + const leafHash = (0, taprootutils_1.tapleafHash)({ + output: a.redeem.output, + version: o.redeemVersion, + }); + if (!(0, taprootutils_1.findScriptPath)(hashTree, leafHash)) + throw new TypeError('Redeem script not in tree'); } const witness = _witness(); // compare the provided redeem data with the one computed from witness diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index 3da6103fd..aaa82fbb4 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -1175,6 +1175,20 @@ ] } } + }, + { + "description": "Redeem script not in tree", + "exception": "Redeem script not in tree", + "options": {}, + "arguments": { + "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", + "scriptTree": { + "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG" + }, + "redeem": { + "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c19 OP_CHECKSIG" + } + } } ], "dynamic": { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 0da70c11e..71f7437a3 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -231,9 +231,19 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Invalid pubkey for p2tr'); } - if (a.hash && a.scriptTree) { - const hash = _hashTree()!.hash; - if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); + const hashTree = _hashTree(); + + if (a.hash && hashTree) { + if (!a.hash.equals(hashTree.hash)) throw new TypeError('Hash mismatch'); + } + + if (a.redeem && a.redeem.output && hashTree) { + const leafHash = tapleafHash({ + output: a.redeem.output, + version: o.redeemVersion, + }); + if (!findScriptPath(hashTree, leafHash)) + throw new TypeError('Redeem script not in tree'); } const witness = _witness(); From 6ea2f8b746adedd63b1dac38e9548a73a4748ad9 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Mon, 21 Mar 2022 07:57:08 -0700 Subject: [PATCH 70/73] Improve comments and code clarity --- src/payments/p2tr.js | 2 +- src/payments/taprootutils.d.ts | 20 ++++++++++------- src/payments/taprootutils.js | 31 +++++++++++-------------- src/types.d.ts | 5 +++++ ts_src/payments/p2tr.ts | 2 +- ts_src/payments/taprootutils.ts | 40 ++++++++++++++++----------------- ts_src/types.ts | 5 +++++ 7 files changed, 57 insertions(+), 48 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index ce30f4662..c24c16914 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -163,7 +163,7 @@ function p2tr(a, opts) { [ buffer_1.Buffer.from([o.redeemVersion | outputKey.parity]), a.internalPubkey, - ].concat(path.reverse()), + ].concat(path), ); return [a.redeem.output, controlBock]; } diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index 1635168c3..a5739c44f 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -10,21 +10,25 @@ interface HashBranch { left: HashTree; right: HashTree; } +/** + * Binary tree representing leaf, branch, and root node hashes of a Taptree. + * Each node contains a hash, and potentially left and right branch hashes. + * This tree is used for 2 purposes: Providing the root hash for tweaking, + * and calculating merkle inclusion proofs when constructing a control block. + */ export declare type HashTree = HashLeaf | HashBranch; /** - * Build the hash tree from the scripts binary tree. - * The binary tree can be balanced or not. - * @param scriptTree - is a list representing a binary tree where an element can be: - * - a taproot leaf [(output, version)], or - * - a pair of two taproot leafs [(output, version), (output, version)], or - * - one taproot leaf and a list of elements + * Build a hash tree of merkle nodes from the scripts binary tree. + * @param scriptTree - the tree of scripts to pairwise hash. */ export declare function toHashTree(scriptTree: Taptree): HashTree; /** - * Given a MAST tree, it finds the path of a particular hash. + * Given a HashTree, finds the path from a particular hash to the root. * @param node - the root of the tree * @param hash - the hash to search for - * @returns - and array of hashes representing the path, undefined if no path is found + * @returns - array of sibling hashes, from leaf (inclusive) to root + * (exclusive) needed to prove inclusion of the specified hash. undefined if no + * path is found */ export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[] | undefined; export declare function tapleafHash(leaf: Tapleaf): Buffer; diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 0cf1b1657..85576960b 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -22,12 +22,8 @@ function rootHashFromPath(controlBlock, leafHash) { exports.rootHashFromPath = rootHashFromPath; const isHashBranch = ht => 'left' in ht && 'right' in ht; /** - * Build the hash tree from the scripts binary tree. - * The binary tree can be balanced or not. - * @param scriptTree - is a list representing a binary tree where an element can be: - * - a taproot leaf [(output, version)], or - * - a pair of two taproot leafs [(output, version), (output, version)], or - * - one taproot leaf and a list of elements + * Build a hash tree of merkle nodes from the scripts binary tree. + * @param scriptTree - the tree of scripts to pairwise hash. */ function toHashTree(scriptTree) { if ((0, types_1.isTapleaf)(scriptTree)) @@ -43,23 +39,22 @@ function toHashTree(scriptTree) { } exports.toHashTree = toHashTree; /** - * Given a MAST tree, it finds the path of a particular hash. + * Given a HashTree, finds the path from a particular hash to the root. * @param node - the root of the tree * @param hash - the hash to search for - * @returns - and array of hashes representing the path, undefined if no path is found + * @returns - array of sibling hashes, from leaf (inclusive) to root + * (exclusive) needed to prove inclusion of the specified hash. undefined if no + * path is found */ function findScriptPath(node, hash) { - if (!isHashBranch(node)) { - if (node.hash.equals(hash)) { - return []; - } else { - return undefined; - } + if (isHashBranch(node)) { + const leftPath = findScriptPath(node.left, hash); + if (leftPath !== undefined) return [...leftPath, node.right.hash]; + const rightPath = findScriptPath(node.right, hash); + if (rightPath !== undefined) return [...rightPath, node.left.hash]; + } else if (node.hash.equals(hash)) { + return []; } - const leftPath = findScriptPath(node.left, hash); - if (leftPath !== undefined) return [node.right.hash, ...leftPath]; - const rightPath = findScriptPath(node.right, hash); - if (rightPath !== undefined) return [node.left.hash, ...rightPath]; return undefined; } exports.findScriptPath = findScriptPath; diff --git a/src/types.d.ts b/src/types.d.ts index c8048c29a..b3d93589d 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -20,6 +20,11 @@ export interface Tapleaf { } export declare const TAPLEAF_VERSION_MASK = 254; export declare function isTapleaf(o: any): o is Tapleaf; +/** + * Binary tree repsenting script path spends for a Taproot input. + * Each node is either a single Tapleaf, or a pair of Tapleaf | Taptree. + * The tree has no balancing requirements. + */ export declare type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf; export declare function isTaptree(scriptTree: any): scriptTree is Taptree; export interface TinySecp256k1Interface { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 71f7437a3..47a76a114 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -181,7 +181,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { [ NBuffer.from([o.redeemVersion! | outputKey.parity]), a.internalPubkey, - ].concat(path.reverse()), + ].concat(path), ); return [a.redeem.output, controlBock]; } diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index fc043f696..97cc1f6d8 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -38,15 +38,17 @@ interface HashBranch { const isHashBranch = (ht: HashTree): ht is HashBranch => 'left' in ht && 'right' in ht; +/** + * Binary tree representing leaf, branch, and root node hashes of a Taptree. + * Each node contains a hash, and potentially left and right branch hashes. + * This tree is used for 2 purposes: Providing the root hash for tweaking, + * and calculating merkle inclusion proofs when constructing a control block. + */ export type HashTree = HashLeaf | HashBranch; /** - * Build the hash tree from the scripts binary tree. - * The binary tree can be balanced or not. - * @param scriptTree - is a list representing a binary tree where an element can be: - * - a taproot leaf [(output, version)], or - * - a pair of two taproot leafs [(output, version), (output, version)], or - * - one taproot leaf and a list of elements + * Build a hash tree of merkle nodes from the scripts binary tree. + * @param scriptTree - the tree of scripts to pairwise hash. */ export function toHashTree(scriptTree: Taptree): HashTree { if (isTapleaf(scriptTree)) return { hash: tapleafHash(scriptTree) }; @@ -63,29 +65,27 @@ export function toHashTree(scriptTree: Taptree): HashTree { } /** - * Given a MAST tree, it finds the path of a particular hash. + * Given a HashTree, finds the path from a particular hash to the root. * @param node - the root of the tree * @param hash - the hash to search for - * @returns - and array of hashes representing the path, undefined if no path is found + * @returns - array of sibling hashes, from leaf (inclusive) to root + * (exclusive) needed to prove inclusion of the specified hash. undefined if no + * path is found */ export function findScriptPath( node: HashTree, hash: Buffer, ): Buffer[] | undefined { - if (!isHashBranch(node)) { - if (node.hash.equals(hash)) { - return []; - } else { - return undefined; - } + if (isHashBranch(node)) { + const leftPath = findScriptPath(node.left, hash); + if (leftPath !== undefined) return [...leftPath, node.right.hash]; + + const rightPath = findScriptPath(node.right, hash); + if (rightPath !== undefined) return [...rightPath, node.left.hash]; + } else if (node.hash.equals(hash)) { + return []; } - const leftPath = findScriptPath(node.left, hash); - if (leftPath !== undefined) return [node.right.hash, ...leftPath]; - - const rightPath = findScriptPath(node.right, hash); - if (rightPath !== undefined) return [node.left.hash, ...rightPath]; - return undefined; } diff --git a/ts_src/types.ts b/ts_src/types.ts index 1e49361b6..536646e86 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -86,6 +86,11 @@ export function isTapleaf(o: any): o is Tapleaf { return true; } +/** + * Binary tree repsenting script path spends for a Taproot input. + * Each node is either a single Tapleaf, or a pair of Tapleaf | Taptree. + * The tree has no balancing requirements. + */ export type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf; export function isTaptree(scriptTree: any): scriptTree is Taptree { From bbf5cda50dd6840a68452533c879227071cfff80 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 28 Mar 2022 10:27:10 +0300 Subject: [PATCH 71/73] refactor: add explicit initialisation of the ecc library (#5) * refactor: explicit initialization on the ecc library - remove optional `eccLib` parameter for `p2tr` and `psbt` --- src/address.d.ts | 5 +- src/address.js | 11 ++- src/ecc_lib.d.ts | 3 + src/{payments/verifyecc.js => ecc_lib.js} | 23 +++++- src/index.d.ts | 1 + src/index.js | 9 ++- src/payments/index.d.ts | 3 +- src/payments/p2tr.js | 23 +++--- src/payments/verifyecc.d.ts | 2 - src/psbt.d.ts | 5 +- src/psbt.js | 36 +++------ test/address.spec.ts | 14 ++-- test/integration/taproot.spec.ts | 79 +++++++++----------- test/payments.spec.ts | 15 ++-- test/psbt.spec.ts | 26 +++++-- test/psbt.utils.ts | 17 ++--- ts_src/address.ts | 28 ++----- ts_src/{payments/verifyecc.ts => ecc_lib.ts} | 25 ++++++- ts_src/index.ts | 1 + ts_src/payments/index.ts | 3 +- ts_src/payments/p2tr.ts | 31 +++----- ts_src/psbt.ts | 48 +++--------- 22 files changed, 184 insertions(+), 224 deletions(-) create mode 100644 src/ecc_lib.d.ts rename src/{payments/verifyecc.js => ecc_lib.js} (77%) delete mode 100644 src/payments/verifyecc.d.ts rename ts_src/{payments/verifyecc.ts => ecc_lib.ts} (73%) diff --git a/src/address.d.ts b/src/address.d.ts index 13922dab3..be0e00a61 100644 --- a/src/address.d.ts +++ b/src/address.d.ts @@ -1,6 +1,5 @@ /// import { Network } from './networks'; -import { TinySecp256k1Interface } from './types'; export interface Base58CheckResult { hash: Buffer; version: number; @@ -14,5 +13,5 @@ export declare function fromBase58Check(address: string): Base58CheckResult; export declare function fromBech32(address: string): Bech32Result; export declare function toBase58Check(hash: Buffer, version: number): string; export declare function toBech32(data: Buffer, version: number, prefix: string): string; -export declare function fromOutputScript(output: Buffer, network?: Network, eccLib?: TinySecp256k1Interface): string; -export declare function toOutputScript(address: string, network?: Network, eccLib?: TinySecp256k1Interface): Buffer; +export declare function fromOutputScript(output: Buffer, network?: Network): string; +export declare function toOutputScript(address: string, network?: Network): Buffer; diff --git a/src/address.js b/src/address.js index 2c7bc4857..de0154a3a 100644 --- a/src/address.js +++ b/src/address.js @@ -86,7 +86,7 @@ function toBech32(data, version, prefix) { : bech32_1.bech32m.encode(prefix, words); } exports.toBech32 = toBech32; -function fromOutputScript(output, network, eccLib) { +function fromOutputScript(output, network) { // TODO: Network network = network || networks.bitcoin; try { @@ -102,7 +102,7 @@ function fromOutputScript(output, network, eccLib) { return payments.p2wsh({ output, network }).address; } catch (e) {} try { - if (eccLib) return payments.p2tr({ output, network }, { eccLib }).address; + return payments.p2tr({ output, network }).address; } catch (e) {} try { return _toFutureSegwitAddress(output, network); @@ -110,7 +110,7 @@ function fromOutputScript(output, network, eccLib) { throw new Error(bscript.toASM(output) + ' has no matching Address'); } exports.fromOutputScript = fromOutputScript; -function toOutputScript(address, network, eccLib) { +function toOutputScript(address, network) { network = network || networks.bitcoin; let decodeBase58; let decodeBech32; @@ -135,9 +135,8 @@ function toOutputScript(address, network, eccLib) { if (decodeBech32.data.length === 32) return payments.p2wsh({ hash: decodeBech32.data }).output; } else if (decodeBech32.version === 1) { - if (decodeBech32.data.length === 32 && eccLib) - return payments.p2tr({ pubkey: decodeBech32.data }, { eccLib }) - .output; + if (decodeBech32.data.length === 32) + return payments.p2tr({ pubkey: decodeBech32.data }).output; } else if ( decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION && decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && diff --git a/src/ecc_lib.d.ts b/src/ecc_lib.d.ts new file mode 100644 index 000000000..201ebb5cf --- /dev/null +++ b/src/ecc_lib.d.ts @@ -0,0 +1,3 @@ +import { TinySecp256k1Interface } from './types'; +export declare function initEccLib(eccLib: TinySecp256k1Interface | undefined): void; +export declare function getEccLib(): TinySecp256k1Interface; diff --git a/src/payments/verifyecc.js b/src/ecc_lib.js similarity index 77% rename from src/payments/verifyecc.js rename to src/ecc_lib.js index 9a1eebd64..eaa8a5327 100644 --- a/src/payments/verifyecc.js +++ b/src/ecc_lib.js @@ -1,6 +1,26 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.verifyEcc = void 0; +exports.getEccLib = exports.initEccLib = void 0; +const _ECCLIB_CACHE = {}; +function initEccLib(eccLib) { + if (!eccLib) { + // allow clearing the library + _ECCLIB_CACHE.eccLib = eccLib; + } else if (eccLib !== _ECCLIB_CACHE.eccLib) { + // new instance, verify it + verifyEcc(eccLib); + _ECCLIB_CACHE.eccLib = eccLib; + } +} +exports.initEccLib = initEccLib; +function getEccLib() { + if (!_ECCLIB_CACHE.eccLib) + throw new Error( + 'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance', + ); + return _ECCLIB_CACHE.eccLib; +} +exports.getEccLib = getEccLib; const h = hex => Buffer.from(hex, 'hex'); function verifyEcc(ecc) { assert(typeof ecc.isXOnlyPoint === 'function'); @@ -46,7 +66,6 @@ function verifyEcc(ecc) { } }); } -exports.verifyEcc = verifyEcc; function assert(bool) { if (!bool) throw new Error('ecc library invalid'); } diff --git a/src/index.d.ts b/src/index.d.ts index b93c2aa40..420979ffe 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -12,3 +12,4 @@ export { Transaction } from './transaction'; export { Network } from './networks'; export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments'; export { Input as TxInput, Output as TxOutput } from './transaction'; +export { initEccLib } from './ecc_lib'; diff --git a/src/index.js b/src/index.js index 983b0cc76..25d0b5a22 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0; +exports.initEccLib = exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0; const address = require('./address'); exports.address = address; const crypto = require('./crypto'); @@ -39,3 +39,10 @@ Object.defineProperty(exports, 'Transaction', { return transaction_1.Transaction; }, }); +var ecc_lib_1 = require('./ecc_lib'); +Object.defineProperty(exports, 'initEccLib', { + enumerable: true, + get: function() { + return ecc_lib_1.initEccLib; + }, +}); diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 5a71f8cc1..07c12cc48 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -1,6 +1,6 @@ /// import { Network } from '../networks'; -import { TinySecp256k1Interface, Taptree } from '../types'; +import { Taptree } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -34,7 +34,6 @@ export declare type PaymentFunction = () => Payment; export interface PaymentOpts { validate?: boolean; allowIncomplete?: boolean; - eccLib?: TinySecp256k1Interface; } export declare type StackElement = Buffer | number; export declare type Stack = StackElement[]; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index c24c16914..33fa007f8 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -5,10 +5,10 @@ const buffer_1 = require('buffer'); const networks_1 = require('../networks'); const bscript = require('../script'); const types_1 = require('../types'); +const ecc_lib_1 = require('../ecc_lib'); const taprootutils_1 = require('./taprootutils'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); -const verifyecc_1 = require('./verifyecc'); const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; @@ -22,11 +22,6 @@ function p2tr(a, opts) { ) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); - const _ecc = lazy.value(() => { - if (!opts.eccLib) throw new Error('ECC Library is missing for p2tr.'); - (0, verifyecc_1.verifyEcc)(opts.eccLib); - return opts.eccLib; - }); (0, types_1.typeforce)( { address: types_1.typeforce.maybe(types_1.typeforce.String), @@ -132,7 +127,7 @@ function p2tr(a, opts) { if (a.output) return a.output.slice(2); if (a.address) return _address().data; if (o.internalPubkey) { - const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc()); + const tweakedKey = tweakKey(o.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } }); @@ -157,7 +152,7 @@ function p2tr(a, opts) { }); const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); if (!path) return; - const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); + const outputKey = tweakKey(a.internalPubkey, hashTree.hash); if (!outputKey) return; const controlBock = buffer_1.Buffer.concat( [ @@ -198,13 +193,13 @@ function p2tr(a, opts) { else pubkey = a.output.slice(2); } if (a.internalPubkey) { - const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc()); + const tweakedKey = tweakKey(a.internalPubkey, o.hash); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) throw new TypeError('Pubkey mismatch'); else pubkey = tweakedKey.x; } if (pubkey && pubkey.length) { - if (!_ecc().isXOnlyPoint(pubkey)) + if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(pubkey)) throw new TypeError('Invalid pubkey for p2tr'); } const hashTree = _hashTree(); @@ -267,7 +262,7 @@ function p2tr(a, opts) { const internalPubkey = controlBlock.slice(1, 33); if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) throw new TypeError('Internal pubkey mismatch'); - if (!_ecc().isXOnlyPoint(internalPubkey)) + if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(internalPubkey)) throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK; const script = witness[witness.length - 2]; @@ -279,7 +274,7 @@ function p2tr(a, opts) { controlBlock, leafHash, ); - const outputKey = tweakKey(internalPubkey, hash, _ecc()); + const outputKey = tweakKey(internalPubkey, hash); if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); @@ -293,12 +288,12 @@ function p2tr(a, opts) { return Object.assign(o, a); } exports.p2tr = p2tr; -function tweakKey(pubKey, h, eccLib) { +function tweakKey(pubKey, h) { if (!buffer_1.Buffer.isBuffer(pubKey)) return null; if (pubKey.length !== 32) return null; if (h && h.length !== 32) return null; const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h); - const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash); + const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash); if (!res || res.xOnlyPubkey === null) return null; return { parity: res.parity, diff --git a/src/payments/verifyecc.d.ts b/src/payments/verifyecc.d.ts deleted file mode 100644 index 0f23affa7..000000000 --- a/src/payments/verifyecc.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { TinySecp256k1Interface } from '../types'; -export declare function verifyEcc(ecc: TinySecp256k1Interface): void; diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 8b21ce7bb..890f9e115 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -3,7 +3,6 @@ import { Psbt as PsbtBase } from 'bip174'; import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces'; import { Network } from './networks'; import { Transaction } from './transaction'; -import { TinySecp256k1Interface } from './types'; export interface TransactionInput { hash: string | Buffer; index: number; @@ -111,7 +110,6 @@ export declare class Psbt { interface PsbtOptsOptional { network?: Network; maximumFeeRate?: number; - eccLib?: TinySecp256k1Interface; } interface PsbtInputExtended extends PsbtInput, TransactionInput { } @@ -181,8 +179,7 @@ script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH isSegwit: boolean, // Is it segwit? isTapscript: boolean, // Is taproot script path? isP2SH: boolean, // Is it P2SH? -isP2WSH: boolean, // Is it P2WSH? -eccLib?: TinySecp256k1Interface) => { +isP2WSH: boolean) => { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | Buffer[] | undefined; }; diff --git a/src/psbt.js b/src/psbt.js index 694a6cc1b..c14086d0e 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -79,7 +79,6 @@ class Psbt { // We will disable exporting the Psbt when unsafe sign is active. // because it is not BIP174 compliant. __UNSAFE_SIGN_NONSEGWIT: false, - __EC_LIB: opts.eccLib, }; if (this.data.inputs.length === 0) this.setVersion(2); // Make data hidden when enumerating @@ -134,7 +133,6 @@ class Psbt { address = (0, address_1.fromOutputScript)( output.script, this.opts.network, - this.__CACHE.__EC_LIB, ); } catch (_) {} return { @@ -237,11 +235,7 @@ class Psbt { const { address } = outputData; if (typeof address === 'string') { const { network } = this.opts; - const script = (0, address_1.toOutputScript)( - address, - network, - this.__CACHE.__EC_LIB, - ); + const script = (0, address_1.toOutputScript)(address, network); outputData = Object.assign(outputData, { script }); } const c = this.__CACHE; @@ -297,7 +291,6 @@ class Psbt { isP2SH, isP2WSH, isTapscript, - this.__CACHE.__EC_LIB, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); if (finalScriptWitness) { @@ -326,13 +319,9 @@ class Psbt { input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness), - this.__CACHE, ); const type = result.type === 'raw' ? '' : result.type + '-'; - const mainType = classifyScript( - result.meaningfulScript, - this.__CACHE.__EC_LIB, - ); + const mainType = classifyScript(result.meaningfulScript); return type + mainType; } inputHasPubkey(inputIndex, pubkey) { @@ -769,9 +758,9 @@ function isFinalized(input) { return !!input.finalScriptSig || !!input.finalScriptWitness; } function isPaymentFactory(payment) { - return (script, eccLib) => { + return script => { try { - payment({ output: script }, { eccLib }); + payment({ output: script }); return true; } catch (err) { return false; @@ -935,9 +924,8 @@ function getFinalScripts( isP2SH, isP2WSH, isTapscript = false, - eccLib, ) { - const scriptType = classifyScript(script, eccLib); + const scriptType = classifyScript(script); if (isTapscript || !canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -1053,7 +1041,6 @@ function getHashForSig( 'input', input.redeemScript, input.witnessScript, - cache, ); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { hash = unsignedTx.hashForWitnessV0( @@ -1072,7 +1059,7 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(prevout.script, cache.__EC_LIB)) { + } else if (isP2TR(prevout.script)) { const prevOuts = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache), ); @@ -1204,7 +1191,7 @@ function getScriptFromInput(inputIndex, input, cache) { } else { res.script = utxoScript; } - const isTaproot = utxoScript && isP2TR(utxoScript, cache.__EC_LIB); + const isTaproot = utxoScript && isP2TR(utxoScript); // Segregated Witness versions 0 or 1 if (input.witnessScript || isP2WPKH(res.script) || isTaproot) { res.isSegwit = true; @@ -1410,7 +1397,6 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) { 'input', input.redeemScript, input.witnessScript, - cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1422,7 +1408,6 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { 'output', output.redeemScript, output.witnessScript, - cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1471,12 +1456,11 @@ function getMeaningfulScript( ioType, redeemScript, witnessScript, - cache, ) { const isP2SH = isP2SHScript(script); const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); const isP2WSH = isP2WSHScript(script); - const isP2TRScript = isP2TR(script, cache && cache.__EC_LIB); + const isP2TRScript = isP2TR(script); if (isP2SH && redeemScript === undefined) throw new Error('scriptPubkey is P2SH but redeemScript missing'); if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) @@ -1539,12 +1523,12 @@ function isTaprootSpend(scriptType) { !!scriptType && (scriptType === 'taproot' || scriptType.startsWith('p2tr-')) ); } -function classifyScript(script, eccLib) { +function classifyScript(script) { if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash'; if (isP2MS(script)) return 'multisig'; if (isP2PK(script)) return 'pubkey'; - if (isP2TR(script, eccLib)) return 'taproot'; + if (isP2TR(script)) return 'taproot'; return 'nonstandard'; } function range(n) { diff --git a/test/address.spec.ts b/test/address.spec.ts index be08cf803..23c18b9f6 100644 --- a/test/address.spec.ts +++ b/test/address.spec.ts @@ -5,6 +5,8 @@ import * as baddress from '../src/address'; import * as bscript from '../src/script'; import * as fixtures from './fixtures/address.json'; +import { initEccLib } from '../src'; + const NETWORKS = Object.assign( { litecoin: { @@ -66,14 +68,11 @@ describe('address', () => { }); describe('fromOutputScript', () => { + initEccLib(ecc); fixtures.standard.forEach(f => { it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => { const script = bscript.fromASM(f.script); - const address = baddress.fromOutputScript( - script, - NETWORKS[f.network], - ecc, - ); + const address = baddress.fromOutputScript(script, NETWORKS[f.network]); assert.strictEqual(address, f.base58check || f.bech32!.toLowerCase()); }); @@ -84,7 +83,7 @@ describe('address', () => { const script = bscript.fromASM(f.script); assert.throws(() => { - baddress.fromOutputScript(script, undefined, ecc); + baddress.fromOutputScript(script, undefined); }, new RegExp(f.exception)); }); }); @@ -136,7 +135,6 @@ describe('address', () => { const script = baddress.toOutputScript( (f.base58check || f.bech32)!, NETWORKS[f.network], - ecc, ); assert.strictEqual(bscript.toASM(script), f.script); @@ -147,7 +145,7 @@ describe('address', () => { it('throws when ' + (f.exception || f.paymentException), () => { const exception = f.paymentException || `${f.address} ${f.exception}`; assert.throws(() => { - baddress.toOutputScript(f.address, f.network as any, ecc); + baddress.toOutputScript(f.address, f.network as any); }, new RegExp(exception)); }); }); diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 05d7d154d..af1291b37 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -9,6 +9,7 @@ import { buildTapscriptFinalizer, toXOnly } from '../psbt.utils'; const rng = require('randombytes'); const regtest = regtestUtils.network; +bitcoin.initEccLib(ecc); const bip32 = BIP32Factory(ecc); const ECPair = ECPairFactory(ecc); @@ -17,7 +18,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { const myKey = bip32.fromSeed(rng(64), regtest); const output = createKeySpendOutput(myKey.publicKey); - const address = bitcoin.address.fromOutputScript(output, regtest, ecc); + const address = bitcoin.address.fromOutputScript(output, regtest); // amount from faucet const amount = 42e4; // amount to send @@ -51,10 +52,10 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { it('can create (and broadcast via 3PBP) a taproot key-path spend Transaction', async () => { const internalKey = bip32.fromSeed(rng(64), regtest); - const { output, address } = bitcoin.payments.p2tr( - { internalPubkey: toXOnly(internalKey.publicKey), network: regtest }, - { eccLib: ecc }, - ); + const { output, address } = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(internalKey.publicKey), + network: regtest, + }); // amount from faucet const amount = 42e4; @@ -63,7 +64,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { // get faucet const unspent = await regtestUtils.faucetComplex(output!, amount); - const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + const psbt = new bitcoin.Psbt({ network: regtest }); psbt.addInput({ hash: unspent.txId, index: 0, @@ -102,14 +103,11 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { output: leafScript, }; - const { output, address, hash } = bitcoin.payments.p2tr( - { - internalPubkey: toXOnly(internalKey.publicKey), - scriptTree, - network: regtest, - }, - { eccLib: ecc }, - ); + const { output, address, hash } = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + network: regtest, + }); // amount from faucet const amount = 42e4; @@ -118,7 +116,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { // get faucet const unspent = await regtestUtils.faucetComplex(output!, amount); - const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + const psbt = new bitcoin.Psbt({ network: regtest }); psbt.addInput({ hash: unspent.txId, index: 0, @@ -206,15 +204,12 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address } = bitcoin.payments.p2tr( - { - internalPubkey: toXOnly(internalKey.publicKey), - scriptTree, - redeem, - network: regtest, - }, - { eccLib: ecc }, - ); + const { output, address } = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + redeem, + network: regtest, + }); // amount from faucet const amount = 42e4; @@ -223,7 +218,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { // get faucet const unspent = await regtestUtils.faucetComplex(output!, amount); - const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + const psbt = new bitcoin.Psbt({ network: regtest }); psbt.addInput({ hash: unspent.txId, index: 0, @@ -283,15 +278,12 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address } = bitcoin.payments.p2tr( - { - internalPubkey: toXOnly(internalKey.publicKey), - scriptTree, - redeem, - network: regtest, - }, - { eccLib: ecc }, - ); + const { output, address } = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + redeem, + network: regtest, + }); // amount from faucet const amount = 42e4; @@ -300,7 +292,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { // get faucet const unspent = await regtestUtils.faucetComplex(output!, amount); - const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + const psbt = new bitcoin.Psbt({ network: regtest }); psbt.addInput({ hash: unspent.txId, index: 0, @@ -382,15 +374,12 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address } = bitcoin.payments.p2tr( - { - internalPubkey: toXOnly(internalKey.publicKey), - scriptTree, - redeem, - network: regtest, - }, - { eccLib: ecc }, - ); + const { output, address } = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + redeem, + network: regtest, + }); // amount from faucet const amount = 42e4; @@ -399,7 +388,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { // get faucet const unspent = await regtestUtils.faucetComplex(output!, amount); - const psbt = new bitcoin.Psbt({ eccLib: ecc, network: regtest }); + const psbt = new bitcoin.Psbt({ network: regtest }); psbt.addInput({ hash: unspent.txId, index: 0, diff --git a/test/payments.spec.ts b/test/payments.spec.ts index e89834d3b..07b1442f9 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -3,14 +3,15 @@ import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; import { PaymentCreator } from '../src/payments'; import * as u from './payments.utils'; -import { TinySecp256k1Interface } from '../src/types'; +import { initEccLib } from '../src'; ['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach( p => { describe(p, () => { + beforeEach(() => { + initEccLib(p === 'p2tr' ? ecc : undefined); + }); let fn: PaymentCreator; - const eccLib: TinySecp256k1Interface | undefined = - p === 'p2tr' ? ecc : undefined; const payment = require('../src/payments/' + p); if (p === 'embed') { fn = payment.p2data; @@ -21,10 +22,9 @@ import { TinySecp256k1Interface } from '../src/types'; const fixtures = require('./fixtures/' + p); fixtures.valid.forEach((f: any) => { - const options = Object.assign({ eccLib }, f.options || {}); it(f.description + ' as expected', () => { const args = u.preform(f.arguments); - const actual = fn(args, options); + const actual = fn(args, f.options); u.equate(actual, f.expected, f.arguments); }); @@ -33,7 +33,7 @@ import { TinySecp256k1Interface } from '../src/types'; const args = u.preform(f.arguments); const actual = fn( args, - Object.assign({}, options, { + Object.assign({}, f.options, { validate: false, }), ); @@ -43,7 +43,6 @@ import { TinySecp256k1Interface } from '../src/types'; }); fixtures.invalid.forEach((f: any) => { - const options = Object.assign({ eccLib }, f.options || {}); it( 'throws ' + f.exception + @@ -52,7 +51,7 @@ import { TinySecp256k1Interface } from '../src/types'; const args = u.preform(f.arguments); assert.throws(() => { - fn(args, options); + fn(args, f.options); }, new RegExp(f.exception)); }, ); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 871142194..76ab4da49 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -5,6 +5,8 @@ import * as crypto from 'crypto'; import ECPairFactory from 'ecpair'; import { describe, it } from 'mocha'; +import { initEccLib } from '../src'; + const bip32 = BIP32Factory(ecc); const ECPair = ECPairFactory(ecc); @@ -79,6 +81,10 @@ const failedAsyncSigner = (publicKey: Buffer): SignerAsync => { // const b = (hex: string) => Buffer.from(hex, 'hex'); describe(`Psbt`, () => { + beforeEach(() => { + // provide the ECC lib only when required + initEccLib(undefined); + }); describe('BIP174 Test Vectors', () => { fixtures.bip174.invalid.forEach(f => { it(`Invalid: ${f.description}`, () => { @@ -140,8 +146,8 @@ describe(`Psbt`, () => { fixtures.bip174.signer.forEach(f => { it('Signs PSBT to the expected result', () => { - const opts = f.isTaproot ? { eccLib: ecc } : {}; - const psbt = Psbt.fromBase64(f.psbt, opts); + if (f.isTaproot) initEccLib(ecc); + const psbt = Psbt.fromBase64(f.psbt); f.keys.forEach(({ inputToSign, WIF }) => { const keyPair = ECPair.fromWIF(WIF, NETWORKS.testnet); @@ -168,8 +174,8 @@ describe(`Psbt`, () => { fixtures.bip174.finalizer.forEach(f => { it('Finalizes inputs and gives the expected PSBT', () => { - const opts = f.isTaproot ? { eccLib: ecc } : {}; - const psbt = Psbt.fromBase64(f.psbt, opts); + if (f.isTaproot) initEccLib(ecc); + const psbt = Psbt.fromBase64(f.psbt); psbt.finalizeAllInputs(); @@ -964,7 +970,8 @@ describe(`Psbt`, () => { describe('validateSignaturesOfTaprootInput', () => { const f = fixtures.validateSignaturesOfTaprootInput; it('Correctly validates a signature', () => { - const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); + initEccLib(ecc); + const psbt = Psbt.fromBase64(f.psbt); assert.strictEqual( psbt.validateSignaturesOfInput(f.index, schnorrValidator), true, @@ -972,7 +979,8 @@ describe(`Psbt`, () => { }); it('Correctly validates a signature against a pubkey', () => { - const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); + initEccLib(ecc); + const psbt = Psbt.fromBase64(f.psbt); assert.strictEqual( psbt.validateSignaturesOfInput( f.index, @@ -993,8 +1001,9 @@ describe(`Psbt`, () => { describe('finalizeTaprootInput', () => { it('Correctly finalizes a taproot script-path spend', () => { + initEccLib(ecc); const f = fixtures.finalizeTaprootScriptPathSpendInput; - const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); + const psbt = Psbt.fromBase64(f.psbt); const tapscriptFinalizer = buildTapscriptFinalizer( f.internalPublicKey as any, f.scriptTree, @@ -1005,8 +1014,9 @@ describe(`Psbt`, () => { }); it('Failes to finalize a taproot script-path spend when a finalizer is not provided', () => { + initEccLib(ecc); const f = fixtures.finalizeTaprootScriptPathSpendInput; - const psbt = Psbt.fromBase64(f.psbt, { eccLib: ecc }); + const psbt = Psbt.fromBase64(f.psbt); assert.throws(() => { psbt.finalizeInput(0); diff --git a/test/psbt.utils.ts b/test/psbt.utils.ts index 59cccb323..100aa27e7 100644 --- a/test/psbt.utils.ts +++ b/test/psbt.utils.ts @@ -1,6 +1,5 @@ import { PsbtInput } from 'bip174/src/lib/interfaces'; import * as bitcoin from './..'; -import { TinySecp256k1Interface } from '../src/types'; /** * Build finalizer function for Tapscript. @@ -20,7 +19,6 @@ const buildTapscriptFinalizer = ( _isP2SH: boolean, _isP2WSH: boolean, _isTapscript: boolean, - eccLib?: TinySecp256k1Interface, ): { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | Buffer[] | undefined; @@ -29,15 +27,12 @@ const buildTapscriptFinalizer = ( throw new Error(`Can not finalize taproot input #${inputIndex}`); try { - const tapscriptSpend = bitcoin.payments.p2tr( - { - internalPubkey: toXOnly(internalPubkey), - scriptTree, - redeem: { output: script }, - network, - }, - { eccLib }, - ); + const tapscriptSpend = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(internalPubkey), + scriptTree, + redeem: { output: script }, + network, + }); const sigs = (input.partialSig || []).map(ps => ps.signature) as Buffer[]; const finalScriptWitness = sigs.concat( tapscriptSpend.witness as Buffer[], diff --git a/ts_src/address.ts b/ts_src/address.ts index 62bcf2ef7..8004b2668 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -2,13 +2,7 @@ import { Network } from './networks'; import * as networks from './networks'; import * as payments from './payments'; import * as bscript from './script'; -import { - typeforce, - tuple, - Hash160bit, - UInt8, - TinySecp256k1Interface, -} from './types'; +import { typeforce, tuple, Hash160bit, UInt8 } from './types'; import { bech32, bech32m } from 'bech32'; import * as bs58check from 'bs58check'; export interface Base58CheckResult { @@ -119,11 +113,7 @@ export function toBech32( : bech32m.encode(prefix, words); } -export function fromOutputScript( - output: Buffer, - network?: Network, - eccLib?: TinySecp256k1Interface, -): string { +export function fromOutputScript(output: Buffer, network?: Network): string { // TODO: Network network = network || networks.bitcoin; @@ -140,8 +130,7 @@ export function fromOutputScript( return payments.p2wsh({ output, network }).address as string; } catch (e) {} try { - if (eccLib) - return payments.p2tr({ output, network }, { eccLib }).address as string; + return payments.p2tr({ output, network }).address as string; } catch (e) {} try { return _toFutureSegwitAddress(output, network); @@ -150,11 +139,7 @@ export function fromOutputScript( throw new Error(bscript.toASM(output) + ' has no matching Address'); } -export function toOutputScript( - address: string, - network?: Network, - eccLib?: TinySecp256k1Interface, -): Buffer { +export function toOutputScript(address: string, network?: Network): Buffer { network = network || networks.bitcoin; let decodeBase58: Base58CheckResult | undefined; @@ -182,9 +167,8 @@ export function toOutputScript( if (decodeBech32.data.length === 32) return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer; } else if (decodeBech32.version === 1) { - if (decodeBech32.data.length === 32 && eccLib) - return payments.p2tr({ pubkey: decodeBech32.data }, { eccLib }) - .output as Buffer; + if (decodeBech32.data.length === 32) + return payments.p2tr({ pubkey: decodeBech32.data }).output as Buffer; } else if ( decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION && decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && diff --git a/ts_src/payments/verifyecc.ts b/ts_src/ecc_lib.ts similarity index 73% rename from ts_src/payments/verifyecc.ts rename to ts_src/ecc_lib.ts index 75c2c5062..eb4c59eeb 100644 --- a/ts_src/payments/verifyecc.ts +++ b/ts_src/ecc_lib.ts @@ -1,8 +1,29 @@ -import { TinySecp256k1Interface } from '../types'; +import { TinySecp256k1Interface } from './types'; + +const _ECCLIB_CACHE: { eccLib?: TinySecp256k1Interface } = {}; + +export function initEccLib(eccLib: TinySecp256k1Interface | undefined): void { + if (!eccLib) { + // allow clearing the library + _ECCLIB_CACHE.eccLib = eccLib; + } else if (eccLib !== _ECCLIB_CACHE.eccLib) { + // new instance, verify it + verifyEcc(eccLib!); + _ECCLIB_CACHE.eccLib = eccLib; + } +} + +export function getEccLib(): TinySecp256k1Interface { + if (!_ECCLIB_CACHE.eccLib) + throw new Error( + 'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance', + ); + return _ECCLIB_CACHE.eccLib; +} const h = (hex: string): Buffer => Buffer.from(hex, 'hex'); -export function verifyEcc(ecc: TinySecp256k1Interface): void { +function verifyEcc(ecc: TinySecp256k1Interface): void { assert(typeof ecc.isXOnlyPoint === 'function'); assert( ecc.isXOnlyPoint( diff --git a/ts_src/index.ts b/ts_src/index.ts index d8b8619d1..64c4294c2 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -29,3 +29,4 @@ export { StackElement, } from './payments'; export { Input as TxInput, Output as TxOutput } from './transaction'; +export { initEccLib } from './ecc_lib'; diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 70d7614b7..ca72f72cb 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,5 +1,5 @@ import { Network } from '../networks'; -import { TinySecp256k1Interface, Taptree } from '../types'; +import { Taptree } from '../types'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; import { p2pk } from './p2pk'; @@ -37,7 +37,6 @@ export type PaymentFunction = () => Payment; export interface PaymentOpts { validate?: boolean; allowIncomplete?: boolean; - eccLib?: TinySecp256k1Interface; } export type StackElement = Buffer | number; diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 47a76a114..09a894262 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,12 +1,8 @@ import { Buffer as NBuffer } from 'buffer'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { - typeforce as typef, - isTaptree, - TinySecp256k1Interface, - TAPLEAF_VERSION_MASK, -} from '../types'; +import { typeforce as typef, isTaptree, TAPLEAF_VERSION_MASK } from '../types'; +import { getEccLib } from '../ecc_lib'; import { toHashTree, rootHashFromPath, @@ -18,7 +14,6 @@ import { import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; -import { verifyEcc } from './verifyecc'; const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; @@ -36,13 +31,6 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { opts = Object.assign({ validate: true }, opts || {}); - const _ecc = lazy.value(() => { - if (!opts!.eccLib) throw new Error('ECC Library is missing for p2tr.'); - - verifyEcc(opts!.eccLib); - return opts!.eccLib; - }); - typef( { address: typef.maybe(typef.String), @@ -149,7 +137,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.output) return a.output.slice(2); if (a.address) return _address().data; if (o.internalPubkey) { - const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc()); + const tweakedKey = tweakKey(o.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } }); @@ -175,7 +163,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }); const path = findScriptPath(hashTree, leafHash); if (!path) return; - const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); + const outputKey = tweakKey(a.internalPubkey, hashTree.hash); if (!outputKey) return; const controlBock = NBuffer.concat( [ @@ -220,14 +208,14 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.internalPubkey) { - const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc()); + const tweakedKey = tweakKey(a.internalPubkey, o.hash); if (pubkey.length > 0 && !pubkey.equals(tweakedKey!.x)) throw new TypeError('Pubkey mismatch'); else pubkey = tweakedKey!.x; } if (pubkey && pubkey.length) { - if (!_ecc().isXOnlyPoint(pubkey)) + if (!getEccLib().isXOnlyPoint(pubkey)) throw new TypeError('Invalid pubkey for p2tr'); } @@ -302,7 +290,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey)) throw new TypeError('Internal pubkey mismatch'); - if (!_ecc().isXOnlyPoint(internalPubkey)) + if (!getEccLib().isXOnlyPoint(internalPubkey)) throw new TypeError('Invalid internalPubkey for p2tr witness'); const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK; @@ -311,7 +299,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const leafHash = tapleafHash({ output: script, version: leafVersion }); const hash = rootHashFromPath(controlBlock, leafHash); - const outputKey = tweakKey(internalPubkey, hash, _ecc()); + const outputKey = tweakKey(internalPubkey, hash); if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); @@ -336,7 +324,6 @@ interface TweakedPublicKey { function tweakKey( pubKey: Buffer, h: Buffer | undefined, - eccLib: TinySecp256k1Interface, ): TweakedPublicKey | null { if (!NBuffer.isBuffer(pubKey)) return null; if (pubKey.length !== 32) return null; @@ -344,7 +331,7 @@ function tweakKey( const tweakHash = tapTweakHash(pubKey, h); - const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash); + const res = getEccLib().xOnlyPointAddTweak(pubKey, tweakHash); if (!res || res.xOnlyPubkey === null) return null; return { diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 479421636..1517acffd 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -22,7 +22,6 @@ import * as payments from './payments'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; import { tapleafHash } from './payments/taprootutils'; -import { TinySecp256k1Interface } from './types'; export interface TransactionInput { hash: string | Buffer; @@ -140,7 +139,6 @@ export class Psbt { // We will disable exporting the Psbt when unsafe sign is active. // because it is not BIP174 compliant. __UNSAFE_SIGN_NONSEGWIT: false, - __EC_LIB: opts.eccLib, }; if (this.data.inputs.length === 0) this.setVersion(2); @@ -191,11 +189,7 @@ export class Psbt { return this.__CACHE.__TX.outs.map(output => { let address; try { - address = fromOutputScript( - output.script, - this.opts.network, - this.__CACHE.__EC_LIB, - ); + address = fromOutputScript(output.script, this.opts.network); } catch (_) {} return { script: cloneBuffer(output.script), @@ -309,7 +303,7 @@ export class Psbt { const { address } = outputData as any; if (typeof address === 'string') { const { network } = this.opts; - const script = toOutputScript(address, network, this.__CACHE.__EC_LIB); + const script = toOutputScript(address, network); outputData = Object.assign(outputData, { script }); } const c = this.__CACHE; @@ -375,7 +369,6 @@ export class Psbt { isP2SH, isP2WSH, isTapscript, - this.__CACHE.__EC_LIB, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); @@ -407,13 +400,9 @@ export class Psbt { input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness), - this.__CACHE, ); const type = result.type === 'raw' ? '' : result.type + '-'; - const mainType = classifyScript( - result.meaningfulScript, - this.__CACHE.__EC_LIB, - ); + const mainType = classifyScript(result.meaningfulScript); return (type + mainType) as AllScriptType; } @@ -821,13 +810,11 @@ interface PsbtCache { __FEE?: number; __EXTRACTED_TX?: Transaction; __UNSAFE_SIGN_NONSEGWIT: boolean; - __EC_LIB?: TinySecp256k1Interface; } interface PsbtOptsOptional { network?: Network; maximumFeeRate?: number; - eccLib?: TinySecp256k1Interface; } interface PsbtOpts { @@ -1017,12 +1004,10 @@ function isFinalized(input: PsbtInput): boolean { return !!input.finalScriptSig || !!input.finalScriptWitness; } -function isPaymentFactory( - payment: any, -): (script: Buffer, eccLib?: any) => boolean { - return (script: Buffer, eccLib?: any): boolean => { +function isPaymentFactory(payment: any): (script: Buffer) => boolean { + return (script: Buffer): boolean => { try { - payment({ output: script }, { eccLib }); + payment({ output: script }); return true; } catch (err) { return false; @@ -1225,7 +1210,6 @@ type FinalScriptsFunc = ( isTapscript: boolean, // Is taproot script path? isP2SH: boolean, // Is it P2SH? isP2WSH: boolean, // Is it P2WSH? - eccLib?: TinySecp256k1Interface, // optional lib for checking taproot validity ) => { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | Buffer[] | undefined; @@ -1239,12 +1223,11 @@ function getFinalScripts( isP2SH: boolean, isP2WSH: boolean, isTapscript: boolean = false, - eccLib?: TinySecp256k1Interface, ): { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | undefined; } { - const scriptType = classifyScript(script, eccLib); + const scriptType = classifyScript(script); if (isTapscript || !canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -1379,7 +1362,6 @@ function getHashForSig( 'input', input.redeemScript, input.witnessScript, - cache, ); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { @@ -1399,7 +1381,7 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(prevout.script, cache.__EC_LIB)) { + } else if (isP2TR(prevout.script)) { const prevOuts: Output[] = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache), ); @@ -1553,7 +1535,7 @@ function getScriptFromInput( res.script = utxoScript; } - const isTaproot = utxoScript && isP2TR(utxoScript, cache.__EC_LIB); + const isTaproot = utxoScript && isP2TR(utxoScript); // Segregated Witness versions 0 or 1 if (input.witnessScript || isP2WPKH(res.script!) || isTaproot) { @@ -1816,7 +1798,6 @@ function pubkeyInInput( 'input', input.redeemScript, input.witnessScript, - cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1834,7 +1815,6 @@ function pubkeyInOutput( 'output', output.redeemScript, output.witnessScript, - cache, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1893,7 +1873,6 @@ function getMeaningfulScript( ioType: 'input' | 'output', redeemScript?: Buffer, witnessScript?: Buffer, - cache?: PsbtCache, ): { meaningfulScript: Buffer; type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'p2tr' | 'raw'; @@ -1901,7 +1880,7 @@ function getMeaningfulScript( const isP2SH = isP2SHScript(script); const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); const isP2WSH = isP2WSHScript(script); - const isP2TRScript = isP2TR(script, cache && cache.__EC_LIB); + const isP2TRScript = isP2TR(script); if (isP2SH && redeemScript === undefined) throw new Error('scriptPubkey is P2SH but redeemScript missing'); @@ -2002,15 +1981,12 @@ type ScriptType = | 'pubkey' | 'taproot' | 'nonstandard'; -function classifyScript( - script: Buffer, - eccLib?: TinySecp256k1Interface, -): ScriptType { +function classifyScript(script: Buffer): ScriptType { if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash'; if (isP2MS(script)) return 'multisig'; if (isP2PK(script)) return 'pubkey'; - if (isP2TR(script, eccLib)) return 'taproot'; + if (isP2TR(script)) return 'taproot'; return 'nonstandard'; } From 68361a11df8f22bf350ffc42c8cb19b964078fce Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Thu, 31 Mar 2022 10:03:57 -0700 Subject: [PATCH 72/73] WIP: p2tr_ns --- src/payments/index.d.ts | 3 +- src/payments/index.js | 9 +- src/payments/p2tr.js | 8 +- src/payments/p2tr_ns.d.ts | 2 + src/payments/p2tr_ns.js | 134 +++++++++++++++++++ src/payments/taprootutils.js | 2 +- src/script.d.ts | 1 + src/script.js | 9 +- src/types.d.ts | 4 +- src/types.js | 4 +- test/fixtures/p2tr.json | 56 ++++---- test/fixtures/p2tr_ns.json | 218 +++++++++++++++++++++++++++++++ test/integration/taproot.spec.ts | 31 +++-- test/payments.spec.ts | 204 +++++++++++++++-------------- ts_src/payments/index.ts | 3 +- ts_src/payments/p2tr.ts | 14 +- ts_src/payments/p2tr_ns.ts | 145 ++++++++++++++++++++ ts_src/payments/taprootutils.ts | 2 +- ts_src/script.ts | 7 + ts_src/types.ts | 8 +- 20 files changed, 699 insertions(+), 165 deletions(-) create mode 100644 src/payments/p2tr_ns.d.ts create mode 100644 src/payments/p2tr_ns.js create mode 100644 test/fixtures/p2tr_ns.json create mode 100644 ts_src/payments/p2tr_ns.ts diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 07c12cc48..c53c7443d 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -9,6 +9,7 @@ import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; import { p2tr } from './p2tr'; +import { p2tr_ns } from './p2tr_ns'; export interface Payment { name?: string; network?: Network; @@ -38,4 +39,4 @@ export interface PaymentOpts { export declare type StackElement = Buffer | number; export declare type Stack = StackElement[]; export declare type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, p2tr_ns }; diff --git a/src/payments/index.js b/src/payments/index.js index 9ce55f859..5e0de02e2 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.p2tr = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; +exports.p2tr_ns = exports.p2tr = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; const embed_1 = require('./embed'); Object.defineProperty(exports, 'embed', { enumerable: true, @@ -57,5 +57,12 @@ Object.defineProperty(exports, 'p2tr', { return p2tr_1.p2tr; }, }); +const p2tr_ns_1 = require('./p2tr_ns'); +Object.defineProperty(exports, 'p2tr_ns', { + enumerable: true, + get: function() { + return p2tr_ns_1.p2tr_ns; + }, +}); // TODO // witness commitment diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 33fa007f8..8b0819fab 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -91,7 +91,7 @@ function p2tr(a, opts) { const script = w[w.length - 2]; const leafHash = (0, taprootutils_1.tapleafHash)({ output: script, - version: leafVersion, + redeemVersion: leafVersion, }); return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); } @@ -148,7 +148,7 @@ function p2tr(a, opts) { if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) { const leafHash = (0, taprootutils_1.tapleafHash)({ output: a.redeem.output, - version: o.redeemVersion, + redeemVersion: o.redeemVersion, }); const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); if (!path) return; @@ -209,7 +209,7 @@ function p2tr(a, opts) { if (a.redeem && a.redeem.output && hashTree) { const leafHash = (0, taprootutils_1.tapleafHash)({ output: a.redeem.output, - version: o.redeemVersion, + redeemVersion: o.redeemVersion, }); if (!(0, taprootutils_1.findScriptPath)(hashTree, leafHash)) throw new TypeError('Redeem script not in tree'); @@ -268,7 +268,7 @@ function p2tr(a, opts) { const script = witness[witness.length - 2]; const leafHash = (0, taprootutils_1.tapleafHash)({ output: script, - version: leafVersion, + redeemVersion: leafVersion, }); const hash = (0, taprootutils_1.rootHashFromPath)( controlBlock, diff --git a/src/payments/p2tr_ns.d.ts b/src/payments/p2tr_ns.d.ts new file mode 100644 index 000000000..1194d1197 --- /dev/null +++ b/src/payments/p2tr_ns.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2tr_ns(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2tr_ns.js b/src/payments/p2tr_ns.js new file mode 100644 index 000000000..db0176521 --- /dev/null +++ b/src/payments/p2tr_ns.js @@ -0,0 +1,134 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2tr_ns = void 0; +const ecc_lib_1 = require('../ecc_lib'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const OPS = bscript.OPS; +const typef = require('typeforce'); +function stacksEqual(a, b) { + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); +} +// input: [signatures ...] +// output: [pubKeys[0:n-1] OP_CHECKSIGVERIFY] pubKeys[n-1] OP_CHECKSIG +function p2tr_ns(a, opts) { + if ( + !a.input && + !a.output && + !(a.pubkeys && a.pubkeys.length) && + !a.signatures + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + function isAcceptableSignature(x) { + if (Buffer.isBuffer(x)) + return ( + // empty signatures may be represented as empty buffers + (opts && opts.allowIncomplete && x.length === 0) || + bscript.isCanonicalSchnorrSignature(x) + ); + return !!(opts && opts.allowIncomplete && x === OPS.OP_0); + } + typef( + { + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + pubkeys: typef.maybe( + typef.arrayOf((0, ecc_lib_1.getEccLib)().isXOnlyPoint), + ), + signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), + input: typef.maybe(typef.Buffer), + }, + a, + ); + const network = a.network || networks_1.bitcoin; + const o = { network }; + const _chunks = lazy.value(() => { + if (!a.output) return; + return bscript.decompile(a.output); + }); + lazy.prop(o, 'output', () => { + if (!a.pubkeys) return; + return bscript.compile( + [].concat( + ...a.pubkeys.map((pk, i, pks) => [ + pk, + i === pks.length - 1 ? OPS.OP_CHECKSIG : OPS.OP_CHECKSIGVERIFY, + ]), + ), + ); + }); + lazy.prop(o, 'n', () => { + if (!o.pubkeys) return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', () => { + const chunks = _chunks(); + if (!chunks) return; + return chunks.filter((_, index) => index % 2 === 0); + }); + lazy.prop(o, 'signatures', () => { + if (!a.input) return; + return bscript.decompile(a.input).reverse(); + }); + lazy.prop(o, 'input', () => { + if (!a.signatures) return; + return bscript.compile([...a.signatures].reverse()); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + lazy.prop(o, 'name', () => { + if (!o.n) return; + return `p2tr_ns(${o.n})`; + }); + // extended validation + if (opts.validate) { + const chunks = _chunks(); + if (chunks) { + if (chunks[chunks.length - 1] !== OPS.OP_CHECKSIG) + throw new TypeError('Output ends with unexpected opcode'); + if ( + chunks + .filter((_, index) => index % 2 === 1) + .slice(0, -1) + .some(op => op !== OPS.OP_CHECKSIGVERIFY) + ) + throw new TypeError('Output contains unexpected opcode'); + if (o.n > 16 || o.n !== chunks.length / 2) + throw new TypeError('Output contains too many pubkeys'); + if (o.pubkeys.some(x => !(0, ecc_lib_1.getEccLib)().isXOnlyPoint(x))) + throw new TypeError('Output contains invalid pubkey(s)'); + if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) + throw new TypeError('Pubkeys mismatch'); + } + if (a.pubkeys && a.pubkeys.length) { + o.n = a.pubkeys.length; + } + if (a.signatures) { + if (a.signatures.length < o.n) + throw new TypeError('Not enough signatures provided'); + if (a.signatures.length > o.n) + throw new TypeError('Too many signatures provided'); + } + if (a.input) { + if (!o.signatures.every(isAcceptableSignature)) + throw new TypeError('Input has invalid signature(s)'); + if (a.signatures && !stacksEqual(a.signatures, o.signatures)) + throw new TypeError('Signature mismatch'); + if (o.n !== o.signatures.length) + throw new TypeError( + `Signature count mismatch (n: ${o.n}, signatures.length: ${ + o.signatures.length + }`, + ); + } + } + return Object.assign(o, a); +} +exports.p2tr_ns = p2tr_ns; diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 85576960b..ec67077d9 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -59,7 +59,7 @@ function findScriptPath(node, hash) { } exports.findScriptPath = findScriptPath; function tapleafHash(leaf) { - const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; + const version = leaf.redeemVersion || exports.LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( 'TapLeaf', buffer_1.Buffer.concat([ diff --git a/src/script.d.ts b/src/script.d.ts index 261ecf4af..c01a67b4f 100644 --- a/src/script.d.ts +++ b/src/script.d.ts @@ -13,5 +13,6 @@ export declare function toStack(chunks: Buffer | Array): Buffer export declare function isCanonicalPubKey(buffer: Buffer): boolean; export declare function isDefinedHashType(hashType: number): boolean; export declare function isCanonicalScriptSignature(buffer: Buffer): boolean; +export declare function isCanonicalSchnorrSignature(buffer: Buffer): boolean; export declare const number: typeof scriptNumber; export declare const signature: typeof scriptSignature; diff --git a/src/script.js b/src/script.js index 5e5e17ba1..c502bc407 100644 --- a/src/script.js +++ b/src/script.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.signature = exports.number = exports.isCanonicalScriptSignature = exports.isDefinedHashType = exports.isCanonicalPubKey = exports.toStack = exports.fromASM = exports.toASM = exports.decompile = exports.compile = exports.isPushOnly = exports.OPS = void 0; +exports.signature = exports.number = exports.isCanonicalSchnorrSignature = exports.isCanonicalScriptSignature = exports.isDefinedHashType = exports.isCanonicalPubKey = exports.toStack = exports.fromASM = exports.toASM = exports.decompile = exports.compile = exports.isPushOnly = exports.OPS = void 0; const bip66 = require('./bip66'); const ops_1 = require('./ops'); Object.defineProperty(exports, 'OPS', { @@ -177,6 +177,13 @@ function isCanonicalScriptSignature(buffer) { return bip66.check(buffer.slice(0, -1)); } exports.isCanonicalScriptSignature = isCanonicalScriptSignature; +function isCanonicalSchnorrSignature(buffer) { + if (!Buffer.isBuffer(buffer)) return false; + if (buffer.length === 64) return true; // implied SIGHASH_DEFAULT + if (buffer.length === 65 && isDefinedHashType(buffer[64])) return true; // explicit SIGHASH trailing byte + return false; +} +exports.isCanonicalSchnorrSignature = isCanonicalSchnorrSignature; // tslint:disable-next-line variable-name exports.number = scriptNumber; exports.signature = scriptSignature; diff --git a/src/types.d.ts b/src/types.d.ts index b3d93589d..2c07fbc30 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -16,7 +16,7 @@ export interface XOnlyPointAddTweakResult { } export interface Tapleaf { output: Buffer; - version?: number; + redeemVersion?: number; } export declare const TAPLEAF_VERSION_MASK = 254; export declare function isTapleaf(o: any): o is Tapleaf; @@ -25,7 +25,7 @@ export declare function isTapleaf(o: any): o is Tapleaf; * Each node is either a single Tapleaf, or a pair of Tapleaf | Taptree. * The tree has no balancing requirements. */ -export declare type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf; +export declare type Taptree = [Taptree, Taptree] | Tapleaf; export declare function isTaptree(scriptTree: any): scriptTree is Taptree; export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; diff --git a/src/types.js b/src/types.js index e1d0a528d..61f2f46ad 100644 --- a/src/types.js +++ b/src/types.js @@ -72,8 +72,8 @@ exports.TAPLEAF_VERSION_MASK = 0xfe; function isTapleaf(o) { if (!('output' in o)) return false; if (!buffer_1.Buffer.isBuffer(o.output)) return false; - if (o.version !== undefined) - return (o.version & exports.TAPLEAF_VERSION_MASK) === o.version; + if (o.redeemVersion !== undefined) + return (o.redeemVersion & exports.TAPLEAF_VERSION_MASK) === o.redeemVersion; return true; } exports.isTapleaf = isTapleaf; diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json index aaa82fbb4..deba31c2c 100644 --- a/test/fixtures/p2tr.json +++ b/test/fixtures/p2tr.json @@ -415,7 +415,7 @@ }, "scriptTree": { "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } }, "options": {}, @@ -447,7 +447,7 @@ }, "scriptTree": { "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } }, "options": {}, @@ -476,11 +476,11 @@ "scriptTree": [ { "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "424950333431", - "version": 152 + "redeemVersion": 152 } ] }, @@ -510,11 +510,11 @@ "scriptTree": [ { "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "424950333431", - "version": 152 + "redeemVersion": 152 } ] }, @@ -544,11 +544,11 @@ "scriptTree": [ { "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "546170726f6f74", - "version": 82 + "redeemVersion": 82 } ] }, @@ -578,11 +578,11 @@ "scriptTree": [ { "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "546170726f6f74", - "version": 82 + "redeemVersion": 82 } ] }, @@ -612,16 +612,16 @@ "scriptTree": [ { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, [ { "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } ] ] @@ -652,16 +652,16 @@ "scriptTree": [ { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, [ { "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } ] ] @@ -692,16 +692,16 @@ "scriptTree": [ { "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, [ { "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } ] ] @@ -731,16 +731,16 @@ "scriptTree": [ { "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, [ { "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } ] ] @@ -771,16 +771,16 @@ "scriptTree": [ { "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, [ { "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } ] ] @@ -811,16 +811,16 @@ "scriptTree": [ { "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, [ { "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 }, { "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG", - "version": 192 + "redeemVersion": 192 } ] ] diff --git a/test/fixtures/p2tr_ns.json b/test/fixtures/p2tr_ns.json new file mode 100644 index 000000000..952b80637 --- /dev/null +++ b/test/fixtures/p2tr_ns.json @@ -0,0 +1,218 @@ +{ + "valid": [ + { + "description": "p2tr_ns(1), pubkey, in (from out, sigs)", + "arguments": { + "output": "8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717 OP_CHECKSIG", + "signatures": [ + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ], + "network": "regtest" + }, + "options": {}, + "expected": { + "name": "p2tr_ns(1)", + "n": 1, + "pubkeys": ["8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717"], + "input": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + } + }, + { + "description": "p2tr_ns(1), pubkey, in (from out, sigs) incomplete", + "arguments": { + "output": "8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717 OP_CHECKSIG", + "signatures": [ + 0 + ], + "network": "regtest" + }, + "options": { "allowIncomplete": true }, + "expected": { + "name": "p2tr_ns(1)", + "n": 1, + "pubkeys": ["8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717"], + "input": "OP_0" + } + }, + { + "description": "p2tr_ns(1), out, sigs (from key, in)", + "arguments": { + "pubkeys": ["af455f4989d122e9185f8c351dbaecd13adca3eef8a9d38ef8ffed6867e342e3"], + "input": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "network": "regtest" + }, + "options": {}, + "expected": { + "name": "p2tr_ns(1)", + "n": 1, + "output": "af455f4989d122e9185f8c351dbaecd13adca3eef8a9d38ef8ffed6867e342e3 OP_CHECKSIG", + "signatures": [ + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ] + } + }, + { + "description": "p2tr_ns(2), out, in (from keys, out, sigs)", + "arguments": { + "pubkeys": [ + "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3", + "d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55e" + ], + "output": "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55e OP_CHECKSIG", + "signatures": [ + "beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead", + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ], + "network": "regtest" + }, + "options": {}, + "expected": { + "name": "p2tr_ns(2)", + "n": 2, + "outputHex": "2020040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3ad20d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55ead", + "input": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" + } + }, + { + "description": "p2tr_ns(2), out, in (from keys, sigs)", + "arguments": { + "pubkeys": [ + "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3", + "d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55e" + ], + "signatures": [ + "beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead", + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ], + "network": "regtest" + }, + "options": {}, + "expected": { + "name": "p2tr_ns(2)", + "n": 2, + "outputHex": "2020040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3ad20d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55ead", + "input": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" + } + } + ], + "invalid": [ + { + "description": "p2tr_ns(2), mismatched keys/sigs", + "arguments": { + "pubkeys": [ + "d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55e" + ], + "signatures": [ + "beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead", + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ], + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2), mismatched keys/sigs", + "arguments": { + "pubkeys": [ + "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3", + "d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55e" + ], + "signatures": [ + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ], + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2), mismatched sigs/input", + "arguments": { + "pubkeys": [ + "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3", + "d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55e" + ], + "input": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "signatures": [ + "beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead", + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ], + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2), mismatched keys/input", + "arguments": { + "pubkeys": [ + "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3", + "d806a63b6e2d83f11f22f9a11ba7a49ac451e8acf57591ec058e422eb997d55e" + ], + "input": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(1) no data", + "arguments": { + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(1), bad sig", + "arguments": { + "pubkeys": [ + "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3" + ], + "input": "deadbeef", + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2) swapped opcodes", + "arguments": { + "output": "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIG 8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717 OP_CHECKSIGVERIFY", + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2) wrong internal opcode", + "arguments": { + "output": "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIG 8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717 OP_CHECKSIG", + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2) too many pubkeys", + "arguments": { + "output": "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717 OP_CHECKSIG", + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2) pubkey mismatch", + "arguments": { + "output": "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3 OP_CHECKSIGVERIFY 8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717 OP_CHECKSIG", + "pubkeys": [ + "8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717", + "20040c8338b34cb9c06c6b1bd38095eafa8f9b72398a1084fdb67473d82dfda3" + ], + "network": "regtest" + }, + "options": {} + }, + { + "description": "p2tr_ns(2) bad pubkey", + "arguments": { + "output": "ff20040c8338b34cb9c06c6b1d38095eafa8f9b72398a1084fdb67473d8dfda3 OP_CHECKSIGVERIFY 8f5173bc367914e1574aceb3c7232a178a764fb6f14730b6b20bd36394c6c717 OP_CHECKSIG", + "network": "regtest" + }, + "options": {} + } + ] +} diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index af1291b37..66bcf37c2 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -4,7 +4,7 @@ import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; import { regtestUtils } from './_regtest'; import * as bitcoin from '../..'; -import { Taptree } from '../../src/types'; +import { Taptree, isTaptree } from '../../src/types'; import { buildTapscriptFinalizer, toXOnly } from '../psbt.utils'; const rng = require('randombytes'); @@ -145,16 +145,17 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); }); - it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIG', async () => { + it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIG(VERIFY)', async () => { const internalKey = bip32.fromSeed(rng(64), regtest); - const leafKey = bip32.fromSeed(rng(64), regtest); + const leafKeys = [ + bip32.fromSeed(rng(64), regtest), + bip32.fromSeed(rng(64), regtest), + ]; + const leafXOnlyKeys = leafKeys.map(leafKey => toXOnly(leafKey.publicKey)); - const leafScriptAsm = `${toXOnly(leafKey.publicKey).toString( - 'hex', - )} OP_CHECKSIG`; - const leafScript = bitcoin.script.fromASM(leafScriptAsm); + const redeem = bitcoin.payments.p2tr_ns({ pubkeys: leafXOnlyKeys }); - const scriptTree: Taptree = [ + const scriptTree = [ [ { output: bitcoin.script.fromASM( @@ -192,17 +193,13 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG', ), }, - { - output: leafScript, - }, + redeem, ], ], ], ]; - const redeem = { - output: leafScript, - redeemVersion: 192, - }; + + if (!isTaptree(scriptTree)) throw new Error('Invalid taptree'); const { output, address } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), @@ -227,7 +224,8 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); psbt.addOutput({ value: sendAmount, address: address! }); - psbt.signInput(0, leafKey); + psbt.signInput(0, leafKeys[1]); + psbt.signInput(0, leafKeys[0]); const tapscriptFinalizer = buildTapscriptFinalizer( internalKey.publicKey, @@ -369,6 +367,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }, ], ]; + const redeem = { output: leafScript, redeemVersion: 192, diff --git a/test/payments.spec.ts b/test/payments.spec.ts index 07b1442f9..f223b765c 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -5,120 +5,126 @@ import { PaymentCreator } from '../src/payments'; import * as u from './payments.utils'; import { initEccLib } from '../src'; -['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach( - p => { - describe(p, () => { - beforeEach(() => { - initEccLib(p === 'p2tr' ? ecc : undefined); - }); - let fn: PaymentCreator; - const payment = require('../src/payments/' + p); - if (p === 'embed') { - fn = payment.p2data; - } else { - fn = payment[p]; - } +[ + 'embed', + 'p2ms', + 'p2pk', + 'p2pkh', + 'p2sh', + 'p2wpkh', + 'p2wsh', + 'p2tr', + 'p2tr_ns', +].forEach(p => { + describe(p, () => { + beforeEach(() => { + initEccLib(p.startsWith('p2tr') ? ecc : undefined); + }); + let fn: PaymentCreator; + const payment = require('../src/payments/' + p); + if (p === 'embed') { + fn = payment.p2data; + } else { + fn = payment[p]; + } - const fixtures = require('./fixtures/' + p); + const fixtures = require('./fixtures/' + p); - fixtures.valid.forEach((f: any) => { - it(f.description + ' as expected', () => { - const args = u.preform(f.arguments); - const actual = fn(args, f.options); + fixtures.valid.forEach((f: any) => { + it(f.description + ' as expected', () => { + const args = u.preform(f.arguments); + const actual = fn(args, f.options); - u.equate(actual, f.expected, f.arguments); - }); + u.equate(actual, f.expected, f.arguments); + }); - it(f.description + ' as expected (no validation)', () => { - const args = u.preform(f.arguments); - const actual = fn( - args, - Object.assign({}, f.options, { - validate: false, - }), - ); + it(f.description + ' as expected (no validation)', () => { + const args = u.preform(f.arguments); + const actual = fn( + args, + Object.assign({}, f.options, { + validate: false, + }), + ); - u.equate(actual, f.expected, f.arguments); - }); + u.equate(actual, f.expected, f.arguments); }); + }); - fixtures.invalid.forEach((f: any) => { - it( - 'throws ' + - f.exception + - (f.description ? 'for ' + f.description : ''), - () => { - const args = u.preform(f.arguments); + fixtures.invalid.forEach((f: any) => { + it( + 'throws ' + f.exception + (f.description ? 'for ' + f.description : ''), + () => { + const args = u.preform(f.arguments); - assert.throws(() => { - fn(args, f.options); - }, new RegExp(f.exception)); - }, - ); - }); + assert.throws(() => { + fn(args, f.options); + }, new RegExp(f.exception)); + }, + ); + }); - if (p === 'p2sh') { - const p2wsh = require('../src/payments/p2wsh').p2wsh; - const p2pk = require('../src/payments/p2pk').p2pk; - it('properly assembles nested p2wsh with names', () => { - const actual = fn({ - redeem: p2wsh({ - redeem: p2pk({ - pubkey: Buffer.from( - '03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058', - 'hex', - ), - }), + if (p === 'p2sh') { + const p2wsh = require('../src/payments/p2wsh').p2wsh; + const p2pk = require('../src/payments/p2pk').p2pk; + it('properly assembles nested p2wsh with names', () => { + const actual = fn({ + redeem: p2wsh({ + redeem: p2pk({ + pubkey: Buffer.from( + '03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058', + 'hex', + ), }), - }); - assert.strictEqual( - actual.address, - '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw', - ); - assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk'); - assert.strictEqual(actual.redeem!.name, 'p2wsh-p2pk'); - assert.strictEqual(actual.redeem!.redeem!.name, 'p2pk'); + }), }); - } - - // cross-verify dynamically too - if (!fixtures.dynamic) return; - const { depends, details } = fixtures.dynamic; + assert.strictEqual( + actual.address, + '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw', + ); + assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk'); + assert.strictEqual(actual.redeem!.name, 'p2wsh-p2pk'); + assert.strictEqual(actual.redeem!.redeem!.name, 'p2pk'); + }); + } - details.forEach((f: any) => { - const detail = u.preform(f); - const disabled: any = {}; - if (f.disabled) - f.disabled.forEach((k: string) => { - disabled[k] = true; - }); + // cross-verify dynamically too + if (!fixtures.dynamic) return; + const { depends, details } = fixtures.dynamic; - for (const key in depends) { - if (key in disabled) continue; - const dependencies = depends[key]; + details.forEach((f: any) => { + const detail = u.preform(f); + const disabled: any = {}; + if (f.disabled) + f.disabled.forEach((k: string) => { + disabled[k] = true; + }); - dependencies.forEach((dependency: any) => { - if (!Array.isArray(dependency)) dependency = [dependency]; + for (const key in depends) { + if (key in disabled) continue; + const dependencies = depends[key]; - const args = {}; - dependency.forEach((d: any) => { - u.from(d, detail, args); - }); - const expected = u.from(key, detail); + dependencies.forEach((dependency: any) => { + if (!Array.isArray(dependency)) dependency = [dependency]; - it( - f.description + - ', ' + - key + - ' derives from ' + - JSON.stringify(dependency), - () => { - u.equate(fn(args), expected); - }, - ); + const args = {}; + dependency.forEach((d: any) => { + u.from(d, detail, args); }); - } - }); + const expected = u.from(key, detail); + + it( + f.description + + ', ' + + key + + ' derives from ' + + JSON.stringify(dependency), + () => { + u.equate(fn(args), expected); + }, + ); + }); + } }); - }, -); + }); +}); diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index ca72f72cb..a50912128 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -8,6 +8,7 @@ import { p2sh } from './p2sh'; import { p2wpkh } from './p2wpkh'; import { p2wsh } from './p2wsh'; import { p2tr } from './p2tr'; +import { p2tr_ns } from './p2tr_ns'; export interface Payment { name?: string; @@ -43,7 +44,7 @@ export type StackElement = Buffer | number; export type Stack = StackElement[]; export type StackFunction = () => Stack; -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, p2tr_ns }; // TODO // witness commitment diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 09a894262..646097c7d 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -101,7 +101,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const controlBlock = w[w.length - 1]; const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK; const script = w[w.length - 2]; - const leafHash = tapleafHash({ output: script, version: leafVersion }); + const leafHash = tapleafHash({ + output: script, + redeemVersion: leafVersion, + }); return rootHashFromPath(controlBlock, leafHash); } return null; @@ -159,7 +162,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) { const leafHash = tapleafHash({ output: a.redeem.output, - version: o.redeemVersion, + redeemVersion: o.redeemVersion, }); const path = findScriptPath(hashTree, leafHash); if (!path) return; @@ -228,7 +231,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if (a.redeem && a.redeem.output && hashTree) { const leafHash = tapleafHash({ output: a.redeem.output, - version: o.redeemVersion, + redeemVersion: o.redeemVersion, }); if (!findScriptPath(hashTree, leafHash)) throw new TypeError('Redeem script not in tree'); @@ -296,7 +299,10 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK; const script = witness[witness.length - 2]; - const leafHash = tapleafHash({ output: script, version: leafVersion }); + const leafHash = tapleafHash({ + output: script, + redeemVersion: leafVersion, + }); const hash = rootHashFromPath(controlBlock, leafHash); const outputKey = tweakKey(internalPubkey, hash); diff --git a/ts_src/payments/p2tr_ns.ts b/ts_src/payments/p2tr_ns.ts new file mode 100644 index 000000000..d2f6221ad --- /dev/null +++ b/ts_src/payments/p2tr_ns.ts @@ -0,0 +1,145 @@ +import { getEccLib } from '../ecc_lib'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { Payment, PaymentOpts, Stack } from './index'; +import * as lazy from './lazy'; +const OPS = bscript.OPS; +const typef = require('typeforce'); + +function stacksEqual(a: Buffer[], b: Buffer[]): boolean { + if (a.length !== b.length) return false; + + return a.every((x, i) => { + return x.equals(b[i]); + }); +} + +// input: [signatures ...] +// output: [pubKeys[0:n-1] OP_CHECKSIGVERIFY] pubKeys[n-1] OP_CHECKSIG +export function p2tr_ns(a: Payment, opts?: PaymentOpts): Payment { + if ( + !a.input && + !a.output && + !(a.pubkeys && a.pubkeys.length) && + !a.signatures + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + function isAcceptableSignature(x: Buffer | number): boolean { + if (Buffer.isBuffer(x)) + return ( + // empty signatures may be represented as empty buffers + (opts && opts.allowIncomplete && x.length === 0) || + bscript.isCanonicalSchnorrSignature(x) + ); + return !!(opts && opts.allowIncomplete && x === OPS.OP_0); + } + + typef( + { + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + pubkeys: typef.maybe(typef.arrayOf(getEccLib().isXOnlyPoint)), + + signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), + input: typef.maybe(typef.Buffer), + }, + a, + ); + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { network }; + + const _chunks = lazy.value(() => { + if (!a.output) return; + return bscript.decompile(a.output) as Stack; + }); + + lazy.prop(o, 'output', () => { + if (!a.pubkeys) return; + return bscript.compile( + ([] as Stack).concat( + ...a.pubkeys.map((pk, i, pks) => [ + pk, + i === pks.length - 1 ? OPS.OP_CHECKSIG : OPS.OP_CHECKSIGVERIFY, + ]), + ), + ); + }); + lazy.prop(o, 'n', () => { + if (!o.pubkeys) return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', () => { + const chunks = _chunks(); + if (!chunks) return; + return chunks.filter((_, index) => index % 2 === 0) as Buffer[]; + }); + lazy.prop(o, 'signatures', () => { + if (!a.input) return; + return bscript.decompile(a.input)!.reverse(); + }); + lazy.prop(o, 'input', () => { + if (!a.signatures) return; + return bscript.compile([...a.signatures].reverse()); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + lazy.prop(o, 'name', () => { + if (!o.n) return; + return `p2tr_ns(${o.n})`; + }); + + // extended validation + if (opts.validate) { + const chunks = _chunks(); + if (chunks) { + if (chunks[chunks.length - 1] !== OPS.OP_CHECKSIG) + throw new TypeError('Output ends with unexpected opcode'); + if ( + chunks + .filter((_, index) => index % 2 === 1) + .slice(0, -1) + .some(op => op !== OPS.OP_CHECKSIGVERIFY) + ) + throw new TypeError('Output contains unexpected opcode'); + if (o.n! > 16 || o.n !== chunks.length / 2) + throw new TypeError('Output contains too many pubkeys'); + if (o.pubkeys!.some(x => !getEccLib().isXOnlyPoint(x))) + throw new TypeError('Output contains invalid pubkey(s)'); + + if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys!)) + throw new TypeError('Pubkeys mismatch'); + } + + if (a.pubkeys && a.pubkeys.length) { + o.n = a.pubkeys.length; + } + + if (a.signatures) { + if (a.signatures.length < o.n!) + throw new TypeError('Not enough signatures provided'); + if (a.signatures.length > o.n!) + throw new TypeError('Too many signatures provided'); + } + + if (a.input) { + if (!o.signatures!.every(isAcceptableSignature)) + throw new TypeError('Input has invalid signature(s)'); + + if (a.signatures && !stacksEqual(a.signatures, o.signatures!)) + throw new TypeError('Signature mismatch'); + if (o.n !== o.signatures!.length) + throw new TypeError( + `Signature count mismatch (n: ${o.n}, signatures.length: ${ + o.signatures!.length + }`, + ); + } + } + + return Object.assign(o, a); +} diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 97cc1f6d8..eb08d5839 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -90,7 +90,7 @@ export function findScriptPath( } export function tapleafHash(leaf: Tapleaf): Buffer { - const version = leaf.version || LEAF_VERSION_TAPSCRIPT; + const version = leaf.redeemVersion || LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( 'TapLeaf', NBuffer.concat([NBuffer.from([version]), serializeScript(leaf.output)]), diff --git a/ts_src/script.ts b/ts_src/script.ts index 5d20ebc01..a10c629af 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -208,6 +208,13 @@ export function isCanonicalScriptSignature(buffer: Buffer): boolean { return bip66.check(buffer.slice(0, -1)); } +export function isCanonicalSchnorrSignature(buffer: Buffer): boolean { + if (!Buffer.isBuffer(buffer)) return false; + if (buffer.length === 64) return true; // implied SIGHASH_DEFAULT + if (buffer.length === 65 && isDefinedHashType(buffer[64])) return true; // explicit SIGHASH trailing byte + return false; +} + // tslint:disable-next-line variable-name export const number = scriptNumber; export const signature = scriptSignature; diff --git a/ts_src/types.ts b/ts_src/types.ts index 536646e86..073395b17 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -74,15 +74,15 @@ export interface XOnlyPointAddTweakResult { export interface Tapleaf { output: Buffer; - version?: number; + redeemVersion?: number; } export const TAPLEAF_VERSION_MASK = 0xfe; export function isTapleaf(o: any): o is Tapleaf { if (!('output' in o)) return false; if (!NBuffer.isBuffer(o.output)) return false; - if (o.version !== undefined) - return (o.version & TAPLEAF_VERSION_MASK) === o.version; + if (o.redeemVersion !== undefined) + return (o.redeemVersion & TAPLEAF_VERSION_MASK) === o.redeemVersion; return true; } @@ -91,7 +91,7 @@ export function isTapleaf(o: any): o is Tapleaf { * Each node is either a single Tapleaf, or a pair of Tapleaf | Taptree. * The tree has no balancing requirements. */ -export type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf; +export type Taptree = [Taptree, Taptree] | Tapleaf; export function isTaptree(scriptTree: any): scriptTree is Taptree { if (!Array(scriptTree)) return isTapleaf(scriptTree); From d8ddfaeaf0d66271aa31f1445901a1c439e6ba19 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Thu, 31 Mar 2022 15:58:53 -0700 Subject: [PATCH 73/73] Have scriptSigFinalizer order sigs properly --- test/integration/taproot.spec.ts | 6 +++--- test/psbt.utils.ts | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 66bcf37c2..01c785e80 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -224,8 +224,8 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); psbt.addOutput({ value: sendAmount, address: address! }); - psbt.signInput(0, leafKeys[1]); psbt.signInput(0, leafKeys[0]); + psbt.signInput(0, leafKeys[1]); const tapscriptFinalizer = buildTapscriptFinalizer( internalKey.publicKey, @@ -344,9 +344,9 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { leafPubkeys.push(toXOnly(leafKey.publicKey).toString('hex')); } - const leafScriptAsm = `${leafPubkeys[2]} OP_CHECKSIG ${ + const leafScriptAsm = `${leafPubkeys[0]} OP_CHECKSIG ${ leafPubkeys[1] - } OP_CHECKSIGADD ${leafPubkeys[0]} OP_CHECKSIGADD OP_3 OP_NUMEQUAL`; + } OP_CHECKSIGADD ${leafPubkeys[2]} OP_CHECKSIGADD OP_3 OP_NUMEQUAL`; const leafScript = bitcoin.script.fromASM(leafScriptAsm); diff --git a/test/psbt.utils.ts b/test/psbt.utils.ts index 100aa27e7..b1aabb330 100644 --- a/test/psbt.utils.ts +++ b/test/psbt.utils.ts @@ -33,7 +33,20 @@ const buildTapscriptFinalizer = ( redeem: { output: script }, network, }); - const sigs = (input.partialSig || []).map(ps => ps.signature) as Buffer[]; + const stack = bitcoin.script.decompile(script); + if (!stack) throw new Error('Invalid script'); + const pushes = stack.filter(chunk => Buffer.isBuffer(chunk)) as Buffer[]; + const pkIdx = (pk: Buffer) => pushes.findIndex(chunk => pk.equals(chunk)); + let sigs: Buffer[] = []; + if (input.partialSig) { + const pkIdxSigs = input.partialSig.map(ps => ({ + idx: pkIdx(ps.pubkey), + sig: ps.signature, + })); + // Sigs need to be reverse script order + pkIdxSigs.sort((a, b) => a.idx - b.idx); + sigs = pkIdxSigs.map(({ sig }) => sig); + } const finalScriptWitness = sigs.concat( tapscriptSpend.witness as Buffer[], );