From 191b9e857341521507b35e21f748bf148ebb5301 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 17 Nov 2021 16:01:08 +0900 Subject: [PATCH 001/249] Add taproot test with new CJS compatible tiny-secp256k1 --- package-lock.json | 46 ++++++---- package.json | 3 +- test/integration/bip32.spec.ts | 5 +- test/integration/taproot.spec.ts | 122 ++++++++++++++++++++++++++ test/integration/transactions.spec.ts | 4 +- test/psbt.spec.ts | 5 +- 6 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 test/integration/taproot.spec.ts diff --git a/package-lock.json b/package-lock.json index c66d2add2..d6b9ce892 100644 --- a/package-lock.json +++ b/package-lock.json @@ -556,16 +556,15 @@ "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ==" }, "bip32": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", - "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz", + "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", "dev": true, "requires": { "@types/node": "10.12.18", "bs58check": "^2.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "tiny-secp256k1": "^1.1.3", "typeforce": "^1.11.5", "wif": "^2.0.6" }, @@ -941,6 +940,21 @@ "tiny-secp256k1": "^1.1.6", "typeforce": "^1.11.3", "wif": "^2.0.1" + }, + "dependencies": { + "tiny-secp256k1": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "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" + } + } } }, "elliptic": { @@ -1707,9 +1721,9 @@ "dev": true }, "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "dev": true }, "node-environment-flags": { @@ -2465,16 +2479,12 @@ } }, "tiny-secp256k1": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.1.tgz", + "integrity": "sha512-pdENPcbI4l3Br6sPVuC5RWONHojcPjBiXljIBvQ5UIN/MD6wPzmJ8mpDnkps3O7FFfT+fLqGXo2MdFdRQaPWUg==", "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" + "uint8array-tools": "0.0.6" } }, "to-fast-properties": { @@ -2583,6 +2593,12 @@ "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true }, + "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 + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", diff --git a/package.json b/package.json index e420511c8..36d90035e 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@types/proxyquire": "^1.3.28", "@types/randombytes": "^2.0.0", "@types/wif": "^2.0.2", - "bip32": "^2.0.6", + "bip32": "^3.0.1", "bip39": "^3.0.2", "bip65": "^1.0.1", "bip68": "^1.0.3", @@ -84,6 +84,7 @@ "randombytes": "^2.1.0", "regtest-client": "0.2.0", "rimraf": "^2.6.3", + "tiny-secp256k1": "^2.1.1", "ts-node": "^8.3.0", "tslint": "^6.1.3", "typescript": "^4.4.4" diff --git a/test/integration/bip32.spec.ts b/test/integration/bip32.spec.ts index 7cd9e2f55..938c28113 100644 --- a/test/integration/bip32.spec.ts +++ b/test/integration/bip32.spec.ts @@ -1,9 +1,12 @@ import * as assert from 'assert'; -import * as bip32 from 'bip32'; +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; import * as bip39 from 'bip39'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; +const bip32 = BIP32Factory(ecc); + function getAddress(node: any, network?: any): string { return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!; } diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts new file mode 100644 index 000000000..f7b3733fa --- /dev/null +++ b/test/integration/taproot.spec.ts @@ -0,0 +1,122 @@ +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; +import { describe, it } from 'mocha'; +import * as bitcoin from '../..'; +import { regtestUtils } from './_regtest'; +const rng = require('randombytes'); +const regtest = regtestUtils.network; +const bip32 = BIP32Factory(ecc); + +describe('bitcoinjs-lib (transaction with taproot)', () => { + it('can create (and broadcast via 3PBP) a taproot keyspend Transaction', async () => { + const myKey = bip32.fromSeed(rng(64), regtest); + + const output = createKeySpendOutput(myKey.publicKey); + const address = bitcoin.address.fromOutputScript(output, regtest); + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output, amount); + + const tx = createSigned( + myKey, + unspent.txId, + unspent.vout, + sendAmount, + [output], + [amount], + ); + + const hex = tx.toHex(); + // console.log('Valid tx sent from:'); + // console.log(address); + // console.log('tx hex:'); + // console.log(hex); + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address, + vout: 0, + value: sendAmount, + }); + }); +}); + +// Order of the curve (N) - 1 +const N_LESS_1 = Buffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', + 'hex', +); +// 1 represented as 32 bytes BE +const ONE = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex', +); + +// Function for creating a tweaked p2tr key-spend only address +// (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 commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey); + const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash); + if (tweakResult === null) throw new Error('Invalid Tweak'); + const { xOnlyPubkey: tweaked } = tweakResult; + // scriptPubkey + return Buffer.concat([ + // witness v1, PUSH_DATA 32 bytes + Buffer.from([0x51, 0x20]), + // x-only tweaked pubkey + tweaked, + ]); +} + +// Function for signing for a tweaked p2tr key-spend only address +// (Required for the above address) +interface KeyPair { + publicKey: Buffer; + privateKey?: Buffer; +} +function signTweaked(messageHash: Buffer, key: KeyPair): Uint8Array { + const privateKey = + key.publicKey[0] === 2 + ? key.privateKey + : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey!)!, ONE)!; + const tweakHash = bitcoin.crypto.taggedHash( + 'TapTweak', + key.publicKey.slice(1, 33), + ); + const newPrivateKey = ecc.privateAdd(privateKey!, tweakHash); + if (newPrivateKey === null) throw new Error('Invalid Tweak'); + return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32)); +} + +// Function for creating signed tx +function createSigned( + key: KeyPair, + txid: string, + vout: number, + amountToSend: number, + scriptPubkeys: Buffer[], + values: number[], +): bitcoin.Transaction { + const tx = new bitcoin.Transaction(); + tx.version = 2; + // Add input + tx.addInput(Buffer.from(txid, 'hex').reverse(), vout); + // Add output + tx.addOutput(scriptPubkeys[0], amountToSend); + const sighash = tx.hashForWitnessV1( + 0, // which input + scriptPubkeys, // All previous outputs of all inputs + values, // All previous values of all inputs + bitcoin.Transaction.SIGHASH_DEFAULT, // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL) + ); + const signature = Buffer.from(signTweaked(sighash, key)); + // witness stack for keypath spend is just the signature. + // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value + tx.ins[0].witness = [signature]; + return tx; +} diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index d4788dcb7..51e44a0ef 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -1,11 +1,13 @@ import * as assert from 'assert'; -import * as bip32 from 'bip32'; +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; const rng = require('randombytes'); const regtest = regtestUtils.network; +const bip32 = BIP32Factory(ecc); const validator = ( pubkey: Buffer, diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 05d446878..32d81ba16 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -1,9 +1,12 @@ import * as assert from 'assert'; -import * as bip32 from 'bip32'; +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; import * as crypto from 'crypto'; import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; +const bip32 = BIP32Factory(ecc); + import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; import * as preFixtures from './fixtures/psbt.json'; From 4674433bb9333cc50858f44aa92b64fb643ed386 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 17 Nov 2021 16:06:45 +0900 Subject: [PATCH 002/249] Update container --- .github/workflows/main_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index a9fdc58be..7bc62cba9 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest services: regtest: - image: junderw/bitcoinjs-regtest-server@sha256:a46ec1a651ca5b1a5408f2b2526ea5f435421dd2bc2f28fae3bc33e1fd614552 + image: junderw/bitcoinjs-regtest-server@sha256:5b69cf95d9edf6d5b3a00504665d6b3c382a6aa3728fe8ce897974c519061463 ports: - 8080:8080 steps: From 93af5afe67dbc491e36bdfc8d48a00179093f7d1 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 27 Nov 2021 08:35:19 +0900 Subject: [PATCH 003/249] Add warning to future segwit version address generation/parsing --- src/address.js | 10 +++++++++- ts_src/address.ts | 12 +++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/address.js b/src/address.js index 12938fc67..164bf7ef1 100644 --- a/src/address.js +++ b/src/address.js @@ -13,6 +13,11 @@ const FUTURE_SEGWIT_MIN_SIZE = 2; const FUTURE_SEGWIT_MAX_VERSION = 16; const FUTURE_SEGWIT_MIN_VERSION = 1; 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. ' + + 'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' + + 'with caution. Wallets should verify the segwit version from the output of fromBech32, ' + + 'then decide when it is safe to use which version of segwit.'; function _toFutureSegwitAddress(output, network) { const data = output.slice(2); if ( @@ -28,6 +33,7 @@ function _toFutureSegwitAddress(output, network) { throw new TypeError('Invalid version for segwit address'); if (output[1] !== data.length) throw new TypeError('Invalid script for segwit address'); + console.warn(FUTURE_SEGWIT_VERSION_WARNING); return toBech32(data, version, network.bech32); } function fromBase58Check(address) { @@ -128,11 +134,13 @@ function toOutputScript(address, network) { decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE && decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE - ) + ) { + console.warn(FUTURE_SEGWIT_VERSION_WARNING); return bscript.compile([ decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF, decodeBech32.data, ]); + } } } throw new Error(address + ' has no matching Script'); diff --git a/ts_src/address.ts b/ts_src/address.ts index d8111a7e4..753589d46 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -23,6 +23,11 @@ const FUTURE_SEGWIT_MIN_SIZE: number = 2; const FUTURE_SEGWIT_MAX_VERSION: number = 16; const FUTURE_SEGWIT_MIN_VERSION: number = 1; 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. ' + + 'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' + + 'with caution. Wallets should verify the segwit version from the output of fromBech32, ' + + 'then decide when it is safe to use which version of segwit.'; function _toFutureSegwitAddress(output: Buffer, network: Network): string { const data = output.slice(2); @@ -44,6 +49,8 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string { if (output[1] !== data.length) throw new TypeError('Invalid script for segwit address'); + console.warn(FUTURE_SEGWIT_VERSION_WARNING); + return toBech32(data, version, network.bech32); } @@ -163,11 +170,14 @@ export function toOutputScript(address: string, network?: Network): Buffer { decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE && decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE - ) + ) { + console.warn(FUTURE_SEGWIT_VERSION_WARNING); + return bscript.compile([ decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF, decodeBech32.data, ]); + } } } From 11202eb74cc9d9a7338ef4aa04c78061f0544289 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 27 Nov 2021 08:42:12 +0900 Subject: [PATCH 004/249] 6.0.1 --- 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 d6b9ce892..2ea42ae58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.0", + "version": "6.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 36d90035e..dc93c53fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.0", + "version": "6.0.1", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From 2edfb992fa09761e09490b5efa27ba13a77de374 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 17 Dec 2021 13:27:35 +0200 Subject: [PATCH 005/249] test: upgrade ecpair lib to version 2.0.1 --- package-lock.json | 123 ++------------------------ package.json | 4 +- test/integration/addresses.spec.ts | 5 +- test/integration/cltv.spec.ts | 5 +- test/integration/csv.spec.ts | 5 +- test/integration/payments.spec.ts | 5 +- test/integration/transactions.spec.ts | 4 +- test/psbt.spec.ts | 3 +- 8 files changed, 32 insertions(+), 122 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ea42ae58..3e5bd51b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -541,15 +541,6 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bip174": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz", @@ -640,12 +631,6 @@ "fill-range": "^7.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -931,59 +916,14 @@ "dev": true }, "ecpair": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-1.0.0.tgz", - "integrity": "sha512-1L+P/ivLC3eKHgqcX1M9tFYQWXDoqwJ3zQnN7zDaTtLpiCQKpFTaAZvnsPC5PkWB4q3EPFAHffCLvjfCqRjuwQ==", - "dev": true, - "requires": { - "randombytes": "^2.0.1", - "tiny-secp256k1": "^1.1.6", - "typeforce": "^1.11.3", - "wif": "^2.0.1" - }, - "dependencies": { - "tiny-secp256k1": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "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" - } - } - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", + "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", "dev": true, "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "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 - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - } + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" } }, "emoji-regex": { @@ -1040,12 +980,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true - }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -1218,16 +1152,6 @@ "safe-buffer": "^5.0.1" } }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -1244,17 +1168,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "hoodwink": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz", @@ -1640,18 +1553,6 @@ "pushdata-bitcoin": "^1.0.1" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1720,12 +1621,6 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true - }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -2479,9 +2374,9 @@ } }, "tiny-secp256k1": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.1.tgz", - "integrity": "sha512-pdENPcbI4l3Br6sPVuC5RWONHojcPjBiXljIBvQ5UIN/MD6wPzmJ8mpDnkps3O7FFfT+fLqGXo2MdFdRQaPWUg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.2.tgz", + "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==", "dev": true, "requires": { "uint8array-tools": "0.0.6" diff --git a/package.json b/package.json index dc93c53fa..c5553d57c 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "bn.js": "^4.11.8", "bs58": "^4.0.0", "dhttp": "^3.0.0", - "ecpair": "^1.0.0", + "ecpair": "^2.0.1", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", "mocha": "^7.1.1", @@ -84,7 +84,7 @@ "randombytes": "^2.1.0", "regtest-client": "0.2.0", "rimraf": "^2.6.3", - "tiny-secp256k1": "^2.1.1", + "tiny-secp256k1": "^2.1.2", "ts-node": "^8.3.0", "tslint": "^6.1.3", "typescript": "^4.4.4" diff --git a/test/integration/addresses.spec.ts b/test/integration/addresses.spec.ts index 2b24ef5a0..d6e758b9d 100644 --- a/test/integration/addresses.spec.ts +++ b/test/integration/addresses.spec.ts @@ -1,8 +1,11 @@ import * as assert from 'assert'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const dhttp = regtestUtils.dhttp; const TESTNET = bitcoin.networks.testnet; diff --git a/test/integration/cltv.spec.ts b/test/integration/cltv.spec.ts index c1a52de76..04944ebf9 100644 --- a/test/integration/cltv.spec.ts +++ b/test/integration/cltv.spec.ts @@ -1,8 +1,11 @@ import * as assert from 'assert'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { before, describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const regtest = regtestUtils.network; const bip65 = require('bip65'); diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 9993d5c30..742d68fa1 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -1,9 +1,12 @@ import * as assert from 'assert'; import { PsbtInput } from 'bip174/src/lib/interfaces'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { before, describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const regtest = regtestUtils.network; const bip68 = require('bip68'); const varuint = require('varuint-bitcoin'); diff --git a/test/integration/payments.spec.ts b/test/integration/payments.spec.ts index ea7294ec2..d9d7fde78 100644 --- a/test/integration/payments.spec.ts +++ b/test/integration/payments.spec.ts @@ -1,7 +1,10 @@ -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const NETWORK = regtestUtils.network; const keyPairs = [ ECPair.makeRandom({ network: NETWORK }), diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index 51e44a0ef..ba7f2fe8b 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -1,10 +1,12 @@ import * as assert from 'assert'; import BIP32Factory from 'bip32'; import * as ecc from 'tiny-secp256k1'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const rng = require('randombytes'); const regtest = regtestUtils.network; const bip32 = BIP32Factory(ecc); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 32d81ba16..f583e8068 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -2,10 +2,11 @@ import * as assert from 'assert'; import BIP32Factory from 'bip32'; import * as ecc from 'tiny-secp256k1'; import * as crypto from 'crypto'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; import { describe, it } from 'mocha'; const bip32 = BIP32Factory(ecc); +const ECPair = ECPairFactory(ecc); import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; From e32ac133624940785c4cf12795443b9bd13829e3 Mon Sep 17 00:00:00 2001 From: Shubham malik Date: Wed, 9 Mar 2022 04:02:44 +0530 Subject: [PATCH 006/249] Update main_ci.yml --- .github/workflows/main_ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index 7bc62cba9..fc571a3ed 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -6,6 +6,9 @@ on: - master pull_request: +permissions: + contents: read + jobs: audit: runs-on: ubuntu-latest From ee5b0a641351f08c76cca9bbc4de568b0ef726c2 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 9 Mar 2022 07:57:42 +0900 Subject: [PATCH 007/249] Pin actions for CI --- .github/workflows/main_ci.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index fc571a3ed..737f39316 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -13,8 +13,8 @@ jobs: audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ @@ -23,8 +23,8 @@ jobs: unit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ @@ -33,8 +33,8 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ @@ -48,8 +48,8 @@ jobs: ports: - 8080:8080 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ @@ -58,8 +58,8 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ @@ -68,8 +68,8 @@ jobs: gitdiff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ @@ -78,8 +78,8 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ @@ -88,8 +88,8 @@ jobs: lint-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 with: node-version: 12 registry-url: https://registry.npmjs.org/ From e9a26ce20f2672fd40c205efca05a43d944eca47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Apr 2022 06:12:55 +0000 Subject: [PATCH 008/249] Bump minimist from 1.2.5 to 1.2.6 Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e5bd51b5..76680b44f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1563,9 +1563,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mkdirp": { From cafa6bd2245d297f1cffa05638f0909c2708264f Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 16 May 2022 08:41:15 +0900 Subject: [PATCH 009/249] Fix: Interpret P2SH redeemscript === OP_FALSE as empty Buffer. Fixes #1801 --- src/payments/p2sh.js | 3 ++- test/fixtures/p2sh.json | 8 +++++++- ts_src/payments/p2sh.ts | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 8710bf19d..48edf304f 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -58,9 +58,10 @@ function p2sh(a, opts) { }); const _redeem = lazy.value(() => { const chunks = _chunks(); + const lastChunk = chunks[chunks.length - 1]; return { network, - output: chunks[chunks.length - 1], + output: lastChunk === OPS.OP_FALSE ? Buffer.from([]) : lastChunk, input: bscript.compile(chunks.slice(0, -1)), witness: a.witness || [], }; diff --git a/test/fixtures/p2sh.json b/test/fixtures/p2sh.json index 8a1f9f68e..b222de52b 100644 --- a/test/fixtures/p2sh.json +++ b/test/fixtures/p2sh.json @@ -289,11 +289,17 @@ } }, { - "exception": "Input is invalid", + "exception": "Redeem.output too short", "arguments": { "input": "OP_0 OP_0" } }, + { + "exception": "Input is invalid", + "arguments": { + "input": "OP_0 OP_3" + } + }, { "exception": "Redeem.input mismatch", "arguments": { diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index 9be5a8c8a..0cea3632c 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -68,9 +68,11 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { const _redeem = lazy.value( (): Payment => { const chunks = _chunks(); + const lastChunk = chunks[chunks.length - 1]; return { network, - output: chunks[chunks.length - 1] as Buffer, + output: + lastChunk === OPS.OP_FALSE ? Buffer.from([]) : (lastChunk as Buffer), input: bscript.compile(chunks.slice(0, -1)), witness: a.witness || [], }; From f9ded32da5261a54d0f8e4ddec4c4682179b812d Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 11 Jun 2022 11:04:02 +0900 Subject: [PATCH 010/249] Fallback to JS with ripemd160 --- package-lock.json | 13 +++++++++++-- package.json | 2 ++ src/crypto.js | 11 ++++++++--- ts_src/crypto.ts | 11 ++++++++--- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76680b44f..d04ae5d28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -436,6 +436,15 @@ "@types/node": "*" } }, + "@types/ripemd160": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", + "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/wif": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", @@ -498,7 +507,7 @@ "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, "arg": { @@ -801,7 +810,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, "concat-map": { diff --git a/package.json b/package.json index c5553d57c..a415a5cd2 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "bip174": "^2.0.1", "bs58check": "^2.1.2", "create-hash": "^1.1.0", + "ripemd160": "^2.0.2", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" @@ -65,6 +66,7 @@ "@types/node": "^16.11.7", "@types/proxyquire": "^1.3.28", "@types/randombytes": "^2.0.0", + "@types/ripemd160": "^2.0.0", "@types/wif": "^2.0.2", "bip32": "^3.0.1", "bip39": "^3.0.2", diff --git a/src/crypto.js b/src/crypto.js index 3c308da11..a3df16764 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -2,15 +2,20 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0; const createHash = require('create-hash'); +const RipeMd160 = require('ripemd160'); function ripemd160(buffer) { try { return createHash('rmd160') .update(buffer) .digest(); } catch (err) { - return createHash('ripemd160') - .update(buffer) - .digest(); + try { + return createHash('ripemd160') + .update(buffer) + .digest(); + } catch (err2) { + return new RipeMd160().update(buffer).digest(); + } } } exports.ripemd160 = ripemd160; diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index b7c355a73..e1cebde53 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,4 +1,5 @@ import * as createHash from 'create-hash'; +import * as RipeMd160 from 'ripemd160'; export function ripemd160(buffer: Buffer): Buffer { try { @@ -6,9 +7,13 @@ export function ripemd160(buffer: Buffer): Buffer { .update(buffer) .digest(); } catch (err) { - return createHash('ripemd160') - .update(buffer) - .digest(); + try { + return createHash('ripemd160') + .update(buffer) + .digest(); + } catch (err2) { + return new RipeMd160().update(buffer).digest(); + } } } From a013fb5058d0a11dc4dddf0130d6ffe24aaf656a Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 14 Jun 2022 08:43:17 +0900 Subject: [PATCH 011/249] Update setup-node action to use matrix and lts notation --- .github/workflows/main_ci.yml | 60 +- .npm-audit-whitelister.json | 1 - .nsprc | 1 + package-lock.json | 1703 ++++++++++++++++++--------------- package.json | 6 +- 5 files changed, 982 insertions(+), 789 deletions(-) delete mode 100644 .npm-audit-whitelister.json create mode 100644 .nsprc diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index 737f39316..b75bea9d1 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -14,34 +14,46 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: 'lts/*' registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: npm run audit unit: runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14, 'lts/*'] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: npm run unit coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: 'lts/*' registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: npm run coverage integration: runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14, 'lts/*'] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ services: regtest: image: junderw/bitcoinjs-regtest-server@sha256:5b69cf95d9edf6d5b3a00504665d6b3c382a6aa3728fe8ce897974c519061463 @@ -49,49 +61,65 @@ jobs: - 8080:8080 steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: APIURL=http://127.0.0.1:8080/1 npm run integration format: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: 'lts/*' registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: npm run format:ci gitdiff: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: 'lts/*' registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: npm run gitdiff:ci lint: runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14, 'lts/*'] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: npm run lint lint-tests: runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14, 'lts/*'] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: 12 + node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ + cache: 'npm' - run: npm ci - run: npm run lint:tests diff --git a/.npm-audit-whitelister.json b/.npm-audit-whitelister.json deleted file mode 100644 index fe51488c7..000000000 --- a/.npm-audit-whitelister.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/.nsprc b/.nsprc new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/.nsprc @@ -0,0 +1 @@ +{} diff --git a/package-lock.json b/package-lock.json index 76680b44f..2e65f1af2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,16 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@babel/code-frame": { "version": "7.15.8", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", @@ -13,186 +23,196 @@ "@babel/highlight": "^7.14.5" } }, + "@babel/compat-data": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz", + "integrity": "sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg==", + "dev": true + }, "@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, - "requires": { - "@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", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.5.tgz", + "integrity": "sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.2", + "@babel/parser": "^7.18.5", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.5", + "@babel/types": "^7.18.4", "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" + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" }, "dependencies": { "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.16.7" } }, "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@babel/generator": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", - "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", "dev": true, "requires": { - "@babel/types": "^7.12.13", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } } }, - "@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==", + "@babel/helper-compilation-targets": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", + "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "@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==", + "@babel/helper-environment-visitor": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", + "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" } }, - "@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==", + "@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.7" } }, "@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, - "requires": { - "@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" - } - }, - "@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, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@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==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", "dev": true, "requires": { - "@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" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" } }, "@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==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", + "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.18.2" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@babel/helpers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.13.tgz", - "integrity": "sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", + "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" } }, "@babel/highlight": { @@ -215,38 +235,38 @@ } }, "@babel/parser": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", - "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.5.tgz", + "integrity": "sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" }, "dependencies": { "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.16.7" } }, "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } @@ -254,67 +274,52 @@ } }, "@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, - "requires": { - "@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", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz", + "integrity": "sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.18.5", + "@babel/types": "^7.18.4", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "globals": "^11.1.0" }, "dependencies": { "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.16.7" } }, "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "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 } } }, "@babel/types": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", - "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, @@ -331,6 +336,12 @@ "resolve-from": "^5.0.0" }, "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -350,6 +361,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -358,21 +378,53 @@ "requires": { "p-limit": "^2.2.0" } - }, - "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 } } }, "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", "dev": true }, + "@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@types/base-x": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/base-x/-/base-x-3.0.0.tgz", @@ -445,6 +497,12 @@ "@types/node": "*" } }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -455,16 +513,28 @@ "indent-string": "^4.0.0" } }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -477,9 +547,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -498,7 +568,7 @@ "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, "arg": { @@ -516,6 +586,12 @@ "sprintf-js": "~1.0.2" } }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -535,10 +611,30 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, + "better-npm-audit": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/better-npm-audit/-/better-npm-audit-3.7.3.tgz", + "integrity": "sha512-zsSiidlP5n7KpCYdAmkellu4JYA4IoRUUwrBMv/R7TwT8vcRfk5CQ2zTg7yUy4bdWkKtAj7VVdPQttdMbx+n5Q==", + "dev": true, + "requires": { + "commander": "^8.0.0", + "dayjs": "^1.10.6", + "lodash.get": "^4.4.2", + "table": "^6.7.1" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + } + } + }, "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==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "bip174": { @@ -637,6 +733,19 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "browserslist": { + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", + "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001349", + "electron-to-chromium": "^1.4.147", + "escalade": "^3.1.1", + "node-releases": "^2.0.5", + "picocolors": "^1.0.0" + } + }, "bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", @@ -680,9 +789,15 @@ } }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001352", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", + "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==", "dev": true }, "chalk": { @@ -708,19 +823,19 @@ } }, "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" + "readdirp": "~3.6.0" } }, "cipher-base": { @@ -739,42 +854,14 @@ "dev": true }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "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 - }, - "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, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "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, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "color-convert": { @@ -801,7 +888,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, "concat-map": { @@ -811,9 +898,9 @@ "dev": true }, "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==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -854,32 +941,35 @@ "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, + "dayjs": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz", + "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==", + "dev": true + }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "dependencies": { + "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 + } } }, "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, "default-require-extensions": { @@ -891,15 +981,6 @@ "strip-bom": "^4.0.0" } }, - "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, - "requires": { - "object-keys": "^1.0.12" - } - }, "dhttp": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dhttp/-/dhttp-3.0.3.tgz", @@ -910,9 +991,9 @@ } }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "ecpair": { @@ -926,41 +1007,17 @@ "wif": "^2.0.6" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "electron-to-chromium": { + "version": "1.4.153", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.153.tgz", + "integrity": "sha512-57AV9DNW1R52HjOqnGOCCTLHMHItLTGu/WjB1KYIa4BQ7p0u8J0j8N78akPcOBStKE801xcMjTpmbAylflfIYQ==", "dev": true }, - "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, - "requires": { - "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" - } - }, - "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, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } + "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 }, "es6-error": { "version": "4.1.1", @@ -968,6 +1025,12 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -980,6 +1043,12 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -1000,9 +1069,9 @@ } }, "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==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -1011,22 +1080,20 @@ } }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true }, "foreground-child": { "version": "2.0.0", @@ -1051,18 +1118,12 @@ "dev": true }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1111,38 +1172,17 @@ "dev": true }, "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 - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "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 - }, "hash-base": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", @@ -1183,7 +1223,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "indent-string": { @@ -1216,40 +1256,22 @@ "binary-extensions": "^2.0.0" } }, - "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 - }, - "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 - }, - "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 - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "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=", + "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 }, "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==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -1267,34 +1289,28 @@ "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", "dev": true }, - "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, - "requires": { - "has": "^1.0.3" - } + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true }, "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==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "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, - "requires": { - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, "is-windows": { @@ -1306,13 +1322,13 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "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==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, "istanbul-lib-hook": { @@ -1345,18 +1361,17 @@ } }, "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==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, "requires": { "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", "p-map": "^3.0.0", "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "uuid": "^8.3.2" }, "dependencies": { "rimraf": { @@ -1399,43 +1414,20 @@ } }, "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==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "requires": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "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 - }, - "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 - } } }, "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -1464,44 +1456,104 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "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==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "p-locate": "^5.0.0" } }, - "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, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, "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==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^2.4.2" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "make-dir": { @@ -1578,35 +1630,101 @@ } }, "mocha": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", - "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", "dev": true, "requires": { - "ansi-colors": "3.2.3", + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", "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", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", "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" + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } + } + } } }, "module-not-found-error": { @@ -1616,20 +1734,16 @@ "dev": true }, "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==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "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, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true }, "node-preload": { "version": "0.2.1", @@ -1640,18 +1754,18 @@ "process-on-spawn": "^1.0.0" } }, + "node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, "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 }, - "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 - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -1687,12 +1801,6 @@ "yargs": "^15.0.2" }, "dependencies": { - "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 - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1702,6 +1810,12 @@ "color-convert": "^2.0.1" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1728,10 +1842,10 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "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==", + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, "find-up": { @@ -1745,25 +1859,19 @@ } }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, - "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 - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1773,6 +1881,24 @@ "p-locate": "^4.1.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -1782,12 +1908,6 @@ "p-limit": "^2.2.0" } }, - "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 - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1797,26 +1917,6 @@ "glob": "^7.1.3" } }, - "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, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "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, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -1828,6 +1928,12 @@ "strip-ansi": "^6.0.0" } }, + "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 + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -1859,40 +1965,6 @@ } } }, - "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 - }, - "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 - }, - "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, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "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, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1903,21 +1975,21 @@ } }, "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==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "^3.0.2" } }, "p-map": { @@ -1948,9 +2020,9 @@ } }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "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 }, "path-is-absolute": { @@ -1984,10 +2056,16 @@ "sha.js": "^2.4.8" } }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", - "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pkg-dir": { @@ -2018,6 +2096,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -2026,12 +2113,6 @@ "requires": { "p-limit": "^2.2.0" } - }, - "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 } } }, @@ -2061,6 +2142,12 @@ "resolve": "~1.8.1" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, "pushdata-bitcoin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", @@ -2080,12 +2167,12 @@ } }, "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { - "picomatch": "^2.0.4" + "picomatch": "^2.2.1" } }, "regtest-client": { @@ -2102,7 +2189,7 @@ "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "requires": { "es6-error": "^4.0.1" @@ -2111,7 +2198,13 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "require-main-filename": { @@ -2180,10 +2273,19 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "sha.js": { @@ -2211,15 +2313,58 @@ "dev": true }, "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + } + } + }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-support": { @@ -2262,15 +2407,6 @@ "requires": { "glob": "^7.1.3" } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -2287,42 +2423,23 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "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, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "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==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -2332,18 +2449,79 @@ "dev": true }, "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=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "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==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "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 + }, + "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 + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "test-exclude": { @@ -2358,17 +2536,28 @@ }, "dependencies": { "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } } } @@ -2385,7 +2574,7 @@ "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=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, "to-regex-range": { @@ -2494,10 +2683,19 @@ "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", "dev": true }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, "varuint-bitcoin": { @@ -2509,9 +2707,9 @@ } }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -2520,18 +2718,9 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, - "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, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "wif": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", @@ -2540,42 +2729,46 @@ "bs58check": "<3.0.0" } }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "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==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "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 - }, - "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==", + "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, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "color-convert": "^2.0.1" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "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, "requires": { - "ansi-regex": "^4.1.0" + "color-name": "~1.1.4" } + }, + "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 } } }, @@ -2598,76 +2791,42 @@ } }, "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "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" - }, - "dependencies": { - "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 - }, - "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, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "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, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "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, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" } }, "yn": { @@ -2675,6 +2834,12 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index c5553d57c..ba2583fd8 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "bitcoinjs" ], "scripts": { - "audit": "NPM_AUDIT_IGNORE_DEV=1 NPM_AUDIT_IGNORE_LEVEL=low npm-audit-whitelister .npm-audit-whitelister.json", + "audit": "better-npm-audit audit -l high", "build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs", "build:tests": "npm run clean:jstests && tsc -p ./test/tsconfig.json", "clean": "rimraf src", @@ -66,6 +66,7 @@ "@types/proxyquire": "^1.3.28", "@types/randombytes": "^2.0.0", "@types/wif": "^2.0.2", + "better-npm-audit": "^3.7.3", "bip32": "^3.0.1", "bip39": "^3.0.2", "bip65": "^1.0.1", @@ -76,8 +77,7 @@ "ecpair": "^2.0.1", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", - "mocha": "^7.1.1", - "npm-audit-whitelister": "^1.0.2", + "mocha": "^10.0.0", "nyc": "^15.1.0", "prettier": "1.16.4", "proxyquire": "^2.0.1", From 04784e4cb188d7b0f01ef56a3874b8c5ed8ac115 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 15 Jun 2022 09:58:42 +0900 Subject: [PATCH 012/249] Reorder jobs into matrix and non-matrix. Also remove lint matrix. --- .github/workflows/main_ci.yml | 73 ++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index b75bea9d1..e9ba67bb2 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -10,20 +10,13 @@ permissions: contents: read jobs: - audit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 - with: - node-version: 'lts/*' - registry-url: https://registry.npmjs.org/ - cache: 'npm' - - run: npm ci - - run: npm run audit + ################## + # Jobs with matrix + ################## unit: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: node-version: [14, 'lts/*'] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ @@ -37,20 +30,10 @@ jobs: cache: 'npm' - run: npm ci - run: npm run unit - coverage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 - with: - node-version: 'lts/*' - registry-url: https://registry.npmjs.org/ - cache: 'npm' - - run: npm ci - - run: npm run coverage integration: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: node-version: [14, 'lts/*'] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ @@ -69,6 +52,34 @@ jobs: cache: 'npm' - run: npm ci - run: APIURL=http://127.0.0.1:8080/1 npm run integration + + + + ##################### + # Jobs without matrix + ##################### + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 + with: + node-version: 'lts/*' + registry-url: https://registry.npmjs.org/ + cache: 'npm' + - run: npm ci + - run: npm run audit + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 + with: + node-version: 'lts/*' + registry-url: https://registry.npmjs.org/ + cache: 'npm' + - run: npm ci + - run: npm run coverage format: runs-on: ubuntu-latest steps: @@ -93,32 +104,22 @@ jobs: - run: npm run gitdiff:ci lint: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14, 'lts/*'] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: ${{ matrix.node-version }} + node-version: 'lts/*' registry-url: https://registry.npmjs.org/ cache: 'npm' - run: npm ci - run: npm run lint lint-tests: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14, 'lts/*'] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 with: - node-version: ${{ matrix.node-version }} + node-version: 'lts/*' registry-url: https://registry.npmjs.org/ cache: 'npm' - run: npm ci From 13324eb167762c32660690fda9f9f39da34ad6c2 Mon Sep 17 00:00:00 2001 From: Jonathan Underwood Date: Mon, 25 Jul 2022 10:53:54 +0900 Subject: [PATCH 013/249] Fix comment on OP_CLTV integration test --- test/integration/cltv.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/cltv.spec.ts b/test/integration/cltv.spec.ts index 04944ebf9..12a7f29d3 100644 --- a/test/integration/cltv.spec.ts +++ b/test/integration/cltv.spec.ts @@ -81,7 +81,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => { const unspent = await regtestUtils.faucet(address!, 1e5); const tx = new bitcoin.Transaction(); tx.locktime = lockTime; - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + // Note: nSequence MUST be <= 0xfffffffe otherwise OP_CHECKLOCKTIMEVERIFY will fail. tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); From e43ddf4ea710da5143c5813c14e47dd0f3d1b90d Mon Sep 17 00:00:00 2001 From: Jonathan Underwood Date: Tue, 26 Jul 2022 09:11:06 +0900 Subject: [PATCH 014/249] Fix more comments for CLTV integration test --- test/integration/cltv.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/cltv.spec.ts b/test/integration/cltv.spec.ts index 12a7f29d3..d81442057 100644 --- a/test/integration/cltv.spec.ts +++ b/test/integration/cltv.spec.ts @@ -130,7 +130,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => { const unspent = await regtestUtils.faucet(address!, 1e5); const tx = new bitcoin.Transaction(); tx.locktime = lockTime; - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + // Note: nSequence MUST be <= 0xfffffffe otherwise OP_CHECKLOCKTIMEVERIFY will fail. tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); @@ -181,7 +181,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => { const unspent = await regtestUtils.faucet(address!, 2e5); const tx = new bitcoin.Transaction(); tx.locktime = lockTime; - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + // Note: nSequence MUST be <= 0xfffffffe otherwise OP_CHECKLOCKTIMEVERIFY will fail. tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 8e4); @@ -229,7 +229,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => { const unspent = await regtestUtils.faucet(address!, 2e4); const tx = new bitcoin.Transaction(); tx.locktime = lockTime; - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + // Note: nSequence MUST be <= 0xfffffffe otherwise OP_CHECKLOCKTIMEVERIFY will fail. tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 1e4); From 26316259ab4399690f4e237b18caa9f47de2b2a5 Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 26 Jul 2022 09:46:10 +0900 Subject: [PATCH 015/249] 6.0.2 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d8f914f..e74c79c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 6.0.2 +__fixed__ +- p2sh payment now uses empty Buffer for redeem.output when redeemScript is OP_FALSE (#1802) +- Fix ripemd160 hashing fallback issue (#1812) + +# 6.0.1 +- No changes to public API + # 6.0.0 __removed__ - bip32: Removed the re-export. Please add as dependency to your app instead. diff --git a/package-lock.json b/package-lock.json index 6cf121559..75ffeaacc 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 44f711394..dd644a20a 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 ec8be198389ec2f2078af820410df92e94168814 Mon Sep 17 00:00:00 2001 From: ocknamo Date: Fri, 2 Sep 2022 21:45:30 +0900 Subject: [PATCH 016/249] refactor: remove unneed flags from tsconfig.json --- tsconfig.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 25f9d61c0..be06379a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,13 +10,6 @@ ], "allowJs": false, "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "alwaysStrict": true, "esModuleInterop": false, "noUnusedLocals": true, "noUnusedParameters": true From e79060d19b55908e455ff4a0f2618d85eae9792f Mon Sep 17 00:00:00 2001 From: Oliver Offing <84942996+OliverOffing@users.noreply.github.com> Date: Fri, 7 Oct 2022 18:01:29 -0300 Subject: [PATCH 017/249] docs(browser): using a different build tool Explains how one might user browserify just to generate a bundle for this specific library which allows the developer integrate this library into a project that uses a build tool different than browserify. --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9389a73e0..d155581b1 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,20 @@ We are not an authorative source of best practice, but, at the very least: ### Browser -The recommended method of using `bitcoinjs-lib` in your browser is through [Browserify](https://github.com/substack/node-browserify). -If you're familiar with how to use browserify, ignore this and carry on, otherwise, it is recommended to read the tutorial at https://browserify.org/. +The recommended method of using `bitcoinjs-lib` in your browser is through [browserify](http://browserify.org/). + +If you'd like to use a different (more modern) build tool than `browserify`, you can compile just this library and its dependencies into a single JavaScript file: + +```sh +$ npm install bitcoinjs-lib browserify +$ npx browserify --standalone bitcoin - -o bitcoinjs-lib.js <<<"module.exports = require('bitcoinjs-lib');" +``` + +Which you can then import as an ESM module: + +```javascript + +```` **NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](https://babeljs.io/docs/plugins/preset-es2015/) preset). From cb07b4a4412b52d03c5bf40289652d4cae7bd74c Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:23:46 +0200 Subject: [PATCH 018/249] Fix: minor typos Fix: minor typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9389a73e0..d51c58104 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,9 @@ It can do this through undermining your random number generation, accidentally p Running tests in your target environment is important and a recommended step to verify continuously. Finally, **adhere to best practice**. -We are not an authorative source of best practice, but, at the very least: +We are not an authoritative source of best practice, but, at the very least: -* [Don't re-use addresses](https://en.bitcoin.it/wiki/Address_reuse). +* [Don't reuse addresses](https://en.bitcoin.it/wiki/Address_reuse). * Don't share BIP32 extended public keys ('xpubs'). [They are a liability](https://bitcoin.stackexchange.com/questions/56916/derivation-of-parent-private-key-from-non-hardened-child), and it only takes 1 misplaced private key (or a buggy implementation!) and you are vulnerable to **catastrophic fund loss**. * [Don't use `Math.random`](https://security.stackexchange.com/questions/181580/why-is-math-random-not-designed-to-be-cryptographically-secure) - in any way - don't. * Enforce that users always verify (manually) a freshly-decoded human-readable version of their intended transaction before broadcast. From 5cf1e437c4e1170f681a1b6f587961a27043fc18 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 17:53:15 +0300 Subject: [PATCH 019/249] 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 75ffeaacc..39ffeed33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -712,10 +712,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", @@ -2577,7 +2576,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 dd644a20a..644402307 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", "ripemd160": "^2.0.2", @@ -73,7 +74,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 083c8c134f7e0ef98a3902910980a92b575e8dd1 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 17:55:41 +0300 Subject: [PATCH 020/249] 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 ef8a4c8b98ff970922279fa9a5905df2dca01b15 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 17:57:14 +0300 Subject: [PATCH 021/249] 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 93185d460a4663cfd9b4ad16ae19694b4c4d753e Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 18:02:59 +0300 Subject: [PATCH 022/249] 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 d1e35e473789932d6fa9817ab5722fb1b95052dd Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 18:04:54 +0300 Subject: [PATCH 023/249] 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 f39812cc21a754e995974c289527c9d7c8b0ccea Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 28 Oct 2021 18:08:20 +0300 Subject: [PATCH 024/249] 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 2a2b403dd0b77f7f62025554a789e565f0595a2b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 29 Oct 2021 14:21:58 +0300 Subject: [PATCH 025/249] 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 e12f1876e2b5c9ca2bd63e7ad77d9c2ac0848875 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 29 Oct 2021 14:43:10 +0300 Subject: [PATCH 026/249] 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 7760b9fcf671694451f64644f9864a098ce3726f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:11:59 +0200 Subject: [PATCH 027/249] 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 d142e0285d0960cbc0289b36c15e114be3759af8 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:12:55 +0200 Subject: [PATCH 028/249] 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 d1c7b051bce859a7b31d945e2c26b44258f82515 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:13:57 +0200 Subject: [PATCH 029/249] 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 46e3ce7d86fd8b03bb7916b8c9442801113ae76b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:15:45 +0200 Subject: [PATCH 030/249] 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 3f1be3260c47df8a2fa3671046eff02255977e8d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:16:17 +0200 Subject: [PATCH 031/249] 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 b6a00487f41b347d86e4128acaa363f1c3447f37 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:17:13 +0200 Subject: [PATCH 032/249] 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 7bc0c8ec33ac70272dde98ed64265c0ec5d92a68 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 1 Nov 2021 16:36:41 +0200 Subject: [PATCH 033/249] 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 61511d4f6346b06610b4ef0d9b018528668e2879 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 15:34:37 +0200 Subject: [PATCH 034/249] 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 725d96adbf29831246e9385c7d7a9c8093c450f9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 16:06:01 +0200 Subject: [PATCH 035/249] 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 c309ff8072384c0d113171d12a409ec2facbd0ee Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 16:57:31 +0200 Subject: [PATCH 036/249] 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 d0ae719fa2858db6979104847ac2fcd178d419d6 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 17:26:04 +0200 Subject: [PATCH 037/249] 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 a8c3f1d47220c7ff36fac2ae8ce01ce8246f20c6 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 17:40:25 +0200 Subject: [PATCH 038/249] 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 6fa79d6e60ea534fadd20d3e6c3bdb49a5e1187a Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 2 Nov 2021 17:49:59 +0200 Subject: [PATCH 039/249] 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 cafcde01e9110b5d0fe8b0e081386b7a15b92370 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 09:43:38 +0200 Subject: [PATCH 040/249] 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 50682239dc571d6cc016d52df7cc1f3bcdb3713a Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 15:47:49 +0200 Subject: [PATCH 041/249] 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 04a50c098ae0902b99b2d37885131603381a91b3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 16:10:00 +0200 Subject: [PATCH 042/249] 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 a98e76b1b8812ce844825e3bf51fabb208af7014 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 19:15:08 +0200 Subject: [PATCH 043/249] 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 bf41ac7bbf32a6b6afa11fd869b5f734d01c9af3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 19:28:22 +0200 Subject: [PATCH 044/249] 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 b3073794390321a46190bddb8a794dbea23ea164 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 3 Nov 2021 20:00:42 +0200 Subject: [PATCH 045/249] 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 a3bfa84c5bcac4bc3e98d4322cd2eed9417a3ed5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 12 Nov 2021 12:13:26 +0200 Subject: [PATCH 046/249] 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 0519d2b06161287aa4377d7192bdebcba275d1e9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 12 Jan 2022 15:17:13 +0200 Subject: [PATCH 047/249] 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 39ffeed33..f343fdd36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2576,19 +2576,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 2efc51e6936c39e1f5b7504d069fef498212b5a4 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 16:54:23 +0200 Subject: [PATCH 048/249] 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 f343fdd36..769a25397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -711,11 +711,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 644402307..71e79082d 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", "ripemd160": "^2.0.2", From 4fd8a349db6068174912ada6ebafe11efa0d7161 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 16:55:49 +0200 Subject: [PATCH 049/249] 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 a8644a8215869172937d8a658ff7e3a2aa1ffb52 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 16:56:14 +0200 Subject: [PATCH 050/249] 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 b866a8019484171ce360147a63229581db8408ac Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 17:03:08 +0200 Subject: [PATCH 051/249] 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 fac6c9052f7b6bac9d0379c950dc99a10082bf9c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 17:04:06 +0200 Subject: [PATCH 052/249] 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 6578097dbf0d78e6be60b8eae639b1d3721384de Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 17:14:33 +0200 Subject: [PATCH 053/249] 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 1577f6671611d0d8c5718d9516dc80a4a465652e Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 18:39:06 +0200 Subject: [PATCH 054/249] 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 af72a78e132be3b7ede5d121178446db5d40fdb9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 13 Jan 2022 18:40:55 +0200 Subject: [PATCH 055/249] 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 f2a0c1d0a8b1e078c2aa398aff6a9d740b0cd6da Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 14 Jan 2022 16:18:13 +0200 Subject: [PATCH 056/249] 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 2e5965d141deee95cec7f14c17a8912a0c5fab03 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 18 Jan 2022 09:10:37 +0200 Subject: [PATCH 057/249] 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 e292387cbd818586d0c9f703ef3558cf04ad4a8e Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 20 Jan 2022 08:51:44 +0200 Subject: [PATCH 058/249] 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 06fc272837493579f74ed7b87d4f2c5f41ab8c93 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 20 Jan 2022 08:52:43 +0200 Subject: [PATCH 059/249] 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 25266421e8dff3394f6f9c7e09b54ca508c68aea Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 27 Jan 2022 14:40:11 +0200 Subject: [PATCH 060/249] 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 fa1e1c3b94d0bd52d3b13137d275f91dbf593325 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 27 Jan 2022 15:09:42 +0200 Subject: [PATCH 061/249] 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 | 1 - 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, 405 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 769a25397..8fdad2276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2566,10 +2566,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" } @@ -2683,8 +2682,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==" }, "uri-js": { "version": "4.4.1", diff --git a/package.json b/package.json index 71e79082d..41f2722ae 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,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 04d0be928a32f543408ae8d5fafaaf877e7968bd Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 28 Jan 2022 15:18:16 +0200 Subject: [PATCH 062/249] 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 | 1 + 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(+), 122 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8fdad2276..0198cd374 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2569,6 +2569,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" } @@ -2682,7 +2683,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 }, "uri-js": { "version": "4.4.1", diff --git a/package.json b/package.json index 41f2722ae..c78538ba0 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,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 222022a93a566265e0a167b1c38b7a56e8ca565c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 10 Feb 2022 13:52:47 +0200 Subject: [PATCH 063/249] 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 4eee26bc7eb4584dc18302f5b66be43e96faef9c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Feb 2022 11:42:23 +0200 Subject: [PATCH 064/249] 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 e91c77d068658ff9ede91a1f6e9ba9e97ce72a7f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Feb 2022 16:29:32 +0200 Subject: [PATCH 065/249] 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 6816f51f8645389e801fbf581b30e5a107e3c740 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Feb 2022 16:41:56 +0200 Subject: [PATCH 066/249] 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 7da1dd55f9048497bb42c962abdc5d6d8e2bd5db Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 22 Feb 2022 13:57:34 +0200 Subject: [PATCH 067/249] 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 e033a6ff41f31e70fcf715b5adf084be8e5880ef Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 17:58:30 +0200 Subject: [PATCH 068/249] 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 1e6aec54766b64faaa82c766cdc795a47ad38d37 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 18:36:48 +0200 Subject: [PATCH 069/249] 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 08cf664c399a482d06eba481289d092029d3f394 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:27:12 +0200 Subject: [PATCH 070/249] 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 0198cd374..d218ee5c8 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": { "@ampproject/remapping": { "version": "2.2.0", 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 3e02a63dde4d0fd41097ff64562e3069c753852f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:28:35 +0200 Subject: [PATCH 071/249] 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 d218ee5c8..62d5ff8a9 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": { "@ampproject/remapping": { "version": "2.2.0", @@ -5441,9 +2047,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 fde11c92d169133a237b29a4e450aeaca2d0f1b9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:29:32 +0200 Subject: [PATCH 072/249] 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 060630b82f70b0da30839315963ab85479a426f1 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 9 Mar 2022 23:46:29 +0200 Subject: [PATCH 073/249] 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 b17dc51733b1389295baa87ba8637132c8883451 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 10 Mar 2022 13:57:43 +0200 Subject: [PATCH 074/249] 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 032201aabe1156812ddaf305b76fa7e0841d8a76 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Mar 2022 17:41:06 +0200 Subject: [PATCH 075/249] 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 fee9fa14c2efc0e8f1d9bea3309d8ed78507fc7e Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Mar 2022 17:43:17 +0200 Subject: [PATCH 076/249] 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 67028cfb1deae797905541378b18e6b5cd992dbd Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:11:14 -0700 Subject: [PATCH 077/249] 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 da61c66bd4678aff83a1f852e8c74d2b12f7aaff Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 12:54:52 -0700 Subject: [PATCH 078/249] 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 d29ada65ae456995fa6833544e304e708651fe40 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 13:59:18 -0700 Subject: [PATCH 079/249] 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 cf18dfdc7c3a8687bf92fcc33dfce4ff69f09de3 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 14:00:26 -0700 Subject: [PATCH 080/249] 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 f76b95434d946bd98c5dd07be21bb3e8dd29a6c3 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 14:49:26 -0700 Subject: [PATCH 081/249] 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 18bcadd61d9587e0a8118d7aa1a53e9b5ca83e9c Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:46:47 -0700 Subject: [PATCH 082/249] 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 c02ed1b54c3f6600e05156be994232e85a25e726 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:54:24 -0700 Subject: [PATCH 083/249] 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 6b9f7767a5ccbca86b2f456610801ef994430f77 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 15:57:05 -0700 Subject: [PATCH 084/249] 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 95d0f51622a5e6e22e0a056bdc775f618162d631 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Fri, 18 Mar 2022 16:05:56 -0700 Subject: [PATCH 085/249] 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 e557a9948beb727bb53584d909ce9ea342e35dd5 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Mon, 21 Mar 2022 07:55:57 -0700 Subject: [PATCH 086/249] 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 c3053e7b2f2c3d00aae10bbe410d462a42e3b296 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Mon, 21 Mar 2022 07:57:08 -0700 Subject: [PATCH 087/249] 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 6f70c889a9f2cf5417ddf8c8a4795dffeccf82d8 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 28 Mar 2022 10:27:10 +0300 Subject: [PATCH 088/249] 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 4fd164e200093a59d073a1ce19d9c34de48c0a84 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 1 Apr 2022 17:56:26 +0300 Subject: [PATCH 089/249] refactor: move tweakKey() to taproot utils --- src/payments/p2tr.js | 23 ++++-------- src/payments/taprootutils.d.ts | 7 +++- src/payments/taprootutils.js | 18 ++++++++-- ts_src/payments/p2tr.ts | 26 +------------- ts_src/payments/taprootutils.ts | 63 +++++++++++++++++++++++---------- 5 files changed, 74 insertions(+), 63 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 33fa007f8..5f235abcb 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -127,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); + const tweakedKey = (0, taprootutils_1.tweakKey)(o.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } }); @@ -152,7 +152,10 @@ function p2tr(a, opts) { }); const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); if (!path) return; - const outputKey = tweakKey(a.internalPubkey, hashTree.hash); + const outputKey = (0, taprootutils_1.tweakKey)( + a.internalPubkey, + hashTree.hash, + ); if (!outputKey) return; const controlBock = buffer_1.Buffer.concat( [ @@ -193,7 +196,7 @@ function p2tr(a, opts) { else pubkey = a.output.slice(2); } if (a.internalPubkey) { - const tweakedKey = tweakKey(a.internalPubkey, o.hash); + const tweakedKey = (0, taprootutils_1.tweakKey)(a.internalPubkey, o.hash); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) throw new TypeError('Pubkey mismatch'); else pubkey = tweakedKey.x; @@ -274,7 +277,7 @@ function p2tr(a, opts) { controlBlock, leafHash, ); - const outputKey = tweakKey(internalPubkey, hash); + const outputKey = (0, taprootutils_1.tweakKey)(internalPubkey, hash); if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); @@ -288,18 +291,6 @@ function p2tr(a, opts) { return Object.assign(o, a); } exports.p2tr = p2tr; -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 = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash); - if (!res || res.xOnlyPubkey === null) return null; - return { - parity: res.parity, - x: buffer_1.Buffer.from(res.xOnlyPubkey), - }; -} function stacksEqual(a, b) { if (a.length !== b.length) return false; return a.every((x, i) => { diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index a5739c44f..371595f31 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,7 +1,6 @@ /// import { Tapleaf, Taptree } from '../types'; export declare const LEAF_VERSION_TAPSCRIPT = 192; -export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer; interface HashLeaf { hash: Buffer; } @@ -10,6 +9,10 @@ interface HashBranch { left: HashTree; right: HashTree; } +interface TweakedPublicKey { + parity: number; + x: Buffer; +} /** * Binary tree representing leaf, branch, and root node hashes of a Taptree. * Each node contains a hash, and potentially left and right branch hashes. @@ -17,6 +20,7 @@ interface HashBranch { * and calculating merkle inclusion proofs when constructing a control block. */ export declare type HashTree = HashLeaf | HashBranch; +export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer; /** * Build a hash tree of merkle nodes from the scripts binary tree. * @param scriptTree - the tree of scripts to pairwise hash. @@ -33,4 +37,5 @@ export declare function toHashTree(scriptTree: Taptree): HashTree; 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 declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; export {}; diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 85576960b..26d97d684 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -1,11 +1,13 @@ '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.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.LEAF_VERSION_TAPSCRIPT = void 0; const buffer_1 = require('buffer'); +const ecc_lib_1 = require('../ecc_lib'); const bcrypto = require('../crypto'); const bufferutils_1 = require('../bufferutils'); const types_1 = require('../types'); exports.LEAF_VERSION_TAPSCRIPT = 0xc0; +const isHashBranch = ht => 'left' in ht && 'right' in ht; function rootHashFromPath(controlBlock, leafHash) { const m = (controlBlock.length - 33) / 32; let kj = leafHash; @@ -20,7 +22,6 @@ function rootHashFromPath(controlBlock, leafHash) { return kj; } exports.rootHashFromPath = rootHashFromPath; -const isHashBranch = ht => 'left' in ht && 'right' in ht; /** * Build a hash tree of merkle nodes from the scripts binary tree. * @param scriptTree - the tree of scripts to pairwise hash. @@ -76,6 +77,19 @@ function tapTweakHash(pubKey, h) { ); } exports.tapTweakHash = tapTweakHash; +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 = tapTweakHash(pubKey, h); + const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash); + if (!res || res.xOnlyPubkey === null) return null; + return { + parity: res.parity, + x: buffer_1.Buffer.from(res.xOnlyPubkey), + }; +} +exports.tweakKey = tweakKey; function tapBranchHash(a, b) { return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b])); } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 09a894262..220bdb74f 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -8,7 +8,7 @@ import { rootHashFromPath, findScriptPath, tapleafHash, - tapTweakHash, + tweakKey, LEAF_VERSION_TAPSCRIPT, } from './taprootutils'; import { Payment, PaymentOpts } from './index'; @@ -316,30 +316,6 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return Object.assign(o, a); } -interface TweakedPublicKey { - parity: number; - x: Buffer; -} - -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 = getEccLib().xOnlyPointAddTweak(pubKey, tweakHash); - if (!res || res.xOnlyPubkey === null) return null; - - return { - parity: res.parity, - x: NBuffer.from(res.xOnlyPubkey), - }; -} - function stacksEqual(a: Buffer[], b: Buffer[]): boolean { if (a.length !== b.length) return false; diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 97cc1f6d8..39d815e44 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -1,4 +1,5 @@ import { Buffer as NBuffer } from 'buffer'; +import { getEccLib } from '../ecc_lib'; import * as bcrypto from '../crypto'; import { varuint } from '../bufferutils'; @@ -6,25 +7,6 @@ import { Tapleaf, Taptree, isTapleaf } from '../types'; export const LEAF_VERSION_TAPSCRIPT = 0xc0; -export function rootHashFromPath( - controlBlock: Buffer, - leafHash: Buffer, -): Buffer { - const m = (controlBlock.length - 33) / 32; - - 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) { - kj = tapBranchHash(kj, ej); - } else { - kj = tapBranchHash(ej, kj); - } - } - - return kj; -} - interface HashLeaf { hash: Buffer; } @@ -35,6 +17,11 @@ interface HashBranch { right: HashTree; } +interface TweakedPublicKey { + parity: number; + x: Buffer; +} + const isHashBranch = (ht: HashTree): ht is HashBranch => 'left' in ht && 'right' in ht; @@ -46,6 +33,25 @@ const isHashBranch = (ht: HashTree): ht is HashBranch => */ export type HashTree = HashLeaf | HashBranch; +export function rootHashFromPath( + controlBlock: Buffer, + leafHash: Buffer, +): Buffer { + const m = (controlBlock.length - 33) / 32; + + 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) { + kj = tapBranchHash(kj, ej); + } else { + kj = tapBranchHash(ej, kj); + } + } + + return kj; +} + /** * Build a hash tree of merkle nodes from the scripts binary tree. * @param scriptTree - the tree of scripts to pairwise hash. @@ -104,6 +110,25 @@ export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { ); } +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 = tapTweakHash(pubKey, h); + + const res = getEccLib().xOnlyPointAddTweak(pubKey, tweakHash); + if (!res || res.xOnlyPubkey === null) return null; + + return { + parity: res.parity, + x: NBuffer.from(res.xOnlyPubkey), + }; +} + function tapBranchHash(a: Buffer, b: Buffer): Buffer { return bcrypto.taggedHash('TapBranch', NBuffer.concat([a, b])); } From 9d4fdcdec4c9002ecd5623345cfc6ff2209f684c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 1 Apr 2022 17:58:55 +0300 Subject: [PATCH 090/249] fix: use witness without annex for o.signature --- src/payments/p2tr.js | 5 +++-- ts_src/payments/p2tr.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 5f235abcb..cb16ab25e 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -139,8 +139,9 @@ function p2tr(a, opts) { }); lazy.prop(o, 'signature', () => { if (a.signature) return a.signature; - if (!a.witness || a.witness.length !== 1) return; - return a.witness[0]; + const witness = _witness(); // witness without annex + if (!witness || witness.length !== 1) return; + return witness[0]; }); lazy.prop(o, 'witness', () => { if (a.witness) return a.witness; diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 220bdb74f..00b9e59ad 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -149,8 +149,9 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'signature', () => { if (a.signature) return a.signature; - if (!a.witness || a.witness.length !== 1) return; - return a.witness[0]; + const witness = _witness(); // witness without annex + if (!witness || witness.length !== 1) return; + return witness[0]; }); lazy.prop(o, 'witness', () => { From 3af7c11040164c1582869e06b2f3a4e2cb7e4c30 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 20 May 2022 14:00:23 +0300 Subject: [PATCH 091/249] feat: use BIP371 fields for taproot inputs - does not cover `TapBip32Derivation` - does not cover taproot outputs --- package-lock.json | 6 +- package.json | 2 +- src/psbt.d.ts | 26 +- src/psbt.js | 712 +++++++++++++++++---------- src/psbt/bip371.d.ts | 19 + src/psbt/bip371.js | 181 +++++++ src/psbt/psbtutils.d.ts | 11 + src/psbt/psbtutils.js | 66 +++ test/fixtures/psbt.json | 172 ++++++- test/integration/taproot.spec.ts | 92 ++-- test/psbt.spec.ts | 119 ++++- test/psbt.utils.ts | 49 -- ts_src/psbt.ts | 810 +++++++++++++++++++++---------- ts_src/psbt/bip371.ts | 240 +++++++++ ts_src/psbt/psbtutils.ts | 73 +++ 15 files changed, 1939 insertions(+), 639 deletions(-) create mode 100644 src/psbt/bip371.d.ts create mode 100644 src/psbt/bip371.js create mode 100644 src/psbt/psbtutils.d.ts create mode 100644 src/psbt/psbtutils.js delete mode 100644 test/psbt.utils.ts create mode 100644 ts_src/psbt/bip371.ts create mode 100644 ts_src/psbt/psbtutils.ts diff --git a/package-lock.json b/package-lock.json index 62d5ff8a9..7cb07959e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -647,9 +647,9 @@ "dev": true }, "bip174": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz", - "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.0.tgz", + "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==" }, "bip32": { "version": "3.0.1", diff --git a/package.json b/package.json index c78538ba0..9cecfc352 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ ], "dependencies": { "bech32": "^2.0.0", - "bip174": "^2.0.1", + "bip174": "^2.1.0", "bs58check": "^2.1.2", "create-hash": "^1.1.0", "ripemd160": "^2.0.2", diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 890f9e115..4c622b31f 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -80,7 +80,10 @@ export declare class Psbt { getFeeRate(): number; getFee(): number; finalizeAllInputs(): this; - finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this; + finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc): this; + finalizeTaprootInput(inputIndex: number, tapLeafHashToFinalize?: Buffer, finalScriptsFunc?: FinalTaprootScriptsFunc): this; + private _finalizeInput; + private _finalizeTaprootInput; getInputType(inputIndex: number): AllScriptType; inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean; inputHasHDKey(inputIndex: number, root: HDSigner): boolean; @@ -88,6 +91,8 @@ export declare class Psbt { outputHasHDKey(outputIndex: number, root: HDSigner): boolean; validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean; validateSignaturesOfInput(inputIndex: number, validator: ValidateSigFunction, pubkey?: Buffer): boolean; + private _validateSignaturesOfInput; + private validateSignaturesOfTaprootInput; signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this; signAllInputsHDAsync(hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise; signInputHD(inputIndex: number, hdKeyPair: HDSigner, sighashTypes?: number[]): this; @@ -95,7 +100,14 @@ export declare class Psbt { signAllInputs(keyPair: Signer, sighashTypes?: number[]): this; signAllInputsAsync(keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise; signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this; + signTaprootInput(inputIndex: number, keyPair: Signer, tapLeafHashToSign?: Buffer, sighashTypes?: number[]): this; + private _signInput; + private _signTaprootInput; signInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise; + signTaprootInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, tapLeafHash?: Buffer, sighashTypes?: number[]): Promise; + private _signInputAsync; + private _signTaprootInputAsync; + private checkTaprootHashesForSig; toBuffer(): Buffer; toHex(): string; toBase64(): string; @@ -143,7 +155,6 @@ 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 @@ -151,7 +162,6 @@ 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; @@ -177,11 +187,15 @@ 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) => { finalScriptSig: Buffer | undefined; - finalScriptWitness: Buffer | Buffer[] | undefined; + finalScriptWitness: Buffer | undefined; +}; +declare type FinalTaprootScriptsFunc = (inputIndex: number, // Which input is it? +input: PsbtInput, // The PSBT input contents +tapLeafHashToFinalize?: Buffer) => { + finalScriptWitness: 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' | 'p2tr-pubkey' | 'p2tr-nonstandard'; +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'; export {}; diff --git a/src/psbt.js b/src/psbt.js index c14086d0e..5496008ca 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -6,12 +6,13 @@ const varuint = require('bip174/src/lib/converter/varint'); const utils_1 = require('bip174/src/lib/utils'); const address_1 = require('./address'); const bufferutils_1 = require('./bufferutils'); -const crypto_1 = require('./crypto'); const networks_1 = require('./networks'); const payments = require('./payments'); +const taprootutils_1 = require('./payments/taprootutils'); const bscript = require('./script'); const transaction_1 = require('./transaction'); -const taprootutils_1 = require('./payments/taprootutils'); +const bip371_1 = require('./psbt/bip371'); +const psbtutils_1 = require('./psbt/psbtutils'); /** * These are the default arguments for a Psbt instance. */ @@ -199,6 +200,7 @@ class Psbt { `Requires single object with at least [hash] and [index]`, ); } + (0, bip371_1.checkTaprootInputFields)(inputData, inputData, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput'); if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); const c = this.__CACHE; @@ -272,15 +274,38 @@ 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, - isTapscript, - } = getScriptFromInput(inputIndex, input, this.__CACHE); + if ((0, bip371_1.isTaprootInput)(input)) + return this._finalizeTaprootInput( + inputIndex, + input, + undefined, + finalScriptsFunc, + ); + return this._finalizeInput(inputIndex, input, finalScriptsFunc); + } + finalizeTaprootInput( + inputIndex, + tapLeafHashToFinalize, + finalScriptsFunc = bip371_1.tapScriptFinalizer, + ) { + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); + if ((0, bip371_1.isTaprootInput)(input)) + return this._finalizeTaprootInput( + inputIndex, + input, + tapLeafHashToFinalize, + finalScriptsFunc, + ); + throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`); + } + _finalizeInput(inputIndex, input, finalScriptsFunc = getFinalScripts) { + const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( + inputIndex, + input, + this.__CACHE, + ); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); const { finalScriptSig, finalScriptWitness } = finalScriptsFunc( @@ -290,28 +315,49 @@ class Psbt { isSegwit, isP2SH, isP2WSH, - isTapscript, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); - 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 (finalScriptWitness) + this.data.updateInput(inputIndex, { finalScriptWitness }); if (!finalScriptSig && !finalScriptWitness) throw new Error(`Unknown error finalizing input #${inputIndex}`); this.data.clearFinalizedInput(inputIndex); return this; } + _finalizeTaprootInput( + inputIndex, + input, + tapLeafHashToFinalize, + finalScriptsFunc = bip371_1.tapScriptFinalizer, + ) { + if (!input.witnessUtxo) + throw new Error( + `Cannot finalize input #${inputIndex}. Missing withness utxo.`, + ); + // Check key spend first. Increased privacy and reduced block space. + if (input.tapKeySig) { + const payment = payments.p2tr({ + output: input.witnessUtxo.script, + signature: input.tapKeySig, + }); + const finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)( + payment.witness, + ); + this.data.updateInput(inputIndex, { finalScriptWitness }); + } else { + const { finalScriptWitness } = finalScriptsFunc( + inputIndex, + input, + tapLeafHashToFinalize, + ); + this.data.updateInput(inputIndex, { finalScriptWitness }); + } + this.data.clearFinalizedInput(inputIndex); + return this; + } getInputType(inputIndex) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); - const { script } = getScriptAndAmountFromUtxo( - inputIndex, - input, - this.__CACHE, - ); + const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); const result = getMeaningfulScript( script, inputIndex, @@ -354,6 +400,16 @@ class Psbt { return results.reduce((final, res) => res === true && final, true); } validateSignaturesOfInput(inputIndex, validator, pubkey) { + const input = this.data.inputs[inputIndex]; + if ((0, bip371_1.isTaprootInput)(input)) + return this.validateSignaturesOfTaprootInput( + inputIndex, + validator, + pubkey, + ); + return this._validateSignaturesOfInput(inputIndex, validator, pubkey); + } + _validateSignaturesOfInput(inputIndex, validator, pubkey) { const input = this.data.inputs[inputIndex]; const partialSig = (input || {}).partialSig; if (!input || !partialSig || partialSig.length < 1) @@ -368,20 +424,13 @@ class Psbt { let hashCache; let scriptCache; let sighashCache; - const scriptType = this.getInputType(inputIndex); for (const pSig of mySigs) { - const sig = isTaprootSpend(scriptType) - ? { - signature: pSig.signature, - hashType: transaction_1.Transaction.SIGHASH_DEFAULT, - } - : bscript.signature.decode(pSig.signature); + const sig = 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, ) @@ -394,6 +443,54 @@ class Psbt { } return results.every(res => res === true); } + validateSignaturesOfTaprootInput(inputIndex, validator, pubkey) { + const input = this.data.inputs[inputIndex]; + const tapKeySig = (input || {}).tapKeySig; + const tapScriptSig = (input || {}).tapScriptSig; + if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length)) + throw new Error('No signatures to validate'); + if (typeof validator !== 'function') + throw new Error('Need validator function to validate signatures'); + pubkey = pubkey && (0, bip371_1.toXOnly)(pubkey); + const allHashses = pubkey + ? getTaprootHashesForSig( + inputIndex, + input, + this.data.inputs, + pubkey, + this.__CACHE, + ) + : getAllTaprootHashesForSig( + inputIndex, + input, + this.data.inputs, + this.__CACHE, + ); + if (!allHashses.length) throw new Error('No signatures for this pubkey'); + const tapKeyHash = allHashses.find(h => !!h.leafHash); + if (tapKeySig && tapKeyHash) { + const isValidTapkeySig = validator( + tapKeyHash.pubkey, + tapKeyHash.hash, + tapKeySig, + ); + if (!isValidTapkeySig) return false; + } + if (tapScriptSig) { + for (const tapSig of tapScriptSig) { + const tapSigHash = allHashses.find(h => tapSig.pubkey.equals(h.pubkey)); + if (tapSigHash) { + const isValidTapScriptSig = validator( + tapSig.pubkey, + tapSigHash.hash, + tapSig.signature, + ); + if (!isValidTapScriptSig) return false; + } + } + } + return true; + } signAllInputsHD( hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], @@ -477,10 +574,7 @@ class Psbt { .catch(reject); }); } - signAllInputs( - keyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { + signAllInputs(keyPair, sighashTypes) { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); // TODO: Add a pubkey/pubkeyhash cache to each input @@ -500,10 +594,7 @@ class Psbt { } return this; } - signAllInputsAsync( - keyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { + signAllInputsAsync(keyPair, sighashTypes) { return new Promise((resolve, reject) => { if (!keyPair || !keyPair.publicKey) return reject(new Error('Need Signer to sign input')); @@ -532,13 +623,40 @@ class Psbt { }); }); } - signInput( + signInput(inputIndex, keyPair, sighashTypes) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); + if ((0, bip371_1.isTaprootInput)(input)) { + return this._signTaprootInput( + inputIndex, + input, + keyPair, + undefined, + sighashTypes, + ); + } + return this._signInput(inputIndex, keyPair, sighashTypes); + } + signTaprootInput(inputIndex, keyPair, tapLeafHashToSign, sighashTypes) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); + if ((0, bip371_1.isTaprootInput)(input)) + return this._signTaprootInput( + inputIndex, + input, + keyPair, + tapLeafHashToSign, + sighashTypes, + ); + throw new Error(`Input #${inputIndex} is not of type Taproot.`); + } + _signInput( inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], ) { - if (!keyPair || !keyPair.publicKey) - throw new Error('Need Signer to sign input'); const { hash, sighashType } = getHashAndSighashType( this.data.inputs, inputIndex, @@ -546,74 +664,191 @@ class Psbt { this.__CACHE, sighashTypes, ); - const scriptType = this.getInputType(inputIndex); - if (isTaprootSpend(scriptType)) { - if (!keyPair.signSchnorr) { - throw new Error( - `Need Schnorr Signer to sign taproot input #${inputIndex}.`, - ); - } - const partialSig = this.data.inputs[inputIndex].partialSig || []; - partialSig.push({ + 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 { + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); + return this; + } + _signTaprootInput( + inputIndex, + input, + keyPair, + tapLeafHashToSign, + allowedSighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT], + ) { + const hashesForSig = this.checkTaprootHashesForSig( + inputIndex, + input, + keyPair, + tapLeafHashToSign, + allowedSighashTypes, + ); + const tapKeySig = hashesForSig + .filter(h => !h.leafHash) + .map(h => + (0, bip371_1.serializeTaprootSignature)( + keyPair.signSchnorr(h.hash), + input.sighashType, + ), + )[0]; + const tapScriptSig = hashesForSig + .filter(h => !!h.leafHash) + .map(h => ({ + pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey), + signature: (0, bip371_1.serializeTaprootSignature)( + keyPair.signSchnorr(h.hash), + input.sighashType, + ), + leafHash: h.leafHash, + })); + if (tapKeySig) { + this.data.updateInput(inputIndex, { tapKeySig }); + } + if (tapScriptSig.length) { + this.data.updateInput(inputIndex, { tapScriptSig }); + } + return this; + } + signInputAsync(inputIndex, keyPair, sighashTypes) { + return Promise.resolve().then(() => { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); + if ((0, bip371_1.isTaprootInput)(input)) + return this._signTaprootInputAsync( + inputIndex, + input, + keyPair, + undefined, + sighashTypes, + ); + return this._signInputAsync(inputIndex, keyPair, sighashTypes); + }); + } + signTaprootInputAsync(inputIndex, keyPair, tapLeafHash, sighashTypes) { + return Promise.resolve().then(() => { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); + if ((0, bip371_1.isTaprootInput)(input)) + return this._signTaprootInputAsync( + inputIndex, + input, + keyPair, + tapLeafHash, + sighashTypes, + ); + throw new Error(`Input #${inputIndex} is not of type Taproot.`); + }); + } + _signInputAsync( + inputIndex, + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + return Promise.resolve(keyPair.sign(hash)).then(signature => { const partialSig = [ { pubkey: keyPair.publicKey, - signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + signature: bscript.signature.encode(signature, sighashType), }, ]; this.data.updateInput(inputIndex, { partialSig }); - } - return this; + }); } - signInputAsync( + async _signTaprootInputAsync( inputIndex, + input, keyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + tapLeafHash, + sighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT], ) { - return Promise.resolve().then(() => { - if (!keyPair || !keyPair.publicKey) - throw new Error('Need Signer to sign input'); - const { hash, sighashType } = getHashAndSighashType( - this.data.inputs, - inputIndex, - keyPair.publicKey, - this.__CACHE, - sighashTypes, - ); - const scriptType = this.getInputType(inputIndex); - 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 = 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; - }); - } - return Promise.resolve(keyPair.sign(hash)).then(signature => { - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(signature, sighashType), + const hashesForSig = this.checkTaprootHashesForSig( + inputIndex, + input, + keyPair, + tapLeafHash, + sighashTypes, + ); + const signaturePromises = []; + const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0]; + if (tapKeyHash) { + const tapKeySigPromise = Promise.resolve( + keyPair.signSchnorr(tapKeyHash.hash), + ).then(sig => { + return { + tapKeySig: (0, bip371_1.serializeTaprootSignature)( + sig, + input.sighashType, + ), + }; + }); + signaturePromises.push(tapKeySigPromise); + } + const tapScriptHashes = hashesForSig.filter(h => !!h.leafHash); + if (tapScriptHashes.length) { + const tapScriptSigPromises = tapScriptHashes.map(tsh => { + return Promise.resolve(keyPair.signSchnorr(tsh.hash)).then( + signature => { + const tapScriptSig = [ + { + pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey), + signature: (0, bip371_1.serializeTaprootSignature)( + signature, + input.sighashType, + ), + leafHash: tsh.leafHash, + }, + ]; + return { tapScriptSig }; }, - ]; - this.data.updateInput(inputIndex, { partialSig }); + ); }); + signaturePromises.push(...tapScriptSigPromises); + } + return Promise.all(signaturePromises).then(results => { + results.forEach(v => this.data.updateInput(inputIndex, v)); }); } + checkTaprootHashesForSig( + inputIndex, + input, + keyPair, + tapLeafHashToSign, + allowedSighashTypes, + ) { + if (typeof keyPair.signSchnorr !== 'function') + throw new Error( + `Need Schnorr Signer to sign taproot input #${inputIndex}.`, + ); + const hashesForSig = getTaprootHashesForSig( + inputIndex, + input, + this.data.inputs, + keyPair.publicKey, + this.__CACHE, + tapLeafHashToSign, + allowedSighashTypes, + ); + if (!hashesForSig || !hashesForSig.length) + throw new Error( + `Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString( + 'hex', + )}`, + ); + return hashesForSig; + } toBuffer() { checkCache(this.__CACHE); return this.data.toBuffer(); @@ -632,6 +867,11 @@ class Psbt { } updateInput(inputIndex, updateData) { if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); + (0, bip371_1.checkTaprootInputFields)( + this.data.inputs[inputIndex], + updateData, + 'updateInput', + ); this.data.updateInput(inputIndex, updateData); if (updateData.nonWitnessUtxo) { addNonWitnessTxCache( @@ -724,7 +964,6 @@ 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 }); @@ -757,23 +996,6 @@ function hasSigs(neededSigs, partialSig, pubkeys) { function isFinalized(input) { return !!input.finalScriptSig || !!input.finalScriptWitness; } -function isPaymentFactory(payment) { - return script => { - try { - payment({ output: script }); - return true; - } catch (err) { - return false; - } - }; -} -const isP2MS = isPaymentFactory(payments.p2ms); -const isP2PK = isPaymentFactory(payments.p2pk); -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; @@ -851,7 +1073,7 @@ function checkPartialSigSighashes(input) { }); } function checkScriptForPubkey(pubkey, script, action) { - if (!pubkeyInScript(pubkey, script)) { + if (!(0, psbtutils_1.pubkeyInScript)(pubkey, script)) { throw new Error( `Can not ${action} for this input with the key ${pubkey.toString('hex')}`, ); @@ -916,17 +1138,9 @@ 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, - isTapscript = false, -) { +function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) { const scriptType = classifyScript(script); - if (isTapscript || !canFinalize(input, script, scriptType)) + if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( script, @@ -953,9 +1167,13 @@ function prepareFinalScripts( const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); if (isSegwit) { if (p2wsh) { - finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness); + finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)( + p2wsh.witness, + ); } else { - finalScriptWitness = witnessStackToScriptWitness(payment.witness); + finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)( + payment.witness, + ); } if (p2sh) { finalScriptSig = p2sh.input; @@ -983,7 +1201,6 @@ function getHashAndSighashType( const { hash, sighashType, script } = getHashForSig( inputIndex, input, - inputs, cache, false, sighashTypes, @@ -994,24 +1211,11 @@ function getHashAndSighashType( sighashType, }; } -function getHashForSig( - inputIndex, - input, - inputs, - cache, - forValidate, - sighashTypes, -) { +function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { const unsignedTx = cache.__TX; const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; - if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { - const str = sighashTypeToString(sighashType); - throw new Error( - `Sighash type is not allowed. Retry the sign method passing the ` + - `sighashTypes array of whitelisted types. Sighash type: ${str}`, - ); - } + checkSighashTypeAllowed(sighashType, sighashTypes); let hash; let prevout; if (input.nonWitnessUtxo) { @@ -1049,7 +1253,7 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2WPKH(meaningfulScript)) { + } else if ((0, psbtutils_1.isP2WPKH)(meaningfulScript)) { // P2WPKH uses the P2PKH template for prevoutScript when signing const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) }) .output; @@ -1059,22 +1263,6 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(prevout.script)) { - 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)({ output: input.witnessScript }) - : undefined; - hash = unsignedTx.hashForWitnessV1( - inputIndex, - signingScripts, - values, - transaction_1.Transaction.SIGHASH_DEFAULT, - leafHash, - ); } else { // non-segwit if ( @@ -1107,6 +1295,89 @@ function getHashForSig( hash, }; } +function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) { + const allPublicKeys = []; + if (input.tapInternalKey) { + const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input); + allPublicKeys.push(outputKey); + } + if (input.tapScriptSig) { + const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey); + allPublicKeys.push(...tapScriptPubkeys); + } + const allHashes = allPublicKeys.map(pubicKey => + getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache), + ); + return allHashes.flat(); +} +function getTaprootHashesForSig( + inputIndex, + input, + inputs, + pubkey, + cache, + tapLeafHashToSign, + allowedSighashTypes, +) { + const unsignedTx = cache.__TX; + const sighashType = + input.sighashType || transaction_1.Transaction.SIGHASH_DEFAULT; + checkSighashTypeAllowed(sighashType, allowedSighashTypes); + 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 hashes = []; + if (input.tapInternalKey && !tapLeafHashToSign) { + const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input); + if ((0, bip371_1.toXOnly)(pubkey).equals(outputKey)) { + const tapKeyHash = unsignedTx.hashForWitnessV1( + inputIndex, + signingScripts, + values, + sighashType, + ); + hashes.push({ pubkey, hash: tapKeyHash }); + } + } + const tapLeafHashes = (input.tapLeafScript || []) + .filter(tapLeaf => (0, psbtutils_1.pubkeyInScript)(pubkey, tapLeaf.script)) + .map(tapLeaf => { + const hash = (0, taprootutils_1.tapleafHash)({ + output: tapLeaf.script, + version: tapLeaf.leafVersion, + }); + return Object.assign({ hash }, tapLeaf); + }) + .filter( + tapLeaf => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash), + ) + .map(tapLeaf => { + const tapScriptHash = unsignedTx.hashForWitnessV1( + inputIndex, + signingScripts, + values, + transaction_1.Transaction.SIGHASH_DEFAULT, + tapLeaf.hash, + ); + return { + pubkey, + hash: tapScriptHash, + leafHash: tapLeaf.hash, + }; + }); + return hashes.concat(tapLeafHashes); +} +function checkSighashTypeAllowed(sighashType, sighashTypes) { + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error( + `Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`, + ); + } +} function getPayment(script, scriptType, partialSig) { let payment; switch (scriptType) { @@ -1137,15 +1408,6 @@ function getPayment(script, scriptType, partialSig) { signature: partialSig[0].signature, }); break; - case 'taproot': - payment = payments.p2tr( - { - output: script, - signature: partialSig[0].signature, - }, - { validate: false }, - ); - break; } return payment; } @@ -1168,39 +1430,31 @@ function getScriptFromInput(inputIndex, input, cache) { const res = { script: null, isSegwit: false, - isTapscript: false, isP2SH: false, isP2WSH: false, }; - 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; - } + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript; if (input.witnessScript) { res.script = input.witnessScript; } else if (input.redeemScript) { res.script = input.redeemScript; } else { - res.script = utxoScript; + 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; + } } - const isTaproot = utxoScript && isP2TR(utxoScript); - // Segregated Witness versions 0 or 1 - if (input.witnessScript || isP2WPKH(res.script) || isTaproot) { + if (input.witnessScript || (0, psbtutils_1.isP2WPKH)(res.script)) { 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) { @@ -1288,28 +1542,6 @@ function sighashTypeToString(sighashType) { } return text; } -function witnessStackToScriptWitness(witness) { - let buffer = Buffer.allocUnsafe(0); - function writeSlice(slice) { - buffer = Buffer.concat([buffer, Buffer.from(slice)]); - } - function writeVarInt(i) { - const currentLen = buffer.length; - const varintLen = varuint.encodingLength(i); - buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); - varuint.encode(i, buffer, currentLen); - } - function writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - function writeVector(vector) { - writeVarInt(vector.length); - vector.forEach(writeVarSlice); - } - writeVector(witness); - return buffer; -} function addNonWitnessTxCache(cache, input, inputIndex) { cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo); @@ -1371,6 +1603,10 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) { } return c[inputIndex]; } +function getScriptFromUtxo(inputIndex, input, cache) { + const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); + return script; +} function getScriptAndAmountFromUtxo(inputIndex, input, cache) { if (input.witnessUtxo !== undefined) { return { @@ -1390,7 +1626,7 @@ function getScriptAndAmountFromUtxo(inputIndex, input, cache) { } } function pubkeyInInput(pubkey, input, inputIndex, cache) { - const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); + const script = getScriptFromUtxo(inputIndex, input, cache); const { meaningfulScript } = getMeaningfulScript( script, inputIndex, @@ -1398,7 +1634,7 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) { input.redeemScript, input.witnessScript, ); - return pubkeyInScript(pubkey, meaningfulScript); + return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript); } function pubkeyInOutput(pubkey, output, outputIndex, cache) { const script = cache.__TX.outs[outputIndex].script; @@ -1409,7 +1645,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { output.redeemScript, output.witnessScript, ); - return pubkeyInScript(pubkey, meaningfulScript); + return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript); } function redeemFromFinalScriptSig(finalScript) { if (!finalScript) return; @@ -1457,10 +1693,10 @@ function getMeaningfulScript( redeemScript, witnessScript, ) { - const isP2SH = isP2SHScript(script); - const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); - const isP2WSH = isP2WSHScript(script); - const isP2TRScript = isP2TR(script); + const isP2SH = (0, psbtutils_1.isP2SHScript)(script); + const isP2SHP2WSH = + isP2SH && redeemScript && (0, psbtutils_1.isP2WSHScript)(redeemScript); + const isP2WSH = (0, psbtutils_1.isP2WSHScript)(script); if (isP2SH && redeemScript === undefined) throw new Error('scriptPubkey is P2SH but redeemScript missing'); if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) @@ -1480,9 +1716,6 @@ 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; } @@ -1494,41 +1727,22 @@ function getMeaningfulScript( ? 'p2sh' : isP2WSH ? 'p2wsh' - : isP2TRScript - ? 'p2tr' : 'raw', }; } function checkInvalidP2WSH(script) { - if (isP2WPKH(script) || isP2SHScript(script)) { + if ( + (0, psbtutils_1.isP2WPKH)(script) || + (0, psbtutils_1.isP2SHScript)(script) + ) { throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); } } -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) || - element.equals(pubkeyXOnly) - ); - }); -} -function isTaprootSpend(scriptType) { - return ( - !!scriptType && (scriptType === 'taproot' || scriptType.startsWith('p2tr-')) - ); -} 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)) return 'taproot'; + if ((0, psbtutils_1.isP2WPKH)(script)) return 'witnesspubkeyhash'; + if ((0, psbtutils_1.isP2PKH)(script)) return 'pubkeyhash'; + if ((0, psbtutils_1.isP2MS)(script)) return 'multisig'; + if ((0, psbtutils_1.isP2PK)(script)) return 'pubkey'; return 'nonstandard'; } function range(n) { diff --git a/src/psbt/bip371.d.ts b/src/psbt/bip371.d.ts new file mode 100644 index 000000000..9545e899a --- /dev/null +++ b/src/psbt/bip371.d.ts @@ -0,0 +1,19 @@ +/// +import { PsbtInput } from 'bip174/src/lib/interfaces'; +export declare const toXOnly: (pubKey: Buffer) => Buffer; +/** + * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided. + * Otherwise it will search for the tapleaf that has at least one signature and has the shortest path. + * @param inputIndex the position of the PSBT input. + * @param input the PSBT input. + * @param tapLeafHashToFinalize optional, if provided the finalizer will search for a tapleaf that has this hash + * and will try to build the finalScriptWitness. + * @returns the finalScriptWitness or throws an exception if no tapleaf found. + */ +export declare function tapScriptFinalizer(inputIndex: number, input: PsbtInput, tapLeafHashToFinalize?: Buffer): { + finalScriptWitness: Buffer | undefined; +}; +export declare function serializeTaprootSignature(sig: Buffer, sighashType?: number): Buffer; +export declare function isTaprootInput(input: PsbtInput): boolean; +export declare function checkTaprootInputFields(inputData: PsbtInput, newInputData: PsbtInput, action: string): void; +export declare function tweakInternalPubKey(inputIndex: number, input: PsbtInput): Buffer; diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js new file mode 100644 index 000000000..115235acf --- /dev/null +++ b/src/psbt/bip371.js @@ -0,0 +1,181 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.tweakInternalPubKey = exports.checkTaprootInputFields = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; +const psbtutils_1 = require('./psbtutils'); +const taprootutils_1 = require('../payments/taprootutils'); +const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); +exports.toXOnly = toXOnly; +/** + * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided. + * Otherwise it will search for the tapleaf that has at least one signature and has the shortest path. + * @param inputIndex the position of the PSBT input. + * @param input the PSBT input. + * @param tapLeafHashToFinalize optional, if provided the finalizer will search for a tapleaf that has this hash + * and will try to build the finalScriptWitness. + * @returns the finalScriptWitness or throws an exception if no tapleaf found. + */ +function tapScriptFinalizer(inputIndex, input, tapLeafHashToFinalize) { + const tapLeaf = findTapLeafToFinalize( + input, + inputIndex, + tapLeafHashToFinalize, + ); + try { + const sigs = sortSignatures(input, tapLeaf); + const witness = sigs.concat(tapLeaf.script).concat(tapLeaf.controlBlock); + return { + finalScriptWitness: (0, psbtutils_1.witnessStackToScriptWitness)(witness), + }; + } catch (err) { + throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`); + } +} +exports.tapScriptFinalizer = tapScriptFinalizer; +function serializeTaprootSignature(sig, sighashType) { + const sighashTypeByte = sighashType + ? Buffer.from([sighashType]) + : Buffer.from([]); + return Buffer.concat([sig, sighashTypeByte]); +} +exports.serializeTaprootSignature = serializeTaprootSignature; +function isTaprootInput(input) { + return ( + input && + !!( + input.tapInternalKey || + input.tapMerkleRoot || + (input.tapLeafScript && input.tapLeafScript.length) || + (input.tapBip32Derivation && input.tapBip32Derivation.length) || + (input.witnessUtxo && (0, psbtutils_1.isP2TR)(input.witnessUtxo.script)) + ) + ); +} +exports.isTaprootInput = isTaprootInput; +function checkTaprootInputFields(inputData, newInputData, action) { + checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action); + checkIfTapLeafInTree(inputData, newInputData, action); +} +exports.checkTaprootInputFields = checkTaprootInputFields; +function tweakInternalPubKey(inputIndex, input) { + const tapInternalKey = input.tapInternalKey; + const outputKey = + tapInternalKey && + (0, taprootutils_1.tweakKey)(tapInternalKey, input.tapMerkleRoot); + if (!outputKey) + throw new Error( + `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey && + tapInternalKey.toString('hex')}`, + ); + return outputKey.x; +} +exports.tweakInternalPubKey = tweakInternalPubKey; +function checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action) { + const isBadTaprootUpdate = + isTaprootInput(inputData) && hasNonTaprootInputFields(newInputData); + const isBadNonTaprootUpdate = + hasNonTaprootInputFields(inputData) && isTaprootInput(newInputData); + const hasMixedFields = + inputData === newInputData && + (isTaprootInput(newInputData) && hasNonTaprootInputFields(newInputData)); + if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) + throw new Error( + `Invalid arguments for Psbt.${action}. ` + + `Cannot use both taproot and non-taproot fields.`, + ); +} +function checkIfTapLeafInTree(inputData, newInputData, action) { + if (newInputData.tapMerkleRoot) { + const newLeafsInTree = (newInputData.tapLeafScript || []).every(l => + isTapLeafInTree(l, newInputData.tapMerkleRoot), + ); + const oldLeafsInTree = (inputData.tapLeafScript || []).every(l => + isTapLeafInTree(l, newInputData.tapMerkleRoot), + ); + if (!newLeafsInTree || !oldLeafsInTree) + throw new Error( + `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`, + ); + } else if (inputData.tapMerkleRoot) { + const newLeafsInTree = (newInputData.tapLeafScript || []).every(l => + isTapLeafInTree(l, inputData.tapMerkleRoot), + ); + if (!newLeafsInTree) + throw new Error( + `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`, + ); + } +} +function isTapLeafInTree(tapLeaf, merkleRoot) { + if (!merkleRoot) return true; + const leafHash = (0, taprootutils_1.tapleafHash)({ + output: tapLeaf.script, + version: tapLeaf.leafVersion, + }); + const rootHash = (0, taprootutils_1.rootHashFromPath)( + tapLeaf.controlBlock, + leafHash, + ); + return rootHash.equals(merkleRoot); +} +function sortSignatures(input, tapLeaf) { + const leafHash = (0, taprootutils_1.tapleafHash)({ + output: tapLeaf.script, + version: tapLeaf.leafVersion, + }); + return (input.tapScriptSig || []) + .filter(tss => tss.leafHash.equals(leafHash)) + .map(tss => addPubkeyPositionInScript(tapLeaf.script, tss)) + .sort((t1, t2) => t2.positionInScript - t1.positionInScript) + .map(t => t.signature); +} +function addPubkeyPositionInScript(script, tss) { + return Object.assign( + { + positionInScript: (0, psbtutils_1.pubkeyPositionInScript)( + tss.pubkey, + script, + ), + }, + tss, + ); +} +/** + * Find tapleaf by hash, or get the signed tapleaf with the shortest path. + */ +function findTapLeafToFinalize(input, inputIndex, leafHashToFinalize) { + if (!input.tapScriptSig || !input.tapScriptSig.length) + throw new Error( + `Can not finalize taproot input #${inputIndex}. No tapleaf script signature provided.`, + ); + const tapLeaf = (input.tapLeafScript || []) + .sort((a, b) => a.controlBlock.length - b.controlBlock.length) + .find(leaf => + canFinalizeLeaf(leaf, input.tapScriptSig, leafHashToFinalize), + ); + if (!tapLeaf) + throw new Error( + `Can not finalize taproot input #${inputIndex}. Signature for tapleaf script not found.`, + ); + return tapLeaf; +} +function canFinalizeLeaf(leaf, tapScriptSig, hash) { + const leafHash = (0, taprootutils_1.tapleafHash)({ + output: leaf.script, + version: leaf.leafVersion, + }); + const whiteListedHash = !hash || hash.equals(leafHash); + return ( + whiteListedHash && + tapScriptSig.find(tss => tss.leafHash.equals(leafHash)) !== undefined + ); +} +function hasNonTaprootInputFields(input) { + return ( + input && + !!( + input.redeemScript || + input.witnessScript || + (input.bip32Derivation && input.bip32Derivation.length) + ) + ); +} diff --git a/src/psbt/psbtutils.d.ts b/src/psbt/psbtutils.d.ts new file mode 100644 index 000000000..c902e6cec --- /dev/null +++ b/src/psbt/psbtutils.d.ts @@ -0,0 +1,11 @@ +/// +export declare const isP2MS: (script: Buffer) => boolean; +export declare const isP2PK: (script: Buffer) => boolean; +export declare const isP2PKH: (script: Buffer) => boolean; +export declare const isP2WPKH: (script: Buffer) => boolean; +export declare const isP2WSHScript: (script: Buffer) => boolean; +export declare const isP2SHScript: (script: Buffer) => boolean; +export declare const isP2TR: (script: Buffer) => boolean; +export declare function witnessStackToScriptWitness(witness: Buffer[]): Buffer; +export declare function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number; +export declare function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean; diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js new file mode 100644 index 000000000..4ef56f6a5 --- /dev/null +++ b/src/psbt/psbtutils.js @@ -0,0 +1,66 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.pubkeyInScript = exports.pubkeyPositionInScript = exports.witnessStackToScriptWitness = exports.isP2TR = exports.isP2SHScript = exports.isP2WSHScript = exports.isP2WPKH = exports.isP2PKH = exports.isP2PK = exports.isP2MS = void 0; +const varuint = require('bip174/src/lib/converter/varint'); +const bscript = require('../script'); +const crypto_1 = require('../crypto'); +const payments = require('../payments'); +function isPaymentFactory(payment) { + return script => { + try { + payment({ output: script }); + return true; + } catch (err) { + return false; + } + }; +} +exports.isP2MS = isPaymentFactory(payments.p2ms); +exports.isP2PK = isPaymentFactory(payments.p2pk); +exports.isP2PKH = isPaymentFactory(payments.p2pkh); +exports.isP2WPKH = isPaymentFactory(payments.p2wpkh); +exports.isP2WSHScript = isPaymentFactory(payments.p2wsh); +exports.isP2SHScript = isPaymentFactory(payments.p2sh); +exports.isP2TR = isPaymentFactory(payments.p2tr); +function witnessStackToScriptWitness(witness) { + let buffer = Buffer.allocUnsafe(0); + function writeSlice(slice) { + buffer = Buffer.concat([buffer, Buffer.from(slice)]); + } + function writeVarInt(i) { + const currentLen = buffer.length; + const varintLen = varuint.encodingLength(i); + buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); + varuint.encode(i, buffer, currentLen); + } + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); + } + function writeVector(vector) { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + writeVector(witness); + return buffer; +} +exports.witnessStackToScriptWitness = witnessStackToScriptWitness; +function pubkeyPositionInScript(pubkey, script) { + const pubkeyHash = (0, crypto_1.hash160)(pubkey); + const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? + const decompiled = bscript.decompile(script); + if (decompiled === null) throw new Error('Unknown script error'); + return decompiled.findIndex(element => { + if (typeof element === 'number') return false; + return ( + element.equals(pubkey) || + element.equals(pubkeyHash) || + element.equals(pubkeyXOnly) + ); + }); +} +exports.pubkeyPositionInScript = pubkeyPositionInScript; +function pubkeyInScript(pubkey, script) { + return pubkeyPositionInScript(pubkey, script) !== -1; +} +exports.pubkeyInScript = pubkeyInScript; diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index 5a78e3527..7bce10812 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -279,8 +279,8 @@ "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=", + "description": "Sign PSBT with 3 inputs [P2PKH, P2TR (key-path), 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+p90ZfGGstEZ83LyfTLDFcfi2OlxAyuARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMgAAAA==", "isTaproot": true, "keys": [ { @@ -289,18 +289,18 @@ }, { "inputToSign": 1, - "WIF": "cNPzVNoVCAfNEadTExqN2HzfC4dX42RtduE39D2i7cxuVEKY3DM3" + "WIF": "cR62L1G154fjHFrBCJMxJxbk1rcxhT2xcTh7WstvFdFDsZ9uFiVj" }, { "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=" + "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+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==" }, { "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", + "psbt": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15iFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA=", "isTaproot": true, "keys": [ { @@ -316,7 +316,32 @@ "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" + "result": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15BFDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nrrqI0FdZdKILLLZgRVK8L9Bn2ijU6IcoqqyImKIWt3MtAA3alBoU7IBCkBk9OHD1wE8fJI4y+lbnTRj48e8AAwRM77q3Rml679qCzGvEAKAs99UNMaXHQIhgGfRP11AMlJkEUj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WuojQV1l0ogsstmBFUrwv0GfaKNTohyiqrIiYoha3cy0DYJZ6Lv7FZPIBRZFfVgF5v3gcRiQnT8aM82Q5IPkwkzZrGo4ThZblvunG/+hu8ZPuJrUU+uXb+s9rcwSH+BihIQRSoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641cq6iNBXWXSiCyy2YEVSvC/QZ9oo1OiHKKqsiJiiFrdzLQEfQ5UkAg4lTbhJxjMzB7hu6ad1fywYxHCXjFXHHrm5PJTOFJLg2oTnwuQToz/Z2AW/UET7Op+WSoHZvW4tzzLhiFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA=" + }, + { + "description": "Sign PSBT with 1 input [P2TR]. Signer pubkey found in two tapleaf scripts (both get signed)", + "psbt": "cHNidP8BAF4CAAAAATNOP+fCPDCsZrgGIptDVbwY/yrkM2xaFfLe05BSLulZAAAAAAD/////AZBBBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaQAAAAAAAEBK6BoBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaRiFcDXgjtX0c88beKd6Y8zRYbANZXEc8dDhlup8v3SCiX0bBpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWWeEQK6JF8Ev4E8Yg25gvDzbizKAO6m6JD082gFjsWNVpIP1IuUG/pcYMyG7KOHraqMXMKKOJb5eIpnjGt7LrvcsWrCDxKlbBaB54iRPj/r6t5N9DtBFItV8uoHfEwGboXpVsHLogt01J+NwSsj7PM7m6QbLDiXLdoYzn9pHNA4kAGF1pL3+6U5zAQhXA14I7V9HPPG3inemPM0WGwDWVxHPHQ4ZbqfL90gol9Gx4Lk64Mk5QMzz2Xo97sRZFcGH0TduAOb6H0hHOB/0CrCMg/Ui5Qb+lxgzIbso4etqoxcwoo4lvl4imeMa3suu9yxaswAAA", + "isTaproot": true, + "keys": [ + { + "inputToSign": 0, + "WIF": "cSu5bjn9TsAEeqZxEFKy5of3FKfHha6FT56KvfAGmu6rMmPNJTrV" + } + ], + "result": "cHNidP8BAF4CAAAAATNOP+fCPDCsZrgGIptDVbwY/yrkM2xaFfLe05BSLulZAAAAAAD/////AZBBBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaQAAAAAAAEBK6BoBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaRBFP1IuUG/pcYMyG7KOHraqMXMKKOJb5eIpnjGt7LrvcsWIjJncH2zqZGpUloPoJZipi/NkKmjWthVrvnedXPOOOdABNUCXyGRAy4nthiGpE8dXioP2P50J+fU5gojlqunTwPKRPvqWcCzOoaJGvUEF+oxpMXZ1+FZWHRppoeoZFParUEU/Ui5Qb+lxgzIbso4etqoxcwoo4lvl4imeMa3suu9yxZZ4RArokXwS/gTxiDbmC8PNuLMoA7qbokPTzaAWOxY1UAF3RzlWz+5cWYG1EqfZTT/CO7O3hGYvMMjlJV5rluR916WCgGYO2hHj9fiEH0rxD3BRbzR78PShCen+beqDncSYhXA14I7V9HPPG3inemPM0WGwDWVxHPHQ4ZbqfL90gol9GwaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1lnhECuiRfBL+BPGINuYLw824sygDupuiQ9PNoBY7FjVaSD9SLlBv6XGDMhuyjh62qjFzCijiW+XiKZ4xrey673LFqwg8SpWwWgeeIkT4/6+reTfQ7QRSLVfLqB3xMBm6F6VbBy6ILdNSfjcErI+zzO5ukGyw4ly3aGM5/aRzQOJABhdaS9/ulOcwEIVwNeCO1fRzzxt4p3pjzNFhsA1lcRzx0OGW6ny/dIKJfRseC5OuDJOUDM89l6Pe7EWRXBh9E3bgDm+h9IRzgf9AqwjIP1IuUG/pcYMyG7KOHraqMXMKKOJb5eIpnjGt7LrvcsWrMAAAA==" + }, + { + "description": "Sign PSBT with 1 input [P2TR]. Signer pubkey found in two tapleaf scripts (sign only the matching tapleaf hash)", + "psbt": "cHNidP8BAF4CAAAAAdXMkUOLeYvgm981k3T7Pmdf5Dr31jOxvpFHiXiU9D2gAAAAAAD/////AZBBBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqwAAAAAAAEBK6BoBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqxiFcAI1JOUM3VATBJC28HI1B5zKQNd5N4/c4g7M1CYhZWnWhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWk4qTbS/aPOMicn4cednnrKiVhdggWbZxsQhaXXg5/EtpICo/Wsfk0hSOCy21lUyn+vXnc1SG1lNVWHlnXCh3xegqrCCcumXmHlTmveMSBLOPSfb6H3laX97tZtF4gqmxzV+gbLogXBeUvuJ0b/mMOa3539xiz0/Nf9RQCWGZXXoCNcFUYde6U5zAQhXACNSTlDN1QEwSQtvByNQecykDXeTeP3OIOzNQmIWVp1pfFb/yNVlibfG8oN35ON4kJ+kBZQ4LphUz16+7jituPiMgKj9ax+TSFI4LLbWVTKf69edzVIbWU1VYeWdcKHfF6CqswAAA", + "isTaproot": true, + "keys": [ + { + "inputToSign": 0, + "tapLeafHashToSign": "f2d9fd9a2f80e0e7abeac881398fc37198f46e5c802ec00c95152aa6f703e71e", + "WIF": "cP76Rzf6bVcmFbnJ3DigWvyNvki2bZeXxoq3B5pcZ8zVRnT4fKdx" + } + ], + "result": "cHNidP8BAF4CAAAAAdXMkUOLeYvgm981k3T7Pmdf5Dr31jOxvpFHiXiU9D2gAAAAAAD/////AZBBBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqwAAAAAAAEBK6BoBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqxBFCo/Wsfk0hSOCy21lUyn+vXnc1SG1lNVWHlnXCh3xegq8tn9mi+A4Oer6siBOY/DcZj0blyALsAMlRUqpvcD5x5Aa7X0m4UCLaHA/Vnjkl+if6rVeiBbIlbHXHLh7RqJyB8Wgs66p6/ZnwSW/HD6o7rMHffIva+jgJgYWf6MvzrfTWIVwAjUk5QzdUBMEkLbwcjUHnMpA13k3j9ziDszUJiFladaGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdaTipNtL9o84yJyfhx52eesqJWF2CBZtnGxCFpdeDn8S2kgKj9ax+TSFI4LLbWVTKf69edzVIbWU1VYeWdcKHfF6CqsIJy6ZeYeVOa94xIEs49J9vofeVpf3u1m0XiCqbHNX6BsuiBcF5S+4nRv+Yw5rfnf3GLPT81/1FAJYZldegI1wVRh17pTnMBCFcAI1JOUM3VATBJC28HI1B5zKQNd5N4/c4g7M1CYhZWnWl8Vv/I1WWJt8byg3fk43iQn6QFlDgumFTPXr7uOK24+IyAqP1rH5NIUjgsttZVMp/r153NUhtZTVVh5Z1wod8XoKqzAAAA=" } ], "combiner": [ @@ -341,8 +366,39 @@ "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==", + "description": "Finalze taproot key-path", + "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==", + "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAEHakcwRAIgaVgpQcm7R73l5IQIE4hPEEnMzCbPDBrh+CzkjUQOrPwCIFpWwd8STrSSLLM49amf8IQOYyvcJ083mcw5VbP4r6tnASECLmi8EfXEugbE+q0WHjzBQSmcz5K3VJs/NHA7uQE0o5AAAQEruAUBAAAAAAAiUSCUIec0sPnSxGfqfdGXxhrLRGfNy8n0ywxXH4tjpcQMrgEIQgFAne/58vkK3le7qwVija6EFgr52XM8JFNJC2YirCUI7X7Sh1lzJJ37Y7dXeCumj2B+3AV84Za1BfthqL42C3HGXgABAR8QJwAAAAAAABYAFE+irKEotvne5TCcT3AFHknGX9cyAQhsAkgwRQIhAKAiJLYIS+eYrjAJpM8GCc2/ofqpjXsGV8QMf9Ojm8SEAiBCwrAc/8HdsD5ZyW9uzpbsTJEz5wshwNgvksR4l/xbzwEhA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660AAAA", + "isTaproot": true + }, + { + "description": "Finalze taproot script-path", + "psbt": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15BFDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nrrqI0FdZdKILLLZgRVK8L9Bn2ijU6IcoqqyImKIWt3MtAA3alBoU7IBCkBk9OHD1wE8fJI4y+lbnTRj48e8AAwRM77q3Rml679qCzGvEAKAs99UNMaXHQIhgGfRP11AMlJkEUj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WuojQV1l0ogsstmBFUrwv0GfaKNTohyiqrIiYoha3cy0DYJZ6Lv7FZPIBRZFfVgF5v3gcRiQnT8aM82Q5IPkwkzZrGo4ThZblvunG/+hu8ZPuJrUU+uXb+s9rcwSH+BihIQRSoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641cq6iNBXWXSiCyy2YEVSvC/QZ9oo1OiHKKqsiJiiFrdzLQEfQ5UkAg4lTbhJxjMzB7hu6ad1fywYxHCXjFXHHrm5PJTOFJLg2oTnwuQToz/Z2AW/UET7Op+WSoHZvW4tzzLhiFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA=", + "result": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS14BCP2PAQVAR9DlSQCDiVNuEnGMzMHuG7pp3V/LBjEcJeMVcceubk8lM4UkuDahOfC5BOjP9nYBb9QRPs6n5ZKgdm9bi3PMuEADdqUGhTsgEKQGT04cPXATx8kjjL6VudNGPjx7wADBEzvurdGaXrv2oLMa8QAoCz31Q0xpcdAiGAZ9E/XUAyUmQNglnou/sVk8gFFkV9WAXm/eBxGJCdPxozzZDkg+TCTNmsajhOFluW+6cb/6G7xk+4mtRT65dv6z2tzBIf4GKEhoII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5xhwWa5A/izV2zsom0w6Hh9PDVAKDGRZQDcVQC5TMofOMO+GlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1gAA", + "isTaproot": true + }, + { + "description": "Finalze taproot script-path (3-of-3)", + "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0ADaUubfpFFrzbU+vL8qCzZE/FO+9unzylfpIgQZ4HTy2qPUtLvbyH59GApdz0SiUZGl8K6Crvt9YIfI/5FxbOLARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==", + "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAEHakcwRAIgaVgpQcm7R73l5IQIE4hPEEnMzCbPDBrh+CzkjUQOrPwCIFpWwd8STrSSLLM49amf8IQOYyvcJ083mcw5VbP4r6tnASECLmi8EfXEugbE+q0WHjzBQSmcz5K3VJs/NHA7uQE0o5AAAQEruAUBAAAAAAAiUSCUIec0sPnSxGfqfdGXxhrLRGfNy8n0ywxXH4tjpcQMrgEIQgFAA2lLm36RRa821Pry/Kgs2RPxTvvbp88pX6SIEGeB08tqj1LS728h+fRgKXc9EolGRpfCugq77fWCHyP+RcWziwABAR8QJwAAAAAAABYAFE+irKEotvne5TCcT3AFHknGX9cyAQhsAkgwRQIhAKAiJLYIS+eYrjAJpM8GCc2/ofqpjXsGV8QMf9Ojm8SEAiBCwrAc/8HdsD5ZyW9uzpbsTJEz5wshwNgvksR4l/xbzwEhA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660AAAA", + "isTaproot": true + }, + { + "description": "Finalze taproot with tapkey sig and tapscript sigs (choose tapkey)", + "psbt": "cHNidP8BAF4CAAAAAQrtX/VtEfTwY2iXi+s8lzx2JZbV7w9a8q6lONJ4SBm1AAAAAAD/////AZBBBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kAAAAAAAEBK6BoBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kBE0DqpLv0n1NC7N2okYWhHwP+mCqaDGOmNGCobgPw54CAtJdHPKY2i/ioNzq/m2Muh0hUYcLlOcU5U4xF7W+6gvRuQRQGAr5sFRVLM9rtDJmH/nCNJu0u72GXSA2wf9k4GEnXrossGVn91s+ghIb0v5fVWfWjsqU6wRJBundeShgEvDIVQNduanOw6c03gjyLWLim7b/7yet7XRp1uNrtwrMklOMSujYI/h2o+TSqS7eNwCkNddW3R6v03L6S9hWHTUYprclBFDhs6fMvKmERoTq2yrBafaRMmYoKkSuw9D4o3xjiC403iywZWf3Wz6CEhvS/l9VZ9aOypTrBEkG6d15KGAS8MhVAMKZJmsPn+pjmlWzj5oPGOj8XSs0uCMnDL1jjnosEuO7G0y9Y46GlGnfOM54w/0n2qKkdDWHpXX14F9gNesiP5EEUfKybjQL7rXdJwSP1Fb15LaJyYFrW3yJnkN+aP9IdhTOGRF1EGo0fkg5hbe3o7iwS+jTgqmGOQDsHExHbJgmL90A8hHY8WzI+xJsFXMzi5ztkZS8pXf07iNlfDnLKZYyk9ZRBkAsJ9oYsvC19Irrq27l918aqfQc6sZBiskZyMs5wQRR8rJuNAvutd0nBI/UVvXktonJgWtbfImeQ35o/0h2FM4ssGVn91s+ghIb0v5fVWfWjsqU6wRJBundeShgEvDIVQFeVntO1MSRANCT3fpEWhijVJAbF6SVBdPlNAaRKc1sn3Gz+/HTfD+f9sqRlSIsF4D+ouhv6URBtAZUCuKHW6vxiFcFB6oQ4lfHrNv47TKgSz/MUn2hgozxlIgmi2demetlGgxpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWhkRdRBqNH5IOYW3t6O4sEvo04KphjkA7BxMR2yYJi/dpIHysm40C+613ScEj9RW9eS2icmBa1t8iZ5Dfmj/SHYUzrCAGAr5sFRVLM9rtDJmH/nCNJu0u72GXSA2wf9k4GEnXrrogOGzp8y8qYRGhOrbKsFp9pEyZigqRK7D0PijfGOILjTe6U5zAQhXBQeqEOJXx6zb+O0yoEs/zFJ9oYKM8ZSIJotnXpnrZRoMvbQYwKuRcSf7lDrRMU3H56Gh6NeGkogAw4hpvAn/mgCMgfKybjQL7rXdJwSP1Fb15LaJyYFrW3yJnkN+aP9IdhTOswAEXIEHqhDiV8es2/jtMqBLP8xSfaGCjPGUiCaLZ16Z62UaDARggNhmyJFW2/EeankNIJjRP9QFlju4/j/nEEcMAnlXe+XcAAA==", + "result": "cHNidP8BAF4CAAAAAQrtX/VtEfTwY2iXi+s8lzx2JZbV7w9a8q6lONJ4SBm1AAAAAAD/////AZBBBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kAAAAAAAEBK6BoBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kBCEIBQOqku/SfU0Ls3aiRhaEfA/6YKpoMY6Y0YKhuA/DngIC0l0c8pjaL+Kg3Or+bYy6HSFRhwuU5xTlTjEXtb7qC9G4AAA==", + "isTaproot": true + }, + { + "description": "Finalze taproot script-path with two tapleafs signed (chooses the one with the shortest path)", + "psbt": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzRBFAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2Fc2gpbUnze59ZSHmflF7erkp8eUGBfJBmAcUZG5WBbC/pAJihlaGGDKsSd04fSyIk5WDACUedUvDE1C4zD80ubbX5qFzgxizY7pGG5zrrGAoW0KOEJ8TW4WEmh/WsE0erDxEEUIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRsRCNvHrz3TPp4Id2O6W4qhmerk87mqanTdLIapgqEamkDnpOB240Ce+yZlOPO3sxZDmpXhmsxtIl6a/R6TS2ekf2VCdZXg4Dqy3OpjzxQ/jDMbXbzETL2mdHlsgQnBnUcoYhXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiIyAFH+vL8eawH1sMTKmTXHtjlVapXblzjQXJtiRDwE9hXKzAohXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrq0JN6gn4QLkyoANzzc29JWULjDrP5UqfSmQaKGchuNJtrHlXZrvaHq6qReW/oKlQ/dX0xKraWx8wgu3JaJuf0K7iijuCxK7L69N4En3IjdGeHqk+26eie3VOHIgdMIh0q9q7FEmRK2sbmE7NhWpbGiU/lLBm6XT+D9HvPO+T63rSMgIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRuswAAA", + "result": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQBCMcDQCYoZWhhgyrEndOH0siJOVgwAlHnVLwxNQuMw/NLm21+ahc4MYs2O6Rhuc66xgKFtCjhCfE1uFhJof1rBNHqw8QiIAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2FcrGHBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiAAA=", + "isTaproot": true + }, + { + "description": "Finalze taproot script-path with two tapleafs signed (explicitly choose leaf)", + "psbt": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzRBFAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2Fc2gpbUnze59ZSHmflF7erkp8eUGBfJBmAcUZG5WBbC/pAJihlaGGDKsSd04fSyIk5WDACUedUvDE1C4zD80ubbX5qFzgxizY7pGG5zrrGAoW0KOEJ8TW4WEmh/WsE0erDxEEUIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRsRCNvHrz3TPp4Id2O6W4qhmerk87mqanTdLIapgqEamkDnpOB240Ce+yZlOPO3sxZDmpXhmsxtIl6a/R6TS2ekf2VCdZXg4Dqy3OpjzxQ/jDMbXbzETL2mdHlsgQnBnUcoYhXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiIyAFH+vL8eawH1sMTKmTXHtjlVapXblzjQXJtiRDwE9hXKzAohXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrq0JN6gn4QLkyoANzzc29JWULjDrP5UqfSmQaKGchuNJtrHlXZrvaHq6qReW/oKlQ/dX0xKraWx8wgu3JaJuf0K7iijuCxK7L69N4En3IjdGeHqk+26eie3VOHIgdMIh0q9q7FEmRK2sbmE7NhWpbGiU/lLBm6XT+D9HvPO+T63rSMgIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRuswAAA", + "result": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQBCMcDQCYoZWhhgyrEndOH0siJOVgwAlHnVLwxNQuMw/NLm21+ahc4MYs2O6Rhuc66xgKFtCjhCfE1uFhJof1rBNHqw8QiIAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2FcrGHBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiAAA=", "isTaproot": true } ], @@ -387,6 +443,51 @@ "index": 2 }, "equals": "cHNidP8BADMCAAAAAQABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4PAgAAAAD/////AAAAAAAAAAA=" + }, + { + "description": "checks for mixed taproot and non-taproot fields", + "inputData": { + "hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')", + "index": 0, + "redeemScript": "Buffer.from('0014000102030405060708090a0b0c0d0e0f00010000', 'hex')", + "tapInternalKey": "Buffer.from('000102030405060708090a0b0c0d0e0f00010203040506070000000000000000', 'hex')" + }, + "exception": "Invalid arguments for Psbt.addInput. Cannot use both taproot and non-taproot fields." + }, + { + "description": "checks for tapleaf in taptree", + "inputData": { + "hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')", + "index": 0, + "tapMerkleRoot": "Buffer.from('5cf5873456b400364b18bdc9a1a233870fa622a6010deef1d4e08474f3a103b4', 'hex')", + "tapLeafScript": [ + { + "leafVersion": 192, + "script": "Buffer.from('20d5e347235eba74ae0cec686b668d5a9432f45a555d6ab22cebf5974bde9dc4f3ac', 'hex')", + "controlBlock": "Buffer.from('c0720768b9946ac22371653d92cc343bf109b8a3f819e231159fd4f2b1328944ecb424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0aee28a3b82c4aecbebd378127dc88dd19e1ea93edba7a27b754e1c881d308874a7ac4489544b7b840d045599ec9415ae71ccb090c2c465c05c9fa05f41c7a06d5', 'hex')" + } + ] + }, + "exception": "Invalid arguments for Psbt.addInput. Tapleaf not part of taptree." + } + ] + }, + "updateInput": { + "checks": [ + { + "description": "checks for new tapleaf in taptree", + "psbt": "cHNidP8BADMCAAAAAQ1YzlMrOEvTUad+B8zippgoNUwrXhwvsHMMGwZb6S7LAAAAAAD/////AAAAAAAAAQEroGgGAAAAAAAiUSD3Vv2gTbCrHTNlF18uY/YKLdATh0ph4pwrCyUHkhtZngEYIHsGySMEjYsxl3fCSP6Ap7tG06AUPLXA8nGnwzSEH70VAAA=", + "index": 0, + "inputData": { + "tapLeafScript": [ + { + "leafVersion": 192, + "script": "Buffer.from('20d5e347235eba74ae0cec686b668d5a9432f45a555d6ab22cebf5974bde9dc4f3ac', 'hex')", + "controlBlock": "Buffer.from('c0720768b9946ac22371653d92cc343bf109b8a3f819e231159fd4f2b1328944ecb424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0aee28a3b82c4aecbebd378127dc88dd19e1ea93edba7a27b754e1c881d308874a7ac4489544b7b840d045599ec9415ae71ccb090c2c465c05c9fa05f41c7a06d5', 'hex')" + } + ] + }, + "exception": "Invalid arguments for Psbt.updateInput. Tapleaf not part of taptree." } ] }, @@ -512,6 +613,27 @@ "inputToCheck": 0, "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" } + }, + { + "description": "checks taproot key-path signer (tweaked key) matches internal tap key", + "isTaproot": true, + "shouldSign": { + "psbt": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBFyBbMLiqcPfsndZoMRtk9GKEFd0jSA3rwEa60nKQNwdHrAEYIOUOtfVyfPBm+ZzIsEfwzo2JL2WTslXAznzBllU7Oa4zAAA=", + "inputToCheck": 0, + "WIF": "KypUz2y1jdgzM8HGDUx9DYLmyzd8EWhruvLX2J5iSL7MiAcc7dBG", + "result": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBE0AAqkKg+dq3eThMoqzjh214urhgUoGTgwHlNyyMQ2RwhfeRIhAp+m9mZwQoXOxK7p2ILjf2G5j28F9KMhMzH7bXARcgWzC4qnD37J3WaDEbZPRihBXdI0gN68BGutJykDcHR6wBGCDlDrX1cnzwZvmcyLBH8M6NiS9lk7JVwM58wZZVOzmuMwAA" + } + + }, + { + "description": "check taproot script-path signer found for the input", + "isTaproot": true, + "shouldSign": { + "psbt": "cHNidP8BAF4CAAAAAUYAJ/rjwsScVTFXg1in8cNdaBDtwLAsQ6l1O8sWijmlAAAAAAD/////AZBBBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB4AAAAAAAEBK6BoBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB5BFNs7WfGuA1DZ7ZorvaC77E4rn/I2hbVtogxHpEnxKZ3DGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZAbjBxWKodWLnyqPP18sLAjhTc/OXtHVtk+Abc/e8SoTIxaOORlmqOegbCKAUyL4+NFdAlgtcUyHUCxWaxJ3ykxGIVwOpmBmLM44rn+zwtvl1cyWYheMCVYuX2agqeiHOvsrXIGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1mkgq1snxZRfWXi8CMjFSaobG3elbWWeL9QqflyaQtTYEG2sINs7WfGuA1DZ7ZorvaC77E4rn/I2hbVtogxHpEnxKZ3DuiB8Z8sVUSbQp0q8mQiD5n5coMrRzQAASnO9NspgIk9pZ7pTnMAAAA==", + "inputToCheck": 0, + "WIF": "L2wCzcNaJwG1W9djnumJnPQZTCpfeCkR2wgwfupphmThSrwTMCR6", + "result": "cHNidP8BAF4CAAAAAUYAJ/rjwsScVTFXg1in8cNdaBDtwLAsQ6l1O8sWijmlAAAAAAD/////AZBBBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB4AAAAAAAEBK6BoBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB5BFKtbJ8WUX1l4vAjIxUmqGxt3pW1lni/UKn5cmkLU2BBtT3q/MbhvStFL2jaKZM6N8TeYvCUfSVytPrzfjkXWQbdAoS5lV4a+yP0Y4nTr7ltbsrwMo+U3TNcC/ebE6U6xk/4er9bXfdvZNvgR+TgqrO/iZjNNI6QcGaudPObgsgYKTEEU2ztZ8a4DUNntmiu9oLvsTiuf8jaFtW2iDEekSfEpncMaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1kBuMHFYqh1YufKo8/XywsCOFNz85e0dW2T4Btz97xKhMjFo45GWao56BsIoBTIvj40V0CWC1xTIdQLFZrEnfKTEYhXA6mYGYszjiuf7PC2+XVzJZiF4wJVi5fZqCp6Ic6+ytcgaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1hpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWaSCrWyfFlF9ZeLwIyMVJqhsbd6VtZZ4v1Cp+XJpC1NgQbawg2ztZ8a4DUNntmiu9oLvsTiuf8jaFtW2iDEekSfEpncO6IHxnyxVRJtCnSryZCIPmflygytHNAABKc702ymAiT2lnulOcwAAA" + } } ] }, @@ -561,6 +683,14 @@ } ] }, + "finalizeInput": { + "finalizeTapleafByHash": { + "psbt": "cHNidP8BAF4CAAAAAXbYuDMSrbXuy1CXHZaiA8jjI+wcNkkCOL2dJD2dNf3kAAAAAAD/////AZBBBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEsAAAAAAAEBK6BoBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEtBFG78QHu9pQ+jR/B1+dz9XQtU0jFgoC5AFXcvibFi1jFAEkntiZ6MdFzn0seJJapRqACzEKXd0ZlMa34/iq0HnrVAfYaCk2E35rtXsMfbr/Hrbk/BtLF2VLepE0vIIB3UOWZEUF8akmlnL4Kh0xwMn4Cp99QivnI0B3XOsYfEznfDHUEUesul6blPS+qc/rm41L9nGaAck9KDAZAutbDsNLSZuXXMWh7785JCWLEpzsZRkM/9RJ+EaLYT/0O2sg4cHNHo1EBHFn/xRZno2fX/vLc1l2qHqZGB2vbLkZDj5KsVfqeJd78uIc5b6uAG0K+BBtq2H6HUvl+8RWd2GWmVoMTYppVNYhXAjGyV+gMPwhuPgQ05yTUt2dmE2xJLdkeHBaiap2ej2SuYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqvuLvWTUb+e6sUIP9JrqDAHAjkYMd3K3PTfk+B6aMTv4IyB6y6XpuU9L6pz+ubjUv2cZoByT0oMBkC61sOw0tJm5dazAohXAjGyV+gMPwhuPgQ05yTUt2dmE2xJLdkeHBaiap2ej2Su0JN6gn4QLkyoANzzc29JWULjDrP5UqfSmQaKGchuNJtrHlXZrvaHq6qReW/oKlQ/dX0xKraWx8wgu3JaJuf0K7iijuCxK7L69N4En3IjdGeHqk+26eie3VOHIgdMIh0q48S7pX3ESdKuEJ4Jo9s+PwRFNvGdokOHBSefrgpE1JCMgbvxAe72lD6NH8HX53P1dC1TSMWCgLkAVdy+JsWLWMUCswAAA", + "index": 0, + "leafHash": "1249ed899e8c745ce7d2c78925aa51a800b310a5ddd1994c6b7e3f8aad079eb5", + "result": "cHNidP8BAF4CAAAAAXbYuDMSrbXuy1CXHZaiA8jjI+wcNkkCOL2dJD2dNf3kAAAAAAD/////AZBBBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEsAAAAAAAEBK6BoBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEsBCP0HAQNAfYaCk2E35rtXsMfbr/Hrbk/BtLF2VLepE0vIIB3UOWZEUF8akmlnL4Kh0xwMn4Cp99QivnI0B3XOsYfEznfDHSIgbvxAe72lD6NH8HX53P1dC1TSMWCgLkAVdy+JsWLWMUCsocCMbJX6Aw/CG4+BDTnJNS3Z2YTbEkt2R4cFqJqnZ6PZK7Qk3qCfhAuTKgA3PNzb0lZQuMOs/lSp9KZBooZyG40m2seVdmu9oerqpF5b+gqVD91fTEqtpbHzCC7clom5/QruKKO4LErsvr03gSfciN0Z4eqT7bp6J7dU4ciB0wiHSrjxLulfcRJ0q4Qngmj2z4/BEU28Z2iQ4cFJ5+uCkTUkAAA=" + } + }, "finalizeAllInputs": [ { "type": "P2PK", @@ -600,25 +730,17 @@ "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", + "validateSignaturesOfTapKeyInput": { + "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==", "index": 1, - "pubkey": "Buffer.from('029421e734b0f9d2c467ea7dd197c61acb4467cdcbc9f4cb0c571f8b63a5c40cae', 'hex')", - "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')", - "nonExistantIndex": 42 + "pubkey": "Buffer.from('024fef5c5163bea69a93e74a59672bbeb081837077cb94cfa6e481a5cf00d8ab18', 'hex')", + "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')" }, - "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" + "validateSignaturesOfTapScriptInput": { + "psbt": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15BFDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nrrqI0FdZdKILLLZgRVK8L9Bn2ijU6IcoqqyImKIWt3MtAA3alBoU7IBCkBk9OHD1wE8fJI4y+lbnTRj48e8AAwRM77q3Rml679qCzGvEAKAs99UNMaXHQIhgGfRP11AMlJkEUj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WuojQV1l0ogsstmBFUrwv0GfaKNTohyiqrIiYoha3cy0DYJZ6Lv7FZPIBRZFfVgF5v3gcRiQnT8aM82Q5IPkwkzZrGo4ThZblvunG/+hu8ZPuJrUU+uXb+s9rcwSH+BihIQRSoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641cq6iNBXWXSiCyy2YEVSvC/QZ9oo1OiHKKqsiJiiFrdzLQEfQ5UkAg4lTbhJxjMzB7hu6ad1fywYxHCXjFXHHrm5PJTOFJLg2oTnwuQToz/Z2AW/UET7Op+WSoHZvW4tzzLhiFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA=", + "index": 0, + "pubkey": "Buffer.from('02395f8129dd63b4a5c2f12124eaa05b7a7ed30f70e51fb93305deecc542e7f9eb', 'hex')", + "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')" }, "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", diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index af1291b37..5e05c8887 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -5,7 +5,7 @@ import { describe, it } from 'mocha'; import { regtestUtils } from './_regtest'; import * as bitcoin from '../..'; import { Taptree } from '../../src/types'; -import { buildTapscriptFinalizer, toXOnly } from '../psbt.utils'; +import { toXOnly } from '../../src/psbt/bip371'; const rng = require('randombytes'); const regtest = regtestUtils.network; @@ -51,12 +51,18 @@ 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 p2pkhKey = bip32.fromSeed(rng(64), regtest); const { output, address } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), network: regtest, }); + const { output: p2pkhOutput } = bitcoin.payments.p2pkh({ + pubkey: p2pkhKey.publicKey, + network: regtest, + }); + // amount from faucet const amount = 42e4; // amount to send @@ -64,16 +70,25 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { // get faucet const unspent = await regtestUtils.faucetComplex(output!, amount); + // non segwit utxo + const p2pkhUnspent = await regtestUtils.faucetComplex(p2pkhOutput!, amount); + const utx = await regtestUtils.fetch(p2pkhUnspent.txId); + const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex'); + const psbt = new bitcoin.Psbt({ network: regtest }); psbt.addInput({ hash: unspent.txId, index: 0, witnessUtxo: { value: amount, script: output! }, + tapInternalKey: toXOnly(internalKey.publicKey), }); + psbt.addInput({ index: 0, hash: p2pkhUnspent.txId, nonWitnessUtxo }); + psbt.addOutput({ value: sendAmount, address: address! }); - const tweakedSigher = tweakSigner(internalKey!, { network: regtest }); - psbt.signInput(0, tweakedSigher); + const tweakedSigner = tweakSigner(internalKey!, { network: regtest }); + await psbt.signInputAsync(0, tweakedSigner); + await psbt.signInputAsync(1, p2pkhKey); psbt.finalizeAllInputs(); const tx = psbt.extractTransaction(); @@ -121,14 +136,16 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { hash: unspent.txId, index: 0, witnessUtxo: { value: amount, script: output! }, + tapInternalKey: toXOnly(internalKey.publicKey), + tapMerkleRoot: hash, }); psbt.addOutput({ value: sendAmount, address: address! }); - const tweakedSigher = tweakSigner(internalKey!, { + const tweakedSigner = tweakSigner(internalKey!, { tweakHash: hash, network: regtest, }); - psbt.signInput(0, tweakedSigher); + psbt.signInput(0, tweakedSigner); psbt.finalizeAllInputs(); const tx = psbt.extractTransaction(); @@ -204,7 +221,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address } = bitcoin.payments.p2tr({ + const { output, address, witness } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), scriptTree, redeem, @@ -223,18 +240,21 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { hash: unspent.txId, index: 0, witnessUtxo: { value: amount, script: output! }, - witnessScript: redeem.output, }); + psbt.updateInput(0, { + tapLeafScript: [ + { + leafVersion: redeem.redeemVersion, + script: redeem.output, + controlBlock: witness![witness!.length - 1], + }, + ], + }); + psbt.addOutput({ value: sendAmount, address: address! }); psbt.signInput(0, leafKey); - - const tapscriptFinalizer = buildTapscriptFinalizer( - internalKey.publicKey, - scriptTree, - regtest, - ); - psbt.finalizeInput(0, tapscriptFinalizer); + psbt.finalizeInput(0); const tx = psbt.extractTransaction(); const rawTx = tx.toBuffer(); const hex = rawTx.toString('hex'); @@ -278,7 +298,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address } = bitcoin.payments.p2tr({ + const { output, address, witness } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), scriptTree, redeem, @@ -298,18 +318,21 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { index: 0, sequence: 10, witnessUtxo: { value: amount, script: output! }, - witnessScript: redeem.output, + }); + psbt.updateInput(0, { + tapLeafScript: [ + { + leafVersion: redeem.redeemVersion, + script: redeem.output, + controlBlock: witness![witness!.length - 1], + }, + ], }); psbt.addOutput({ value: sendAmount, address: address! }); - psbt.signInput(0, leafKey); + await psbt.signInputAsync(0, leafKey); - const tapscriptFinalizer = buildTapscriptFinalizer( - internalKey.publicKey, - scriptTree, - regtest, - ); - psbt.finalizeInput(0, tapscriptFinalizer); + psbt.finalizeInput(0); const tx = psbt.extractTransaction(); const rawTx = tx.toBuffer(); const hex = rawTx.toString('hex'); @@ -374,7 +397,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address } = bitcoin.payments.p2tr({ + const { output, address, witness } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), scriptTree, redeem, @@ -393,20 +416,25 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { hash: unspent.txId, index: 0, witnessUtxo: { value: amount, script: output! }, - witnessScript: redeem.output, }); + psbt.updateInput(0, { + tapLeafScript: [ + { + leafVersion: redeem.redeemVersion, + script: redeem.output, + controlBlock: witness![witness!.length - 1], + }, + ], + }); + psbt.addOutput({ value: sendAmount, address: address! }); - psbt.signInput(0, leafKeys[0]); + // random order for signers psbt.signInput(0, leafKeys[1]); psbt.signInput(0, leafKeys[2]); + psbt.signInput(0, leafKeys[0]); - const tapscriptFinalizer = buildTapscriptFinalizer( - internalKey.publicKey, - scriptTree, - regtest, - ); - psbt.finalizeInput(0, tapscriptFinalizer); + psbt.finalizeInput(0); const tx = psbt.extractTransaction(); const rawTx = tx.toBuffer(); const hex = rawTx.toString('hex'); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 76ab4da49..97f558b2f 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -11,7 +11,6 @@ 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'; @@ -25,7 +24,7 @@ const schnorrValidator = ( pubkey: Buffer, msghash: Buffer, signature: Buffer, -): boolean => ECPair.fromPublicKey(pubkey).verifySchnorr(msghash, signature); +): boolean => ecc.verifySchnorr(msghash, pubkey, signature); const initBuffers = (object: any): typeof preFixtures => JSON.parse(JSON.stringify(object), (_, value) => { @@ -149,9 +148,16 @@ describe(`Psbt`, () => { if (f.isTaproot) initEccLib(ecc); const psbt = Psbt.fromBase64(f.psbt); - f.keys.forEach(({ inputToSign, WIF }) => { + // @ts-ignore // cannot find tapLeafHashToSign + f.keys.forEach(({ inputToSign, tapLeafHashToSign, WIF }) => { const keyPair = ECPair.fromWIF(WIF, NETWORKS.testnet); - psbt.signInput(inputToSign, keyPair); + if (tapLeafHashToSign) + psbt.signTaprootInput( + inputToSign, + keyPair, + Buffer.from(tapLeafHashToSign, 'hex'), + ); + else psbt.signInput(inputToSign, keyPair); }); assert.strictEqual(psbt.toBase64(), f.result); @@ -224,6 +230,7 @@ describe(`Psbt`, () => { describe('signInputAsync', () => { fixtures.signInput.checks.forEach(f => { it(f.description, async () => { + if (f.isTaproot) initEccLib(ecc); if (f.shouldSign) { const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt); await assert.doesNotReject(async () => { @@ -232,14 +239,22 @@ describe(`Psbt`, () => { ECPair.fromWIF(f.shouldSign.WIF), f.shouldSign.sighashTypes || undefined, ); + if (f.shouldSign.result) + assert.strictEqual( + psbtThatShouldsign.toBase64(), + f.shouldSign.result, + ); }); + const failMessage = f.isTaproot + ? /Need Schnorr Signer to sign taproot input #0./ + : /sign failed/; await assert.rejects(async () => { await psbtThatShouldsign.signInputAsync( f.shouldSign.inputToCheck, failedAsyncSigner(ECPair.fromWIF(f.shouldSign.WIF).publicKey), f.shouldSign.sighashTypes || undefined, ); - }, /sign failed/); + }, failMessage); } if (f.shouldThrow) { @@ -271,6 +286,7 @@ describe(`Psbt`, () => { describe('signInput', () => { fixtures.signInput.checks.forEach(f => { it(f.description, () => { + if (f.isTaproot) initEccLib(ecc); if (f.shouldSign) { const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt); assert.doesNotThrow(() => { @@ -303,6 +319,7 @@ describe(`Psbt`, () => { fixtures.signInput.checks.forEach(f => { if (f.description === 'checks the input exists') return; it(f.description, async () => { + if (f.isTaproot) initEccLib(ecc); if (f.shouldSign) { const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt); await assert.doesNotReject(async () => { @@ -333,6 +350,7 @@ describe(`Psbt`, () => { fixtures.signInput.checks.forEach(f => { if (f.description === 'checks the input exists') return; it(f.description, () => { + if (f.isTaproot) initEccLib(ecc); if (f.shouldSign) { const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt); assert.doesNotThrow(() => { @@ -483,6 +501,42 @@ describe(`Psbt`, () => { }); }); + describe('finalizeInput', () => { + it(`Finalizes tapleaf by hash`, () => { + const f = fixtures.finalizeInput.finalizeTapleafByHash; + const psbt = Psbt.fromBase64(f.psbt); + + psbt.finalizeTaprootInput(f.index, Buffer.from(f.leafHash, 'hex')); + + assert.strictEqual(psbt.toBase64(), f.result); + }); + + it(`fails if tapleaf hash not found`, () => { + const f = fixtures.finalizeInput.finalizeTapleafByHash; + const psbt = Psbt.fromBase64(f.psbt); + + assert.throws(() => { + psbt.finalizeTaprootInput( + f.index, + Buffer.from(f.leafHash, 'hex').reverse(), + ); + }, new RegExp('Can not finalize taproot input #0. Signature for tapleaf script not found.')); + }); + + it(`fails if trying to finalzie non-taproot input`, () => { + const psbt = new Psbt(); + psbt.addInput({ + hash: + '0000000000000000000000000000000000000000000000000000000000000000', + index: 0, + }); + + assert.throws(() => { + psbt.finalizeTaprootInput(0); + }, new RegExp('Cannot finalize input #0. Not Taproot.')); + }); + }); + describe('finalizeAllInputs', () => { fixtures.finalizeAllInputs.forEach(f => { it(`Finalizes inputs of type "${f.type}"`, () => { @@ -545,6 +599,20 @@ describe(`Psbt`, () => { }); }); + describe('updateInput', () => { + fixtures.updateInput.checks.forEach(f => { + it(f.description, () => { + const psbt = Psbt.fromBase64(f.psbt); + + if (f.exception) { + assert.throws(() => { + psbt.updateInput(f.index, f.inputData as any); + }, new RegExp(f.exception)); + } + }); + }); + }); + describe('addOutput', () => { fixtures.addOutput.checks.forEach(f => { it(f.description, () => { @@ -967,9 +1035,9 @@ describe(`Psbt`, () => { }); }); - describe('validateSignaturesOfTaprootInput', () => { - const f = fixtures.validateSignaturesOfTaprootInput; - it('Correctly validates a signature', () => { + describe('validateSignaturesOfTapKeyInput', () => { + const f = fixtures.validateSignaturesOfTapKeyInput; + it('Correctly validates all signatures', () => { initEccLib(ecc); const psbt = Psbt.fromBase64(f.psbt); assert.strictEqual( @@ -999,28 +1067,35 @@ describe(`Psbt`, () => { }); }); - describe('finalizeTaprootInput', () => { - it('Correctly finalizes a taproot script-path spend', () => { + describe('validateSignaturesOfTapScriptInput', () => { + const f = fixtures.validateSignaturesOfTapScriptInput; + it('Correctly validates all signatures', () => { initEccLib(ecc); - const f = fixtures.finalizeTaprootScriptPathSpendInput; const psbt = Psbt.fromBase64(f.psbt); - const tapscriptFinalizer = buildTapscriptFinalizer( - f.internalPublicKey as any, - f.scriptTree, - NETWORKS.testnet, + assert.strictEqual( + psbt.validateSignaturesOfInput(f.index, schnorrValidator), + true, ); - 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', () => { + it('Correctly validates a signature against a pubkey', () => { initEccLib(ecc); - const f = fixtures.finalizeTaprootScriptPathSpendInput; const psbt = Psbt.fromBase64(f.psbt); - + assert.strictEqual( + psbt.validateSignaturesOfInput( + f.index, + schnorrValidator, + f.pubkey as any, + ), + true, + ); assert.throws(() => { - psbt.finalizeInput(0); - }, new RegExp('Can not finalize input #0')); + psbt.validateSignaturesOfInput( + f.index, + schnorrValidator, + f.incorrectPubkey as any, + ); + }, new RegExp('No signatures for this pubkey')); }); }); diff --git a/test/psbt.utils.ts b/test/psbt.utils.ts deleted file mode 100644 index 100aa27e7..000000000 --- a/test/psbt.utils.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { PsbtInput } from 'bip174/src/lib/interfaces'; -import * as bitcoin from './..'; - -/** - * 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, - _isTapscript: boolean, - ): { - 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, - }); - 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/psbt.ts b/ts_src/psbt.ts index 1517acffd..3800ef5db 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -11,17 +11,35 @@ import { PsbtOutputUpdate, Transaction as ITransaction, TransactionFromBuffer, + TapKeySig, + TapScriptSig, } from 'bip174/src/lib/interfaces'; import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; - import { fromOutputScript, toOutputScript } from './address'; import { cloneBuffer, reverseBuffer } from './bufferutils'; -import { hash160 } from './crypto'; import { bitcoin as btcNetwork, Network } from './networks'; import * as payments from './payments'; +import { tapleafHash } from './payments/taprootutils'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; -import { tapleafHash } from './payments/taprootutils'; +import { + toXOnly, + tapScriptFinalizer, + serializeTaprootSignature, + isTaprootInput, + checkTaprootInputFields, + tweakInternalPubKey, +} from './psbt/bip371'; +import { + witnessStackToScriptWitness, + pubkeyInScript, + isP2MS, + isP2PK, + isP2PKH, + isP2WPKH, + isP2WSHScript, + isP2SHScript, +} from './psbt/psbtutils'; export interface TransactionInput { hash: string | Buffer; @@ -263,6 +281,7 @@ export class Psbt { `Requires single object with at least [hash] and [index]`, ); } + checkTaprootInputFields(inputData, inputData, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput'); if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); const c = this.__CACHE; @@ -347,16 +366,49 @@ export class Psbt { finalizeInput( inputIndex: number, - finalScriptsFunc: FinalScriptsFunc = getFinalScripts, + finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc, ): this { const input = checkForInput(this.data.inputs, inputIndex); - const { - script, - isP2SH, - isP2WSH, - isSegwit, - isTapscript, - } = getScriptFromInput(inputIndex, input, this.__CACHE); + if (isTaprootInput(input)) + return this._finalizeTaprootInput( + inputIndex, + input, + undefined, + finalScriptsFunc as FinalTaprootScriptsFunc, + ); + return this._finalizeInput( + inputIndex, + input, + finalScriptsFunc as FinalScriptsFunc, + ); + } + + finalizeTaprootInput( + inputIndex: number, + tapLeafHashToFinalize?: Buffer, + finalScriptsFunc: FinalTaprootScriptsFunc = tapScriptFinalizer, + ): this { + const input = checkForInput(this.data.inputs, inputIndex); + if (isTaprootInput(input)) + return this._finalizeTaprootInput( + inputIndex, + input, + tapLeafHashToFinalize, + finalScriptsFunc, + ); + throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`); + } + + private _finalizeInput( + inputIndex: number, + input: PsbtInput, + finalScriptsFunc: FinalScriptsFunc = getFinalScripts, + ): this { + const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( + inputIndex, + input, + this.__CACHE, + ); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); @@ -368,17 +420,11 @@ export class Psbt { isSegwit, isP2SH, isP2WSH, - isTapscript, ); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); - 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 (finalScriptWitness) + this.data.updateInput(inputIndex, { finalScriptWitness }); if (!finalScriptSig && !finalScriptWitness) throw new Error(`Unknown error finalizing input #${inputIndex}`); @@ -386,13 +432,42 @@ export class Psbt { return this; } + private _finalizeTaprootInput( + inputIndex: number, + input: PsbtInput, + tapLeafHashToFinalize?: Buffer, + finalScriptsFunc = tapScriptFinalizer, + ): this { + if (!input.witnessUtxo) + throw new Error( + `Cannot finalize input #${inputIndex}. Missing withness utxo.`, + ); + + // Check key spend first. Increased privacy and reduced block space. + if (input.tapKeySig) { + const payment = payments.p2tr({ + output: input.witnessUtxo.script, + signature: input.tapKeySig, + }); + const finalScriptWitness = witnessStackToScriptWitness(payment.witness!); + this.data.updateInput(inputIndex, { finalScriptWitness }); + } else { + const { finalScriptWitness } = finalScriptsFunc( + inputIndex, + input, + tapLeafHashToFinalize, + ); + this.data.updateInput(inputIndex, { finalScriptWitness }); + } + + this.data.clearFinalizedInput(inputIndex); + + return this; + } + getInputType(inputIndex: number): AllScriptType { const input = checkForInput(this.data.inputs, inputIndex); - const { script } = getScriptAndAmountFromUtxo( - inputIndex, - input, - this.__CACHE, - ); + const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); const result = getMeaningfulScript( script, inputIndex, @@ -444,6 +519,22 @@ export class Psbt { inputIndex: number, validator: ValidateSigFunction, pubkey?: Buffer, + ): boolean { + const input = this.data.inputs[inputIndex]; + if (isTaprootInput(input)) + return this.validateSignaturesOfTaprootInput( + inputIndex, + validator, + pubkey, + ); + + return this._validateSignaturesOfInput(inputIndex, validator, pubkey); + } + + private _validateSignaturesOfInput( + inputIndex: number, + validator: ValidateSigFunction, + pubkey?: Buffer, ): boolean { const input = this.data.inputs[inputIndex]; const partialSig = (input || {}).partialSig; @@ -459,22 +550,13 @@ export class Psbt { let hashCache: Buffer; let scriptCache: Buffer; let sighashCache: number; - const scriptType = this.getInputType(inputIndex); - for (const pSig of mySigs) { - const sig = isTaprootSpend(scriptType) - ? { - signature: pSig.signature, - hashType: Transaction.SIGHASH_DEFAULT, - } - : bscript.signature.decode(pSig.signature); - + const sig = 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, ) @@ -488,6 +570,64 @@ export class Psbt { return results.every(res => res === true); } + private validateSignaturesOfTaprootInput( + inputIndex: number, + validator: ValidateSigFunction, + pubkey?: Buffer, + ): boolean { + const input = this.data.inputs[inputIndex]; + const tapKeySig = (input || {}).tapKeySig; + const tapScriptSig = (input || {}).tapScriptSig; + if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length)) + throw new Error('No signatures to validate'); + if (typeof validator !== 'function') + throw new Error('Need validator function to validate signatures'); + + pubkey = pubkey && toXOnly(pubkey); + const allHashses = pubkey + ? getTaprootHashesForSig( + inputIndex, + input, + this.data.inputs, + pubkey, + this.__CACHE, + ) + : getAllTaprootHashesForSig( + inputIndex, + input, + this.data.inputs, + this.__CACHE, + ); + + if (!allHashses.length) throw new Error('No signatures for this pubkey'); + + const tapKeyHash = allHashses.find(h => !!h.leafHash); + if (tapKeySig && tapKeyHash) { + const isValidTapkeySig = validator( + tapKeyHash.pubkey, + tapKeyHash.hash, + tapKeySig, + ); + if (!isValidTapkeySig) return false; + } + + if (tapScriptSig) { + for (const tapSig of tapScriptSig) { + const tapSigHash = allHashses.find(h => tapSig.pubkey.equals(h.pubkey)); + if (tapSigHash) { + const isValidTapScriptSig = validator( + tapSig.pubkey, + tapSigHash.hash, + tapSig.signature, + ); + if (!isValidTapScriptSig) return false; + } + } + } + + return true; + } + signAllInputsHD( hdKeyPair: HDSigner, sighashTypes: number[] = [Transaction.SIGHASH_ALL], @@ -589,10 +729,7 @@ export class Psbt { ); } - signAllInputs( - keyPair: Signer, - sighashTypes: number[] = [Transaction.SIGHASH_ALL], - ): this { + signAllInputs(keyPair: Signer, sighashTypes?: number[]): this { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); @@ -616,7 +753,7 @@ export class Psbt { signAllInputsAsync( keyPair: Signer | SignerAsync, - sighashTypes: number[] = [Transaction.SIGHASH_ALL], + sighashTypes?: number[], ): Promise { return new Promise( (resolve, reject): any => { @@ -653,10 +790,51 @@ export class Psbt { signInput( inputIndex: number, keyPair: Signer, - sighashTypes: number[] = [Transaction.SIGHASH_ALL], + sighashTypes?: number[], ): this { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); + + const input = checkForInput(this.data.inputs, inputIndex); + + if (isTaprootInput(input)) { + return this._signTaprootInput( + inputIndex, + input, + keyPair, + undefined, + sighashTypes, + ); + } + return this._signInput(inputIndex, keyPair, sighashTypes); + } + + signTaprootInput( + inputIndex: number, + keyPair: Signer, + tapLeafHashToSign?: Buffer, + sighashTypes?: number[], + ): this { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const input = checkForInput(this.data.inputs, inputIndex); + + if (isTaprootInput(input)) + return this._signTaprootInput( + inputIndex, + input, + keyPair, + tapLeafHashToSign, + sighashTypes, + ); + throw new Error(`Input #${inputIndex} is not of type Taproot.`); + } + + private _signInput( + inputIndex: number, + keyPair: Signer, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): this { const { hash, sighashType } = getHashAndSighashType( this.data.inputs, inputIndex, @@ -665,29 +843,61 @@ export class Psbt { sighashTypes, ); - const scriptType = this.getInputType(inputIndex); - - if (isTaprootSpend(scriptType)) { - if (!keyPair.signSchnorr) { - throw new Error( - `Need Schnorr Signer to sign taproot input #${inputIndex}.`, - ); - } - const partialSig = this.data.inputs[inputIndex].partialSig || []; - partialSig.push({ + 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 }); + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + + this.data.updateInput(inputIndex, { partialSig }); + return this; + } + + private _signTaprootInput( + inputIndex: number, + input: PsbtInput, + keyPair: Signer, + tapLeafHashToSign?: Buffer, + allowedSighashTypes: number[] = [Transaction.SIGHASH_DEFAULT], + ): this { + const hashesForSig = this.checkTaprootHashesForSig( + inputIndex, + input, + keyPair, + tapLeafHashToSign, + allowedSighashTypes, + ); + + const tapKeySig: TapKeySig = hashesForSig + .filter(h => !h.leafHash) + .map(h => + serializeTaprootSignature( + keyPair.signSchnorr!(h.hash), + input.sighashType, + ), + )[0]; + + const tapScriptSig: TapScriptSig[] = hashesForSig + .filter(h => !!h.leafHash) + .map( + h => + ({ + pubkey: toXOnly(keyPair.publicKey), + signature: serializeTaprootSignature( + keyPair.signSchnorr!(h.hash), + input.sighashType, + ), + leafHash: h.leafHash, + } as TapScriptSig), + ); + + if (tapKeySig) { + this.data.updateInput(inputIndex, { tapKeySig }); + } + + if (tapScriptSig.length) { + this.data.updateInput(inputIndex, { tapScriptSig }); } return this; @@ -696,52 +906,160 @@ export class Psbt { signInputAsync( inputIndex: number, keyPair: Signer | SignerAsync, - sighashTypes: number[] = [Transaction.SIGHASH_ALL], + sighashTypes?: number[], ): Promise { return Promise.resolve().then(() => { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); - const { hash, sighashType } = getHashAndSighashType( - this.data.inputs, - inputIndex, - keyPair.publicKey, - this.__CACHE, - sighashTypes, - ); - const scriptType = this.getInputType(inputIndex); + const input = checkForInput(this.data.inputs, inputIndex); + if (isTaprootInput(input)) + return this._signTaprootInputAsync( + inputIndex, + input, + keyPair, + undefined, + sighashTypes, + ); - 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 = 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; - }); - } + return this._signInputAsync(inputIndex, keyPair, sighashTypes); + }); + } - return Promise.resolve(keyPair.sign(hash)).then(signature => { - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(signature, sighashType), - }, - ]; + signTaprootInputAsync( + inputIndex: number, + keyPair: Signer | SignerAsync, + tapLeafHash?: Buffer, + sighashTypes?: number[], + ): Promise { + return Promise.resolve().then(() => { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + + const input = checkForInput(this.data.inputs, inputIndex); + if (isTaprootInput(input)) + return this._signTaprootInputAsync( + inputIndex, + input, + keyPair, + tapLeafHash, + sighashTypes, + ); + + throw new Error(`Input #${inputIndex} is not of type Taproot.`); + }); + } - this.data.updateInput(inputIndex, { partialSig }); + private _signInputAsync( + inputIndex: number, + keyPair: Signer | SignerAsync, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): Promise { + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + + return Promise.resolve(keyPair.sign(hash)).then(signature => { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(signature, sighashType), + }, + ]; + + this.data.updateInput(inputIndex, { partialSig }); + }); + } + + private async _signTaprootInputAsync( + inputIndex: number, + input: PsbtInput, + keyPair: Signer | SignerAsync, + tapLeafHash?: Buffer, + sighashTypes: number[] = [Transaction.SIGHASH_DEFAULT], + ): Promise { + const hashesForSig = this.checkTaprootHashesForSig( + inputIndex, + input, + keyPair, + tapLeafHash, + sighashTypes, + ); + + const signaturePromises: Promise[] = []; + const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0]; + if (tapKeyHash) { + const tapKeySigPromise = Promise.resolve( + keyPair.signSchnorr!(tapKeyHash.hash), + ).then(sig => { + return { tapKeySig: serializeTaprootSignature(sig, input.sighashType) }; + }); + signaturePromises.push(tapKeySigPromise); + } + + const tapScriptHashes = hashesForSig.filter(h => !!h.leafHash); + if (tapScriptHashes.length) { + const tapScriptSigPromises = tapScriptHashes.map(tsh => { + return Promise.resolve(keyPair.signSchnorr!(tsh.hash)).then( + signature => { + const tapScriptSig = [ + { + pubkey: toXOnly(keyPair.publicKey), + signature: serializeTaprootSignature( + signature, + input.sighashType, + ), + leafHash: tsh.leafHash, + } as TapScriptSig, + ]; + return { tapScriptSig }; + }, + ); }); + signaturePromises.push(...tapScriptSigPromises); + } + + return Promise.all(signaturePromises).then(results => { + results.forEach(v => this.data.updateInput(inputIndex, v)); }); } + private checkTaprootHashesForSig( + inputIndex: number, + input: PsbtInput, + keyPair: Signer | SignerAsync, + tapLeafHashToSign?: Buffer, + allowedSighashTypes?: number[], + ): { hash: Buffer; leafHash?: Buffer }[] { + if (typeof keyPair.signSchnorr !== 'function') + throw new Error( + `Need Schnorr Signer to sign taproot input #${inputIndex}.`, + ); + + const hashesForSig = getTaprootHashesForSig( + inputIndex, + input, + this.data.inputs, + keyPair.publicKey, + this.__CACHE, + tapLeafHashToSign, + allowedSighashTypes, + ); + + if (!hashesForSig || !hashesForSig.length) + throw new Error( + `Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString( + 'hex', + )}`, + ); + + return hashesForSig; + } + toBuffer(): Buffer { checkCache(this.__CACHE); return this.data.toBuffer(); @@ -764,6 +1082,11 @@ export class Psbt { updateInput(inputIndex: number, updateData: PsbtInputUpdate): this { if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); + checkTaprootInputFields( + this.data.inputs[inputIndex], + updateData, + 'updateInput', + ); this.data.updateInput(inputIndex, updateData); if (updateData.nonWitnessUtxo) { addNonWitnessTxCache( @@ -858,7 +1181,6 @@ 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; } /** @@ -867,7 +1189,6 @@ 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 { @@ -963,7 +1284,6 @@ function canFinalize( case 'pubkey': case 'pubkeyhash': case 'witnesspubkeyhash': - case 'taproot': return hasSigs(1, input.partialSig); case 'multisig': const p2ms = payments.p2ms({ output: script }); @@ -1004,24 +1324,6 @@ function isFinalized(input: PsbtInput): boolean { return !!input.finalScriptSig || !!input.finalScriptWitness; } -function isPaymentFactory(payment: any): (script: Buffer) => boolean { - return (script: Buffer): boolean => { - try { - payment({ output: script }); - return true; - } catch (err) { - return false; - } - }; -} -const isP2MS = isPaymentFactory(payments.p2ms); -const isP2PK = isPaymentFactory(payments.p2pk); -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, ): (d: Bip32Derivation) => boolean { @@ -1207,12 +1509,18 @@ 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? ) => { finalScriptSig: Buffer | undefined; - finalScriptWitness: Buffer | Buffer[] | undefined; + finalScriptWitness: Buffer | undefined; +}; +type FinalTaprootScriptsFunc = ( + inputIndex: number, // Which input is it? + input: PsbtInput, // The PSBT input contents + tapLeafHashToFinalize?: Buffer, // Only finalize this specific leaf +) => { + finalScriptWitness: Buffer | undefined; }; function getFinalScripts( @@ -1222,13 +1530,12 @@ function getFinalScripts( isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, - isTapscript: boolean = false, ): { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | undefined; } { const scriptType = classifyScript(script); - if (isTapscript || !canFinalize(input, script, scriptType)) + if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( script, @@ -1295,7 +1602,6 @@ function getHashAndSighashType( const { hash, sighashType, script } = getHashForSig( inputIndex, input, - inputs, cache, false, sighashTypes, @@ -1310,7 +1616,6 @@ function getHashAndSighashType( function getHashForSig( inputIndex: number, input: PsbtInput, - inputs: PsbtInput[], cache: PsbtCache, forValidate: boolean, sighashTypes?: number[], @@ -1321,13 +1626,8 @@ function getHashForSig( } { const unsignedTx = cache.__TX; const sighashType = input.sighashType || Transaction.SIGHASH_ALL; - if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { - const str = sighashTypeToString(sighashType); - throw new Error( - `Sighash type is not allowed. Retry the sign method passing the ` + - `sighashTypes array of whitelisted types. Sighash type: ${str}`, - ); - } + checkSighashTypeAllowed(sighashType, sighashTypes); + let hash: Buffer; let prevout: Output; @@ -1381,23 +1681,6 @@ function getHashForSig( prevout.value, sighashType, ); - } else if (isP2TR(prevout.script)) { - 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({ output: input.witnessScript }) - : undefined; - - hash = unsignedTx.hashForWitnessV1( - inputIndex, - signingScripts, - values, - Transaction.SIGHASH_DEFAULT, - leafHash, - ); } else { // non-segwit if ( @@ -1432,6 +1715,108 @@ function getHashForSig( }; } +function getAllTaprootHashesForSig( + inputIndex: number, + input: PsbtInput, + inputs: PsbtInput[], + cache: PsbtCache, +): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] { + const allPublicKeys = []; + if (input.tapInternalKey) { + const outputKey = tweakInternalPubKey(inputIndex, input); + allPublicKeys.push(outputKey); + } + + if (input.tapScriptSig) { + const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey); + allPublicKeys.push(...tapScriptPubkeys); + } + + const allHashes = allPublicKeys.map(pubicKey => + getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache), + ); + + return allHashes.flat(); +} + +function getTaprootHashesForSig( + inputIndex: number, + input: PsbtInput, + inputs: PsbtInput[], + pubkey: Buffer, + cache: PsbtCache, + tapLeafHashToSign?: Buffer, + allowedSighashTypes?: number[], +): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] { + const unsignedTx = cache.__TX; + + const sighashType = input.sighashType || Transaction.SIGHASH_DEFAULT; + checkSighashTypeAllowed(sighashType, allowedSighashTypes); + + 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 hashes = []; + if (input.tapInternalKey && !tapLeafHashToSign) { + const outputKey = tweakInternalPubKey(inputIndex, input); + if (toXOnly(pubkey).equals(outputKey)) { + const tapKeyHash = unsignedTx.hashForWitnessV1( + inputIndex, + signingScripts, + values, + sighashType, + ); + hashes.push({ pubkey, hash: tapKeyHash }); + } + } + + const tapLeafHashes = (input.tapLeafScript || []) + .filter(tapLeaf => pubkeyInScript(pubkey, tapLeaf.script)) + .map(tapLeaf => { + const hash = tapleafHash({ + output: tapLeaf.script, + version: tapLeaf.leafVersion, + }); + return Object.assign({ hash }, tapLeaf); + }) + .filter( + tapLeaf => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash), + ) + .map(tapLeaf => { + const tapScriptHash = unsignedTx.hashForWitnessV1( + inputIndex, + signingScripts, + values, + Transaction.SIGHASH_DEFAULT, + tapLeaf.hash, + ); + + return { + pubkey, + hash: tapScriptHash, + leafHash: tapLeaf.hash, + }; + }); + + return hashes.concat(tapLeafHashes); +} + +function checkSighashTypeAllowed( + sighashType: number, + sighashTypes?: number[], +): void { + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error( + `Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`, + ); + } +} + function getPayment( script: Buffer, scriptType: string, @@ -1466,15 +1851,6 @@ function getPayment( signature: partialSig[0].signature, }); break; - case 'taproot': - payment = payments.p2tr( - { - output: script, - signature: partialSig[0].signature, - }, - { validate: false }, // skip validation - ); - break; } return payment!; } @@ -1497,7 +1873,6 @@ function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { interface GetScriptReturn { script: Buffer | null; isSegwit: boolean; - isTapscript: boolean; isP2SH: boolean; isP2WSH: boolean; } @@ -1510,45 +1885,31 @@ function getScriptFromInput( const res: GetScriptReturn = { script: null, isSegwit: false, - isTapscript: false, isP2SH: false, isP2WSH: false, }; - 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; - } - + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript; if (input.witnessScript) { res.script = input.witnessScript; } else if (input.redeemScript) { res.script = input.redeemScript; } else { - res.script = utxoScript; + 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; + } } - - const isTaproot = utxoScript && isP2TR(utxoScript); - - // Segregated Witness versions 0 or 1 - if (input.witnessScript || isP2WPKH(res.script!) || isTaproot) { + if (input.witnessScript || isP2WPKH(res.script!)) { res.isSegwit = true; } - - if (isTaproot && input.witnessScript) { - res.isTapscript = true; - } - - res.isP2SH = !!input.redeemScript; - res.isP2WSH = !!input.witnessScript && !res.isTapscript; - return res; } @@ -1650,36 +2011,6 @@ function sighashTypeToString(sighashType: number): string { return text; } -function witnessStackToScriptWitness(witness: Buffer[]): Buffer { - let buffer = Buffer.allocUnsafe(0); - - function writeSlice(slice: Buffer): void { - buffer = Buffer.concat([buffer, Buffer.from(slice)]); - } - - function writeVarInt(i: number): void { - const currentLen = buffer.length; - const varintLen = varuint.encodingLength(i); - - buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); - varuint.encode(i, buffer, currentLen); - } - - function writeVarSlice(slice: Buffer): void { - writeVarInt(slice.length); - writeSlice(slice); - } - - function writeVector(vector: Buffer[]): void { - writeVarInt(vector.length); - vector.forEach(writeVarSlice); - } - - writeVector(witness); - - return buffer; -} - function addNonWitnessTxCache( cache: PsbtCache, input: PsbtInput, @@ -1762,6 +2093,15 @@ function nonWitnessUtxoTxFromCache( return c[inputIndex]; } +function getScriptFromUtxo( + inputIndex: number, + input: PsbtInput, + cache: PsbtCache, +): Buffer { + const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); + return script; +} + function getScriptAndAmountFromUtxo( inputIndex: number, input: PsbtInput, @@ -1791,7 +2131,7 @@ function pubkeyInInput( inputIndex: number, cache: PsbtCache, ): boolean { - const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); + const script = getScriptFromUtxo(inputIndex, input, cache); const { meaningfulScript } = getMeaningfulScript( script, inputIndex, @@ -1875,12 +2215,11 @@ function getMeaningfulScript( witnessScript?: Buffer, ): { meaningfulScript: Buffer; - type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'p2tr' | 'raw'; + type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw'; } { const isP2SH = isP2SHScript(script); const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); const isP2WSH = isP2WSHScript(script); - const isP2TRScript = isP2TR(script); if (isP2SH && redeemScript === undefined) throw new Error('scriptPubkey is P2SH but redeemScript missing'); @@ -1903,9 +2242,6 @@ 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; } @@ -1917,8 +2253,6 @@ function getMeaningfulScript( ? 'p2sh' : isP2WSH ? 'p2wsh' - : isP2TRScript - ? 'p2tr' : 'raw', }; } @@ -1929,35 +2263,11 @@ 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) || - element.equals(pubkeyXOnly) - ); - }); -} - -function isTaprootSpend(scriptType: string): boolean { - return ( - !!scriptType && (scriptType === 'taproot' || scriptType.startsWith('p2tr-')) - ); -} - type AllScriptType = | 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' - | 'taproot' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' @@ -1971,22 +2281,18 @@ type AllScriptType = | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' - | 'p2sh-p2wsh-nonstandard' - | 'p2tr-pubkey' - | 'p2tr-nonstandard'; + | 'p2sh-p2wsh-nonstandard'; type ScriptType = | 'witnesspubkeyhash' | '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)) return 'taproot'; return 'nonstandard'; } diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts new file mode 100644 index 000000000..618ff25c5 --- /dev/null +++ b/ts_src/psbt/bip371.ts @@ -0,0 +1,240 @@ +import { + PsbtInput, + TapLeafScript, + TapScriptSig, +} from 'bip174/src/lib/interfaces'; + +import { + witnessStackToScriptWitness, + pubkeyPositionInScript, + isP2TR, +} from './psbtutils'; +import { + tweakKey, + tapleafHash, + rootHashFromPath, +} from '../payments/taprootutils'; + +export const toXOnly = (pubKey: Buffer) => + pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); + +/** + * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided. + * Otherwise it will search for the tapleaf that has at least one signature and has the shortest path. + * @param inputIndex the position of the PSBT input. + * @param input the PSBT input. + * @param tapLeafHashToFinalize optional, if provided the finalizer will search for a tapleaf that has this hash + * and will try to build the finalScriptWitness. + * @returns the finalScriptWitness or throws an exception if no tapleaf found. + */ +export function tapScriptFinalizer( + inputIndex: number, + input: PsbtInput, + tapLeafHashToFinalize?: Buffer, +): { + finalScriptWitness: Buffer | undefined; +} { + const tapLeaf = findTapLeafToFinalize( + input, + inputIndex, + tapLeafHashToFinalize, + ); + + try { + const sigs = sortSignatures(input, tapLeaf); + const witness = sigs.concat(tapLeaf.script).concat(tapLeaf.controlBlock); + return { finalScriptWitness: witnessStackToScriptWitness(witness) }; + } catch (err) { + throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`); + } +} + +export function serializeTaprootSignature( + sig: Buffer, + sighashType?: number, +): Buffer { + const sighashTypeByte = sighashType + ? Buffer.from([sighashType!]) + : Buffer.from([]); + + return Buffer.concat([sig, sighashTypeByte]); +} + +export function isTaprootInput(input: PsbtInput): boolean { + return ( + input && + !!( + input.tapInternalKey || + input.tapMerkleRoot || + (input.tapLeafScript && input.tapLeafScript.length) || + (input.tapBip32Derivation && input.tapBip32Derivation.length) || + (input.witnessUtxo && isP2TR(input.witnessUtxo.script)) + ) + ); +} + +export function checkTaprootInputFields( + inputData: PsbtInput, + newInputData: PsbtInput, + action: string, +): void { + checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action); + checkIfTapLeafInTree(inputData, newInputData, action); +} + +export function tweakInternalPubKey( + inputIndex: number, + input: PsbtInput, +): Buffer { + const tapInternalKey = input.tapInternalKey; + const outputKey = + tapInternalKey && tweakKey(tapInternalKey, input.tapMerkleRoot); + + if (!outputKey) + throw new Error( + `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey && + tapInternalKey.toString('hex')}`, + ); + return outputKey.x; +} + +function checkMixedTaprootAndNonTaprootFields( + inputData: PsbtInput, + newInputData: PsbtInput, + action: string, +): void { + const isBadTaprootUpdate = + isTaprootInput(inputData) && hasNonTaprootInputFields(newInputData); + const isBadNonTaprootUpdate = + hasNonTaprootInputFields(inputData) && isTaprootInput(newInputData); + const hasMixedFields = + inputData === newInputData && + (isTaprootInput(newInputData) && hasNonTaprootInputFields(newInputData)); + + if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) + throw new Error( + `Invalid arguments for Psbt.${action}. ` + + `Cannot use both taproot and non-taproot fields.`, + ); +} + +function checkIfTapLeafInTree( + inputData: PsbtInput, + newInputData: PsbtInput, + action: string, +): void { + if (newInputData.tapMerkleRoot) { + const newLeafsInTree = (newInputData.tapLeafScript || []).every(l => + isTapLeafInTree(l, newInputData.tapMerkleRoot), + ); + const oldLeafsInTree = (inputData.tapLeafScript || []).every(l => + isTapLeafInTree(l, newInputData.tapMerkleRoot), + ); + if (!newLeafsInTree || !oldLeafsInTree) + throw new Error( + `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`, + ); + } else if (inputData.tapMerkleRoot) { + const newLeafsInTree = (newInputData.tapLeafScript || []).every(l => + isTapLeafInTree(l, inputData.tapMerkleRoot), + ); + if (!newLeafsInTree) + throw new Error( + `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`, + ); + } +} + +function isTapLeafInTree(tapLeaf: TapLeafScript, merkleRoot?: Buffer): boolean { + if (!merkleRoot) return true; + + const leafHash = tapleafHash({ + output: tapLeaf.script, + version: tapLeaf.leafVersion, + }); + + const rootHash = rootHashFromPath(tapLeaf.controlBlock, leafHash); + return rootHash.equals(merkleRoot); +} + +function sortSignatures(input: PsbtInput, tapLeaf: TapLeafScript): Buffer[] { + const leafHash = tapleafHash({ + output: tapLeaf.script, + version: tapLeaf.leafVersion, + }); + + return (input.tapScriptSig || []) + .filter(tss => tss.leafHash.equals(leafHash)) + .map(tss => addPubkeyPositionInScript(tapLeaf.script, tss)) + .sort((t1, t2) => t2.positionInScript - t1.positionInScript) + .map(t => t.signature) as Buffer[]; +} + +function addPubkeyPositionInScript( + script: Buffer, + tss: TapScriptSig, +): TapScriptSigWitPosition { + return Object.assign( + { + positionInScript: pubkeyPositionInScript(tss.pubkey, script), + }, + tss, + ) as TapScriptSigWitPosition; +} + +/** + * Find tapleaf by hash, or get the signed tapleaf with the shortest path. + */ +function findTapLeafToFinalize( + input: PsbtInput, + inputIndex: number, + leafHashToFinalize?: Buffer, +): TapLeafScript { + if (!input.tapScriptSig || !input.tapScriptSig.length) + throw new Error( + `Can not finalize taproot input #${inputIndex}. No tapleaf script signature provided.`, + ); + const tapLeaf = (input.tapLeafScript || []) + .sort((a, b) => a.controlBlock.length - b.controlBlock.length) + .find(leaf => + canFinalizeLeaf(leaf, input.tapScriptSig!, leafHashToFinalize), + ); + + if (!tapLeaf) + throw new Error( + `Can not finalize taproot input #${inputIndex}. Signature for tapleaf script not found.`, + ); + + return tapLeaf; +} + +function canFinalizeLeaf( + leaf: TapLeafScript, + tapScriptSig: TapScriptSig[], + hash?: Buffer, +): boolean { + const leafHash = tapleafHash({ + output: leaf.script, + version: leaf.leafVersion, + }); + const whiteListedHash = !hash || hash.equals(leafHash); + return ( + whiteListedHash && + tapScriptSig!.find(tss => tss.leafHash.equals(leafHash)) !== undefined + ); +} + +function hasNonTaprootInputFields(input: PsbtInput): boolean { + return ( + input && + !!( + input.redeemScript || + input.witnessScript || + (input.bip32Derivation && input.bip32Derivation.length) + ) + ); +} + +interface TapScriptSigWitPosition extends TapScriptSig { + positionInScript: number; +} diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts new file mode 100644 index 000000000..1a891f788 --- /dev/null +++ b/ts_src/psbt/psbtutils.ts @@ -0,0 +1,73 @@ +import * as varuint from 'bip174/src/lib/converter/varint'; +import * as bscript from '../script'; +import { hash160 } from '../crypto'; +import * as payments from '../payments'; + +function isPaymentFactory(payment: any): (script: Buffer) => boolean { + return (script: Buffer): boolean => { + try { + payment({ output: script }); + return true; + } catch (err) { + return false; + } + }; +} +export const isP2MS = isPaymentFactory(payments.p2ms); +export const isP2PK = isPaymentFactory(payments.p2pk); +export const isP2PKH = isPaymentFactory(payments.p2pkh); +export const isP2WPKH = isPaymentFactory(payments.p2wpkh); +export const isP2WSHScript = isPaymentFactory(payments.p2wsh); +export const isP2SHScript = isPaymentFactory(payments.p2sh); +export const isP2TR = isPaymentFactory(payments.p2tr); + +export function witnessStackToScriptWitness(witness: Buffer[]): Buffer { + let buffer = Buffer.allocUnsafe(0); + + function writeSlice(slice: Buffer): void { + buffer = Buffer.concat([buffer, Buffer.from(slice)]); + } + + function writeVarInt(i: number): void { + const currentLen = buffer.length; + const varintLen = varuint.encodingLength(i); + + buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); + varuint.encode(i, buffer, currentLen); + } + + function writeVarSlice(slice: Buffer): void { + writeVarInt(slice.length); + writeSlice(slice); + } + + function writeVector(vector: Buffer[]): void { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + + writeVector(witness); + + return buffer; +} + +export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { + const pubkeyHash = hash160(pubkey); + const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? + + const decompiled = bscript.decompile(script); + if (decompiled === null) throw new Error('Unknown script error'); + + return decompiled.findIndex(element => { + if (typeof element === 'number') return false; + return ( + element.equals(pubkey) || + element.equals(pubkeyHash) || + element.equals(pubkeyXOnly) + ); + }); +} + +export function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean { + return pubkeyPositionInScript(pubkey, script) !== -1; +} From 6cbac53c1d03b18ba0f016f5eca313fcb3d6400a Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 24 May 2022 11:22:02 +0300 Subject: [PATCH 092/249] feat: add to/from Psbt TapTree conversion --- src/payments/taprootutils.d.ts | 1 + src/payments/taprootutils.js | 3 +- src/psbt/bip371.d.ts | 21 +++++++- src/psbt/bip371.js | 73 +++++++++++++++++++++++++- ts_src/payments/taprootutils.ts | 1 + ts_src/psbt/bip371.ts | 92 +++++++++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 3 deletions(-) diff --git a/src/payments/taprootutils.d.ts b/src/payments/taprootutils.d.ts index 371595f31..8dd82409a 100644 --- a/src/payments/taprootutils.d.ts +++ b/src/payments/taprootutils.d.ts @@ -1,6 +1,7 @@ /// import { Tapleaf, Taptree } from '../types'; export declare const LEAF_VERSION_TAPSCRIPT = 192; +export declare const MAX_TAPTREE_DEPTH = 128; interface HashLeaf { hash: Buffer; } diff --git a/src/payments/taprootutils.js b/src/payments/taprootutils.js index 26d97d684..4832d358d 100644 --- a/src/payments/taprootutils.js +++ b/src/payments/taprootutils.js @@ -1,12 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.LEAF_VERSION_TAPSCRIPT = void 0; +exports.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.MAX_TAPTREE_DEPTH = exports.LEAF_VERSION_TAPSCRIPT = void 0; const buffer_1 = require('buffer'); const ecc_lib_1 = require('../ecc_lib'); const bcrypto = require('../crypto'); const bufferutils_1 = require('../bufferutils'); const types_1 = require('../types'); exports.LEAF_VERSION_TAPSCRIPT = 0xc0; +exports.MAX_TAPTREE_DEPTH = 128; const isHashBranch = ht => 'left' in ht && 'right' in ht; function rootHashFromPath(controlBlock, leafHash) { const m = (controlBlock.length - 33) / 32; diff --git a/src/psbt/bip371.d.ts b/src/psbt/bip371.d.ts index 9545e899a..a3784cfd1 100644 --- a/src/psbt/bip371.d.ts +++ b/src/psbt/bip371.d.ts @@ -1,5 +1,6 @@ /// -import { PsbtInput } from 'bip174/src/lib/interfaces'; +import { Taptree } from '../types'; +import { PsbtInput, TapLeaf } from 'bip174/src/lib/interfaces'; export declare const toXOnly: (pubKey: Buffer) => Buffer; /** * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided. @@ -17,3 +18,21 @@ export declare function serializeTaprootSignature(sig: Buffer, sighashType?: num export declare function isTaprootInput(input: PsbtInput): boolean; export declare function checkTaprootInputFields(inputData: PsbtInput, newInputData: PsbtInput, action: string): void; export declare function tweakInternalPubKey(inputIndex: number, input: PsbtInput): Buffer; +/** + * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371): + * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree, + * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that + * the tree is correctly reconstructed. + * @param tree the binary tap tree + * @returns a list of BIP 371 tapleaves + */ +export declare function tapTreeToList(tree: Taptree): TapLeaf[]; +/** + * Convert a BIP371 TapLeaf list to a TapTree (binary). + * @param leaves a list of tapleaves where each element of the list is (according to BIP371): + * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree, + * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that + * the tree is correctly reconstructed. + * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed + */ +export declare function tapTreeFromList(leaves?: TapLeaf[]): Taptree; diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 115235acf..40a54918b 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -1,6 +1,7 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tweakInternalPubKey = exports.checkTaprootInputFields = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; +exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootInputFields = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; +const types_1 = require('../types'); const psbtutils_1 = require('./psbtutils'); const taprootutils_1 = require('../payments/taprootutils'); const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); @@ -69,6 +70,76 @@ function tweakInternalPubKey(inputIndex, input) { return outputKey.x; } exports.tweakInternalPubKey = tweakInternalPubKey; +/** + * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371): + * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree, + * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that + * the tree is correctly reconstructed. + * @param tree the binary tap tree + * @returns a list of BIP 371 tapleaves + */ +function tapTreeToList(tree) { + return _tapTreeToList(tree); +} +exports.tapTreeToList = tapTreeToList; +/** + * Convert a BIP371 TapLeaf list to a TapTree (binary). + * @param leaves a list of tapleaves where each element of the list is (according to BIP371): + * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree, + * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that + * the tree is correctly reconstructed. + * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed + */ +function tapTreeFromList(leaves = []) { + if (leaves.length === 1 && leaves[0].depth === 0) + return { + output: leaves[0].script, + version: leaves[0].leafVersion, + }; + return instertLeavesInTree(leaves); +} +exports.tapTreeFromList = tapTreeFromList; +function _tapTreeToList(tree, leaves = [], depth = 0) { + if (depth > taprootutils_1.MAX_TAPTREE_DEPTH) + throw new Error('Max taptree depth exceeded.'); + if (!tree) return []; + if ((0, types_1.isTapleaf)(tree)) { + leaves.push({ + depth, + leafVersion: tree.version || taprootutils_1.LEAF_VERSION_TAPSCRIPT, + script: tree.output, + }); + return leaves; + } + if (tree[0]) _tapTreeToList(tree[0], leaves, depth + 1); + if (tree[1]) _tapTreeToList(tree[1], leaves, depth + 1); + return leaves; +} +function instertLeavesInTree(leaves) { + let tree; + for (const leaf of leaves) { + tree = instertLeafInTree(leaf, tree); + if (!tree) throw new Error(`No room left to insert tapleaf in tree`); + } + return tree; +} +function instertLeafInTree(leaf, tree, depth = 0) { + if (depth > taprootutils_1.MAX_TAPTREE_DEPTH) + throw new Error('Max taptree depth exceeded.'); + if (leaf.depth === depth) { + if (!tree) + return { + output: leaf.script, + version: leaf.leafVersion, + }; + return; + } + if ((0, types_1.isTapleaf)(tree)) return; + const leftSide = instertLeafInTree(leaf, tree && tree[0], depth + 1); + if (leftSide) return [leftSide, tree && tree[1]]; + const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1); + if (rightSide) return [tree && tree[0], rightSide]; +} function checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action) { const isBadTaprootUpdate = isTaprootInput(inputData) && hasNonTaprootInputFields(newInputData); diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/taprootutils.ts index 39d815e44..503819325 100644 --- a/ts_src/payments/taprootutils.ts +++ b/ts_src/payments/taprootutils.ts @@ -6,6 +6,7 @@ import { varuint } from '../bufferutils'; import { Tapleaf, Taptree, isTapleaf } from '../types'; export const LEAF_VERSION_TAPSCRIPT = 0xc0; +export const MAX_TAPTREE_DEPTH = 128; interface HashLeaf { hash: Buffer; diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 618ff25c5..b056a5371 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -1,7 +1,9 @@ +import { Taptree, Tapleaf, isTapleaf } from '../types'; import { PsbtInput, TapLeafScript, TapScriptSig, + TapLeaf, } from 'bip174/src/lib/interfaces'; import { @@ -13,6 +15,8 @@ import { tweakKey, tapleafHash, rootHashFromPath, + LEAF_VERSION_TAPSCRIPT, + MAX_TAPTREE_DEPTH, } from '../payments/taprootutils'; export const toXOnly = (pubKey: Buffer) => @@ -98,6 +102,94 @@ export function tweakInternalPubKey( return outputKey.x; } +/** + * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371): + * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree, + * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that + * the tree is correctly reconstructed. + * @param tree the binary tap tree + * @returns a list of BIP 371 tapleaves + */ +export function tapTreeToList(tree: Taptree): TapLeaf[] { + return _tapTreeToList(tree); +} + +/** + * Convert a BIP371 TapLeaf list to a TapTree (binary). + * @param leaves a list of tapleaves where each element of the list is (according to BIP371): + * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree, + * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that + * the tree is correctly reconstructed. + * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed + */ +export function tapTreeFromList(leaves: TapLeaf[] = []): Taptree { + if (leaves.length === 1 && leaves[0].depth === 0) + return { + output: leaves[0].script, + version: leaves[0].leafVersion, + }; + + return instertLeavesInTree(leaves); +} + +function _tapTreeToList( + tree: Taptree, + leaves: TapLeaf[] = [], + depth = 0, +): TapLeaf[] { + if (depth > MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); + if (!tree) return []; + if (isTapleaf(tree)) { + leaves.push({ + depth, + leafVersion: tree.version || LEAF_VERSION_TAPSCRIPT, + script: tree.output, + }); + return leaves; + } + if (tree[0]) _tapTreeToList(tree[0], leaves, depth + 1); + if (tree[1]) _tapTreeToList(tree[1], leaves, depth + 1); + return leaves; +} + +// Just like Taptree, but it accepts empty branches +type PartialTaptree = + | [PartialTaptree | Tapleaf, PartialTaptree | Tapleaf] + | Tapleaf + | undefined; +function instertLeavesInTree(leaves: TapLeaf[]): Taptree { + let tree: PartialTaptree; + for (const leaf of leaves) { + tree = instertLeafInTree(leaf, tree); + if (!tree) throw new Error(`No room left to insert tapleaf in tree`); + } + + return tree as Taptree; +} + +function instertLeafInTree( + leaf: TapLeaf, + tree?: PartialTaptree, + depth = 0, +): PartialTaptree { + if (depth > MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); + if (leaf.depth === depth) { + if (!tree) + return { + output: leaf.script, + version: leaf.leafVersion, + }; + return; + } + + if (isTapleaf(tree)) return; + const leftSide = instertLeafInTree(leaf, tree && tree[0], depth + 1); + if (leftSide) return [leftSide, tree && tree[1]]; + + const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1); + if (rightSide) return [tree && tree[0], rightSide]; +} + function checkMixedTaprootAndNonTaprootFields( inputData: PsbtInput, newInputData: PsbtInput, From 9eb0790de436ec797f592a11c5a09bbe4fc3ec8f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 24 May 2022 14:10:09 +0300 Subject: [PATCH 093/249] test: add tests for taptree conversion to tapleaf --- src/psbt/bip371.js | 4 +++ src/types.js | 2 +- test/payments.utils.ts | 9 ++++-- test/psbt.spec.ts | 66 ++++++++++++++++++++++++++++++++++++++++++ ts_src/psbt/bip371.ts | 6 +++- ts_src/types.ts | 2 +- 6 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 40a54918b..9b9fac161 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -79,6 +79,10 @@ exports.tweakInternalPubKey = tweakInternalPubKey; * @returns a list of BIP 371 tapleaves */ function tapTreeToList(tree) { + if (!(0, types_1.isTaptree)(tree)) + throw new Error( + 'Cannot convert taptree to tapleaf list. Expecting a tapree structure.', + ); return _tapTreeToList(tree); } exports.tapTreeToList = tapTreeToList; diff --git a/src/types.js b/src/types.js index e1d0a528d..3a2dc51ea 100644 --- a/src/types.js +++ b/src/types.js @@ -70,7 +70,7 @@ exports.Network = exports.typeforce.compile({ }); exports.TAPLEAF_VERSION_MASK = 0xfe; function isTapleaf(o) { - if (!('output' in o)) return false; + if (!o || !('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; diff --git a/test/payments.utils.ts b/test/payments.utils.ts index 12fc20712..3f86770e8 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -186,11 +186,14 @@ export function from(path: string, object: any, result?: any): any { return result; } -function convertScriptTree(scriptTree: any): any { - if (Array.isArray(scriptTree)) return scriptTree.map(convertScriptTree); +export function convertScriptTree(scriptTree: any, leafVersion?: number): any { + if (Array.isArray(scriptTree)) + return scriptTree.map(t => convertScriptTree(t, leafVersion)); const script = Object.assign({}, scriptTree); - if (typeof script.output === 'string') + if (typeof script.output === 'string') { script.output = asmToBuffer(scriptTree.output); + script.version = script.version || leafVersion; + } return script; } diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 97f558b2f..39fcf4a7f 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -5,6 +5,10 @@ import * as crypto from 'crypto'; import ECPairFactory from 'ecpair'; import { describe, it } from 'mocha'; +import { convertScriptTree } from './payments.utils'; +import { LEAF_VERSION_TAPSCRIPT } from '../src/payments/taprootutils'; +import { tapTreeToList, tapTreeFromList } from '../src/psbt/bip371'; +import { Taptree } from '../src/types'; import { initEccLib } from '../src'; const bip32 = BIP32Factory(ecc); @@ -13,6 +17,7 @@ const ECPair = ECPairFactory(ecc); import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; import * as preFixtures from './fixtures/psbt.json'; +import * as taprootFixtures from './fixtures/p2tr.json'; const validator = ( pubkey: Buffer, @@ -1099,6 +1104,67 @@ describe(`Psbt`, () => { }); }); + describe('tapTreeToList/tapTreeFromList', () => { + it('Correctly converts a Taptree to a Tapleaf list and back', () => { + taprootFixtures.valid + .filter(f => f.arguments.scriptTree) + .map(f => f.arguments.scriptTree) + .forEach(scriptTree => { + const originalTree = convertScriptTree( + scriptTree, + LEAF_VERSION_TAPSCRIPT, + ); + const list = tapTreeToList(originalTree); + const treeFromList = tapTreeFromList(list); + + assert.deepStrictEqual(treeFromList, originalTree); + }); + }); + it('Throws if too many leaves on a given level', () => { + const list = Array.from({ length: 5 }).map(() => ({ + depth: 2, + leafVersion: LEAF_VERSION_TAPSCRIPT, + script: Buffer.from([]), + })); + assert.throws(() => { + tapTreeFromList(list); + }, new RegExp('No room left to insert tapleaf in tree')); + }); + it('Throws if taptree depth is exceeded', () => { + let tree: Taptree = [ + { output: Buffer.from([]) }, + { output: Buffer.from([]) }, + ]; + Array.from({ length: 129 }).forEach( + () => (tree = [tree, { output: Buffer.from([]) }]), + ); + assert.throws(() => { + tapTreeToList(tree as Taptree); + }, new RegExp('Max taptree depth exceeded.')); + }); + it('Throws if tapleaf depth is to high', () => { + const list = [ + { + depth: 129, + leafVersion: LEAF_VERSION_TAPSCRIPT, + script: Buffer.from([]), + }, + ]; + assert.throws(() => { + tapTreeFromList(list); + }, new RegExp('Max taptree depth exceeded.')); + }); + it('Throws if not a valid taptree structure', () => { + const tree = Array.from({ length: 3 }).map(() => ({ + output: Buffer.from([]), + })); + + assert.throws(() => { + tapTreeToList(tree as Taptree); + }, new RegExp('Cannot convert taptree to tapleaf list. Expecting a tapree structure.')); + }); + }); + describe('getFeeRate', () => { it('Throws error if called before inputs are finalized', () => { const f = fixtures.getFeeRate; diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index b056a5371..93569c491 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -1,4 +1,4 @@ -import { Taptree, Tapleaf, isTapleaf } from '../types'; +import { Taptree, Tapleaf, isTapleaf, isTaptree } from '../types'; import { PsbtInput, TapLeafScript, @@ -111,6 +111,10 @@ export function tweakInternalPubKey( * @returns a list of BIP 371 tapleaves */ export function tapTreeToList(tree: Taptree): TapLeaf[] { + if (!isTaptree(tree)) + throw new Error( + 'Cannot convert taptree to tapleaf list. Expecting a tapree structure.', + ); return _tapTreeToList(tree); } diff --git a/ts_src/types.ts b/ts_src/types.ts index 536646e86..b904fd49b 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -79,7 +79,7 @@ export interface Tapleaf { export const TAPLEAF_VERSION_MASK = 0xfe; export function isTapleaf(o: any): o is Tapleaf { - if (!('output' in o)) return false; + if (!o || !('output' in o)) return false; if (!NBuffer.isBuffer(o.output)) return false; if (o.version !== undefined) return (o.version & TAPLEAF_VERSION_MASK) === o.version; From bc3dc464a63da105401a068e65e569ea64a5dcdf Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 24 May 2022 14:31:54 +0300 Subject: [PATCH 094/249] chore: code lint --- test/psbt.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 39fcf4a7f..d2026e9a6 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -1120,6 +1120,7 @@ describe(`Psbt`, () => { assert.deepStrictEqual(treeFromList, originalTree); }); }); + it('Throws if too many leaves on a given level', () => { const list = Array.from({ length: 5 }).map(() => ({ depth: 2, @@ -1130,6 +1131,7 @@ describe(`Psbt`, () => { tapTreeFromList(list); }, new RegExp('No room left to insert tapleaf in tree')); }); + it('Throws if taptree depth is exceeded', () => { let tree: Taptree = [ { output: Buffer.from([]) }, @@ -1142,6 +1144,7 @@ describe(`Psbt`, () => { tapTreeToList(tree as Taptree); }, new RegExp('Max taptree depth exceeded.')); }); + it('Throws if tapleaf depth is to high', () => { const list = [ { @@ -1154,6 +1157,7 @@ describe(`Psbt`, () => { tapTreeFromList(list); }, new RegExp('Max taptree depth exceeded.')); }); + it('Throws if not a valid taptree structure', () => { const tree = Array.from({ length: 3 }).map(() => ({ output: Buffer.from([]), From d7e24cbbda838cceb8a15d361b7c2fbe8b06d463 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 25 May 2022 13:53:06 +0300 Subject: [PATCH 095/249] test: use `tapInternalKey` to generate the address --- test/integration/taproot.spec.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 5e05c8887..e537cf075 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -53,7 +53,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { const internalKey = bip32.fromSeed(rng(64), regtest); const p2pkhKey = bip32.fromSeed(rng(64), regtest); - const { output, address } = bitcoin.payments.p2tr({ + const { output } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), network: regtest, }); @@ -84,7 +84,17 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); psbt.addInput({ index: 0, hash: p2pkhUnspent.txId, nonWitnessUtxo }); - psbt.addOutput({ value: sendAmount, address: address! }); + const sendInternalKey = bip32.fromSeed(rng(64), regtest); + const sendPubKey = toXOnly(sendInternalKey.publicKey); + const { address: sendAddress } = bitcoin.payments.p2tr({ + internalPubkey: sendPubKey, + network: regtest, + }); + + psbt.addOutput({ + value: sendAmount, + tapInternalKey: sendPubKey, + }); const tweakedSigner = tweakSigner(internalKey!, { network: regtest }); await psbt.signInputAsync(0, tweakedSigner); @@ -99,7 +109,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { await regtestUtils.broadcast(hex); await regtestUtils.verify({ txId: tx.getId(), - address: address!, + address: sendAddress!, vout: 0, value: sendAmount, }); From 58258a722ecea34626450b9db8776e30c8932f00 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 25 May 2022 14:51:34 +0300 Subject: [PATCH 096/249] feat: do taproot checks for `addOutput()` --- src/psbt.d.ts | 10 ++- src/psbt.js | 21 +++++- src/psbt/bip371.d.ts | 9 ++- src/psbt/bip371.js | 88 +++++++++++++++++++++---- test/fixtures/psbt.json | 108 +++++++++++++++++++++++++++++-- test/integration/taproot.spec.ts | 20 ++++-- test/psbt.spec.ts | 6 +- ts_src/psbt.ts | 35 +++++++++- ts_src/psbt/bip371.ts | 102 +++++++++++++++++++++++++---- 9 files changed, 360 insertions(+), 39 deletions(-) diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 4c622b31f..646edd5c0 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -1,6 +1,6 @@ /// import { Psbt as PsbtBase } from 'bip174'; -import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces'; +import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TapInternalKey, TapTree } from 'bip174/src/lib/interfaces'; import { Network } from './networks'; import { Transaction } from './transaction'; export interface TransactionInput { @@ -125,7 +125,7 @@ interface PsbtOptsOptional { } interface PsbtInputExtended extends PsbtInput, TransactionInput { } -declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; +declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript | PsbtOutputExtendedTaproot; interface PsbtOutputExtendedAddress extends PsbtOutput { address: string; value: number; @@ -134,6 +134,12 @@ interface PsbtOutputExtendedScript extends PsbtOutput { script: Buffer; value: number; } +interface PsbtOutputExtendedTaproot extends PsbtOutput { + tapInternalKey: TapInternalKey; + tapTree?: TapTree; + script?: Buffer; + value: number; +} interface HDSignerBase { /** * DER format compressed publicKey buffer diff --git a/src/psbt.js b/src/psbt.js index 5496008ca..ced6105ac 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -226,11 +226,13 @@ class Psbt { arguments.length > 1 || !outputData || outputData.value === undefined || - (outputData.address === undefined && outputData.script === undefined) + (outputData.address === undefined && + outputData.script === undefined && + outputData.tapInternalKey === undefined) ) { throw new Error( `Invalid arguments for Psbt.addOutput. ` + - `Requires single object with at least [script or address] and [value]`, + `Requires single object with at least [script, address or tapInternalKey] and [value]`, ); } checkInputsForPartialSig(this.data.inputs, 'addOutput'); @@ -240,6 +242,20 @@ class Psbt { const script = (0, address_1.toOutputScript)(address, network); outputData = Object.assign(outputData, { script }); } + if ((0, bip371_1.isTaprootOutput)(outputData)) { + (0, bip371_1.checkTaprootOutputFields)( + outputData, + outputData, + 'addOutput', + ); + const scriptAndAddress = (0, bip371_1.getNewTaprootScriptAndAddress)( + outputData, + outputData, + this.opts.network, + ); + if (scriptAndAddress) + outputData = Object.assign(outputData, scriptAndAddress); + } const c = this.__CACHE; this.data.addOutput(outputData); c.__FEE = undefined; @@ -1028,6 +1044,7 @@ function checkFees(psbt, cache, opts) { } } function checkInputsForPartialSig(inputs, action) { + // todo: add for taproot inputs.forEach(input => { let throws = false; let pSigs = []; diff --git a/src/psbt/bip371.d.ts b/src/psbt/bip371.d.ts index a3784cfd1..411c7e3c3 100644 --- a/src/psbt/bip371.d.ts +++ b/src/psbt/bip371.d.ts @@ -1,6 +1,7 @@ /// import { Taptree } from '../types'; -import { PsbtInput, TapLeaf } from 'bip174/src/lib/interfaces'; +import { PsbtInput, PsbtOutput, TapLeaf } from 'bip174/src/lib/interfaces'; +import { Network } from '../networks'; export declare const toXOnly: (pubKey: Buffer) => Buffer; /** * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided. @@ -16,7 +17,13 @@ export declare function tapScriptFinalizer(inputIndex: number, input: PsbtInput, }; export declare function serializeTaprootSignature(sig: Buffer, sighashType?: number): Buffer; export declare function isTaprootInput(input: PsbtInput): boolean; +export declare function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean; export declare function checkTaprootInputFields(inputData: PsbtInput, newInputData: PsbtInput, action: string): void; +export declare function checkTaprootOutputFields(outputData: PsbtOutput, newOutputData: PsbtOutput, action: string): void; +export declare function getNewTaprootScriptAndAddress(outputData: PsbtOutput, newOutputData: PsbtOutput, network?: Network): { + script: Buffer; + address: string; +} | undefined; export declare function tweakInternalPubKey(inputIndex: number, input: PsbtInput): Buffer; /** * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371): diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 9b9fac161..677f20fca 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -1,9 +1,10 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootInputFields = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; +exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.getNewTaprootScriptAndAddress = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; const types_1 = require('../types'); const psbtutils_1 = require('./psbtutils'); const taprootutils_1 = require('../payments/taprootutils'); +const payments_1 = require('../payments'); const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); exports.toXOnly = toXOnly; /** @@ -52,11 +53,54 @@ function isTaprootInput(input) { ); } exports.isTaprootInput = isTaprootInput; +function isTaprootOutput(output, script) { + return ( + output && + !!( + output.tapInternalKey || + output.tapTree || + (output.tapBip32Derivation && output.tapBip32Derivation.length) || + (script && (0, psbtutils_1.isP2TR)(script)) + ) + ); +} +exports.isTaprootOutput = isTaprootOutput; function checkTaprootInputFields(inputData, newInputData, action) { - checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action); + checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action); checkIfTapLeafInTree(inputData, newInputData, action); } exports.checkTaprootInputFields = checkTaprootInputFields; +function checkTaprootOutputFields(outputData, newOutputData, action) { + checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action); +} +exports.checkTaprootOutputFields = checkTaprootOutputFields; +function getNewTaprootScriptAndAddress(outputData, newOutputData, network) { + if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return; + const tapInternalKey = + newOutputData.tapInternalKey || outputData.tapInternalKey; + const tapTree = newOutputData.tapTree || outputData.tapTree; + if (tapInternalKey) { + const { script, address } = getTaprootScriptAndAddress( + tapInternalKey, + tapTree, + network, + ); + const { script: newScript } = newOutputData; + if (newScript && !newScript.equals(script)) + throw new Error('Error adding output. Script or address missmatch.'); + return { script, address }; + } +} +exports.getNewTaprootScriptAndAddress = getNewTaprootScriptAndAddress; +function getTaprootScriptAndAddress(tapInternalKey, tapTree, network) { + const scriptTree = tapTree && tapTreeFromList(tapTree.leaves); + const { output, address } = (0, payments_1.p2tr)({ + internalPubkey: tapInternalKey, + scriptTree, + network, + }); + return { script: output, address: address }; +} function tweakInternalPubKey(inputIndex, input) { const tapInternalKey = input.tapInternalKey; const outputKey = @@ -144,14 +188,36 @@ function instertLeafInTree(leaf, tree, depth = 0) { const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1); if (rightSide) return [tree && tree[0], rightSide]; } -function checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action) { +function checkMixedTaprootAndNonTaprootInputFields( + inputData, + newInputData, + action, +) { const isBadTaprootUpdate = - isTaprootInput(inputData) && hasNonTaprootInputFields(newInputData); + isTaprootInput(inputData) && hasNonTaprootFields(newInputData); const isBadNonTaprootUpdate = - hasNonTaprootInputFields(inputData) && isTaprootInput(newInputData); + hasNonTaprootFields(inputData) && isTaprootInput(newInputData); const hasMixedFields = inputData === newInputData && - (isTaprootInput(newInputData) && hasNonTaprootInputFields(newInputData)); + (isTaprootInput(newInputData) && hasNonTaprootFields(newInputData)); // todo: bad? use !=== + if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) + throw new Error( + `Invalid arguments for Psbt.${action}. ` + + `Cannot use both taproot and non-taproot fields.`, + ); +} +function checkMixedTaprootAndNonTaprootOutputFields( + inputData, + newInputData, + action, +) { + const isBadTaprootUpdate = + isTaprootOutput(inputData) && hasNonTaprootFields(newInputData); + const isBadNonTaprootUpdate = + hasNonTaprootFields(inputData) && isTaprootOutput(newInputData); + const hasMixedFields = + inputData === newInputData && + (isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData)); if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error( `Invalid arguments for Psbt.${action}. ` + @@ -244,13 +310,13 @@ function canFinalizeLeaf(leaf, tapScriptSig, hash) { tapScriptSig.find(tss => tss.leafHash.equals(leafHash)) !== undefined ); } -function hasNonTaprootInputFields(input) { +function hasNonTaprootFields(io) { return ( - input && + io && !!( - input.redeemScript || - input.witnessScript || - (input.bip32Derivation && input.bip32Derivation.length) + io.redeemScript || + io.witnessScript || + (io.bip32Derivation && io.bip32Derivation.length) ) ); } diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index 7bce10812..f2c990a8e 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -338,7 +338,7 @@ { "inputToSign": 0, "tapLeafHashToSign": "f2d9fd9a2f80e0e7abeac881398fc37198f46e5c802ec00c95152aa6f703e71e", - "WIF": "cP76Rzf6bVcmFbnJ3DigWvyNvki2bZeXxoq3B5pcZ8zVRnT4fKdx" + "WIF": "cP76Rzf6bVcmFbnJ3DigWvyNvki2bZeXxoq3B5pcZ8zVRnT4fKdx" } ], "result": "cHNidP8BAF4CAAAAAdXMkUOLeYvgm981k3T7Pmdf5Dr31jOxvpFHiXiU9D2gAAAAAAD/////AZBBBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqwAAAAAAAEBK6BoBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqxBFCo/Wsfk0hSOCy21lUyn+vXnc1SG1lNVWHlnXCh3xegq8tn9mi+A4Oer6siBOY/DcZj0blyALsAMlRUqpvcD5x5Aa7X0m4UCLaHA/Vnjkl+if6rVeiBbIlbHXHLh7RqJyB8Wgs66p6/ZnwSW/HD6o7rMHffIva+jgJgYWf6MvzrfTWIVwAjUk5QzdUBMEkLbwcjUHnMpA13k3j9ziDszUJiFladaGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdaTipNtL9o84yJyfhx52eesqJWF2CBZtnGxCFpdeDn8S2kgKj9ax+TSFI4LLbWVTKf69edzVIbWU1VYeWdcKHfF6CqsIJy6ZeYeVOa94xIEs49J9vofeVpf3u1m0XiCqbHNX6BsuiBcF5S+4nRv+Yw5rfnf3GLPT81/1FAJYZldegI1wVRh17pTnMBCFcAI1JOUM3VATBJC28HI1B5zKQNd5N4/c4g7M1CYhZWnWl8Vv/I1WWJt8byg3fk43iQn6QFlDgumFTPXr7uOK24+IyAqP1rH5NIUjgsttZVMp/r153NUhtZTVVh5Z1wod8XoKqzAAAA=" @@ -501,12 +501,111 @@ }, "exception": "Error adding output." }, + { + "description": "Checks that the output script can be computed", + "outputData": { + "value": 1000 + }, + "exception": "Invalid arguments for Psbt\\.addOutput. Requires single object with at least \\[script, address or tapInternalKey\\] and \\[value\\]" + }, { "description": "Adds output normally", "outputData": { "address": "1P2NFEBp32V2arRwZNww6tgXEV58FG94mr", "value": 42 } + }, + { + "description": "Adds taproot output with internal key only (address derived)", + "isTaproot": true, + "psbt": "cHNidP8BAFwCAAAAAo6LlrlIROHUfunj2OPeOpJlw/sG4cG99LrNl88u7rcrAAAAAAD/////LR6UXz/GymI8bcsv2b+NIzWKT/d9cEUPwWHREjtSapcAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRIAkRWDXSsncord4FtaUxPSWLua73cQbIN0uq2qyWztP3ARcgAqgH8rOX0SzoemaM+YlCy9DYeU1w9CrcR7Vm5/PPlxYAAQDAAgAAAAG67fXBoOPSqNrW6dbIkadKg+T6+tnU2pqoCSiOxyVdhQAAAABrSDBFAiEA9sK/dshQbR7W58jOzsH069Ms5C7Q4b0CDiDB//7BccECICDpjneIIw2Wgr9fq7VvqZfOgghSlXjiMaVr1Qqkb4qUASEC/r+CrU8sH0Jy0FIrGL6XQ8p0tDVOfnVpl6M6NKkwqZT/////AaBoBgAAAAAAGXapFLuFzFEeVsEgi+khfH7+Tc7+AvKBiKwAAAAAAAA=", + "outputData": { + "tapInternalKey": "Buffer.from('1a954a44837e47815f7285eae2dc3716f98eafb41a1155e999bfa66853b2cc47', 'hex')", + "value": 410000 + }, + "result": "cHNidP8BAIcCAAAAAo6LlrlIROHUfunj2OPeOpJlw/sG4cG99LrNl88u7rcrAAAAAAD/////LR6UXz/GymI8bcsv2b+NIzWKT/d9cEUPwWHREjtSapcAAAAAAP////8BkEEGAAAAAAAiUSCTYMKBNf5sZR1VO8dJdu2z5lO0vHqjXMjmOYotibmHLwAAAAAAAQEroGgGAAAAAAAiUSAJEVg10rJ3KK3eBbWlMT0li7mu93EGyDdLqtqsls7T9wEXIAKoB/Kzl9Es6HpmjPmJQsvQ2HlNcPQq3Ee1Zufzz5cWAAEAwAIAAAABuu31waDj0qja1unWyJGnSoPk+vrZ1NqaqAkojsclXYUAAAAAa0gwRQIhAPbCv3bIUG0e1ufIzs7B9OvTLOQu0OG9Ag4gwf/+wXHBAiAg6Y53iCMNloK/X6u1b6mXzoIIUpV44jGla9UKpG+KlAEhAv6/gq1PLB9CctBSKxi+l0PKdLQ1Tn51aZejOjSpMKmU/////wGgaAYAAAAAABl2qRS7hcxRHlbBIIvpIXx+/k3O/gLygYisAAAAAAABBSAalUpEg35HgV9yheri3DcW+Y6vtBoRVemZv6ZoU7LMRwA=" + }, + { + "description": "Adds taproot output with internal key and tap tree (address derived)", + "isTaproot": true, + "psbt": "cHNidP8BADMCAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AAAAAAAAAQEroGgGAAAAAAAiUSClLxmVQ6aZXLEwkYA/WGZuIE91BHT7xP7DIEAvz/NITaIVwd2NnolThxX+QCpgFksj8u93bzuZy6olaHxJNHaArwK1tCTeoJ+EC5MqADc83NvSVlC4w6z+VKn0pkGihnIbjSbax5V2a72h6uqkXlv6CpUP3V9MSq2lsfMILtyWibn9Cu4oo7gsSuy+vTeBJ9yI3Rnh6pPtunont1ThyIHTCIdKesRIlUS3uEDQRVmeyUFa5xzLCQwsRlwFyfoF9Bx6BtUjIFX368Cp2d6uXSbyrn23vC4KMjRRlEssuTTh7ThM6bXQrMAAAA==", + "outputData": { + "tapInternalKey": "Buffer.from('f6d4ce132444de7f0e3a1d2be9b38ceec798cf9a76eeeac585869445830eb167', 'hex')", + "tapTree": { + "leaves": [ + { + "depth": 2, + "leafVersion": 192, + "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ac', 'hex')" + }, + { + "depth": 3, + "leafVersion": 192, + "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac1ac', 'hex')" + }, + { + "depth": 3, + "leafVersion": 192, + "script": "Buffer.from('202258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7ac', 'hex')" + }, + { + "depth": 2, + "leafVersion": 192, + "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac2ac', 'hex')" + }, + { + "depth": 3, + "leafVersion": 192, + "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac3ac', 'hex')" + }, + { + "depth": 4, + "leafVersion": 192, + "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac', 'hex')" + }, + { + "depth": 4, + "leafVersion": 192, + "script": "Buffer.from('2055f7ebc0a9d9deae5d26f2ae7db7bc2e0a323451944b2cb934e1ed384ce9b5d0ac', 'hex')" + } + ] + }, + "value": 410000 + }, + "result": "cHNidP8BAF4CAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AZBBBgAAAAAAIlEgNWEIXy34qyxTUMwj9KxV0rr9ScQ+yVC4JtLcaL2MZqcAAAAAAAEBK6BoBgAAAAAAIlEgpS8ZlUOmmVyxMJGAP1hmbiBPdQR0+8T+wyBAL8/zSE2iFcHdjZ6JU4cV/kAqYBZLI/Lvd287mcuqJWh8STR2gK8CtbQk3qCfhAuTKgA3PNzb0lZQuMOs/lSp9KZBooZyG40m2seVdmu9oerqpF5b+gqVD91fTEqtpbHzCC7clom5/QruKKO4LErsvr03gSfciN0Z4eqT7bp6J7dU4ciB0wiHSnrESJVEt7hA0EVZnslBWuccywkMLEZcBcn6BfQcegbVIyBV9+vAqdnerl0m8q59t7wuCjI0UZRLLLk04e04TOm10KzAAAEFIPbUzhMkRN5/DjodK+mzjO7HmM+adu7qxYWGlEWDDrFnAQb9AwECwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsCsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrBrAPAIiAiWLHDFgvghkpUGFTuyRZKVy8JT3ViYoKBqAc7uJFzp6wCwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsKsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrDrATAIiBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6xKwEwCIgVffrwKnZ3q5dJvKufbe8LgoyNFGUSyy5NOHtOEzptdCsAA==" + }, + { + "description": "Adds taproot output with internal key only and correct address", + "isTaproot": true, + "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=", + "outputData": { + "tapInternalKey": "Buffer.from('884d969439deced21d1ab71ecd9ef9a6a8795215588ce7eff4ad5efc903e40ec', 'hex')", + "address": "bc1p74zfvfd7rndcn8lvzuec4hj4kzp9nnjvapvx5fgtqsegzz656hhsee8kwu", + "value": 410000 + }, + "result": "cHNidP8BAIcCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8BkEEGAAAAAAAiUSD1RJYlvhzbiZ/sFzOK3lWwglnOTOhYaiULBDKBC1TV7wAAAAAAAQEroGgGAAAAAAAiUSDamnamg4Xj009vanSkEnFJsicRHg8Ndhy45DGDFUpZ5AEXIAzeGyc4sdx3RPPhVDOlIANZRCqYKSCJlVHj5gBmIe8mAAEAwAIAAAABWyKGgCag3f3+JmaaYGFeFEfW4FPJU/aUx+P2gyXRVFoAAAAAa0gwRQIhAOiUtMTHUkIRvvYAD0sRrOh2s8mrsjVATMK5hd/OiAMcAiBosh/UBwsyjgU7YPlbCEheJ1IvTXwLSQLQtNidN/iNOwEhA7tbZBCPFtW3eqqz2d7i2h7xPdAQ1L0QsL008z2D188H/////wGgaAYAAAAAABl2qRS+UBH+dZOEwOzKeXdfCDNnXau/aIisAAAAAAABBSCITZaUOd7O0h0atx7NnvmmqHlSFViM5+/0rV78kD5A7AA=" + }, + { + "description": "Adds taproot output with internal key and bad address", + "isTaproot": true, + "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=", + "outputData": { + "tapInternalKey": "Buffer.from('884d969439deced21d1ab71ecd9ef9a6a8795215588ce7eff4ad5efc903e40ec', 'hex')", + "address": "bc1p3efq8ujsj0qr5xvms7mv89p8cz0crqdtuxe9ms6grqgxc9sgsntslthf6w", + "value": 410000 + }, + "exception": "Error adding output. Script or address missmatch." + }, + { + "description": "Adds taproot output with both taproot and non-taproot fields", + "isTaproot": true, + "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=", + "outputData": { + "tapInternalKey": "Buffer.from('884d969439deced21d1ab71ecd9ef9a6a8795215588ce7eff4ad5efc903e40ec', 'hex')", + "redeemScript": "Buffer.from('5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae', 'hex')", + "value": 410000 + }, + "exception": "Invalid arguments for Psbt.addOutput. Cannot use both taproot and non-taproot fields." } ] }, @@ -609,7 +708,9 @@ "description": "allows signing non-whitelisted sighashtype when explicitly passed in", "shouldSign": { "psbt": "cHNidP8BADMBAAAAAYaq+PdOUY2PnV9kZKa82XlqrPByOqwH2TRg2+LQdqs2AAAAAAD/////AAAAAAAAAQEgAOH1BQAAAAAXqRSTNeWHqa9INvSnQ120SZeJc+6JSocBAwSBAAAAAQQWABQvLKRyDqYsPYImhD3eURpDGL10RwAA", - "sighashTypes": [129], + "sighashTypes": [ + 129 + ], "inputToCheck": 0, "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" } @@ -623,7 +724,6 @@ "WIF": "KypUz2y1jdgzM8HGDUx9DYLmyzd8EWhruvLX2J5iSL7MiAcc7dBG", "result": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBE0AAqkKg+dq3eThMoqzjh214urhgUoGTgwHlNyyMQ2RwhfeRIhAp+m9mZwQoXOxK7p2ILjf2G5j28F9KMhMzH7bXARcgWzC4qnD37J3WaDEbZPRihBXdI0gN68BGutJykDcHR6wBGCDlDrX1cnzwZvmcyLBH8M6NiS9lk7JVwM58wZZVOzmuMwAA" } - }, { "description": "check taproot script-path signer found for the input", @@ -756,4 +856,4 @@ "clone": { "psbt": "cHNidP8BAKYCAAAAAlwKQ3suPWwEJ/zQ9sZsIioOcHKU1KoLMxlMNSXVIkEWAAAAAAD/////YYDJMap+mYgbTrCNAdpWHN+EkKvl+XYao/6co/EQfwMAAAAAAP////8CkF8BAAAAAAAWABRnPBAmVHz2HL+8/1U+QG5L2thjmjhKAAAAAAAAIgAg700yfFRyhWzQnPHIUb/XQqsjlpf4A0uw682pCVWuQ8IAAAAAAAEBKzB1AAAAAAAAIgAgth9oE4cDfC5aV58VgkW5CptHsIxppYzJV8C5kT6aTo8iAgNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06UgwRQIhALO0xRpuqP3aVkm+DPykrtqe6fPNSgNblp9K9MAwmtHJAiAHV5ZvZN8Vi49n/o9ISFyvtHsPXXPKqBxC9m2m2HlpYgEBBSMhA182CcWcQ9L2zg9j8jlt7OlaIATNDgNFMBKB1J14wzTprAABASuAOAEAAAAAACIAILYfaBOHA3wuWlefFYJFuQqbR7CMaaWMyVfAuZE+mk6PIgIDXzYJxZxD0vbOD2PyOW3s6VogBM0OA0UwEoHUnXjDNOlIMEUCIQC6XN6tpo9mYlZej4BXSSh5D1K6aILBfQ4WBWXUrISx6wIgVaxFUsz8y59xJ1V4sZ1qarHX9pZ+MJmLKbl2ZSadisoBAQUjIQNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06awAAAA=" } -} +} \ No newline at end of file diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index e537cf075..ea676cc94 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -5,7 +5,7 @@ import { describe, it } from 'mocha'; import { regtestUtils } from './_regtest'; import * as bitcoin from '../..'; import { Taptree } from '../../src/types'; -import { toXOnly } from '../../src/psbt/bip371'; +import { toXOnly, tapTreeToList } from '../../src/psbt/bip371'; const rng = require('randombytes'); const regtest = regtestUtils.network; @@ -231,7 +231,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address, witness } = bitcoin.payments.p2tr({ + const { output, witness } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), scriptTree, redeem, @@ -261,7 +261,19 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { ], }); - psbt.addOutput({ value: sendAmount, address: address! }); + const sendInternalKey = bip32.fromSeed(rng(64), regtest); + const sendPubKey = toXOnly(sendInternalKey.publicKey); + const { address: sendAddress } = bitcoin.payments.p2tr({ + internalPubkey: sendPubKey, + scriptTree: scriptTree, + network: regtest, + }); + + psbt.addOutput({ + value: sendAmount, + tapInternalKey: sendPubKey, + tapTree: { leaves: tapTreeToList(scriptTree) }, + }); psbt.signInput(0, leafKey); psbt.finalizeInput(0); @@ -272,7 +284,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { await regtestUtils.broadcast(hex); await regtestUtils.verify({ txId: tx.getId(), - address: address!, + address: sendAddress!, vout: 0, value: sendAmount, }); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index d2026e9a6..21a0b1c29 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -621,7 +621,8 @@ describe(`Psbt`, () => { describe('addOutput', () => { fixtures.addOutput.checks.forEach(f => { it(f.description, () => { - const psbt = new Psbt(); + if (f.isTaproot) initEccLib(ecc); + const psbt = f.psbt ? Psbt.fromBase64(f.psbt) : new Psbt(); if (f.exception) { assert.throws(() => { @@ -634,6 +635,9 @@ describe(`Psbt`, () => { assert.doesNotThrow(() => { psbt.addOutput(f.outputData as any); }); + if (f.result) { + assert.strictEqual(psbt.toBase64(), f.result); + } assert.doesNotThrow(() => { psbt.addOutputs([f.outputData as any]); }); diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 3800ef5db..8467a9bf7 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -13,6 +13,8 @@ import { TransactionFromBuffer, TapKeySig, TapScriptSig, + TapInternalKey, + TapTree, } from 'bip174/src/lib/interfaces'; import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; import { fromOutputScript, toOutputScript } from './address'; @@ -27,8 +29,11 @@ import { tapScriptFinalizer, serializeTaprootSignature, isTaprootInput, + isTaprootOutput, checkTaprootInputFields, + checkTaprootOutputFields, tweakInternalPubKey, + getNewTaprootScriptAndAddress, } from './psbt/bip371'; import { witnessStackToScriptWitness, @@ -311,11 +316,12 @@ export class Psbt { !outputData || outputData.value === undefined || ((outputData as any).address === undefined && - (outputData as any).script === undefined) + (outputData as any).script === undefined && + (outputData as any).tapInternalKey === undefined) ) { throw new Error( `Invalid arguments for Psbt.addOutput. ` + - `Requires single object with at least [script or address] and [value]`, + `Requires single object with at least [script, address or tapInternalKey] and [value]`, ); } checkInputsForPartialSig(this.data.inputs, 'addOutput'); @@ -325,6 +331,18 @@ export class Psbt { const script = toOutputScript(address, network); outputData = Object.assign(outputData, { script }); } + + if (isTaprootOutput(outputData)) { + checkTaprootOutputFields(outputData, outputData, 'addOutput'); + const scriptAndAddress = getNewTaprootScriptAndAddress( + outputData, + outputData, + this.opts.network, + ); + if (scriptAndAddress) + outputData = Object.assign(outputData, scriptAndAddress); + } + const c = this.__CACHE; this.data.addOutput(outputData); c.__FEE = undefined; @@ -1147,7 +1165,10 @@ interface PsbtOpts { interface PsbtInputExtended extends PsbtInput, TransactionInput {} -type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; +type PsbtOutputExtended = + | PsbtOutputExtendedAddress + | PsbtOutputExtendedScript + | PsbtOutputExtendedTaproot; interface PsbtOutputExtendedAddress extends PsbtOutput { address: string; @@ -1159,6 +1180,13 @@ interface PsbtOutputExtendedScript extends PsbtOutput { value: number; } +interface PsbtOutputExtendedTaproot extends PsbtOutput { + tapInternalKey: TapInternalKey; + tapTree?: TapTree; + script?: Buffer; + value: number; +} + interface HDSignerBase { /** * DER format compressed publicKey buffer @@ -1361,6 +1389,7 @@ function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void { } function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void { + // todo: add for taproot inputs.forEach(input => { let throws = false; let pSigs: PartialSig[] = []; diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 93569c491..6d3216fa1 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -1,9 +1,12 @@ import { Taptree, Tapleaf, isTapleaf, isTaptree } from '../types'; import { PsbtInput, + PsbtOutput, TapLeafScript, TapScriptSig, TapLeaf, + TapTree, + TapInternalKey, } from 'bip174/src/lib/interfaces'; import { @@ -18,6 +21,8 @@ import { LEAF_VERSION_TAPSCRIPT, MAX_TAPTREE_DEPTH, } from '../payments/taprootutils'; +import { p2tr } from '../payments'; +import { Network } from '../networks'; export const toXOnly = (pubKey: Buffer) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); @@ -77,15 +82,71 @@ export function isTaprootInput(input: PsbtInput): boolean { ); } +export function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean { + return ( + output && + !!( + output.tapInternalKey || + output.tapTree || + (output.tapBip32Derivation && output.tapBip32Derivation.length) || + (script && isP2TR(script)) + ) + ); +} + export function checkTaprootInputFields( inputData: PsbtInput, newInputData: PsbtInput, action: string, ): void { - checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action); + checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action); checkIfTapLeafInTree(inputData, newInputData, action); } +export function checkTaprootOutputFields( + outputData: PsbtOutput, + newOutputData: PsbtOutput, + action: string, +): void { + checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action); +} + +export function getNewTaprootScriptAndAddress( + outputData: PsbtOutput, + newOutputData: PsbtOutput, + network?: Network, +): { script: Buffer; address: string } | undefined { + if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return; + const tapInternalKey = + newOutputData.tapInternalKey || outputData.tapInternalKey; + const tapTree = newOutputData.tapTree || outputData.tapTree; + if (tapInternalKey) { + const { script, address } = getTaprootScriptAndAddress( + tapInternalKey, + tapTree, + network, + ); + const { script: newScript } = newOutputData as any; + if (newScript && !newScript.equals(script)) + throw new Error('Error adding output. Script or address missmatch.'); + return { script, address }; + } +} + +function getTaprootScriptAndAddress( + tapInternalKey: TapInternalKey, + tapTree?: TapTree, + network?: Network, +): { script: Buffer; address: string } { + const scriptTree = tapTree && tapTreeFromList(tapTree.leaves); + const { output, address } = p2tr({ + internalPubkey: tapInternalKey, + scriptTree, + network, + }); + return { script: output!, address: address! }; +} + export function tweakInternalPubKey( inputIndex: number, input: PsbtInput, @@ -194,18 +255,37 @@ function instertLeafInTree( if (rightSide) return [tree && tree[0], rightSide]; } -function checkMixedTaprootAndNonTaprootFields( - inputData: PsbtInput, +function checkMixedTaprootAndNonTaprootInputFields( + inputData: PsbtOutput, newInputData: PsbtInput, action: string, ): void { const isBadTaprootUpdate = - isTaprootInput(inputData) && hasNonTaprootInputFields(newInputData); + isTaprootInput(inputData) && hasNonTaprootFields(newInputData); + const isBadNonTaprootUpdate = + hasNonTaprootFields(inputData) && isTaprootInput(newInputData); + const hasMixedFields = + inputData === newInputData && + (isTaprootInput(newInputData) && hasNonTaprootFields(newInputData)); // todo: bad? use !=== + + if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) + throw new Error( + `Invalid arguments for Psbt.${action}. ` + + `Cannot use both taproot and non-taproot fields.`, + ); +} +function checkMixedTaprootAndNonTaprootOutputFields( + inputData: PsbtOutput, + newInputData: PsbtOutput, + action: string, +): void { + const isBadTaprootUpdate = + isTaprootOutput(inputData) && hasNonTaprootFields(newInputData); const isBadNonTaprootUpdate = - hasNonTaprootInputFields(inputData) && isTaprootInput(newInputData); + hasNonTaprootFields(inputData) && isTaprootOutput(newInputData); const hasMixedFields = inputData === newInputData && - (isTaprootInput(newInputData) && hasNonTaprootInputFields(newInputData)); + (isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData)); if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error( @@ -320,13 +400,13 @@ function canFinalizeLeaf( ); } -function hasNonTaprootInputFields(input: PsbtInput): boolean { +function hasNonTaprootFields(io: PsbtInput | PsbtOutput): boolean { return ( - input && + io && !!( - input.redeemScript || - input.witnessScript || - (input.bip32Derivation && input.bip32Derivation.length) + io.redeemScript || + io.witnessScript || + (io.bip32Derivation && io.bip32Derivation.length) ) ); } From 8ca34c05daa9551bb9e882c34b2e91c8061ebece Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 25 May 2022 16:15:34 +0300 Subject: [PATCH 097/249] refactor: simplify `addOutput()` checks --- src/psbt.d.ts | 10 ++------- src/psbt.js | 23 +++++--------------- src/psbt/bip371.d.ts | 5 ----- src/psbt/bip371.js | 24 ++++++++------------- test/fixtures/psbt.json | 24 +++++++-------------- test/integration/taproot.spec.ts | 2 ++ ts_src/psbt.ts | 36 +++++++------------------------- ts_src/psbt/bip371.ts | 30 +++++++++++--------------- 8 files changed, 45 insertions(+), 109 deletions(-) diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 646edd5c0..4c622b31f 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -1,6 +1,6 @@ /// import { Psbt as PsbtBase } from 'bip174'; -import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TapInternalKey, TapTree } from 'bip174/src/lib/interfaces'; +import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces'; import { Network } from './networks'; import { Transaction } from './transaction'; export interface TransactionInput { @@ -125,7 +125,7 @@ interface PsbtOptsOptional { } interface PsbtInputExtended extends PsbtInput, TransactionInput { } -declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript | PsbtOutputExtendedTaproot; +declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; interface PsbtOutputExtendedAddress extends PsbtOutput { address: string; value: number; @@ -134,12 +134,6 @@ interface PsbtOutputExtendedScript extends PsbtOutput { script: Buffer; value: number; } -interface PsbtOutputExtendedTaproot extends PsbtOutput { - tapInternalKey: TapInternalKey; - tapTree?: TapTree; - script?: Buffer; - value: number; -} interface HDSignerBase { /** * DER format compressed publicKey buffer diff --git a/src/psbt.js b/src/psbt.js index ced6105ac..4e19eae8a 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -226,13 +226,11 @@ class Psbt { arguments.length > 1 || !outputData || outputData.value === undefined || - (outputData.address === undefined && - outputData.script === undefined && - outputData.tapInternalKey === undefined) + (outputData.address === undefined && outputData.script === undefined) ) { throw new Error( `Invalid arguments for Psbt.addOutput. ` + - `Requires single object with at least [script, address or tapInternalKey] and [value]`, + `Requires single object with at least [script or address] and [value]`, ); } checkInputsForPartialSig(this.data.inputs, 'addOutput'); @@ -242,20 +240,7 @@ class Psbt { const script = (0, address_1.toOutputScript)(address, network); outputData = Object.assign(outputData, { script }); } - if ((0, bip371_1.isTaprootOutput)(outputData)) { - (0, bip371_1.checkTaprootOutputFields)( - outputData, - outputData, - 'addOutput', - ); - const scriptAndAddress = (0, bip371_1.getNewTaprootScriptAndAddress)( - outputData, - outputData, - this.opts.network, - ); - if (scriptAndAddress) - outputData = Object.assign(outputData, scriptAndAddress); - } + (0, bip371_1.checkTaprootOutputFields)(outputData, outputData, 'addOutput'); const c = this.__CACHE; this.data.addOutput(outputData); c.__FEE = undefined; @@ -899,6 +884,8 @@ class Psbt { return this; } updateOutput(outputIndex, updateData) { + // const outputData = this.data.outputs[outputIndex]; + // checkTaprootOutputFields(outputData, updateData, 'updateOutput'); this.data.updateOutput(outputIndex, updateData); return this; } diff --git a/src/psbt/bip371.d.ts b/src/psbt/bip371.d.ts index 411c7e3c3..b734c87f5 100644 --- a/src/psbt/bip371.d.ts +++ b/src/psbt/bip371.d.ts @@ -1,7 +1,6 @@ /// import { Taptree } from '../types'; import { PsbtInput, PsbtOutput, TapLeaf } from 'bip174/src/lib/interfaces'; -import { Network } from '../networks'; export declare const toXOnly: (pubKey: Buffer) => Buffer; /** * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided. @@ -20,10 +19,6 @@ export declare function isTaprootInput(input: PsbtInput): boolean; export declare function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean; export declare function checkTaprootInputFields(inputData: PsbtInput, newInputData: PsbtInput, action: string): void; export declare function checkTaprootOutputFields(outputData: PsbtOutput, newOutputData: PsbtOutput, action: string): void; -export declare function getNewTaprootScriptAndAddress(outputData: PsbtOutput, newOutputData: PsbtOutput, network?: Network): { - script: Buffer; - address: string; -} | undefined; export declare function tweakInternalPubKey(inputIndex: number, input: PsbtInput): Buffer; /** * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371): diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 677f20fca..fbc7eb7e3 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.getNewTaprootScriptAndAddress = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; +exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; const types_1 = require('../types'); const psbtutils_1 = require('./psbtutils'); const taprootutils_1 = require('../payments/taprootutils'); @@ -72,34 +72,28 @@ function checkTaprootInputFields(inputData, newInputData, action) { exports.checkTaprootInputFields = checkTaprootInputFields; function checkTaprootOutputFields(outputData, newOutputData, action) { checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action); + checkTaprootScriptPubkey(outputData, newOutputData); } exports.checkTaprootOutputFields = checkTaprootOutputFields; -function getNewTaprootScriptAndAddress(outputData, newOutputData, network) { +function checkTaprootScriptPubkey(outputData, newOutputData) { if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return; const tapInternalKey = newOutputData.tapInternalKey || outputData.tapInternalKey; const tapTree = newOutputData.tapTree || outputData.tapTree; if (tapInternalKey) { - const { script, address } = getTaprootScriptAndAddress( - tapInternalKey, - tapTree, - network, - ); - const { script: newScript } = newOutputData; - if (newScript && !newScript.equals(script)) + const { script: scriptPubkey } = outputData; + const script = getTaprootScripPubkey(tapInternalKey, tapTree); + if (scriptPubkey && !scriptPubkey.equals(script)) throw new Error('Error adding output. Script or address missmatch.'); - return { script, address }; } } -exports.getNewTaprootScriptAndAddress = getNewTaprootScriptAndAddress; -function getTaprootScriptAndAddress(tapInternalKey, tapTree, network) { +function getTaprootScripPubkey(tapInternalKey, tapTree) { const scriptTree = tapTree && tapTreeFromList(tapTree.leaves); - const { output, address } = (0, payments_1.p2tr)({ + const { output } = (0, payments_1.p2tr)({ internalPubkey: tapInternalKey, scriptTree, - network, }); - return { script: output, address: address }; + return output; } function tweakInternalPubKey(inputIndex, input) { const tapInternalKey = input.tapInternalKey; diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index f2c990a8e..b35bef2f4 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -506,7 +506,7 @@ "outputData": { "value": 1000 }, - "exception": "Invalid arguments for Psbt\\.addOutput. Requires single object with at least \\[script, address or tapInternalKey\\] and \\[value\\]" + "exception": "Invalid arguments for Psbt\\.addOutput. Requires single object with at least \\[script or address\\] and \\[value\\]" }, { "description": "Adds output normally", @@ -516,20 +516,12 @@ } }, { - "description": "Adds taproot output with internal key only (address derived)", - "isTaproot": true, - "psbt": "cHNidP8BAFwCAAAAAo6LlrlIROHUfunj2OPeOpJlw/sG4cG99LrNl88u7rcrAAAAAAD/////LR6UXz/GymI8bcsv2b+NIzWKT/d9cEUPwWHREjtSapcAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRIAkRWDXSsncord4FtaUxPSWLua73cQbIN0uq2qyWztP3ARcgAqgH8rOX0SzoemaM+YlCy9DYeU1w9CrcR7Vm5/PPlxYAAQDAAgAAAAG67fXBoOPSqNrW6dbIkadKg+T6+tnU2pqoCSiOxyVdhQAAAABrSDBFAiEA9sK/dshQbR7W58jOzsH069Ms5C7Q4b0CDiDB//7BccECICDpjneIIw2Wgr9fq7VvqZfOgghSlXjiMaVr1Qqkb4qUASEC/r+CrU8sH0Jy0FIrGL6XQ8p0tDVOfnVpl6M6NKkwqZT/////AaBoBgAAAAAAGXapFLuFzFEeVsEgi+khfH7+Tc7+AvKBiKwAAAAAAAA=", - "outputData": { - "tapInternalKey": "Buffer.from('1a954a44837e47815f7285eae2dc3716f98eafb41a1155e999bfa66853b2cc47', 'hex')", - "value": 410000 - }, - "result": "cHNidP8BAIcCAAAAAo6LlrlIROHUfunj2OPeOpJlw/sG4cG99LrNl88u7rcrAAAAAAD/////LR6UXz/GymI8bcsv2b+NIzWKT/d9cEUPwWHREjtSapcAAAAAAP////8BkEEGAAAAAAAiUSCTYMKBNf5sZR1VO8dJdu2z5lO0vHqjXMjmOYotibmHLwAAAAAAAQEroGgGAAAAAAAiUSAJEVg10rJ3KK3eBbWlMT0li7mu93EGyDdLqtqsls7T9wEXIAKoB/Kzl9Es6HpmjPmJQsvQ2HlNcPQq3Ee1Zufzz5cWAAEAwAIAAAABuu31waDj0qja1unWyJGnSoPk+vrZ1NqaqAkojsclXYUAAAAAa0gwRQIhAPbCv3bIUG0e1ufIzs7B9OvTLOQu0OG9Ag4gwf/+wXHBAiAg6Y53iCMNloK/X6u1b6mXzoIIUpV44jGla9UKpG+KlAEhAv6/gq1PLB9CctBSKxi+l0PKdLQ1Tn51aZejOjSpMKmU/////wGgaAYAAAAAABl2qRS7hcxRHlbBIIvpIXx+/k3O/gLygYisAAAAAAABBSAalUpEg35HgV9yheri3DcW+Y6vtBoRVemZv6ZoU7LMRwA=" - }, - { - "description": "Adds taproot output with internal key and tap tree (address derived)", + "description": "Adds taproot output with internal key and tap tree", "isTaproot": true, "psbt": "cHNidP8BADMCAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AAAAAAAAAQEroGgGAAAAAAAiUSClLxmVQ6aZXLEwkYA/WGZuIE91BHT7xP7DIEAvz/NITaIVwd2NnolThxX+QCpgFksj8u93bzuZy6olaHxJNHaArwK1tCTeoJ+EC5MqADc83NvSVlC4w6z+VKn0pkGihnIbjSbax5V2a72h6uqkXlv6CpUP3V9MSq2lsfMILtyWibn9Cu4oo7gsSuy+vTeBJ9yI3Rnh6pPtunont1ThyIHTCIdKesRIlUS3uEDQRVmeyUFa5xzLCQwsRlwFyfoF9Bx6BtUjIFX368Cp2d6uXSbyrn23vC4KMjRRlEssuTTh7ThM6bXQrMAAAA==", "outputData": { + "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3", + "value": 410000, "tapInternalKey": "Buffer.from('f6d4ce132444de7f0e3a1d2be9b38ceec798cf9a76eeeac585869445830eb167', 'hex')", "tapTree": { "leaves": [ @@ -569,8 +561,7 @@ "script": "Buffer.from('2055f7ebc0a9d9deae5d26f2ae7db7bc2e0a323451944b2cb934e1ed384ce9b5d0ac', 'hex')" } ] - }, - "value": 410000 + } }, "result": "cHNidP8BAF4CAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AZBBBgAAAAAAIlEgNWEIXy34qyxTUMwj9KxV0rr9ScQ+yVC4JtLcaL2MZqcAAAAAAAEBK6BoBgAAAAAAIlEgpS8ZlUOmmVyxMJGAP1hmbiBPdQR0+8T+wyBAL8/zSE2iFcHdjZ6JU4cV/kAqYBZLI/Lvd287mcuqJWh8STR2gK8CtbQk3qCfhAuTKgA3PNzb0lZQuMOs/lSp9KZBooZyG40m2seVdmu9oerqpF5b+gqVD91fTEqtpbHzCC7clom5/QruKKO4LErsvr03gSfciN0Z4eqT7bp6J7dU4ciB0wiHSnrESJVEt7hA0EVZnslBWuccywkMLEZcBcn6BfQcegbVIyBV9+vAqdnerl0m8q59t7wuCjI0UZRLLLk04e04TOm10KzAAAEFIPbUzhMkRN5/DjodK+mzjO7HmM+adu7qxYWGlEWDDrFnAQb9AwECwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsCsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrBrAPAIiAiWLHDFgvghkpUGFTuyRZKVy8JT3ViYoKBqAc7uJFzp6wCwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsKsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrDrATAIiBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6xKwEwCIgVffrwKnZ3q5dJvKufbe8LgoyNFGUSyy5NOHtOEzptdCsAA==" }, @@ -601,9 +592,10 @@ "isTaproot": true, "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=", "outputData": { + "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3", + "value": 410000, "tapInternalKey": "Buffer.from('884d969439deced21d1ab71ecd9ef9a6a8795215588ce7eff4ad5efc903e40ec', 'hex')", - "redeemScript": "Buffer.from('5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae', 'hex')", - "value": 410000 + "redeemScript": "Buffer.from('5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae', 'hex')" }, "exception": "Invalid arguments for Psbt.addOutput. Cannot use both taproot and non-taproot fields." } diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index ea676cc94..80a1160db 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -93,6 +93,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { psbt.addOutput({ value: sendAmount, + address: sendAddress!, tapInternalKey: sendPubKey, }); @@ -271,6 +272,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { psbt.addOutput({ value: sendAmount, + address: sendAddress!, tapInternalKey: sendPubKey, tapTree: { leaves: tapTreeToList(scriptTree) }, }); diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 8467a9bf7..802840b37 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -13,8 +13,6 @@ import { TransactionFromBuffer, TapKeySig, TapScriptSig, - TapInternalKey, - TapTree, } from 'bip174/src/lib/interfaces'; import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; import { fromOutputScript, toOutputScript } from './address'; @@ -29,11 +27,9 @@ import { tapScriptFinalizer, serializeTaprootSignature, isTaprootInput, - isTaprootOutput, checkTaprootInputFields, checkTaprootOutputFields, tweakInternalPubKey, - getNewTaprootScriptAndAddress, } from './psbt/bip371'; import { witnessStackToScriptWitness, @@ -316,12 +312,11 @@ export class Psbt { !outputData || outputData.value === undefined || ((outputData as any).address === undefined && - (outputData as any).script === undefined && - (outputData as any).tapInternalKey === undefined) + (outputData as any).script === undefined) ) { throw new Error( `Invalid arguments for Psbt.addOutput. ` + - `Requires single object with at least [script, address or tapInternalKey] and [value]`, + `Requires single object with at least [script or address] and [value]`, ); } checkInputsForPartialSig(this.data.inputs, 'addOutput'); @@ -331,17 +326,7 @@ export class Psbt { const script = toOutputScript(address, network); outputData = Object.assign(outputData, { script }); } - - if (isTaprootOutput(outputData)) { - checkTaprootOutputFields(outputData, outputData, 'addOutput'); - const scriptAndAddress = getNewTaprootScriptAndAddress( - outputData, - outputData, - this.opts.network, - ); - if (scriptAndAddress) - outputData = Object.assign(outputData, scriptAndAddress); - } + checkTaprootOutputFields(outputData, outputData, 'addOutput'); const c = this.__CACHE; this.data.addOutput(outputData); @@ -1117,6 +1102,9 @@ export class Psbt { } updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this { + // const outputData = this.data.outputs[outputIndex]; + // checkTaprootOutputFields(outputData, updateData, 'updateOutput'); + this.data.updateOutput(outputIndex, updateData); return this; } @@ -1165,10 +1153,7 @@ interface PsbtOpts { interface PsbtInputExtended extends PsbtInput, TransactionInput {} -type PsbtOutputExtended = - | PsbtOutputExtendedAddress - | PsbtOutputExtendedScript - | PsbtOutputExtendedTaproot; +type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; interface PsbtOutputExtendedAddress extends PsbtOutput { address: string; @@ -1180,13 +1165,6 @@ interface PsbtOutputExtendedScript extends PsbtOutput { value: number; } -interface PsbtOutputExtendedTaproot extends PsbtOutput { - tapInternalKey: TapInternalKey; - tapTree?: TapTree; - script?: Buffer; - value: number; -} - interface HDSignerBase { /** * DER format compressed publicKey buffer diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 6d3216fa1..64de6c2d2 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -22,7 +22,6 @@ import { MAX_TAPTREE_DEPTH, } from '../payments/taprootutils'; import { p2tr } from '../payments'; -import { Network } from '../networks'; export const toXOnly = (pubKey: Buffer) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); @@ -109,42 +108,37 @@ export function checkTaprootOutputFields( action: string, ): void { checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action); + checkTaprootScriptPubkey(outputData, newOutputData); } -export function getNewTaprootScriptAndAddress( +function checkTaprootScriptPubkey( outputData: PsbtOutput, newOutputData: PsbtOutput, - network?: Network, -): { script: Buffer; address: string } | undefined { +): void { if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return; + const tapInternalKey = newOutputData.tapInternalKey || outputData.tapInternalKey; const tapTree = newOutputData.tapTree || outputData.tapTree; + if (tapInternalKey) { - const { script, address } = getTaprootScriptAndAddress( - tapInternalKey, - tapTree, - network, - ); - const { script: newScript } = newOutputData as any; - if (newScript && !newScript.equals(script)) + const { script: scriptPubkey } = outputData as any; + const script = getTaprootScripPubkey(tapInternalKey, tapTree); + if (scriptPubkey && !scriptPubkey.equals(script)) throw new Error('Error adding output. Script or address missmatch.'); - return { script, address }; } } -function getTaprootScriptAndAddress( +function getTaprootScripPubkey( tapInternalKey: TapInternalKey, tapTree?: TapTree, - network?: Network, -): { script: Buffer; address: string } { +): Buffer { const scriptTree = tapTree && tapTreeFromList(tapTree.leaves); - const { output, address } = p2tr({ + const { output } = p2tr({ internalPubkey: tapInternalKey, scriptTree, - network, }); - return { script: output!, address: address! }; + return output!; } export function tweakInternalPubKey( From e17e2bd298de6f4e169dbed66d81715581696bd5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 25 May 2022 17:23:22 +0300 Subject: [PATCH 098/249] feat: do taproot checks for `updateOutput()` --- src/psbt.js | 8 ++++++-- test/fixtures/psbt.json | 30 ++++++++++++++++++++++++++++++ test/integration/taproot.spec.ts | 20 +++++++++++++++++--- test/psbt.spec.ts | 1 + ts_src/psbt.ts | 4 ++-- 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 4e19eae8a..e0c8a1af2 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -884,8 +884,12 @@ class Psbt { return this; } updateOutput(outputIndex, updateData) { - // const outputData = this.data.outputs[outputIndex]; - // checkTaprootOutputFields(outputData, updateData, 'updateOutput'); + const outputData = this.data.outputs[outputIndex]; + (0, bip371_1.checkTaprootOutputFields)( + outputData, + updateData, + 'updateOutput', + ); this.data.updateOutput(outputIndex, updateData); return this; } diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index b35bef2f4..b213d98f8 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -247,6 +247,36 @@ } ], "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" + }, + { + "description": "Update taproot output", + "isTaproot": true, + "psbt": "cHNidP8BAF4CAAAAAbc48X2AGPRzFoMvXHqldFViBB89ZrPfTH6yxr42pJsWAAAAAAAKAAAAAZBBBgAAAAAAIlEgwjn5jxVDmCN6B+wTPt4zCjlF+5KOEDapZ4S9Y3HZxdwAAAAAAAEBK6BoBgAAAAAAIlEgOJumKbXWO2VGchS5k1l1NzOsAyg8aBVQbDNJ88BPtH5iFcF6ESg3Xx4VxbYhRfEEyJc6dumg3zlxfNO8piELt1JMShpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYmWrJ1IAOmFFKDvSwulomGXZP6LYOYOGBHzSRnrh2w9cb0vzIGrMAAAA==", + "outputData": [ + { + "tapInternalKey": "Buffer.from('b3310106b13af5def8fc341fe7120e844ab73d4437649aefcadd306d974c9958', 'hex')", + "tapTree": { + "leaves": [ + { + "depth": 1, + "leafVersion": 192, + "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ac', 'hex')" + }, + { + "depth": 2, + "leafVersion": 192, + "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ac', 'hex')" + }, + { + "depth": 2, + "leafVersion": 192, + "script": "Buffer.from('5ab2752003a6145283bd2c2e9689865d93fa2d8398386047cd2467ae1db0f5c6f4bf3206ac', 'hex')" + } + ] + } + } + ], + "result": "cHNidP8BAF4CAAAAAbc48X2AGPRzFoMvXHqldFViBB89ZrPfTH6yxr42pJsWAAAAAAAKAAAAAZBBBgAAAAAAIlEgwjn5jxVDmCN6B+wTPt4zCjlF+5KOEDapZ4S9Y3HZxdwAAAAAAAEBK6BoBgAAAAAAIlEgOJumKbXWO2VGchS5k1l1NzOsAyg8aBVQbDNJ88BPtH5iFcF6ESg3Xx4VxbYhRfEEyJc6dumg3zlxfNO8piELt1JMShpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYmWrJ1IAOmFFKDvSwulomGXZP6LYOYOGBHzSRnrh2w9cb0vzIGrMAAAQUgszEBBrE69d74/DQf5xIOhEq3PUQ3ZJrvyt0wbZdMmVgBBnIBwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsCsAsAiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrArALAJVqydSADphRSg70sLpaJhl2T+i2DmDhgR80kZ64dsPXG9L8yBqwA" } ], "signer": [ diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 80a1160db..2707462ca 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -322,7 +322,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { redeemVersion: 192, }; - const { output, address, witness } = bitcoin.payments.p2tr({ + const { output, witness } = bitcoin.payments.p2tr({ internalPubkey: toXOnly(internalKey.publicKey), scriptTree, redeem, @@ -352,7 +352,21 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }, ], }); - psbt.addOutput({ value: sendAmount, address: address! }); + + const sendInternalKey = bip32.fromSeed(rng(64), regtest); + const sendPubKey = toXOnly(sendInternalKey.publicKey); + const { address: sendAddress } = bitcoin.payments.p2tr({ + internalPubkey: sendPubKey, + scriptTree: scriptTree, + network: regtest, + }); + + psbt.addOutput({ value: sendAmount, address: sendAddress! }); + // just to test that updateOutput works as expected + psbt.updateOutput(0, { + tapInternalKey: sendPubKey, + tapTree: { leaves: tapTreeToList(scriptTree) }, + }); await psbt.signInputAsync(0, leafKey); @@ -376,7 +390,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { await regtestUtils.broadcast(hex); await regtestUtils.verify({ txId: tx.getId(), - address: address!, + address: sendAddress!, vout: 0, value: sendAmount, }); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 21a0b1c29..bbddb67c8 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -132,6 +132,7 @@ describe(`Psbt`, () => { fixtures.bip174.updater.forEach(f => { it('Updates PSBT to the expected result', () => { + if (f.isTaproot) initEccLib(ecc); const psbt = Psbt.fromBase64(f.psbt); for (const inputOrOutput of ['input', 'output']) { diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 802840b37..93637772a 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1102,8 +1102,8 @@ export class Psbt { } updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this { - // const outputData = this.data.outputs[outputIndex]; - // checkTaprootOutputFields(outputData, updateData, 'updateOutput'); + const outputData = this.data.outputs[outputIndex]; + checkTaprootOutputFields(outputData, updateData, 'updateOutput'); this.data.updateOutput(outputIndex, updateData); return this; From 057cdc55aef57143aa57d48ae36e0ffc9067e745 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 26 May 2022 14:47:58 +0300 Subject: [PATCH 099/249] feat: check if taproot signature are present before allowing psbt changes --- src/psbt.js | 102 +++++++++++++++++++++++---------- test/fixtures/psbt.json | 30 ++++++++++ ts_src/psbt.ts | 124 ++++++++++++++++++++++++++++++---------- 3 files changed, 195 insertions(+), 61 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index e0c8a1af2..2e58596dc 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1035,41 +1035,81 @@ function checkFees(psbt, cache, opts) { } } function checkInputsForPartialSig(inputs, action) { - // todo: add for taproot inputs.forEach(input => { - let throws = false; - let pSigs = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) return; - pSigs = getPsigsFromInputFinalScripts(input); - } else { - pSigs = input.partialSig; - } - pSigs.forEach(pSig => { - const { hashType } = bscript.signature.decode(pSig.signature); - const whitelist = []; - const isAnyoneCanPay = - hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case transaction_1.Transaction.SIGHASH_ALL: - break; - case transaction_1.Transaction.SIGHASH_SINGLE: - case transaction_1.Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - throws = true; - } - }); - if (throws) { + const throws = (0, bip371_1.isTaprootInput)(input) + ? checkTaprootInputForSigs(input, action) + : checkInputForSig(input, action); + if (throws) throw new Error('Can not modify transaction, signatures exist.'); - } }); } +function checkInputForSig(input, action) { + const pSigs = extractPartialSigs(input); + return pSigs.some(pSig => + signatureBlocksAction(pSig, bscript.signature.decode, action), + ); +} +function checkTaprootInputForSigs(input, action) { + const sigs = extractTaprootSigs(input); + return sigs.some(sig => + signatureBlocksAction(sig, decodeSchnorSignature, action), + ); +} +function decodeSchnorSignature(signature) { + return { + signature: signature.slice(0, 64), + hashType: + signature.slice(64)[0] || transaction_1.Transaction.SIGHASH_DEFAULT, + }; +} +function extractTaprootSigs(input) { + const sigs = []; + if (input.tapKeySig) sigs.push(input.tapKeySig); + if (input.tapScriptSig) + sigs.push(...input.tapScriptSig.map(s => s.signature)); + if (!sigs.length) { + const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness); + if (finalTapKeySig) sigs.push(finalTapKeySig); + } + return sigs; +} +function getTapKeySigFromWithness(finalScriptWitness) { + if (!finalScriptWitness) return; + const witness = finalScriptWitness.slice(2); + // todo: add schnor signature validation + if (witness.length === 64 || witness.length === 65) return witness; +} +function extractPartialSigs(input) { + let pSigs = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return []; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig; + } + return pSigs.map(p => p.signature); +} +function signatureBlocksAction(signature, signatureDecodeFn, action) { + const { hashType } = signatureDecodeFn(signature); + const whitelist = []; + const isAnyoneCanPay = + hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case transaction_1.Transaction.SIGHASH_ALL: + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + case transaction_1.Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + return true; + } + return false; +} function checkPartialSigSighashes(input) { if (!input.sighashType || !input.partialSig) return; const { partialSig, sighashType } = input; diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index b213d98f8..815bcb9df 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -628,6 +628,36 @@ "redeemScript": "Buffer.from('5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae', 'hex')" }, "exception": "Invalid arguments for Psbt.addOutput. Cannot use both taproot and non-taproot fields." + }, + { + "description": "Adds output after tapkey signature has been added", + "isTaproot": true, + "psbt": "cHNidP8BAIcCAAAAAvz6EyMK2VQ0DFKWZyIyGZLZCnaIDtG5RI55OrGVs6u7AAAAAAD/////oB9A2l2NN5WPNjBDaRsz9Nmla1WdM3jl96ElSV5WWHMAAAAAAP////8BkEEGAAAAAAAiUSCnucwjGG2x9zGqP+E0PwxTkptd/9AYMW12ch7ffaogBAAAAAAAAQEroGgGAAAAAAAiUSDbRhL5phuWYLyqKbSqVb/Pic2mpPL46idVKQur9i06ugETQOPidQWL9dHDxd819Pn5QNNGyH0fmDv58pexSJVDalZ0W/HPVliPFZy7s//0TfTbvFce87/nKXcrwz5YuItIY6cBFyAOgJIoTQyYcpAqNfsK9CutgWckFE3mBMgpL50lKXb7oQABAL8CAAAAAcdkwPNWdYy1O8QM1xRkrJjas72QGQPEanYEpv6iT3PXAAAAAGpHMEQCIFpl6Nx8hoZA1o5Huo958RS/Hndy+DloLhp0nvRyBzN8AiB9jEbitThSs6TZ1XVBHZebj4nZbLMg3CCtss8WSZpv7QEhA+VxIxwCWeqoeDMYcEw3EC0ZPxIkOqt0YSK9Y3hAKTd1/////wGgaAYAAAAAABl2qRS9gA7n636cw49cUBJWm7IDJPKuuoisAAAAACICAknGKVC7w5Ek4l3IK5XCUwLNCeU4DD+76cdMAYDB72g3RzBEAiAjekFbBddaHY/LZidwKd3sBbifjmbEpka2Ps0B2Crg+wIgUoN4BhGCwMRTDaHbAX+0MXSGYA9nSlqeQJXpWQRCBfQBAAEFIE6DvlLK0f4BrtpMgez01WQYKLUQZ8HtbfuDPD52hmSvAA==", + "outputData": { + "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3", + "value": 410000 + }, + "exception": "Can not modify transaction, signatures exist." + }, + { + "description": "Adds output after tapkey spend input has been finalized", + "isTaproot": true, + "psbt": "cHNidP8BAF4CAAAAAfyhtBTm+3OPYBuMHvdPMf9jqniZDY925hbmnt8hxbA6AAAAAAD/////AZBBBgAAAAAAIlEgV6CyzSs5a6Tc8A+TYOiozWnh6FRH9E/VWFanhMAEbLEAAAAAAAEBK6BoBgAAAAAAIlEgV6CyzSs5a6Tc8A+TYOiozWnh6FRH9E/VWFanhMAEbLEBCEIBQOAqWBD/2jhPWzQvesT8sjkN2Cowphp3QvmlWbHiLx753ChcUovvWyBlWiCq77Kk+lZGEhC4vjClSjc26br+dc8AAA==", + "outputData": { + "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3", + "value": 410000 + }, + "exception": "Can not modify transaction, signatures exist." + }, + { + "description": "Adds output after tapscript signatures have been added", + "isTaproot": true, + "psbt": "cHNidP8BAF4CAAAAAa/0mhnSBXdEBKbbMC+2hm6AZZtCLBxBeubd1sjtau5dAAAAAAD/////AZBBBgAAAAAAIlEgISRIfamb9rCYzad52ikfoUUuFlvyTcImZMavR0jEaUQAAAAAAAEBK6BoBgAAAAAAIlEgISRIfamb9rCYzad52ikfoUUuFlvyTcImZMavR0jEaURBFBnEcOpwiHjNYPtWJOrQ8Pgc9bxBKyZh/i2D837Z1rC8BibL1C4Z/5e6dKzWfkzpsIbE5WEVn1bYpAAjrqIKMHlAKkl3w3Gfpkl9b0yDVbTlZd4yCEL9V2DJs6zpPrEmn3wiohBy8wwE6EZ0FxQdrCupnHKXhHBjpcHVwfJRQfcy9EEULx1ijisiHgGb/9/hBNhsIOv1ZyWsfmi/Ql+oz7AOuqAGJsvULhn/l7p0rNZ+TOmwhsTlYRWfVtikACOuogoweUBKNkxBf6vT8m7ISt1WikLWW9udCP7OQLXztr1IPalJT5z+esAWmgeLS7QoLgzTu8AnYp/rHxsgZ6CgiV8tlkciQRRXE7VxCk67h7Ee6CbSgNyotChx7CgwNTfxdJkyvCS0DgYmy9QuGf+XunSs1n5M6bCGxOVhFZ9W2KQAI66iCjB5QFN6DGtLlSIFBjZbdh3rbKBtBcEDSiEcuVxnSPpdM1RnQRmw5Ujo+/76wZfmGBMFzV0IA7vnHzzXN73jT6O8/wJiFcDBdB6IhNxYUBYgZT1K7FG5SblQ3S6nQMKRLc2vPcA0BhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpIFcTtXEKTruHsR7oJtKA3Ki0KHHsKDA1N/F0mTK8JLQOrCAvHWKOKyIeAZv/3+EE2Gwg6/VnJax+aL9CX6jPsA66oLogGcRw6nCIeM1g+1Yk6tDw+Bz1vEErJmH+LYPzftnWsLy6U5zAAAA=", + "outputData": { + "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3", + "value": 410000 + }, + "exception": "Can not modify transaction, signatures exist." } ] }, diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 93637772a..b419e02fb 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1367,41 +1367,105 @@ function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void { } function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void { - // todo: add for taproot inputs.forEach(input => { - let throws = false; - let pSigs: PartialSig[] = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) return; - pSigs = getPsigsFromInputFinalScripts(input); - } else { - pSigs = input.partialSig!; - } - pSigs.forEach(pSig => { - const { hashType } = bscript.signature.decode(pSig.signature); - const whitelist: string[] = []; - const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case Transaction.SIGHASH_ALL: - break; - case Transaction.SIGHASH_SINGLE: - case Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - throws = true; - } - }); - if (throws) { + const throws = isTaprootInput(input) + ? checkTaprootInputForSigs(input, action) + : checkInputForSig(input, action); + if (throws) throw new Error('Can not modify transaction, signatures exist.'); - } }); } +function checkInputForSig(input: PsbtInput, action: string): boolean { + const pSigs = extractPartialSigs(input); + return pSigs.some(pSig => + signatureBlocksAction(pSig, bscript.signature.decode, action), + ); +} + +function checkTaprootInputForSigs(input: PsbtInput, action: string): boolean { + const sigs = extractTaprootSigs(input); + return sigs.some(sig => + signatureBlocksAction(sig, decodeSchnorSignature, action), + ); +} + +function decodeSchnorSignature( + signature: Buffer, +): { + signature: Buffer; + hashType: number; +} { + return { + signature: signature.slice(0, 64), + hashType: signature.slice(64)[0] || Transaction.SIGHASH_DEFAULT, + }; +} + +function extractTaprootSigs(input: PsbtInput): Buffer[] { + const sigs: Buffer[] = []; + if (input.tapKeySig) sigs.push(input.tapKeySig); + if (input.tapScriptSig) + sigs.push(...input.tapScriptSig.map(s => s.signature)); + if (!sigs.length) { + const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness); + if (finalTapKeySig) sigs.push(finalTapKeySig); + } + + return sigs; +} + +function getTapKeySigFromWithness( + finalScriptWitness?: Buffer, +): Buffer | undefined { + if (!finalScriptWitness) return; + const witness = finalScriptWitness.slice(2); + // todo: add schnor signature validation + if (witness.length === 64 || witness.length === 65) return witness; +} + +function extractPartialSigs(input: PsbtInput): Buffer[] { + let pSigs: PartialSig[] = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return []; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig!; + } + return pSigs.map(p => p.signature); +} + +type SignatureDecodeFunc = ( + buffer: Buffer, +) => { + signature: Buffer; + hashType: number; +}; +function signatureBlocksAction( + signature: Buffer, + signatureDecodeFn: SignatureDecodeFunc, + action: string, +): boolean { + const { hashType } = signatureDecodeFn(signature); + const whitelist: string[] = []; + const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case Transaction.SIGHASH_ALL: + break; + case Transaction.SIGHASH_SINGLE: + case Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + return true; + } + return false; +} + function checkPartialSigSighashes(input: PsbtInput): void { if (!input.sighashType || !input.partialSig) return; const { partialSig, sighashType } = input; From f32d706713b606c903c14ed763fd990166972b2d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 26 May 2022 15:02:03 +0300 Subject: [PATCH 100/249] refactor: move out some utils --- src/psbt.js | 85 +------------------------------ src/psbt/bip371.d.ts | 1 + src/psbt/bip371.js | 35 ++++++++++++- src/psbt/psbtutils.d.ts | 8 +++ src/psbt/psbtutils.js | 56 +++++++++++++++++++- ts_src/psbt.ts | 107 +-------------------------------------- ts_src/psbt/bip371.ts | 48 ++++++++++++++++++ ts_src/psbt/psbtutils.ts | 66 ++++++++++++++++++++++++ 8 files changed, 216 insertions(+), 190 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 2e58596dc..2bc55e065 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1037,79 +1037,12 @@ function checkFees(psbt, cache, opts) { function checkInputsForPartialSig(inputs, action) { inputs.forEach(input => { const throws = (0, bip371_1.isTaprootInput)(input) - ? checkTaprootInputForSigs(input, action) - : checkInputForSig(input, action); + ? (0, bip371_1.checkTaprootInputForSigs)(input, action) + : (0, psbtutils_1.checkInputForSig)(input, action); if (throws) throw new Error('Can not modify transaction, signatures exist.'); }); } -function checkInputForSig(input, action) { - const pSigs = extractPartialSigs(input); - return pSigs.some(pSig => - signatureBlocksAction(pSig, bscript.signature.decode, action), - ); -} -function checkTaprootInputForSigs(input, action) { - const sigs = extractTaprootSigs(input); - return sigs.some(sig => - signatureBlocksAction(sig, decodeSchnorSignature, action), - ); -} -function decodeSchnorSignature(signature) { - return { - signature: signature.slice(0, 64), - hashType: - signature.slice(64)[0] || transaction_1.Transaction.SIGHASH_DEFAULT, - }; -} -function extractTaprootSigs(input) { - const sigs = []; - if (input.tapKeySig) sigs.push(input.tapKeySig); - if (input.tapScriptSig) - sigs.push(...input.tapScriptSig.map(s => s.signature)); - if (!sigs.length) { - const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness); - if (finalTapKeySig) sigs.push(finalTapKeySig); - } - return sigs; -} -function getTapKeySigFromWithness(finalScriptWitness) { - if (!finalScriptWitness) return; - const witness = finalScriptWitness.slice(2); - // todo: add schnor signature validation - if (witness.length === 64 || witness.length === 65) return witness; -} -function extractPartialSigs(input) { - let pSigs = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) return []; - pSigs = getPsigsFromInputFinalScripts(input); - } else { - pSigs = input.partialSig; - } - return pSigs.map(p => p.signature); -} -function signatureBlocksAction(signature, signatureDecodeFn, action) { - const { hashType } = signatureDecodeFn(signature); - const whitelist = []; - const isAnyoneCanPay = - hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case transaction_1.Transaction.SIGHASH_ALL: - break; - case transaction_1.Transaction.SIGHASH_SINGLE: - case transaction_1.Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - return true; - } - return false; -} function checkPartialSigSighashes(input) { if (!input.sighashType || !input.partialSig) return; const { partialSig, sighashType } = input; @@ -1459,20 +1392,6 @@ function getPayment(script, scriptType, partialSig) { } return payment; } -function getPsigsFromInputFinalScripts(input) { - const scriptItems = !input.finalScriptSig - ? [] - : bscript.decompile(input.finalScriptSig) || []; - const witnessItems = !input.finalScriptWitness - ? [] - : bscript.decompile(input.finalScriptWitness) || []; - return scriptItems - .concat(witnessItems) - .filter(item => { - return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); - }) - .map(sig => ({ signature: sig })); -} function getScriptFromInput(inputIndex, input, cache) { const unsignedTx = cache.__TX; const res = { diff --git a/src/psbt/bip371.d.ts b/src/psbt/bip371.d.ts index b734c87f5..fae6e756b 100644 --- a/src/psbt/bip371.d.ts +++ b/src/psbt/bip371.d.ts @@ -38,3 +38,4 @@ export declare function tapTreeToList(tree: Taptree): TapLeaf[]; * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed */ export declare function tapTreeFromList(leaves?: TapLeaf[]): Taptree; +export declare function checkTaprootInputForSigs(input: PsbtInput, action: string): boolean; diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index fbc7eb7e3..eee6b1393 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -1,10 +1,12 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; +exports.checkTaprootInputForSigs = exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; const types_1 = require('../types'); +const transaction_1 = require('../transaction'); const psbtutils_1 = require('./psbtutils'); const taprootutils_1 = require('../payments/taprootutils'); const payments_1 = require('../payments'); +const psbtutils_2 = require('./psbtutils'); const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); exports.toXOnly = toXOnly; /** @@ -141,6 +143,37 @@ function tapTreeFromList(leaves = []) { return instertLeavesInTree(leaves); } exports.tapTreeFromList = tapTreeFromList; +function checkTaprootInputForSigs(input, action) { + const sigs = extractTaprootSigs(input); + return sigs.some(sig => + (0, psbtutils_2.signatureBlocksAction)(sig, decodeSchnorSignature, action), + ); +} +exports.checkTaprootInputForSigs = checkTaprootInputForSigs; +function decodeSchnorSignature(signature) { + return { + signature: signature.slice(0, 64), + hashType: + signature.slice(64)[0] || transaction_1.Transaction.SIGHASH_DEFAULT, + }; +} +function extractTaprootSigs(input) { + const sigs = []; + if (input.tapKeySig) sigs.push(input.tapKeySig); + if (input.tapScriptSig) + sigs.push(...input.tapScriptSig.map(s => s.signature)); + if (!sigs.length) { + const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness); + if (finalTapKeySig) sigs.push(finalTapKeySig); + } + return sigs; +} +function getTapKeySigFromWithness(finalScriptWitness) { + if (!finalScriptWitness) return; + const witness = finalScriptWitness.slice(2); + // todo: add schnor signature validation + if (witness.length === 64 || witness.length === 65) return witness; +} function _tapTreeToList(tree, leaves = [], depth = 0) { if (depth > taprootutils_1.MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); diff --git a/src/psbt/psbtutils.d.ts b/src/psbt/psbtutils.d.ts index c902e6cec..6491132a8 100644 --- a/src/psbt/psbtutils.d.ts +++ b/src/psbt/psbtutils.d.ts @@ -1,4 +1,5 @@ /// +import { PsbtInput } from 'bip174/src/lib/interfaces'; export declare const isP2MS: (script: Buffer) => boolean; export declare const isP2PK: (script: Buffer) => boolean; export declare const isP2PKH: (script: Buffer) => boolean; @@ -9,3 +10,10 @@ export declare const isP2TR: (script: Buffer) => boolean; export declare function witnessStackToScriptWitness(witness: Buffer[]): Buffer; export declare function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number; export declare function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean; +export declare function checkInputForSig(input: PsbtInput, action: string): boolean; +declare type SignatureDecodeFunc = (buffer: Buffer) => { + signature: Buffer; + hashType: number; +}; +export declare function signatureBlocksAction(signature: Buffer, signatureDecodeFn: SignatureDecodeFunc, action: string): boolean; +export {}; diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index 4ef56f6a5..086faa4f0 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -1,8 +1,9 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.pubkeyInScript = exports.pubkeyPositionInScript = exports.witnessStackToScriptWitness = exports.isP2TR = exports.isP2SHScript = exports.isP2WSHScript = exports.isP2WPKH = exports.isP2PKH = exports.isP2PK = exports.isP2MS = void 0; +exports.signatureBlocksAction = exports.checkInputForSig = exports.pubkeyInScript = exports.pubkeyPositionInScript = exports.witnessStackToScriptWitness = exports.isP2TR = exports.isP2SHScript = exports.isP2WSHScript = exports.isP2WPKH = exports.isP2PKH = exports.isP2PK = exports.isP2MS = void 0; const varuint = require('bip174/src/lib/converter/varint'); const bscript = require('../script'); +const transaction_1 = require('../transaction'); const crypto_1 = require('../crypto'); const payments = require('../payments'); function isPaymentFactory(payment) { @@ -64,3 +65,56 @@ function pubkeyInScript(pubkey, script) { return pubkeyPositionInScript(pubkey, script) !== -1; } exports.pubkeyInScript = pubkeyInScript; +function checkInputForSig(input, action) { + const pSigs = extractPartialSigs(input); + return pSigs.some(pSig => + signatureBlocksAction(pSig, bscript.signature.decode, action), + ); +} +exports.checkInputForSig = checkInputForSig; +function signatureBlocksAction(signature, signatureDecodeFn, action) { + const { hashType } = signatureDecodeFn(signature); + const whitelist = []; + const isAnyoneCanPay = + hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case transaction_1.Transaction.SIGHASH_ALL: + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + case transaction_1.Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + return true; + } + return false; +} +exports.signatureBlocksAction = signatureBlocksAction; +function extractPartialSigs(input) { + let pSigs = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return []; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig; + } + return pSigs.map(p => p.signature); +} +function getPsigsFromInputFinalScripts(input) { + const scriptItems = !input.finalScriptSig + ? [] + : bscript.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : bscript.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); + }) + .map(sig => ({ signature: sig })); +} diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index b419e02fb..a3b66c7e0 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -30,9 +30,11 @@ import { checkTaprootInputFields, checkTaprootOutputFields, tweakInternalPubKey, + checkTaprootInputForSigs, } from './psbt/bip371'; import { witnessStackToScriptWitness, + checkInputForSig, pubkeyInScript, isP2MS, isP2PK, @@ -1376,96 +1378,6 @@ function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void { }); } -function checkInputForSig(input: PsbtInput, action: string): boolean { - const pSigs = extractPartialSigs(input); - return pSigs.some(pSig => - signatureBlocksAction(pSig, bscript.signature.decode, action), - ); -} - -function checkTaprootInputForSigs(input: PsbtInput, action: string): boolean { - const sigs = extractTaprootSigs(input); - return sigs.some(sig => - signatureBlocksAction(sig, decodeSchnorSignature, action), - ); -} - -function decodeSchnorSignature( - signature: Buffer, -): { - signature: Buffer; - hashType: number; -} { - return { - signature: signature.slice(0, 64), - hashType: signature.slice(64)[0] || Transaction.SIGHASH_DEFAULT, - }; -} - -function extractTaprootSigs(input: PsbtInput): Buffer[] { - const sigs: Buffer[] = []; - if (input.tapKeySig) sigs.push(input.tapKeySig); - if (input.tapScriptSig) - sigs.push(...input.tapScriptSig.map(s => s.signature)); - if (!sigs.length) { - const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness); - if (finalTapKeySig) sigs.push(finalTapKeySig); - } - - return sigs; -} - -function getTapKeySigFromWithness( - finalScriptWitness?: Buffer, -): Buffer | undefined { - if (!finalScriptWitness) return; - const witness = finalScriptWitness.slice(2); - // todo: add schnor signature validation - if (witness.length === 64 || witness.length === 65) return witness; -} - -function extractPartialSigs(input: PsbtInput): Buffer[] { - let pSigs: PartialSig[] = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) return []; - pSigs = getPsigsFromInputFinalScripts(input); - } else { - pSigs = input.partialSig!; - } - return pSigs.map(p => p.signature); -} - -type SignatureDecodeFunc = ( - buffer: Buffer, -) => { - signature: Buffer; - hashType: number; -}; -function signatureBlocksAction( - signature: Buffer, - signatureDecodeFn: SignatureDecodeFunc, - action: string, -): boolean { - const { hashType } = signatureDecodeFn(signature); - const whitelist: string[] = []; - const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case Transaction.SIGHASH_ALL: - break; - case Transaction.SIGHASH_SINGLE: - case Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - return true; - } - return false; -} - function checkPartialSigSighashes(input: PsbtInput): void { if (!input.sighashType || !input.partialSig) return; const { partialSig, sighashType } = input; @@ -1926,21 +1838,6 @@ function getPayment( return payment!; } -function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { - const scriptItems = !input.finalScriptSig - ? [] - : bscript.decompile(input.finalScriptSig) || []; - const witnessItems = !input.finalScriptWitness - ? [] - : bscript.decompile(input.finalScriptWitness) || []; - return scriptItems - .concat(witnessItems) - .filter(item => { - return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); - }) - .map(sig => ({ signature: sig })) as PartialSig[]; -} - interface GetScriptReturn { script: Buffer | null; isSegwit: boolean; diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 64de6c2d2..153e289ec 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -9,6 +9,8 @@ import { TapInternalKey, } from 'bip174/src/lib/interfaces'; +import { Transaction } from '../transaction'; + import { witnessStackToScriptWitness, pubkeyPositionInScript, @@ -23,6 +25,8 @@ import { } from '../payments/taprootutils'; import { p2tr } from '../payments'; +import { signatureBlocksAction } from './psbtutils'; + export const toXOnly = (pubKey: Buffer) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); @@ -191,6 +195,50 @@ export function tapTreeFromList(leaves: TapLeaf[] = []): Taptree { return instertLeavesInTree(leaves); } +export function checkTaprootInputForSigs( + input: PsbtInput, + action: string, +): boolean { + const sigs = extractTaprootSigs(input); + return sigs.some(sig => + signatureBlocksAction(sig, decodeSchnorSignature, action), + ); +} + +function decodeSchnorSignature( + signature: Buffer, +): { + signature: Buffer; + hashType: number; +} { + return { + signature: signature.slice(0, 64), + hashType: signature.slice(64)[0] || Transaction.SIGHASH_DEFAULT, + }; +} + +function extractTaprootSigs(input: PsbtInput): Buffer[] { + const sigs: Buffer[] = []; + if (input.tapKeySig) sigs.push(input.tapKeySig); + if (input.tapScriptSig) + sigs.push(...input.tapScriptSig.map(s => s.signature)); + if (!sigs.length) { + const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness); + if (finalTapKeySig) sigs.push(finalTapKeySig); + } + + return sigs; +} + +function getTapKeySigFromWithness( + finalScriptWitness?: Buffer, +): Buffer | undefined { + if (!finalScriptWitness) return; + const witness = finalScriptWitness.slice(2); + // todo: add schnor signature validation + if (witness.length === 64 || witness.length === 65) return witness; +} + function _tapTreeToList( tree: Taptree, leaves: TapLeaf[] = [], diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 1a891f788..d51e2df54 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -1,5 +1,7 @@ import * as varuint from 'bip174/src/lib/converter/varint'; +import { PartialSig, PsbtInput } from 'bip174/src/lib/interfaces'; import * as bscript from '../script'; +import { Transaction } from '../transaction'; import { hash160 } from '../crypto'; import * as payments from '../payments'; @@ -71,3 +73,67 @@ export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { export function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean { return pubkeyPositionInScript(pubkey, script) !== -1; } + +export function checkInputForSig(input: PsbtInput, action: string): boolean { + const pSigs = extractPartialSigs(input); + return pSigs.some(pSig => + signatureBlocksAction(pSig, bscript.signature.decode, action), + ); +} + +type SignatureDecodeFunc = ( + buffer: Buffer, +) => { + signature: Buffer; + hashType: number; +}; +export function signatureBlocksAction( + signature: Buffer, + signatureDecodeFn: SignatureDecodeFunc, + action: string, +): boolean { + const { hashType } = signatureDecodeFn(signature); + const whitelist: string[] = []; + const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case Transaction.SIGHASH_ALL: + break; + case Transaction.SIGHASH_SINGLE: + case Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + return true; + } + return false; +} + +function extractPartialSigs(input: PsbtInput): Buffer[] { + let pSigs: PartialSig[] = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return []; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig!; + } + return pSigs.map(p => p.signature); +} + +function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { + const scriptItems = !input.finalScriptSig + ? [] + : bscript.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : bscript.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); + }) + .map(sig => ({ signature: sig })) as PartialSig[]; +} From c9b334b3825b9c67a2ed53951bd8e6e01dd9e5b1 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 26 May 2022 17:31:31 +0300 Subject: [PATCH 101/249] test: add custom taproot finalizer (partial) --- test/integration/taproot.spec.ts | 109 ++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 2707462ca..d35c9c474 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -2,10 +2,13 @@ import BIP32Factory from 'bip32'; import ECPairFactory from 'ecpair'; import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; +import { PsbtInput, TapLeafScript } from 'bip174/src/lib/interfaces'; import { regtestUtils } from './_regtest'; import * as bitcoin from '../..'; import { Taptree } from '../../src/types'; -import { toXOnly, tapTreeToList } from '../../src/psbt/bip371'; +import { toXOnly, tapTreeToList, tapTreeFromList } from '../../src/psbt/bip371'; +import { witnessStackToScriptWitness } from '../../src/psbt/psbtutils'; +import { TapLeaf } from 'bip174/src/lib/interfaces'; const rng = require('randombytes'); const regtest = regtestUtils.network; @@ -485,8 +488,112 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { value: sendAmount, }); }); + + it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - custom finalizer', async () => { + + + const leafCount = 8; + const leaves = Array.from({ length: leafCount }).map( + (_, index) => + ({ + depth: 3, + leafVersion: 192, + script: bitcoin.script.fromASM(`OP_ADD OP_${index * 2} OP_EQUAL`), + } as TapLeaf), + ); + const scriptTree = tapTreeFromList(leaves); + + for (let leafIndex = 1; leafIndex < leafCount; leafIndex++) { + const redeem = { + output: bitcoin.script.fromASM(`OP_ADD OP_${leafIndex * 2} OP_EQUAL`), + redeemVersion: 192, + }; + + const internalKey = bip32.fromSeed(rng(64), regtest); + const { output, witness } = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(internalKey.publicKey), + scriptTree, + redeem, + network: regtest, + }); + + + // 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({ network: regtest }); + psbt.addInput({ + hash: unspent.txId, + index: 0, + witnessUtxo: { value: amount, script: output! }, + }); + + const tapLeafScript: TapLeafScript = { + leafVersion: redeem.redeemVersion, + script: redeem.output, + controlBlock: witness![witness!.length - 1], + }; + psbt.updateInput(0, { tapLeafScript: [tapLeafScript] }); + + const sendAddress = + 'bcrt1pqknex3jwpsaatu5e5dcjw70nac3fr5k5y3hcxr4hgg6rljzp59nqs6a0vh'; + psbt.addOutput({ + value: sendAmount, + address: sendAddress, + }); + + const leafIndexFinalizerFn = buildLeafIndexFinalizer( + tapLeafScript, + leafIndex, + ); + psbt.finalizeInput(0, leafIndexFinalizerFn); + console.log('### psbt finalized', psbt.toBase64()); + const tx = psbt.extractTransaction(); + const rawTx = tx.toBuffer(); + const hex = rawTx.toString('hex'); + + console.log('### hex', hex) + await regtestUtils.broadcast(hex); + console.log('### verify') + await regtestUtils.verify({ + txId: tx.getId(), + address: sendAddress!, + vout: 0, + value: sendAmount, + }); + } + }); }); +function buildLeafIndexFinalizer( + tapLeafScript: TapLeafScript, + leafIndex: number, +) { + return ( + inputIndex: number, + _input: PsbtInput, + _tapLeafHashToFinalize?: Buffer, + ): { + finalScriptWitness: Buffer | undefined; + } => { + try { + const scriptSolution = [ + bitcoin.script.fromASM(`OP_${leafIndex} OP_${leafIndex}`), + ]; + const witness = scriptSolution + .concat(tapLeafScript.script) + .concat(tapLeafScript.controlBlock); + return { finalScriptWitness: witnessStackToScriptWitness(witness) }; + } catch (err) { + throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`); + } + }; +} + // Order of the curve (N) - 1 const N_LESS_1 = Buffer.from( 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', From 8b4cee91110aa9aecc246cb50589ea9efa263311 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 26 May 2022 17:52:19 +0300 Subject: [PATCH 102/249] chore: code clean-up --- test/fixtures/psbt.json | 10 +++++----- test/integration/taproot.spec.ts | 6 ------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index 815bcb9df..ec6eda328 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -532,7 +532,7 @@ "exception": "Error adding output." }, { - "description": "Checks that the output script can be computed", + "description": "Checks the mandatory values for adding an output", "outputData": { "value": 1000 }, @@ -546,7 +546,7 @@ } }, { - "description": "Adds taproot output with internal key and tap tree", + "description": "Adds taproot output with internal tap key and tap tree", "isTaproot": true, "psbt": "cHNidP8BADMCAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AAAAAAAAAQEroGgGAAAAAAAiUSClLxmVQ6aZXLEwkYA/WGZuIE91BHT7xP7DIEAvz/NITaIVwd2NnolThxX+QCpgFksj8u93bzuZy6olaHxJNHaArwK1tCTeoJ+EC5MqADc83NvSVlC4w6z+VKn0pkGihnIbjSbax5V2a72h6uqkXlv6CpUP3V9MSq2lsfMILtyWibn9Cu4oo7gsSuy+vTeBJ9yI3Rnh6pPtunont1ThyIHTCIdKesRIlUS3uEDQRVmeyUFa5xzLCQwsRlwFyfoF9Bx6BtUjIFX368Cp2d6uXSbyrn23vC4KMjRRlEssuTTh7ThM6bXQrMAAAA==", "outputData": { @@ -596,7 +596,7 @@ "result": "cHNidP8BAF4CAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AZBBBgAAAAAAIlEgNWEIXy34qyxTUMwj9KxV0rr9ScQ+yVC4JtLcaL2MZqcAAAAAAAEBK6BoBgAAAAAAIlEgpS8ZlUOmmVyxMJGAP1hmbiBPdQR0+8T+wyBAL8/zSE2iFcHdjZ6JU4cV/kAqYBZLI/Lvd287mcuqJWh8STR2gK8CtbQk3qCfhAuTKgA3PNzb0lZQuMOs/lSp9KZBooZyG40m2seVdmu9oerqpF5b+gqVD91fTEqtpbHzCC7clom5/QruKKO4LErsvr03gSfciN0Z4eqT7bp6J7dU4ciB0wiHSnrESJVEt7hA0EVZnslBWuccywkMLEZcBcn6BfQcegbVIyBV9+vAqdnerl0m8q59t7wuCjI0UZRLLLk04e04TOm10KzAAAEFIPbUzhMkRN5/DjodK+mzjO7HmM+adu7qxYWGlEWDDrFnAQb9AwECwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsCsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrBrAPAIiAiWLHDFgvghkpUGFTuyRZKVy8JT3ViYoKBqAc7uJFzp6wCwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsKsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrDrATAIiBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6xKwEwCIgVffrwKnZ3q5dJvKufbe8LgoyNFGUSyy5NOHtOEzptdCsAA==" }, { - "description": "Adds taproot output with internal key only and correct address", + "description": "Adds taproot output with internal tap key and correct address", "isTaproot": true, "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=", "outputData": { @@ -607,7 +607,7 @@ "result": "cHNidP8BAIcCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8BkEEGAAAAAAAiUSD1RJYlvhzbiZ/sFzOK3lWwglnOTOhYaiULBDKBC1TV7wAAAAAAAQEroGgGAAAAAAAiUSDamnamg4Xj009vanSkEnFJsicRHg8Ndhy45DGDFUpZ5AEXIAzeGyc4sdx3RPPhVDOlIANZRCqYKSCJlVHj5gBmIe8mAAEAwAIAAAABWyKGgCag3f3+JmaaYGFeFEfW4FPJU/aUx+P2gyXRVFoAAAAAa0gwRQIhAOiUtMTHUkIRvvYAD0sRrOh2s8mrsjVATMK5hd/OiAMcAiBosh/UBwsyjgU7YPlbCEheJ1IvTXwLSQLQtNidN/iNOwEhA7tbZBCPFtW3eqqz2d7i2h7xPdAQ1L0QsL008z2D188H/////wGgaAYAAAAAABl2qRS+UBH+dZOEwOzKeXdfCDNnXau/aIisAAAAAAABBSCITZaUOd7O0h0atx7NnvmmqHlSFViM5+/0rV78kD5A7AA=" }, { - "description": "Adds taproot output with internal key and bad address", + "description": "Adds taproot output with internal tap key and bad address", "isTaproot": true, "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=", "outputData": { @@ -640,7 +640,7 @@ "exception": "Can not modify transaction, signatures exist." }, { - "description": "Adds output after tapkey spend input has been finalized", + "description": "Adds output after tap key input has been finalized", "isTaproot": true, "psbt": "cHNidP8BAF4CAAAAAfyhtBTm+3OPYBuMHvdPMf9jqniZDY925hbmnt8hxbA6AAAAAAD/////AZBBBgAAAAAAIlEgV6CyzSs5a6Tc8A+TYOiozWnh6FRH9E/VWFanhMAEbLEAAAAAAAEBK6BoBgAAAAAAIlEgV6CyzSs5a6Tc8A+TYOiozWnh6FRH9E/VWFanhMAEbLEBCEIBQOAqWBD/2jhPWzQvesT8sjkN2Cowphp3QvmlWbHiLx753ChcUovvWyBlWiCq77Kk+lZGEhC4vjClSjc26br+dc8AAA==", "outputData": { diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index d35c9c474..ba870c5f1 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -490,8 +490,6 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - custom finalizer', async () => { - - const leafCount = 8; const leaves = Array.from({ length: leafCount }).map( (_, index) => @@ -517,7 +515,6 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { network: regtest, }); - // amount from faucet const amount = 42e4; // amount to send @@ -551,14 +548,11 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { leafIndex, ); psbt.finalizeInput(0, leafIndexFinalizerFn); - console.log('### psbt finalized', psbt.toBase64()); const tx = psbt.extractTransaction(); const rawTx = tx.toBuffer(); const hex = rawTx.toString('hex'); - console.log('### hex', hex) await regtestUtils.broadcast(hex); - console.log('### verify') await regtestUtils.verify({ txId: tx.getId(), address: sendAddress!, From 94f52911512b122e6d16d13f2a1ae8c664d7a1b5 Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 27 May 2022 00:47:13 +0900 Subject: [PATCH 103/249] Fix integration test --- test/integration/taproot.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index ba870c5f1..4d1c343af 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -576,7 +576,8 @@ function buildLeafIndexFinalizer( } => { try { const scriptSolution = [ - bitcoin.script.fromASM(`OP_${leafIndex} OP_${leafIndex}`), + Buffer.from([leafIndex]), + Buffer.from([leafIndex]), ]; const witness = scriptSolution .concat(tapLeafScript.script) From 724be84bebb749ac0207221fcb21bbdf81e3ea12 Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 27 May 2022 00:52:17 +0900 Subject: [PATCH 104/249] Fix test lints --- test/integration/taproot.spec.ts | 12 +++++++++--- test/payments.utils.ts | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 4d1c343af..b12c46f31 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -269,7 +269,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { const sendPubKey = toXOnly(sendInternalKey.publicKey); const { address: sendAddress } = bitcoin.payments.p2tr({ internalPubkey: sendPubKey, - scriptTree: scriptTree, + scriptTree, network: regtest, }); @@ -360,7 +360,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { const sendPubKey = toXOnly(sendInternalKey.publicKey); const { address: sendAddress } = bitcoin.payments.p2tr({ internalPubkey: sendPubKey, - scriptTree: scriptTree, + scriptTree, network: regtest, }); @@ -566,7 +566,13 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { function buildLeafIndexFinalizer( tapLeafScript: TapLeafScript, leafIndex: number, -) { +): ( + inputIndex: number, + _input: PsbtInput, + _tapLeafHashToFinalize?: Buffer, +) => { + finalScriptWitness: Buffer | undefined; +} { return ( inputIndex: number, _input: PsbtInput, diff --git a/test/payments.utils.ts b/test/payments.utils.ts index 3f86770e8..e2599204c 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -188,7 +188,7 @@ export function from(path: string, object: any, result?: any): any { export function convertScriptTree(scriptTree: any, leafVersion?: number): any { if (Array.isArray(scriptTree)) - return scriptTree.map(t => convertScriptTree(t, leafVersion)); + return scriptTree.map(tr => convertScriptTree(tr, leafVersion)); const script = Object.assign({}, scriptTree); if (typeof script.output === 'string') { From 4db845597fb8048a5d6f69d8cdc6aeb7af1f74a0 Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 27 May 2022 00:56:53 +0900 Subject: [PATCH 105/249] Fix audit --- package-lock.json | 413 ++++++++++++---------------------------------- 1 file changed, 104 insertions(+), 309 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7cb07959e..608cf71eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,15 +24,15 @@ } }, "@babel/compat-data": { - "version": "7.18.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz", - "integrity": "sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", "dev": true }, "@babel/core": { - "version": "7.18.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.5.tgz", - "integrity": "sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", + "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", @@ -41,10 +41,10 @@ "@babel/helper-compilation-targets": "^7.18.2", "@babel/helper-module-transforms": "^7.18.0", "@babel/helpers": "^7.18.2", - "@babel/parser": "^7.18.5", + "@babel/parser": "^7.18.0", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.5", - "@babel/types": "^7.18.4", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -235,9 +235,9 @@ } }, "@babel/parser": { - "version": "7.18.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.5.tgz", - "integrity": "sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.3.tgz", + "integrity": "sha512-rL50YcEuHbbauAFAysNsJA4/f89fGTOBRNs9P81sniKnKAr4xULe5AecolcsKbi88xu0ByWYDj/S1AJ3FSFuSQ==", "dev": true }, "@babel/template": { @@ -274,9 +274,9 @@ } }, "@babel/traverse": { - "version": "7.18.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz", - "integrity": "sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz", + "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", @@ -285,8 +285,8 @@ "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.18.5", - "@babel/types": "^7.18.4", + "@babel/parser": "^7.18.0", + "@babel/types": "^7.18.2", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -314,9 +314,9 @@ } }, "@babel/types": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", - "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.2.tgz", + "integrity": "sha512-0On6B8A4/+mFUto5WERt3EEuG1NznDirvwca1O8UwXQHVY8g3R7OzYgxXdOfMwLO08UrpUD/2+3Bclyq+/C94Q==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", @@ -336,12 +336,6 @@ "resolve-from": "^5.0.0" }, "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -361,15 +355,6 @@ "p-locate": "^4.1.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -488,15 +473,6 @@ "@types/node": "*" } }, - "@types/ripemd160": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", - "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/wif": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", @@ -522,18 +498,6 @@ "indent-string": "^4.0.0" } }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -595,12 +559,6 @@ "sprintf-js": "~1.0.2" } }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -620,26 +578,6 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, - "better-npm-audit": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/better-npm-audit/-/better-npm-audit-3.7.3.tgz", - "integrity": "sha512-zsSiidlP5n7KpCYdAmkellu4JYA4IoRUUwrBMv/R7TwT8vcRfk5CQ2zTg7yUy4bdWkKtAj7VVdPQttdMbx+n5Q==", - "dev": true, - "requires": { - "commander": "^8.0.0", - "dayjs": "^1.10.6", - "lodash.get": "^4.4.2", - "table": "^6.7.1" - }, - "dependencies": { - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true - } - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -737,15 +675,15 @@ "dev": true }, "browserslist": { - "version": "4.20.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", - "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001349", - "electron-to-chromium": "^1.4.147", + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", "escalade": "^3.1.1", - "node-releases": "^2.0.5", + "node-releases": "^2.0.3", "picocolors": "^1.0.0" } }, @@ -792,15 +730,15 @@ } }, "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "caniuse-lite": { - "version": "1.0.30001352", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", - "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==", + "version": "1.0.30001343", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001343.tgz", + "integrity": "sha512-8KeCrAtPMabo/XW14B+R9sZYoClx1n0b+WYgwDKZPtWR3TcdvWzdSy7mPyFEmR5WU1St9v1PW6sdO5dkFOEzfA==", "dev": true }, "chalk": { @@ -946,12 +884,6 @@ "which": "^2.0.1" } }, - "dayjs": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz", - "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==", - "dev": true - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -970,9 +902,9 @@ } }, "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "default-require-extensions": { @@ -1011,9 +943,9 @@ } }, "electron-to-chromium": { - "version": "1.4.153", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.153.tgz", - "integrity": "sha512-57AV9DNW1R52HjOqnGOCCTLHMHItLTGu/WjB1KYIa4BQ7p0u8J0j8N78akPcOBStKE801xcMjTpmbAylflfIYQ==", + "version": "1.4.139", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.139.tgz", + "integrity": "sha512-lYxzcUCjWxxVug+A7UxBCUiVr13TCjfZFYJS9Lq1VpU/ErwV4a6zUQo9dfojuGpw/L/x9REGuBl6ICQPGgbs3g==", "dev": true }, "emoji-regex": { @@ -1046,12 +978,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -1226,7 +1152,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "indent-string": { @@ -1262,7 +1188,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { @@ -1307,7 +1233,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, "is-unicode-supported": { @@ -1325,7 +1251,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "istanbul-lib-coverage": { @@ -1364,17 +1290,18 @@ } }, "istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "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, "requires": { "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.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": "^8.3.2" + "uuid": "^3.3.3" }, "dependencies": { "rimraf": { @@ -1459,12 +1386,6 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "json5": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", @@ -1483,19 +1404,7 @@ "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, "log-symbols": { @@ -1769,6 +1678,12 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "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 + }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -1813,12 +1728,6 @@ "color-convert": "^2.0.1" } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1845,12 +1754,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1893,15 +1796,6 @@ "brace-expansion": "^1.1.7" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -1931,12 +1825,6 @@ "strip-ansi": "^6.0.0" } }, - "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 - }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -1978,12 +1866,12 @@ } }, "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "yocto-queue": "^0.1.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -1993,6 +1881,17 @@ "dev": true, "requires": { "p-limit": "^3.0.2" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } } }, "p-map": { @@ -2099,15 +1998,6 @@ "p-locate": "^4.1.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -2145,12 +2035,6 @@ "resolve": "~1.8.1" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, "pushdata-bitcoin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", @@ -2192,7 +2076,7 @@ "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { "es6-error": "^4.0.1" @@ -2201,13 +2085,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { @@ -2288,7 +2166,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "sha.js": { @@ -2321,49 +2199,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - } - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2474,59 +2309,6 @@ } } }, - "table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "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 - }, - "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 - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -2577,7 +2359,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "to-regex-range": { @@ -2686,19 +2468,10 @@ "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", "dev": true }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "varuint-bitcoin": { @@ -2721,7 +2494,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "wif": { @@ -2794,9 +2567,9 @@ } }, "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "yargs": { @@ -2812,6 +2585,14 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" + }, + "dependencies": { + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + } } }, "yargs-parser": { @@ -2830,6 +2611,20 @@ "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } } }, "yn": { From 7839476784c7dcb747dba6c2b4b5e3704ab21257 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 1 Jun 2022 06:33:51 +0300 Subject: [PATCH 106/249] refactor: rename `taprootutils` to `bip341` --- .../{taprootutils.d.ts => bip341.d.ts} | 0 src/payments/{taprootutils.js => bip341.js} | 0 src/payments/p2tr.js | 36 ++++++++----------- src/psbt.js | 4 +-- src/psbt/bip371.js | 18 +++++----- test/psbt.spec.ts | 2 +- .../payments/{taprootutils.ts => bip341.ts} | 0 ts_src/payments/p2tr.ts | 2 +- ts_src/psbt.ts | 2 +- ts_src/psbt/bip371.ts | 2 +- 10 files changed, 30 insertions(+), 36 deletions(-) rename src/payments/{taprootutils.d.ts => bip341.d.ts} (100%) rename src/payments/{taprootutils.js => bip341.js} (100%) rename ts_src/payments/{taprootutils.ts => bip341.ts} (100%) diff --git a/src/payments/taprootutils.d.ts b/src/payments/bip341.d.ts similarity index 100% rename from src/payments/taprootutils.d.ts rename to src/payments/bip341.d.ts diff --git a/src/payments/taprootutils.js b/src/payments/bip341.js similarity index 100% rename from src/payments/taprootutils.js rename to src/payments/bip341.js diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index cb16ab25e..182aa62c6 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -6,7 +6,7 @@ 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 bip341_1 = require('./bip341'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); const OPS = bscript.OPS; @@ -69,7 +69,7 @@ function p2tr(a, opts) { return a.witness.slice(); }); const _hashTree = lazy.value(() => { - if (a.scriptTree) return (0, taprootutils_1.toHashTree)(a.scriptTree); + if (a.scriptTree) return (0, bip341_1.toHashTree)(a.scriptTree); if (a.hash) return { hash: a.hash }; return; }); @@ -89,11 +89,11 @@ 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, bip341_1.tapleafHash)({ output: script, version: leafVersion, }); - return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); + return (0, bip341_1.rootHashFromPath)(controlBlock, leafHash); } return null; }); @@ -110,7 +110,7 @@ function p2tr(a, opts) { ) { return a.redeem.redeemVersion; } - return taprootutils_1.LEAF_VERSION_TAPSCRIPT; + return bip341_1.LEAF_VERSION_TAPSCRIPT; }); lazy.prop(o, 'redeem', () => { const witness = _witness(); // witness without annex @@ -127,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 = (0, taprootutils_1.tweakKey)(o.internalPubkey, o.hash); + const tweakedKey = (0, bip341_1.tweakKey)(o.internalPubkey, o.hash); if (tweakedKey) return tweakedKey.x; } }); @@ -147,16 +147,13 @@ function p2tr(a, opts) { if (a.witness) return a.witness; const hashTree = _hashTree(); if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) { - const leafHash = (0, taprootutils_1.tapleafHash)({ + const leafHash = (0, bip341_1.tapleafHash)({ output: a.redeem.output, version: o.redeemVersion, }); - const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); + const path = (0, bip341_1.findScriptPath)(hashTree, leafHash); if (!path) return; - const outputKey = (0, taprootutils_1.tweakKey)( - a.internalPubkey, - hashTree.hash, - ); + const outputKey = (0, bip341_1.tweakKey)(a.internalPubkey, hashTree.hash); if (!outputKey) return; const controlBock = buffer_1.Buffer.concat( [ @@ -197,7 +194,7 @@ function p2tr(a, opts) { else pubkey = a.output.slice(2); } if (a.internalPubkey) { - const tweakedKey = (0, taprootutils_1.tweakKey)(a.internalPubkey, o.hash); + const tweakedKey = (0, bip341_1.tweakKey)(a.internalPubkey, o.hash); if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) throw new TypeError('Pubkey mismatch'); else pubkey = tweakedKey.x; @@ -211,11 +208,11 @@ function p2tr(a, opts) { if (!a.hash.equals(hashTree.hash)) throw new TypeError('Hash mismatch'); } if (a.redeem && a.redeem.output && hashTree) { - const leafHash = (0, taprootutils_1.tapleafHash)({ + const leafHash = (0, bip341_1.tapleafHash)({ output: a.redeem.output, version: o.redeemVersion, }); - if (!(0, taprootutils_1.findScriptPath)(hashTree, leafHash)) + if (!(0, bip341_1.findScriptPath)(hashTree, leafHash)) throw new TypeError('Redeem script not in tree'); } const witness = _witness(); @@ -270,15 +267,12 @@ 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, bip341_1.tapleafHash)({ output: script, version: leafVersion, }); - const hash = (0, taprootutils_1.rootHashFromPath)( - controlBlock, - leafHash, - ); - const outputKey = (0, taprootutils_1.tweakKey)(internalPubkey, hash); + const hash = (0, bip341_1.rootHashFromPath)(controlBlock, leafHash); + const outputKey = (0, bip341_1.tweakKey)(internalPubkey, hash); if (!outputKey) // todo: needs test data throw new TypeError('Invalid outputKey for p2tr witness'); diff --git a/src/psbt.js b/src/psbt.js index 2bc55e065..2f2e94a96 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -8,7 +8,7 @@ const address_1 = require('./address'); const bufferutils_1 = require('./bufferutils'); const networks_1 = require('./networks'); const payments = require('./payments'); -const taprootutils_1 = require('./payments/taprootutils'); +const bip341_1 = require('./payments/bip341'); const bscript = require('./script'); const transaction_1 = require('./transaction'); const bip371_1 = require('./psbt/bip371'); @@ -1325,7 +1325,7 @@ function getTaprootHashesForSig( const tapLeafHashes = (input.tapLeafScript || []) .filter(tapLeaf => (0, psbtutils_1.pubkeyInScript)(pubkey, tapLeaf.script)) .map(tapLeaf => { - const hash = (0, taprootutils_1.tapleafHash)({ + const hash = (0, bip341_1.tapleafHash)({ output: tapLeaf.script, version: tapLeaf.leafVersion, }); diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index eee6b1393..fdf23c008 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -4,7 +4,7 @@ exports.checkTaprootInputForSigs = exports.tapTreeFromList = exports.tapTreeToLi const types_1 = require('../types'); const transaction_1 = require('../transaction'); const psbtutils_1 = require('./psbtutils'); -const taprootutils_1 = require('../payments/taprootutils'); +const bip341_1 = require('../payments/bip341'); const payments_1 = require('../payments'); const psbtutils_2 = require('./psbtutils'); const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); @@ -101,7 +101,7 @@ function tweakInternalPubKey(inputIndex, input) { const tapInternalKey = input.tapInternalKey; const outputKey = tapInternalKey && - (0, taprootutils_1.tweakKey)(tapInternalKey, input.tapMerkleRoot); + (0, bip341_1.tweakKey)(tapInternalKey, input.tapMerkleRoot); if (!outputKey) throw new Error( `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey && @@ -175,13 +175,13 @@ function getTapKeySigFromWithness(finalScriptWitness) { if (witness.length === 64 || witness.length === 65) return witness; } function _tapTreeToList(tree, leaves = [], depth = 0) { - if (depth > taprootutils_1.MAX_TAPTREE_DEPTH) + if (depth > bip341_1.MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); if (!tree) return []; if ((0, types_1.isTapleaf)(tree)) { leaves.push({ depth, - leafVersion: tree.version || taprootutils_1.LEAF_VERSION_TAPSCRIPT, + leafVersion: tree.version || bip341_1.LEAF_VERSION_TAPSCRIPT, script: tree.output, }); return leaves; @@ -199,7 +199,7 @@ function instertLeavesInTree(leaves) { return tree; } function instertLeafInTree(leaf, tree, depth = 0) { - if (depth > taprootutils_1.MAX_TAPTREE_DEPTH) + if (depth > bip341_1.MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); if (leaf.depth === depth) { if (!tree) @@ -275,18 +275,18 @@ function checkIfTapLeafInTree(inputData, newInputData, action) { } function isTapLeafInTree(tapLeaf, merkleRoot) { if (!merkleRoot) return true; - const leafHash = (0, taprootutils_1.tapleafHash)({ + const leafHash = (0, bip341_1.tapleafHash)({ output: tapLeaf.script, version: tapLeaf.leafVersion, }); - const rootHash = (0, taprootutils_1.rootHashFromPath)( + const rootHash = (0, bip341_1.rootHashFromPath)( tapLeaf.controlBlock, leafHash, ); return rootHash.equals(merkleRoot); } function sortSignatures(input, tapLeaf) { - const leafHash = (0, taprootutils_1.tapleafHash)({ + const leafHash = (0, bip341_1.tapleafHash)({ output: tapLeaf.script, version: tapLeaf.leafVersion, }); @@ -327,7 +327,7 @@ function findTapLeafToFinalize(input, inputIndex, leafHashToFinalize) { return tapLeaf; } function canFinalizeLeaf(leaf, tapScriptSig, hash) { - const leafHash = (0, taprootutils_1.tapleafHash)({ + const leafHash = (0, bip341_1.tapleafHash)({ output: leaf.script, version: leaf.leafVersion, }); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index bbddb67c8..6d295a80f 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -6,7 +6,7 @@ import ECPairFactory from 'ecpair'; import { describe, it } from 'mocha'; import { convertScriptTree } from './payments.utils'; -import { LEAF_VERSION_TAPSCRIPT } from '../src/payments/taprootutils'; +import { LEAF_VERSION_TAPSCRIPT } from '../src/payments/bip341'; import { tapTreeToList, tapTreeFromList } from '../src/psbt/bip371'; import { Taptree } from '../src/types'; import { initEccLib } from '../src'; diff --git a/ts_src/payments/taprootutils.ts b/ts_src/payments/bip341.ts similarity index 100% rename from ts_src/payments/taprootutils.ts rename to ts_src/payments/bip341.ts diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 00b9e59ad..64c591ffd 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -10,7 +10,7 @@ import { tapleafHash, tweakKey, LEAF_VERSION_TAPSCRIPT, -} from './taprootutils'; +} from './bip341'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index a3b66c7e0..df3cf22ad 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -19,7 +19,7 @@ import { fromOutputScript, toOutputScript } from './address'; import { cloneBuffer, reverseBuffer } from './bufferutils'; import { bitcoin as btcNetwork, Network } from './networks'; import * as payments from './payments'; -import { tapleafHash } from './payments/taprootutils'; +import { tapleafHash } from './payments/bip341'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; import { diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 153e289ec..d743fdb66 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -22,7 +22,7 @@ import { rootHashFromPath, LEAF_VERSION_TAPSCRIPT, MAX_TAPTREE_DEPTH, -} from '../payments/taprootutils'; +} from '../payments/bip341'; import { p2tr } from '../payments'; import { signatureBlocksAction } from './psbtutils'; From 6f40ec84fae08fe5047c5e5f6214a1551e26e000 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:02:40 +0200 Subject: [PATCH 107/249] chore: fix typo --- src/psbt/bip371.js | 4 ++-- ts_src/psbt/bip371.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index fdf23c008..c9706b7bb 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -146,11 +146,11 @@ exports.tapTreeFromList = tapTreeFromList; function checkTaprootInputForSigs(input, action) { const sigs = extractTaprootSigs(input); return sigs.some(sig => - (0, psbtutils_2.signatureBlocksAction)(sig, decodeSchnorSignature, action), + (0, psbtutils_2.signatureBlocksAction)(sig, decodeSchnorrSignature, action), ); } exports.checkTaprootInputForSigs = checkTaprootInputForSigs; -function decodeSchnorSignature(signature) { +function decodeSchnorrSignature(signature) { return { signature: signature.slice(0, 64), hashType: diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index d743fdb66..c1f146b0b 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -201,11 +201,11 @@ export function checkTaprootInputForSigs( ): boolean { const sigs = extractTaprootSigs(input); return sigs.some(sig => - signatureBlocksAction(sig, decodeSchnorSignature, action), + signatureBlocksAction(sig, decodeSchnorrSignature, action), ); } -function decodeSchnorSignature( +function decodeSchnorrSignature( signature: Buffer, ): { signature: Buffer; From 80a31dbe7f3fef085242da3fc55b40c0febe5c33 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:06:59 +0200 Subject: [PATCH 108/249] fix: add check for `controlBlock` length --- src/payments/bip341.js | 6 ++++++ ts_src/payments/bip341.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/payments/bip341.js b/src/payments/bip341.js index 4832d358d..02c976b5f 100644 --- a/src/payments/bip341.js +++ b/src/payments/bip341.js @@ -10,6 +10,12 @@ exports.LEAF_VERSION_TAPSCRIPT = 0xc0; exports.MAX_TAPTREE_DEPTH = 128; const isHashBranch = ht => 'left' in ht && 'right' in ht; function rootHashFromPath(controlBlock, leafHash) { + if (controlBlock.length < 33) + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); const m = (controlBlock.length - 33) / 32; let kj = leafHash; for (let j = 0; j < m; j++) { diff --git a/ts_src/payments/bip341.ts b/ts_src/payments/bip341.ts index 503819325..5a1dd068d 100644 --- a/ts_src/payments/bip341.ts +++ b/ts_src/payments/bip341.ts @@ -38,6 +38,12 @@ export function rootHashFromPath( controlBlock: Buffer, leafHash: Buffer, ): Buffer { + if (controlBlock.length < 33) + throw new TypeError( + `The control-block length is too small. Got ${ + controlBlock.length + }, expected min 33.`, + ); const m = (controlBlock.length - 33) / 32; let kj = leafHash; From d5181dfe704bb707c0a1a2c3fb80c270383996e3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:10:33 +0200 Subject: [PATCH 109/249] chore: typo in comment --- ts_src/psbt/bip371.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index c1f146b0b..d4eaaa5f3 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -235,7 +235,7 @@ function getTapKeySigFromWithness( ): Buffer | undefined { if (!finalScriptWitness) return; const witness = finalScriptWitness.slice(2); - // todo: add schnor signature validation + // todo: add schnorr signature validation if (witness.length === 64 || witness.length === 65) return witness; } From 99603061acb6a90964e952bf317e4e2b074ef2ec Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:17:40 +0200 Subject: [PATCH 110/249] chore: remove `any` type --- src/psbt/bip371.js | 2 +- ts_src/psbt.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index c9706b7bb..8e76ecda7 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -171,7 +171,7 @@ function extractTaprootSigs(input) { function getTapKeySigFromWithness(finalScriptWitness) { if (!finalScriptWitness) return; const witness = finalScriptWitness.slice(2); - // todo: add schnor signature validation + // todo: add schnorr signature validation if (witness.length === 64 || witness.length === 65) return witness; } function _tapTreeToList(tree, leaves = [], depth = 0) { diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index df3cf22ad..824e70a87 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1739,8 +1739,8 @@ function getTaprootHashesForSig( 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 signingScripts = prevOuts.map(o => o.script); + const values = prevOuts.map(o => o.value); const hashes = []; if (input.tapInternalKey && !tapLeafHashToSign) { From 57f915e8647c88a589bc659a971566d4012df34f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:19:34 +0200 Subject: [PATCH 111/249] chore: add explicit type for returned signatures --- ts_src/psbt.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 824e70a87..aa546bc71 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -995,7 +995,9 @@ export class Psbt { sighashTypes, ); - const signaturePromises: Promise[] = []; + const signaturePromises: Promise< + { tapKeySig: Buffer } | { tapScriptSig: TapScriptSig[] } + >[] = []; const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0]; if (tapKeyHash) { const tapKeySigPromise = Promise.resolve( From c3b517375936a8970febffc102744de628295211 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:44:23 +0200 Subject: [PATCH 112/249] chore: sync `package-lock.json` --- package-lock.json | 154 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 608cf71eb..f7ade2d30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -473,6 +473,15 @@ "@types/node": "*" } }, + "@types/ripemd160": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", + "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/wif": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", @@ -498,6 +507,18 @@ "indent-string": "^4.0.0" } }, + "ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -559,6 +580,12 @@ "sprintf-js": "~1.0.2" } }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -578,6 +605,26 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, + "better-npm-audit": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/better-npm-audit/-/better-npm-audit-3.7.3.tgz", + "integrity": "sha512-zsSiidlP5n7KpCYdAmkellu4JYA4IoRUUwrBMv/R7TwT8vcRfk5CQ2zTg7yUy4bdWkKtAj7VVdPQttdMbx+n5Q==", + "dev": true, + "requires": { + "commander": "^8.0.0", + "dayjs": "^1.10.6", + "lodash.get": "^4.4.2", + "table": "^6.7.1" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + } + } + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -884,6 +931,12 @@ "which": "^2.0.1" } }, + "dayjs": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz", + "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -978,6 +1031,12 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -1386,6 +1445,12 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "json5": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", @@ -1407,6 +1472,18 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -1678,12 +1755,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "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 - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -2035,6 +2106,12 @@ "resolve": "~1.8.1" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, "pushdata-bitcoin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", @@ -2088,6 +2165,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -2199,6 +2282,43 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2309,6 +2429,19 @@ } } }, + "table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -2468,6 +2601,15 @@ "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", "dev": true }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", From a72be9184950032158ef3c9f9d27f0a33139b5a9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:54:47 +0200 Subject: [PATCH 113/249] fix: audit issue for `minimatch` --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7ade2d30..2b477ac20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1595,9 +1595,9 @@ } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" From b994d465a269fade0cabb8516f480782200182ce Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 29 Nov 2022 10:58:44 +0200 Subject: [PATCH 114/249] 6.1.0-rc.0 --- 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 2b477ac20..17e16965a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.2", + "version": "6.1.0-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9cecfc352..b2414f42f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.2", + "version": "6.1.0-rc.0", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From 1724115060369b6df4901988f639bbf91d85de14 Mon Sep 17 00:00:00 2001 From: Feli <> Date: Mon, 5 Dec 2022 12:40:54 -0500 Subject: [PATCH 115/249] - upgraded prettier - installed eslint integrations - removed tslint - added rules to match old tslint rules - ran linter on ts_src and tests --- .eslintrc | 57 + .prettierignore | 1 + .prettierrc.json | 5 +- CONTRIBUTING.md | 2 +- package-lock.json | 6560 ++++++++++++++++++++++--- package.json | 12 +- src/address.js | 8 +- src/bufferutils.js | 15 +- src/crypto.d.ts | 2 +- src/crypto.js | 24 +- src/index.js | 22 +- src/payments/bip341.d.ts | 2 +- src/payments/bip341.js | 14 +- src/payments/index.d.ts | 10 +- src/payments/index.js | 26 +- src/payments/p2tr.js | 4 +- src/psbt.d.ts | 10 +- src/psbt.js | 33 +- src/psbt/bip371.js | 24 +- src/psbt/psbtutils.d.ts | 2 +- src/psbt/psbtutils.js | 14 +- src/script.js | 17 +- src/types.d.ts | 2 +- src/types.js | 36 +- test/integration/bip32.spec.ts | 5 +- test/integration/csv.spec.ts | 1 - test/integration/payments.spec.ts | 5 +- test/integration/taproot.spec.ts | 5 +- test/integration/transactions.spec.ts | 9 +- test/psbt.spec.ts | 62 +- test/script_signature.spec.ts | 4 +- test/transaction.spec.ts | 2 +- ts_src/crypto.ts | 16 +- ts_src/payments/bip341.ts | 4 +- ts_src/payments/p2sh.ts | 24 +- ts_src/payments/p2tr.ts | 4 +- ts_src/psbt.ts | 140 +- ts_src/psbt/bip371.ts | 15 +- ts_src/psbt/psbtutils.ts | 4 +- ts_src/script.ts | 1 - ts_src/types.ts | 6 +- tslint.json | 40 - 42 files changed, 6125 insertions(+), 1124 deletions(-) create mode 100644 .eslintrc delete mode 100644 tslint.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..cae665a54 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,57 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "prettier", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "rules": { + "prettier/prettier": ["error", { + "singleQuote": true, + "trailingComma": "all", + + "endOfLine": "auto", + "arrowParens": "avoid", + "tabWidth": 2 + }], + + "arrow-body-style": "off", + "prefer-arrow-callback": "off", + + "@typescript-eslint/array-type": 0, + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-unused-vars": "off", + + "arrow-parens": "off", + "curly": "off", + "no-case-declarations": "off", + + "quotes": "off", + "@typescript-eslint/quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], + "prefer-rest-params": "off", + "no-bitwise": "off", + "no-console": "off", + "no-empty": ["error", { "allowEmptyCatch": true }], + + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-explicit-any": "off", + + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": "off", + + "space-before-function-paren": "off" + }, + "env": { + "browser": true, + "amd": true, + "node": true + } + } + \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index e69de29bb..191ae4cc9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -0,0 +1 @@ +*.d.ts \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json index a20502b7f..ccb9a0c17 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,7 @@ { "singleQuote": true, - "trailingComma": "all" + "trailingComma": "all", + "endOfLine": "auto", + "arrowParens": "avoid", + "tabWidth": 2 } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c72633de..c1df5e090 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ Refer to the [Git manual](https://git-scm.com/doc) for any information about `gi ## Regarding TypeScript -This library is written in TypeScript with tslint, prettier, and the tsc transpiler. These tools will help during testing to notice improper logic before committing and sending a pull request. +This library is written in TypeScript with eslint, prettier, and the tsc transpiler. These tools will help during testing to notice improper logic before committing and sending a pull request. Some rules regarding TypeScript: diff --git a/package-lock.json b/package-lock.json index 17e16965a..67af0eb4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,4457 @@ { "name": "bitcoinjs-lib", "version": "6.1.0-rc.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "bitcoinjs-lib", + "version": "6.1.0-rc.0", + "license": "MIT", + "dependencies": { + "bech32": "^2.0.0", + "bip174": "^2.1.0", + "bs58check": "^2.1.2", + "create-hash": "^1.1.0", + "ripemd160": "^2.0.2", + "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/ripemd160": "^2.0.0", + "@types/wif": "^2.0.2", + "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/parser": "^5.45.0", + "better-npm-audit": "^3.7.3", + "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", + "eslint": "^8.29.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "hoodwink": "^2.0.0", + "minimaldata": "^1.0.2", + "mocha": "^10.0.0", + "nyc": "^15.1.0", + "prettier": "^2.8.0", + "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", + "typescript": "^4.4.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/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/@babel/generator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/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/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/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": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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/@babel/parser": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/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/@babel/types": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", + "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "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/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/@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/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==", + "dev": true, + "dependencies": { + "base-x": "^3.0.6" + } + }, + "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/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "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.18.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.4.tgz", + "integrity": "sha512-9qGjJ5GyShZjUfx2ArBIGM+xExdfLvvaCyQR0t6yRXKPcWCVYF/WemtX/uIU3r7FYECXRXkIiw2Vnhn6y8d+pw==", + "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/ripemd160": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", + "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "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/@typescript-eslint/eslint-plugin": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", + "integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.45.0", + "@typescript-eslint/type-utils": "5.45.0", + "@typescript-eslint/utils": "5.45.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.45.0.tgz", + "integrity": "sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.45.0", + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/typescript-estree": "5.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz", + "integrity": "sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/visitor-keys": "5.45.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz", + "integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.45.0", + "@typescript-eslint/utils": "5.45.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.45.0.tgz", + "integrity": "sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz", + "integrity": "sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/visitor-keys": "5.45.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz", + "integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.45.0", + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/typescript-estree": "5.45.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz", + "integrity": "sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.45.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "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": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "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/better-npm-audit": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/better-npm-audit/-/better-npm-audit-3.7.3.tgz", + "integrity": "sha512-zsSiidlP5n7KpCYdAmkellu4JYA4IoRUUwrBMv/R7TwT8vcRfk5CQ2zTg7yUy4bdWkKtAj7VVdPQttdMbx+n5Q==", + "dev": true, + "dependencies": { + "commander": "^8.0.0", + "dayjs": "^1.10.6", + "lodash.get": "^4.4.2", + "table": "^6.7.1" + }, + "bin": { + "better-npm-audit": "index.js" + }, + "engines": { + "node": ">= 8.12" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bip174": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.0.tgz", + "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bip32": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.1.0.tgz", + "integrity": "sha512-eoeajYEzJ4d6yyVtby8C+XkCeKItiC4Mx56a0M9VaqTMC73SWOm4xVZG7SaR8e/yp4eSyky2XcBpH3DApPdu7Q==", + "dev": true, + "dependencies": { + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.2", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bip39": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", + "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", + "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/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "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.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/caniuse-lite": { + "version": "1.0.30001436", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", + "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/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/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": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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/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/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "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/dayjs": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz", + "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ecpair": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", + "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "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/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/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", + "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "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.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/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/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "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": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "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.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "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": "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/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "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/hasha/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/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/ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "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": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "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-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "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": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "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.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "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.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "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-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "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/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "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": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimaldata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minimaldata/-/minimaldata-1.0.2.tgz", + "integrity": "sha512-ZR9tWALR8ZszYd/zP34TmmVwRDVBNCT5+hkNXfTp3rpEDmZmgmYt1Sh/tu9qYFuPvSvEEEeJGE2BY8MBmeAzQQ==", + "dev": true, + "dependencies": { + "bitcoin-ops": "^1.3.0", + "pushdata-bitcoin": "^1.0.1" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", + "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true + }, + "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/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "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/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "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/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/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/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/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-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/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/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/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/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/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "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/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "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/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "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-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", + "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pushdata-bitcoin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", + "integrity": "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==", + "dev": true, + "dependencies": { + "bitcoin-ops": "^1.3.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/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/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "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": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "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": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "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.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "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/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "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.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "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/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.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/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "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/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tiny-secp256k1": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz", + "integrity": "sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng==", + "dev": true, + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "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": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "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.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, + "node_modules/ts-node/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/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/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uint8array-tools": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", + "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/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": "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/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "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": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { "@ampproject/remapping": { "version": "2.2.0", @@ -15,36 +4464,36 @@ } }, "@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==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.18.6" } }, "@babel/compat-data": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", - "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", "dev": true }, "@babel/core": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", - "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-compilation-targets": "^7.18.2", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helpers": "^7.18.2", - "@babel/parser": "^7.18.0", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -52,26 +4501,6 @@ "semver": "^6.3.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -81,23 +4510,23 @@ } }, "@babel/generator": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", - "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "dev": true, "requires": { - "@babel/types": "^7.18.2", - "@jridgewell/gen-mapping": "^0.3.0", + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", - "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.0", + "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" } @@ -105,14 +4534,14 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", - "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.20.2", + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", "semver": "^6.3.0" }, "dependencies": { @@ -125,204 +4554,265 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", - "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", "dev": true }, "@babel/helper-function-name": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", - "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", "dev": true, "requires": { - "@babel/template": "^7.16.7", - "@babel/types": "^7.17.0" + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" } }, "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-module-transforms": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", - "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.17.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" } }, "@babel/helper-simple-access": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", - "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", "dev": true, "requires": { - "@babel/types": "^7.18.2" + "@babel/types": "^7.20.2" } }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true + }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", "dev": true }, "@babel/helpers": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", - "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", "dev": true, "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2" + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" } }, "@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==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, "dependencies": { - "@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==", + "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, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "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, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } } } }, "@babel/parser": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.3.tgz", - "integrity": "sha512-rL50YcEuHbbauAFAysNsJA4/f89fGTOBRNs9P81sniKnKAr4xULe5AecolcsKbi88xu0ByWYDj/S1AJ3FSFuSQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", "dev": true }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - } + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" } }, "@babel/traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz", - "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.18.0", - "@babel/types": "^7.18.2", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } + "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 } } }, "@babel/types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.2.tgz", - "integrity": "sha512-0On6B8A4/+mFUto5WERt3EEuG1NznDirvwca1O8UwXQHVY8g3R7OzYgxXdOfMwLO08UrpUD/2+3Bclyq+/C94Q==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" } }, + "@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", + "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -336,6 +4826,15 @@ "resolve-from": "^5.0.0" }, "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -346,6 +4845,16 @@ "path-exists": "^4.0.0" } }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -355,6 +4864,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -363,6 +4881,12 @@ "requires": { "p-limit": "^2.2.0" } + }, + "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 } } }, @@ -383,49 +4907,66 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true }, "@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", - "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, - "@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==", + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "requires": { - "@types/node": "*" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" } }, "@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==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==", "dev": true, "requires": { - "@types/base-x": "*" + "base-x": "^3.0.6" } }, "@types/bs58check": { @@ -446,6 +4987,12 @@ "@types/node": "*" } }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, "@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", @@ -453,9 +5000,9 @@ "dev": true }, "@types/node": { - "version": "16.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", - "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==", + "version": "16.18.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.4.tgz", + "integrity": "sha512-9qGjJ5GyShZjUfx2ArBIGM+xExdfLvvaCyQR0t6yRXKPcWCVYF/WemtX/uIU3r7FYECXRXkIiw2Vnhn6y8d+pw==", "dev": true }, "@types/proxyquire": { @@ -482,6 +5029,12 @@ "@types/node": "*" } }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "@types/wif": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", @@ -491,12 +5044,117 @@ "@types/node": "*" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "@typescript-eslint/eslint-plugin": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", + "integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.45.0", + "@typescript-eslint/type-utils": "5.45.0", + "@typescript-eslint/utils": "5.45.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.45.0.tgz", + "integrity": "sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.45.0", + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/typescript-estree": "5.45.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz", + "integrity": "sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/visitor-keys": "5.45.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz", + "integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.45.0", + "@typescript-eslint/utils": "5.45.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.45.0.tgz", + "integrity": "sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz", + "integrity": "sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/visitor-keys": "5.45.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz", + "integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.45.0", + "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/typescript-estree": "5.45.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz", + "integrity": "sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.45.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -508,14 +5166,14 @@ } }, "ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, @@ -532,18 +5190,18 @@ "dev": true }, "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==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" } }, "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -566,19 +5224,22 @@ "dev": true }, "arg": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", - "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true }, "astral-regex": { "version": "2.0.0", @@ -587,15 +5248,15 @@ "dev": true }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "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==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", "requires": { "safe-buffer": "^5.0.1" } @@ -615,14 +5276,6 @@ "dayjs": "^1.10.6", "lodash.get": "^4.4.2", "table": "^6.7.1" - }, - "dependencies": { - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true - } } }, "binary-extensions": { @@ -637,31 +5290,23 @@ "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==" }, "bip32": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz", - "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.1.0.tgz", + "integrity": "sha512-eoeajYEzJ4d6yyVtby8C+XkCeKItiC4Mx56a0M9VaqTMC73SWOm4xVZG7SaR8e/yp4eSyky2XcBpH3DApPdu7Q==", "dev": true, "requires": { - "@types/node": "10.12.18", "bs58check": "^2.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", + "ripemd160": "^2.0.2", "typeforce": "^1.11.5", "wif": "^2.0.6" - }, - "dependencies": { - "@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 - } } }, "bip39": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", - "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", + "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", "dev": true, "requires": { "@types/node": "11.11.6", @@ -722,22 +5367,21 @@ "dev": true }, "browserslist": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", - "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001332", - "electron-to-chromium": "^1.4.118", - "escalade": "^3.1.1", - "node-releases": "^2.0.3", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" } }, "bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "requires": { "base-x": "^3.0.2" } @@ -753,15 +5397,9 @@ } }, "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 - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "caching-transform": { @@ -776,6 +5414,12 @@ "write-file-atomic": "^3.0.0" } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -783,31 +5427,19 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001343", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001343.tgz", - "integrity": "sha512-8KeCrAtPMabo/XW14B+R9sZYoClx1n0b+WYgwDKZPtWR3TcdvWzdSy7mPyFEmR5WU1St9v1PW6sdO5dkFOEzfA==", + "version": "1.0.30001436", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", + "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==", "dev": true }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "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, - "requires": { - "has-flag": "^3.0.0" - } - } + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "chokidar": { @@ -824,6 +5456,17 @@ "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" + }, + "dependencies": { + "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, + "requires": { + "is-glob": "^4.0.1" + } + } } }, "cipher-base": { @@ -853,24 +5496,24 @@ } }, "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==", + "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, "requires": { - "color-name": "1.1.3" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "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 }, "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==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true }, "commondir": { @@ -882,17 +5525,14 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "create-hash": { "version": "1.2.0", @@ -943,27 +5583,25 @@ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "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 - } + "ms": "2.1.2" } }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "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==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", "dev": true, "requires": { "strip-bom": "^4.0.0" @@ -984,10 +5622,28 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "ecpair": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", - "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", + "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", "dev": true, "requires": { "randombytes": "^2.1.0", @@ -996,9 +5652,9 @@ } }, "electron-to-chromium": { - "version": "1.4.139", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.139.tgz", - "integrity": "sha512-lYxzcUCjWxxVug+A7UxBCUiVr13TCjfZFYJS9Lq1VpU/ErwV4a6zUQo9dfojuGpw/L/x9REGuBl6ICQPGgbs3g==", + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", "dev": true }, "emoji-regex": { @@ -1020,27 +5676,258 @@ "dev": true }, "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=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", + "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, "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 }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "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, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", "dev": true, "requires": { "is-object": "~1.0.1", @@ -1083,6 +5970,33 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -1102,7 +6016,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "fsevents": { @@ -1112,6 +6026,12 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1131,9 +6051,9 @@ "dev": true }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -1145,19 +6065,36 @@ } }, "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" } }, "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 + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } }, "graceful-fs": { "version": "4.2.10", @@ -1165,19 +6102,35 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" } }, "hasha": { @@ -1188,6 +6141,14 @@ "requires": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" + }, + "dependencies": { + "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 + } } }, "he": { @@ -1208,10 +6169,26 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "indent-string": { @@ -1223,7 +6200,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -1231,9 +6208,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-binary-path": { "version": "2.1.0", @@ -1244,10 +6221,19 @@ "binary-extensions": "^2.0.0" } }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { @@ -1272,9 +6258,15 @@ "dev": true }, "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, "is-plain-obj": { @@ -1292,7 +6284,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, "is-unicode-supported": { @@ -1310,7 +6302,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "istanbul-lib-coverage": { @@ -1349,18 +6341,17 @@ } }, "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==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, "requires": { "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", "p-map": "^3.0.0", "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "uuid": "^8.3.2" }, "dependencies": { "rimraf": { @@ -1383,23 +6374,6 @@ "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", "supports-color": "^7.1.0" - }, - "dependencies": { - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "istanbul-lib-source-maps": { @@ -1414,15 +6388,21 @@ } }, "istanbul-reports": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", - "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, "requires": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, + "js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1430,13 +6410,12 @@ "dev": true }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsesc": { @@ -1446,9 +6425,15 @@ "dev": true }, "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "json5": { @@ -1457,6 +6442,16 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1469,7 +6464,7 @@ "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, "lodash.get": { @@ -1478,6 +6473,12 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -1492,57 +6493,15 @@ "requires": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" } }, "make-dir": { @@ -1563,9 +6522,9 @@ } }, "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, "md5.js": { @@ -1581,13 +6540,29 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, "minimaldata": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/minimaldata/-/minimaldata-1.0.2.tgz", - "integrity": "sha1-AfOywB2LJzmEP9hT1AY2xaLrdms=", + "integrity": "sha512-ZR9tWALR8ZszYd/zP34TmmVwRDVBNCT5+hkNXfTp3rpEDmZmgmYt1Sh/tu9qYFuPvSvEEEeJGE2BY8MBmeAzQQ==", "dev": true, "requires": { "bitcoin-ops": "^1.3.0", @@ -1603,28 +6578,12 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, "mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", + "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", @@ -1648,50 +6607,13 @@ "yargs-unparser": "2.0.0" }, "dependencies": { - "argparse": { + "brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "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" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" + "balanced-match": "^1.0.0" } }, "minimatch": { @@ -1701,17 +6623,21 @@ "dev": true, "requires": { "brace-expansion": "^2.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -1719,13 +6645,13 @@ "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=", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", "dev": true }, "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "nanoid": { @@ -1734,6 +6660,18 @@ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -1744,9 +6682,9 @@ } }, "node-releases": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", - "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, "normalize-path": { @@ -1790,15 +6728,6 @@ "yargs": "^15.0.2" }, "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1810,21 +6739,6 @@ "wrap-ansi": "^6.2.0" } }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1835,20 +6749,6 @@ "path-exists": "^4.0.0" } }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1858,13 +6758,13 @@ "p-locate": "^4.1.0" } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "p-try": "^2.0.0" } }, "p-locate": { @@ -1876,6 +6776,12 @@ "p-limit": "^2.2.0" } }, + "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 + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1896,6 +6802,12 @@ "strip-ansi": "^6.0.0" } }, + "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 + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -1930,19 +6842,33 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "requires": { "wrappy": "1" } }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -1952,17 +6878,6 @@ "dev": true, "requires": { "p-limit": "^3.0.2" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } } }, "p-map": { @@ -1992,6 +6907,15 @@ "release-zalgo": "^1.0.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2001,7 +6925,7 @@ "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=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "path-key": { @@ -2016,6 +6940,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -2069,6 +6999,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -2080,12 +7019,27 @@ } } }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "prettier": { - "version": "1.16.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", - "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", + "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", "dev": true }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -2096,14 +7050,14 @@ } }, "proxyquire": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.0.tgz", - "integrity": "sha512-kptdFArCfGRtQFv3Qwjr10lwbEV0TBJYvfqzhwucyfEXqVgmnAkyEw/S3FYzR5HI9i5QOq4rcqQjZ6AlknlCDQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", "dev": true, "requires": { "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.0", - "resolve": "~1.8.1" + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" } }, "punycode": { @@ -2115,12 +7069,18 @@ "pushdata-bitcoin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", - "integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=", + "integrity": "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==", "dev": true, "requires": { "bitcoin-ops": "^1.3.0" } }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2130,6 +7090,16 @@ "safe-buffer": "^5.1.0" } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2139,6 +7109,12 @@ "picomatch": "^2.2.1" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "regtest-client": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/regtest-client/-/regtest-client-0.2.0.tgz", @@ -2153,7 +7129,7 @@ "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "requires": { "es6-error": "^4.0.1" @@ -2162,7 +7138,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, "require-from-string": { @@ -2178,43 +7154,35 @@ "dev": true }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "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" - } - } } }, "ripemd160": { @@ -2226,16 +7194,28 @@ "inherits": "^2.0.1" } }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "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==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "serialize-javascript": { "version": "6.0.0", @@ -2249,7 +7229,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "sha.js": { @@ -2282,6 +7262,12 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -2291,32 +7277,6 @@ "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - } } }, "source-map": { @@ -2326,21 +7286,13 @@ "dev": true }, "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==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "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 - } } }, "spawn-wrap": { @@ -2371,15 +7323,23 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2413,22 +7373,20 @@ "dev": true }, "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "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, "requires": { "has-flag": "^4.0.0" - }, - "dependencies": { - "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 - } } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", @@ -2440,6 +7398,26 @@ "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "test-exclude": { @@ -2451,48 +7429,27 @@ "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "tiny-secp256k1": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.0.tgz", - "integrity": "sha512-2hPuUGCroLrxh6xxwoe+1RgPpOOK1w2uTnhgiHBpvoutBR+krNuT4hOXQyOaaYnZgoXBB6hBYkuAJHxyeBOPzQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz", + "integrity": "sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng==", "dev": true, "requires": { - "uint8array-tools": "0.0.6" + "uint8array-tools": "0.0.7" } }, "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=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, "to-regex-range": { @@ -2505,22 +7462,22 @@ } }, "ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", "dev": true, "requires": { "arg": "^4.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" + "source-map-support": "^0.5.17", + "yn": "3.1.1" }, "dependencies": { "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true } } @@ -2531,48 +7488,28 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { - "@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" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } + "tslib": "^1.8.1" } }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "tslib": "^1.8.1" + "prelude-ls": "^1.2.1" } }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, "typedarray-to-buffer": { @@ -2590,17 +7527,27 @@ "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" }, "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", "dev": true }, "uint8array-tools": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.6.tgz", - "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", + "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", "dev": true }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2610,10 +7557,15 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, "varuint-bitcoin": { @@ -2636,17 +7588,23 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, "wif": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", - "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", "requires": { "bs58check": "<3.0.0" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", @@ -2662,38 +7620,12 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "write-file-atomic": { @@ -2709,9 +7641,15 @@ } }, "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "yargs": { @@ -2727,14 +7665,6 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" - }, - "dependencies": { - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - } } }, "yargs-parser": { diff --git a/package.json b/package.json index b2414f42f..4334faa4f 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "format:ci": "npm run prettier -- --check && npm run prettierjs -- --check", "gitdiff:ci": "npm run build && git diff --exit-code", "integration": "npm run build && npm run nobuild:integration", - "lint": "tslint -p tsconfig.json -c tslint.json", - "lint:tests": "tslint -p test/tsconfig.json -c tslint.json", + "lint": "eslint ts_src/** src/**/*.js", + "lint:tests": "eslint test/**/*.spec.ts", "mocha:ts": "mocha --recursive --require test/ts-node-register", "nobuild:coverage-report": "nyc report --reporter=lcov", "nobuild:coverage-html": "nyc report --reporter=html", @@ -68,6 +68,8 @@ "@types/randombytes": "^2.0.0", "@types/ripemd160": "^2.0.0", "@types/wif": "^2.0.2", + "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/parser": "^5.45.0", "better-npm-audit": "^3.7.3", "bip32": "^3.0.1", "bip39": "^3.0.2", @@ -76,18 +78,20 @@ "bs58": "^4.0.0", "dhttp": "^3.0.0", "ecpair": "^2.0.1", + "eslint": "^8.29.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", "mocha": "^10.0.0", "nyc": "^15.1.0", - "prettier": "1.16.4", + "prettier": "^2.8.0", "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" }, "license": "MIT" diff --git a/src/address.js b/src/address.js index de0154a3a..d58c9a69e 100644 --- a/src/address.js +++ b/src/address.js @@ -1,6 +1,12 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.toOutputScript = exports.fromOutputScript = exports.toBech32 = exports.toBase58Check = exports.fromBech32 = exports.fromBase58Check = void 0; +exports.toOutputScript = + exports.fromOutputScript = + exports.toBech32 = + exports.toBase58Check = + exports.fromBech32 = + exports.fromBase58Check = + void 0; const networks = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); diff --git a/src/bufferutils.js b/src/bufferutils.js index 83a013b34..0f7fd10f4 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,6 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.BufferReader = exports.BufferWriter = exports.cloneBuffer = exports.reverseBuffer = exports.writeUInt64LE = exports.readUInt64LE = exports.varuint = void 0; +exports.BufferReader = + exports.BufferWriter = + exports.cloneBuffer = + exports.reverseBuffer = + exports.writeUInt64LE = + exports.readUInt64LE = + exports.varuint = + void 0; const types = require('./types'); const { typeforce } = types; const varuint = require('varuint-bitcoin'); @@ -53,14 +60,14 @@ exports.cloneBuffer = cloneBuffer; * Helper class for serialization of bitcoin data types into a pre-allocated buffer. */ class BufferWriter { + static withCapacity(size) { + return new BufferWriter(Buffer.alloc(size)); + } constructor(buffer, offset = 0) { this.buffer = buffer; this.offset = offset; typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); } - static withCapacity(size) { - return new BufferWriter(Buffer.alloc(size)); - } writeUInt8(i) { this.offset = this.buffer.writeUInt8(i, this.offset); } diff --git a/src/crypto.d.ts b/src/crypto.d.ts index ec088f3e3..4d88c2f39 100644 --- a/src/crypto.d.ts +++ b/src/crypto.d.ts @@ -5,6 +5,6 @@ export declare function sha256(buffer: Buffer): Buffer; export declare function hash160(buffer: Buffer): Buffer; export declare function hash256(buffer: Buffer): Buffer; declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"]; -export declare type TaggedHashPrefix = typeof TAGS[number]; +export type TaggedHashPrefix = typeof TAGS[number]; export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer; export {}; diff --git a/src/crypto.js b/src/crypto.js index a3df16764..b84f2701e 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,18 +1,20 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0; +exports.taggedHash = + exports.hash256 = + exports.hash160 = + exports.sha256 = + exports.sha1 = + exports.ripemd160 = + void 0; const createHash = require('create-hash'); const RipeMd160 = require('ripemd160'); function ripemd160(buffer) { try { - return createHash('rmd160') - .update(buffer) - .digest(); + return createHash('rmd160').update(buffer).digest(); } catch (err) { try { - return createHash('ripemd160') - .update(buffer) - .digest(); + return createHash('ripemd160').update(buffer).digest(); } catch (err2) { return new RipeMd160().update(buffer).digest(); } @@ -20,15 +22,11 @@ function ripemd160(buffer) { } exports.ripemd160 = ripemd160; function sha1(buffer) { - return createHash('sha1') - .update(buffer) - .digest(); + return createHash('sha1').update(buffer).digest(); } exports.sha1 = sha1; function sha256(buffer) { - return createHash('sha256') - .update(buffer) - .digest(); + return createHash('sha256').update(buffer).digest(); } exports.sha256 = sha256; function hash160(buffer) { diff --git a/src/index.js b/src/index.js index 25d0b5a22..4159064bd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,16 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.initEccLib = 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'); @@ -14,35 +24,35 @@ exports.script = script; var block_1 = require('./block'); Object.defineProperty(exports, 'Block', { enumerable: true, - get: function() { + get: function () { return block_1.Block; }, }); var psbt_1 = require('./psbt'); Object.defineProperty(exports, 'Psbt', { enumerable: true, - get: function() { + get: function () { return psbt_1.Psbt; }, }); var ops_1 = require('./ops'); Object.defineProperty(exports, 'opcodes', { enumerable: true, - get: function() { + get: function () { return ops_1.OPS; }, }); var transaction_1 = require('./transaction'); Object.defineProperty(exports, 'Transaction', { enumerable: true, - get: function() { + get: function () { return transaction_1.Transaction; }, }); var ecc_lib_1 = require('./ecc_lib'); Object.defineProperty(exports, 'initEccLib', { enumerable: true, - get: function() { + get: function () { return ecc_lib_1.initEccLib; }, }); diff --git a/src/payments/bip341.d.ts b/src/payments/bip341.d.ts index 8dd82409a..fe9ecf0d9 100644 --- a/src/payments/bip341.d.ts +++ b/src/payments/bip341.d.ts @@ -20,7 +20,7 @@ interface TweakedPublicKey { * 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; +export type HashTree = HashLeaf | HashBranch; export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer; /** * Build a hash tree of merkle nodes from the scripts binary tree. diff --git a/src/payments/bip341.js b/src/payments/bip341.js index 02c976b5f..35609d788 100644 --- a/src/payments/bip341.js +++ b/src/payments/bip341.js @@ -1,6 +1,14 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.MAX_TAPTREE_DEPTH = exports.LEAF_VERSION_TAPSCRIPT = void 0; +exports.tweakKey = + exports.tapTweakHash = + exports.tapleafHash = + exports.findScriptPath = + exports.toHashTree = + exports.rootHashFromPath = + exports.MAX_TAPTREE_DEPTH = + exports.LEAF_VERSION_TAPSCRIPT = + void 0; const buffer_1 = require('buffer'); const ecc_lib_1 = require('../ecc_lib'); const bcrypto = require('../crypto'); @@ -12,9 +20,7 @@ const isHashBranch = ht => 'left' in ht && 'right' in ht; function rootHashFromPath(controlBlock, leafHash) { if (controlBlock.length < 33) throw new TypeError( - `The control-block length is too small. Got ${ - controlBlock.length - }, expected min 33.`, + `The control-block length is too small. Got ${controlBlock.length}, expected min 33.`, ); const m = (controlBlock.length - 33) / 32; let kj = leafHash; diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 07c12cc48..57a0369b8 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -29,13 +29,13 @@ export interface Payment { scriptTree?: Taptree; witness?: Buffer[]; } -export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; -export declare type PaymentFunction = () => Payment; +export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; +export type PaymentFunction = () => Payment; export interface PaymentOpts { validate?: boolean; allowIncomplete?: boolean; } -export declare type StackElement = Buffer | number; -export declare type Stack = StackElement[]; -export declare type StackFunction = () => Stack; +export type StackElement = Buffer | number; +export type Stack = StackElement[]; +export type StackFunction = () => Stack; export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; diff --git a/src/payments/index.js b/src/payments/index.js index 9ce55f859..f820ddeee 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,59 +1,67 @@ '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 = + 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, - get: function() { + get: function () { return embed_1.p2data; }, }); const p2ms_1 = require('./p2ms'); Object.defineProperty(exports, 'p2ms', { enumerable: true, - get: function() { + get: function () { return p2ms_1.p2ms; }, }); const p2pk_1 = require('./p2pk'); Object.defineProperty(exports, 'p2pk', { enumerable: true, - get: function() { + get: function () { return p2pk_1.p2pk; }, }); const p2pkh_1 = require('./p2pkh'); Object.defineProperty(exports, 'p2pkh', { enumerable: true, - get: function() { + get: function () { return p2pkh_1.p2pkh; }, }); const p2sh_1 = require('./p2sh'); Object.defineProperty(exports, 'p2sh', { enumerable: true, - get: function() { + get: function () { return p2sh_1.p2sh; }, }); const p2wpkh_1 = require('./p2wpkh'); Object.defineProperty(exports, 'p2wpkh', { enumerable: true, - get: function() { + get: function () { return p2wpkh_1.p2wpkh; }, }); const p2wsh_1 = require('./p2wsh'); Object.defineProperty(exports, 'p2wsh', { enumerable: true, - get: function() { + get: function () { return p2wsh_1.p2wsh; }, }); const p2tr_1 = require('./p2tr'); Object.defineProperty(exports, 'p2tr', { enumerable: true, - get: function() { + get: function () { return p2tr_1.p2tr; }, }); diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 182aa62c6..d2cbc02a8 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -247,9 +247,7 @@ function p2tr(a, opts) { 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.`, + `The control-block length is too small. Got ${controlBlock.length}, expected min 33.`, ); if ((controlBlock.length - 33) % 32 !== 0) throw new TypeError( diff --git a/src/psbt.d.ts b/src/psbt.d.ts index 4c622b31f..de7bbb3d4 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -18,7 +18,7 @@ export interface TransactionOutput { export interface PsbtTxOutput extends TransactionOutput { address: string | undefined; } -export declare type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean; +export type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean; /** * Psbt class can parse and generate a PSBT binary based off of the BIP174. * There are 6 roles that this class fulfills. (Explained in BIP174) @@ -125,7 +125,7 @@ interface PsbtOptsOptional { } interface PsbtInputExtended extends PsbtInput, TransactionInput { } -declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; +type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; interface PsbtOutputExtendedAddress extends PsbtOutput { address: string; value: number; @@ -183,7 +183,7 @@ export interface SignerAsync { * ie. `Can not finalize input #${inputIndex}` * 2. Create the finalScriptSig and finalScriptWitness Buffers. */ -declare type FinalScriptsFunc = (inputIndex: number, // Which input is it? +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? @@ -192,10 +192,10 @@ isP2WSH: boolean) => { finalScriptSig: Buffer | undefined; finalScriptWitness: Buffer | undefined; }; -declare type FinalTaprootScriptsFunc = (inputIndex: number, // Which input is it? +type FinalTaprootScriptsFunc = (inputIndex: number, // Which input is it? input: PsbtInput, // The PSBT input contents tapLeafHashToFinalize?: Buffer) => { 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'; +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'; export {}; diff --git a/src/psbt.js b/src/psbt.js index 2f2e94a96..9aa36686a 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -62,6 +62,20 @@ const DEFAULT_OPTS = { * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ class Psbt { + static fromBase64(data, opts = {}) { + const buffer = Buffer.from(data, 'base64'); + return this.fromBuffer(buffer, opts); + } + static fromHex(data, opts = {}) { + const buffer = Buffer.from(data, 'hex'); + return this.fromBuffer(buffer, opts); + } + static fromBuffer(buffer, opts = {}) { + const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); + const psbt = new Psbt(opts, psbtBase); + checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); + return psbt; + } constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) { this.data = data; // set defaults @@ -91,20 +105,6 @@ class Psbt { dpew(this, '__CACHE', false, true); dpew(this, 'opts', false, true); } - static fromBase64(data, opts = {}) { - const buffer = Buffer.from(data, 'base64'); - return this.fromBuffer(buffer, opts); - } - static fromHex(data, opts = {}) { - const buffer = Buffer.from(data, 'hex'); - return this.fromBuffer(buffer, opts); - } - static fromBuffer(buffer, opts = {}) { - const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); - const psbt = new Psbt(opts, psbtBase); - checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); - return psbt; - } get inputCount() { return this.data.inputs.length; } @@ -1236,8 +1236,9 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { ); } else if ((0, psbtutils_1.isP2WPKH)(meaningfulScript)) { // P2WPKH uses the P2PKH template for prevoutScript when signing - const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) }) - .output; + const signingScript = payments.p2pkh({ + hash: meaningfulScript.slice(2), + }).output; hash = unsignedTx.hashForWitnessV0( inputIndex, signingScript, diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 8e76ecda7..61196ead2 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -1,6 +1,17 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.checkTaprootInputForSigs = exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; +exports.checkTaprootInputForSigs = + exports.tapTreeFromList = + exports.tapTreeToList = + exports.tweakInternalPubKey = + exports.checkTaprootOutputFields = + exports.checkTaprootInputFields = + exports.isTaprootOutput = + exports.isTaprootInput = + exports.serializeTaprootSignature = + exports.tapScriptFinalizer = + exports.toXOnly = + void 0; const types_1 = require('../types'); const transaction_1 = require('../transaction'); const psbtutils_1 = require('./psbtutils'); @@ -104,8 +115,9 @@ function tweakInternalPubKey(inputIndex, input) { (0, bip341_1.tweakKey)(tapInternalKey, input.tapMerkleRoot); if (!outputKey) throw new Error( - `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey && - tapInternalKey.toString('hex')}`, + `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${ + tapInternalKey && tapInternalKey.toString('hex') + }`, ); return outputKey.x; } @@ -226,7 +238,8 @@ function checkMixedTaprootAndNonTaprootInputFields( hasNonTaprootFields(inputData) && isTaprootInput(newInputData); const hasMixedFields = inputData === newInputData && - (isTaprootInput(newInputData) && hasNonTaprootFields(newInputData)); // todo: bad? use !=== + isTaprootInput(newInputData) && + hasNonTaprootFields(newInputData); // todo: bad? use !=== if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error( `Invalid arguments for Psbt.${action}. ` + @@ -244,7 +257,8 @@ function checkMixedTaprootAndNonTaprootOutputFields( hasNonTaprootFields(inputData) && isTaprootOutput(newInputData); const hasMixedFields = inputData === newInputData && - (isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData)); + isTaprootOutput(newInputData) && + hasNonTaprootFields(newInputData); if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error( `Invalid arguments for Psbt.${action}. ` + diff --git a/src/psbt/psbtutils.d.ts b/src/psbt/psbtutils.d.ts index 6491132a8..db5f0b516 100644 --- a/src/psbt/psbtutils.d.ts +++ b/src/psbt/psbtutils.d.ts @@ -11,7 +11,7 @@ export declare function witnessStackToScriptWitness(witness: Buffer[]): Buffer; export declare function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number; export declare function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean; export declare function checkInputForSig(input: PsbtInput, action: string): boolean; -declare type SignatureDecodeFunc = (buffer: Buffer) => { +type SignatureDecodeFunc = (buffer: Buffer) => { signature: Buffer; hashType: number; }; diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index 086faa4f0..2e8bd435a 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -1,6 +1,18 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.signatureBlocksAction = exports.checkInputForSig = exports.pubkeyInScript = exports.pubkeyPositionInScript = exports.witnessStackToScriptWitness = exports.isP2TR = exports.isP2SHScript = exports.isP2WSHScript = exports.isP2WPKH = exports.isP2PKH = exports.isP2PK = exports.isP2MS = void 0; +exports.signatureBlocksAction = + exports.checkInputForSig = + exports.pubkeyInScript = + exports.pubkeyPositionInScript = + exports.witnessStackToScriptWitness = + exports.isP2TR = + exports.isP2SHScript = + exports.isP2WSHScript = + exports.isP2WPKH = + exports.isP2PKH = + exports.isP2PK = + exports.isP2MS = + void 0; const varuint = require('bip174/src/lib/converter/varint'); const bscript = require('../script'); const transaction_1 = require('../transaction'); diff --git a/src/script.js b/src/script.js index 5e5e17ba1..5dff149f3 100644 --- a/src/script.js +++ b/src/script.js @@ -1,11 +1,23 @@ '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.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', { enumerable: true, - get: function() { + get: function () { return ops_1.OPS; }, }); @@ -177,6 +189,5 @@ function isCanonicalScriptSignature(buffer) { return bip66.check(buffer.slice(0, -1)); } exports.isCanonicalScriptSignature = isCanonicalScriptSignature; -// 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..035d73efc 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -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 type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | 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 3a2dc51ea..acb51ad0b 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,34 @@ '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.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; +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); @@ -86,10 +114,10 @@ exports.isTaptree = isTaptree; exports.Buffer256bit = exports.typeforce.BufferN(32); exports.Hash160bit = exports.typeforce.BufferN(20); exports.Hash256bit = exports.typeforce.BufferN(32); -exports.Number = exports.typeforce.Number; // tslint:disable-line variable-name +exports.Number = exports.typeforce.Number; exports.Array = exports.typeforce.Array; -exports.Boolean = exports.typeforce.Boolean; // tslint:disable-line variable-name -exports.String = exports.typeforce.String; // tslint:disable-line variable-name +exports.Boolean = exports.typeforce.Boolean; +exports.String = exports.typeforce.String; exports.Buffer = exports.typeforce.Buffer; exports.Hex = exports.typeforce.Hex; exports.maybe = exports.typeforce.maybe; diff --git a/test/integration/bip32.spec.ts b/test/integration/bip32.spec.ts index 938c28113..2ecf308c4 100644 --- a/test/integration/bip32.spec.ts +++ b/test/integration/bip32.spec.ts @@ -60,10 +60,7 @@ describe('bitcoinjs-lib (BIP32)', () => { const child1 = root.derivePath(path); // option 2, manually - const child1b = root - .deriveHardened(0) - .derive(0) - .derive(0); + const child1b = root.deriveHardened(0).derive(0).derive(0); assert.strictEqual( getAddress(child1), diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 742d68fa1..2da433877 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -75,7 +75,6 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => { // but after sequence1 time, _alice can allow the multisig to become 1 of 3. // but after sequence2 time, _alice can sign for the output all by themself. - /* tslint:disable-next-line */ // Ref: https://github.com/bitcoinbook/bitcoinbook/blob/f8b883dcd4e3d1b9adf40fed59b7e898fbd9241f/ch07.asciidoc#complex-script-example // Note: bitcoinjs-lib will not offer specific support for problems with diff --git a/test/integration/payments.spec.ts b/test/integration/payments.spec.ts index d9d7fde78..4bf01f49f 100644 --- a/test/integration/payments.spec.ts +++ b/test/integration/payments.spec.ts @@ -42,10 +42,7 @@ async function buildAndSign( } return regtestUtils.broadcast( - psbt - .finalizeAllInputs() - .extractTransaction() - .toHex(), + psbt.finalizeAllInputs().extractTransaction().toHex(), ); } diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index b12c46f31..87dce290d 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -410,9 +410,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { 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 leafScriptAsm = `${leafPubkeys[2]} OP_CHECKSIG ${leafPubkeys[1]} OP_CHECKSIGADD ${leafPubkeys[0]} OP_CHECKSIGADD OP_3 OP_NUMEQUAL`; const leafScript = bitcoin.script.fromASM(leafScriptAsm); @@ -674,6 +672,7 @@ function createSigned( // This logic will be extracted to ecpair function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore let privateKey: Uint8Array | undefined = signer.privateKey!; if (!privateKey) { diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index ba7f2fe8b..264d99a2f 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -460,13 +460,8 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { 'p2sh-p2wsh', ); { - const { - hash, - index, - witnessUtxo, - redeemScript, - witnessScript, - } = inputData; + const { hash, index, witnessUtxo, redeemScript, witnessScript } = + inputData; assert.deepStrictEqual( { hash, index, witnessUtxo, redeemScript, witnessScript }, inputData, diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 6d295a80f..c61ec1732 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -52,18 +52,16 @@ const toAsyncSigner = (signer: Signer): SignerAsync => { const ret: SignerAsync = { publicKey: signer.publicKey, sign: (hash: Buffer, lowerR: boolean | undefined): Promise => { - return new Promise( - (resolve, rejects): void => { - setTimeout(() => { - try { - const r = signer.sign(hash, lowerR); - resolve(r); - } catch (e) { - rejects(e); - } - }, 10); - }, - ); + return new Promise((resolve, rejects): void => { + setTimeout(() => { + try { + const r = signer.sign(hash, lowerR); + resolve(r); + } catch (e) { + rejects(e); + } + }, 10); + }); }, }; return ret; @@ -72,13 +70,11 @@ const failedAsyncSigner = (publicKey: Buffer): SignerAsync => { return { publicKey, sign: (__: Buffer): Promise => { - return new Promise( - (_, reject): void => { - setTimeout(() => { - reject(new Error('sign failed')); - }, 10); - }, - ); + return new Promise((_, reject): void => { + setTimeout(() => { + reject(new Error('sign failed')); + }, 10); + }); }, }; }; @@ -154,6 +150,7 @@ describe(`Psbt`, () => { if (f.isTaproot) initEccLib(ecc); const psbt = Psbt.fromBase64(f.psbt); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // cannot find tapLeafHashToSign f.keys.forEach(({ inputToSign, tapLeafHashToSign, WIF }) => { const keyPair = ECPair.fromWIF(WIF, NETWORKS.testnet); @@ -532,8 +529,7 @@ describe(`Psbt`, () => { it(`fails if trying to finalzie non-taproot input`, () => { const psbt = new Psbt(); psbt.addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, }); @@ -556,8 +552,7 @@ describe(`Psbt`, () => { it('fails if no script found', () => { const psbt = new Psbt(); psbt.addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, }); assert.throws(() => { @@ -671,8 +666,7 @@ describe(`Psbt`, () => { it('Sets the sequence number for a given input', () => { const psbt = new Psbt(); psbt.addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, }); @@ -685,8 +679,7 @@ describe(`Psbt`, () => { it('throws if input index is too high', () => { const psbt = new Psbt(); psbt.addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, }); @@ -729,8 +722,7 @@ describe(`Psbt`, () => { const psbt = new Psbt(); psbt .addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, witnessUtxo: { script: outerScript(innerScript(publicKey)), @@ -802,8 +794,7 @@ describe(`Psbt`, () => { const path = "m/0'/0"; const psbt = new Psbt(); psbt.addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, bip32Derivation: [ { @@ -822,8 +813,7 @@ describe(`Psbt`, () => { it('should throw', () => { const psbt = new Psbt(); psbt.addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, }); @@ -897,8 +887,7 @@ describe(`Psbt`, () => { const psbt = new Psbt(); psbt .addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, }) .addOutput({ @@ -925,8 +914,7 @@ describe(`Psbt`, () => { const psbt = new Psbt(); psbt .addInput({ - hash: - '0000000000000000000000000000000000000000000000000000000000000000', + hash: '0000000000000000000000000000000000000000000000000000000000000000', index: 0, }) .addOutput({ diff --git a/test/script_signature.spec.ts b/test/script_signature.spec.ts index 54c416e35..f70caa475 100644 --- a/test/script_signature.spec.ts +++ b/test/script_signature.spec.ts @@ -11,9 +11,7 @@ describe('Script Signatures', () => { ); } - function toRaw( - signature: Buffer, - ): { + function toRaw(signature: Buffer): { r: string; s: string; } { diff --git a/test/transaction.spec.ts b/test/transaction.spec.ts index 13d64d128..bf62acedf 100644 --- a/test/transaction.spec.ts +++ b/test/transaction.spec.ts @@ -266,7 +266,7 @@ describe('Transaction', () => { tx.addOutput(randScript, 5000000000); const original = (tx as any).__toBuffer; - (tx as any).__toBuffer = function( + (tx as any).__toBuffer = function ( this: Transaction, a: any, b: any, diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index e1cebde53..5af3bfbbe 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -3,14 +3,10 @@ import * as RipeMd160 from 'ripemd160'; export function ripemd160(buffer: Buffer): Buffer { try { - return createHash('rmd160') - .update(buffer) - .digest(); + return createHash('rmd160').update(buffer).digest(); } catch (err) { try { - return createHash('ripemd160') - .update(buffer) - .digest(); + return createHash('ripemd160').update(buffer).digest(); } catch (err2) { return new RipeMd160().update(buffer).digest(); } @@ -18,15 +14,11 @@ export function ripemd160(buffer: Buffer): Buffer { } export function sha1(buffer: Buffer): Buffer { - return createHash('sha1') - .update(buffer) - .digest(); + return createHash('sha1').update(buffer).digest(); } export function sha256(buffer: Buffer): Buffer { - return createHash('sha256') - .update(buffer) - .digest(); + return createHash('sha256').update(buffer).digest(); } export function hash160(buffer: Buffer): Buffer { diff --git a/ts_src/payments/bip341.ts b/ts_src/payments/bip341.ts index 5a1dd068d..40793779f 100644 --- a/ts_src/payments/bip341.ts +++ b/ts_src/payments/bip341.ts @@ -40,9 +40,7 @@ export function rootHashFromPath( ): Buffer { if (controlBlock.length < 33) throw new TypeError( - `The control-block length is too small. Got ${ - controlBlock.length - }, expected min 33.`, + `The control-block length is too small. Got ${controlBlock.length}, expected min 33.`, ); const m = (controlBlock.length - 33) / 32; diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index 0cea3632c..272d094a3 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -65,19 +65,17 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { const _chunks = lazy.value(() => { return bscript.decompile(a.input!); }) as StackFunction; - const _redeem = lazy.value( - (): Payment => { - const chunks = _chunks(); - const lastChunk = chunks[chunks.length - 1]; - return { - network, - output: - lastChunk === OPS.OP_FALSE ? Buffer.from([]) : (lastChunk as Buffer), - input: bscript.compile(chunks.slice(0, -1)), - witness: a.witness || [], - }; - }, - ) as PaymentFunction; + const _redeem = lazy.value((): Payment => { + const chunks = _chunks(); + const lastChunk = chunks[chunks.length - 1]; + return { + network, + output: + lastChunk === OPS.OP_FALSE ? Buffer.from([]) : (lastChunk as Buffer), + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [], + }; + }) as PaymentFunction; // output dependents lazy.prop(o, 'address', () => { diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 64c591ffd..231a263fc 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -271,9 +271,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 - }, expected min 33.`, + `The control-block length is too small. Got ${controlBlock.length}, expected min 33.`, ); if ((controlBlock.length - 33) % 32 !== 0) diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index aa546bc71..e7ca28976 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -660,34 +660,32 @@ export class Psbt { hdKeyPair: HDSigner | HDSignerAsync, sighashTypes: number[] = [Transaction.SIGHASH_ALL], ): Promise { - return new Promise( - (resolve, reject): any => { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - return reject(new Error('Need HDSigner to sign input')); - } + return new Promise((resolve, reject): any => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } - const results: boolean[] = []; - const promises: Array> = []; - for (const i of range(this.data.inputs.length)) { - promises.push( - this.signInputHDAsync(i, hdKeyPair, sighashTypes).then( - () => { - results.push(true); - }, - () => { - results.push(false); - }, - ), - ); + const results: boolean[] = []; + const promises: Array> = []; + for (const i of range(this.data.inputs.length)) { + promises.push( + this.signInputHDAsync(i, hdKeyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); } - return Promise.all(promises).then(() => { - if (results.every(v => v === false)) { - return reject(new Error('No inputs were signed')); - } - resolve(); - }); - }, - ); + resolve(); + }); + }); } signInputHD( @@ -712,26 +710,20 @@ export class Psbt { hdKeyPair: HDSigner | HDSignerAsync, sighashTypes: number[] = [Transaction.SIGHASH_ALL], ): Promise { - return new Promise( - (resolve, reject): any => { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - return reject(new Error('Need HDSigner to sign input')); - } - const signers = getSignersFromHD( - inputIndex, - this.data.inputs, - hdKeyPair, - ); - const promises = signers.map(signer => - this.signInputAsync(inputIndex, signer, sighashTypes), - ); - return Promise.all(promises) - .then(() => { - resolve(); - }) - .catch(reject); - }, - ); + return new Promise((resolve, reject): any => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); + const promises = signers.map(signer => + this.signInputAsync(inputIndex, signer, sighashTypes), + ); + return Promise.all(promises) + .then(() => { + resolve(); + }) + .catch(reject); + }); } signAllInputs(keyPair: Signer, sighashTypes?: number[]): this { @@ -760,36 +752,34 @@ export class Psbt { keyPair: Signer | SignerAsync, sighashTypes?: number[], ): Promise { - return new Promise( - (resolve, reject): any => { - if (!keyPair || !keyPair.publicKey) - return reject(new Error('Need Signer to sign input')); - - // TODO: Add a pubkey/pubkeyhash cache to each input - // as input information is added, then eventually - // optimize this method. - const results: boolean[] = []; - const promises: Array> = []; - for (const [i] of this.data.inputs.entries()) { - promises.push( - this.signInputAsync(i, keyPair, sighashTypes).then( - () => { - results.push(true); - }, - () => { - results.push(false); - }, - ), - ); + return new Promise((resolve, reject): any => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results: boolean[] = []; + const promises: Array> = []; + for (const [i] of this.data.inputs.entries()) { + promises.push( + this.signInputAsync(i, keyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); } - return Promise.all(promises).then(() => { - if (results.every(v => v === false)) { - return reject(new Error('No inputs were signed')); - } - resolve(); - }); - }, - ); + resolve(); + }); + }); } signInput( diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index d4eaaa5f3..375d35cc6 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -155,8 +155,9 @@ export function tweakInternalPubKey( if (!outputKey) throw new Error( - `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey && - tapInternalKey.toString('hex')}`, + `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${ + tapInternalKey && tapInternalKey.toString('hex') + }`, ); return outputKey.x; } @@ -205,9 +206,7 @@ export function checkTaprootInputForSigs( ); } -function decodeSchnorrSignature( - signature: Buffer, -): { +function decodeSchnorrSignature(signature: Buffer): { signature: Buffer; hashType: number; } { @@ -308,7 +307,8 @@ function checkMixedTaprootAndNonTaprootInputFields( hasNonTaprootFields(inputData) && isTaprootInput(newInputData); const hasMixedFields = inputData === newInputData && - (isTaprootInput(newInputData) && hasNonTaprootFields(newInputData)); // todo: bad? use !=== + isTaprootInput(newInputData) && + hasNonTaprootFields(newInputData); // todo: bad? use !=== if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error( @@ -327,7 +327,8 @@ function checkMixedTaprootAndNonTaprootOutputFields( hasNonTaprootFields(inputData) && isTaprootOutput(newInputData); const hasMixedFields = inputData === newInputData && - (isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData)); + isTaprootOutput(newInputData) && + hasNonTaprootFields(newInputData); if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error( diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index d51e2df54..173e0215c 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -81,9 +81,7 @@ export function checkInputForSig(input: PsbtInput, action: string): boolean { ); } -type SignatureDecodeFunc = ( - buffer: Buffer, -) => { +type SignatureDecodeFunc = (buffer: Buffer) => { signature: Buffer; hashType: number; }; diff --git a/ts_src/script.ts b/ts_src/script.ts index 5d20ebc01..ca2c557f5 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -208,6 +208,5 @@ export function isCanonicalScriptSignature(buffer: Buffer): boolean { return bip66.check(buffer.slice(0, -1)); } -// 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 b904fd49b..d4f4ac774 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -112,10 +112,10 @@ export interface TinySecp256k1Interface { export const Buffer256bit = typeforce.BufferN(32); export const Hash160bit = typeforce.BufferN(20); export const Hash256bit = typeforce.BufferN(32); -export const Number = typeforce.Number; // tslint:disable-line variable-name +export const Number = typeforce.Number; export const Array = typeforce.Array; -export const Boolean = typeforce.Boolean; // tslint:disable-line variable-name -export const String = typeforce.String; // tslint:disable-line variable-name +export const Boolean = typeforce.Boolean; +export const String = typeforce.String; export const Buffer = typeforce.Buffer; export const Hex = typeforce.Hex; export const maybe = typeforce.maybe; diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 90e513dfe..000000000 --- a/tslint.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": ["tslint:recommended"], - "rules": { - "array-type": false, - "arrow-parens": false, - "curly": false, - "indent": [ - true, - "spaces", - 2 - ], - "interface-name": [false], - "match-default-export-name": true, - "max-classes-per-file": [false], - "member-access": [true, "no-public"], - "no-bitwise": false, - "no-console": false, - "no-empty": [true, "allow-empty-catch"], - "no-implicit-dependencies": [true, "dev"], - "no-return-await": true, - "no-var-requires": false, - "no-unused-expression": false, - "object-literal-sort-keys": false, - "quotemark": [true, "single", "avoid-escape"], - "typedef": [ - true, - "call-signature", - "property-declaration" - ], - "variable-name": [ - true, - "ban-keywords", - "check-format", - "allow-leading-underscore", - "allow-pascal-case" - ] - }, - "rulesDirectory": [] -} From f9e970a6a5f2c667489fb095834a377b61fbd45e Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 7 Dec 2022 19:24:08 +0900 Subject: [PATCH 116/249] Update CHANGELOG and remove old taproot example --- CHANGELOG.md | 4 + test/integration/taproot.md | 157 ------------------------------- test/integration/taproot.spec.ts | 112 ---------------------- 3 files changed, 4 insertions(+), 269 deletions(-) delete mode 100644 test/integration/taproot.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e74c79c57..b0d11209c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 6.1.0 +__added__ +- taproot support for payments (p2tr) and PSBT. See taproot.spec.ts integration test for examples. (#1742) + # 6.0.2 __fixed__ - p2sh payment now uses empty Buffer for redeem.output when redeemScript is OP_FALSE (#1802) diff --git a/test/integration/taproot.md b/test/integration/taproot.md deleted file mode 100644 index 7627450e2..000000000 --- a/test/integration/taproot.md +++ /dev/null @@ -1,157 +0,0 @@ -# Taproot - -A simple keyspend example that is possible with the current API is below. - -## Current state of taproot support - -- [x] segwit v1 address support via bech32m -- [x] segwit v1 sighash calculation on Transaction class - -## TODO - -- [x] p2tr payment API to make script spends easier -- [ ] Support within the Psbt class - - partial support added - -## Example - -### Requirements -- npm dependencies - - bitcoinjs-lib v6.x.x - - bip32 v3.x.x - - tiny-secp256k1 v2.x.x - - regtest-client vx.x.x -- local regtest-server docker container running - - `docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server` -- node >= v14 - -```js -// Run this whole file as async -// Catch any errors at the bottom of the file -// and exit the process with 1 error code -(async () => { - -// Order of the curve (N) - 1 -const N_LESS_1 = Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', - 'hex' -); -// 1 represented as 32 bytes BE -const ONE = Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000001', - 'hex' -); - -const crypto = require('crypto'); -// bitcoinjs-lib v6 -const bitcoin = require('bitcoinjs-lib'); -// bip32 v3 wraps tiny-secp256k1 -const BIP32Wrapper = require('bip32').default; -const RegtestUtils = require('regtest-client').RegtestUtils; -// tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async -const ecc = await import('tiny-secp256k1'); -// wrap the bip32 library -const bip32 = BIP32Wrapper(ecc); -// set up dependencies -const APIPASS = process.env.APIPASS || 'satoshi'; -// docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server -const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1'; -const regtestUtils = new RegtestUtils({ APIPASS, APIURL }); -// End imports - -const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network); - -const output = createKeySpendOutput(myKey.publicKey); -const address = bitcoin.address.fromOutputScript( - output, - regtestUtils.network -); -// amount from faucet -const amount = 42e4; -// amount to send -const sendAmount = amount - 1e4; -// get faucet -const unspent = await regtestUtils.faucetComplex(output, amount); - -const tx = createSigned( - myKey, - unspent.txId, - unspent.vout, - sendAmount, - [output], - [amount] -); - -const hex = tx.toHex(); -console.log('Valid tx sent from:'); -console.log(address); -console.log('tx hex:'); -console.log(hex); -await regtestUtils.broadcast(hex); -await regtestUtils.verify({ - txId: tx.getId(), - address, - vout: 0, - value: sendAmount, -}); - -// Function for creating a tweaked p2tr key-spend only address -// (This is recommended by BIP341) -function createKeySpendOutput(publicKey) { - // x-only pubkey (remove 1 byte y parity) - const myXOnlyPubkey = publicKey.slice(1, 33); - const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey); - const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash); - if (tweakResult === null) throw new Error('Invalid Tweak'); - const { xOnlyPubkey: tweaked } = tweakResult; - // scriptPubkey - return Buffer.concat([ - // witness v1, PUSH_DATA 32 bytes - Buffer.from([0x51, 0x20]), - // x-only tweaked pubkey - tweaked, - ]); -} - -// Function for signing for a tweaked p2tr key-spend only address -// (Required for the above address) -function signTweaked(messageHash, key) { - const privateKey = - key.publicKey[0] === 2 - ? key.privateKey - : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey), ONE); - const tweakHash = bitcoin.crypto.taggedHash( - 'TapTweak', - key.publicKey.slice(1, 33) - ); - const newPrivateKey = ecc.privateAdd(privateKey, tweakHash); - if (newPrivateKey === null) throw new Error('Invalid Tweak'); - return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32)); -} - -// Function for creating signed tx -function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) { - const tx = new bitcoin.Transaction(); - tx.version = 2; - // Add input - tx.addInput(Buffer.from(txid, 'hex').reverse(), vout); - // Add output - tx.addOutput(scriptPubkeys[0], amountToSend); - const sighash = tx.hashForWitnessV1( - 0, // which input - scriptPubkeys, // All previous outputs of all inputs - values, // All previous values of all inputs - bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL) - ); - const signature = Buffer.from(signTweaked(sighash, key)); - // witness stack for keypath spend is just the signature. - // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value - tx.ins[0].witness = [signature]; - return tx; -} - -})().catch((err) => { - console.error(err); - process.exit(1); -}); -``` \ No newline at end of file diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 87dce290d..ec870b761 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -17,41 +17,6 @@ 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 () => { - const myKey = bip32.fromSeed(rng(64), regtest); - - const output = createKeySpendOutput(myKey.publicKey); - const address = bitcoin.address.fromOutputScript(output, regtest); - // amount from faucet - const amount = 42e4; - // amount to send - const sendAmount = amount - 1e4; - // get faucet - const unspent = await regtestUtils.faucetComplex(output, amount); - - const tx = createSigned( - myKey, - unspent.txId, - unspent.vout, - sendAmount, - [output], - [amount], - ); - - const hex = tx.toHex(); - // console.log('Valid tx sent from:'); - // console.log(address); - // console.log('tx hex:'); - // console.log(hex); - await regtestUtils.broadcast(hex); - await regtestUtils.verify({ - txId: tx.getId(), - address, - vout: 0, - value: sendAmount, - }); - }); - it('can create (and broadcast via 3PBP) a taproot key-path spend Transaction', async () => { const internalKey = bip32.fromSeed(rng(64), regtest); const p2pkhKey = bip32.fromSeed(rng(64), regtest); @@ -593,83 +558,6 @@ function buildLeafIndexFinalizer( }; } -// Order of the curve (N) - 1 -const N_LESS_1 = Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', - 'hex', -); -// 1 represented as 32 bytes BE -const ONE = Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000001', - 'hex', -); - -// Function for creating a tweaked p2tr key-spend only address -// (This is recommended by BIP341) -function createKeySpendOutput(publicKey: Buffer): Buffer { - // x-only pubkey (remove 1 byte y parity) - 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'); - const { xOnlyPubkey: tweaked } = tweakResult; - // scriptPubkey - return Buffer.concat([ - // witness v1, PUSH_DATA 32 bytes - Buffer.from([0x51, 0x20]), - // x-only tweaked pubkey - tweaked, - ]); -} - -// Function for signing for a tweaked p2tr key-spend only address -// (Required for the above address) -interface KeyPair { - publicKey: Buffer; - privateKey?: Buffer; -} -function signTweaked(messageHash: Buffer, key: KeyPair): Uint8Array { - const privateKey = - key.publicKey[0] === 2 - ? key.privateKey - : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey!)!, ONE)!; - const tweakHash = bitcoin.crypto.taggedHash( - 'TapTweak', - toXOnly(key.publicKey), - ); - const newPrivateKey = ecc.privateAdd(privateKey!, tweakHash); - if (newPrivateKey === null) throw new Error('Invalid Tweak'); - return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32)); -} - -// Function for creating signed tx -function createSigned( - key: KeyPair, - txid: string, - vout: number, - amountToSend: number, - scriptPubkeys: Buffer[], - values: number[], -): bitcoin.Transaction { - const tx = new bitcoin.Transaction(); - tx.version = 2; - // Add input - tx.addInput(Buffer.from(txid, 'hex').reverse(), vout); - // Add output - tx.addOutput(scriptPubkeys[0], amountToSend); - const sighash = tx.hashForWitnessV1( - 0, // which input - scriptPubkeys, // All previous outputs of all inputs - values, // All previous values of all inputs - bitcoin.Transaction.SIGHASH_DEFAULT, // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL) - ); - const signature = Buffer.from(signTweaked(sighash, key)); - // witness stack for keypath spend is just the signature. - // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value - tx.ins[0].witness = [signature]; - return tx; -} - // This logic will be extracted to ecpair function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer { // eslint-disable-next-line @typescript-eslint/ban-ts-comment From f221e1f7ac01c11b715e3398e04514a7df64ae42 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 7 Dec 2022 19:24:23 +0900 Subject: [PATCH 117/249] 6.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67af0eb4d..e6de61ff9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bitcoinjs-lib", - "version": "6.1.0-rc.0", + "version": "6.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bitcoinjs-lib", - "version": "6.1.0-rc.0", + "version": "6.1.0", "license": "MIT", "dependencies": { "bech32": "^2.0.0", diff --git a/package.json b/package.json index 4334faa4f..b973d3d3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.0-rc.0", + "version": "6.1.0", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From db98a14e5dfb6d616456a883b3636cee2be9af79 Mon Sep 17 00:00:00 2001 From: Greg Tonoski <111286121+GregTonoski@users.noreply.github.com> Date: Thu, 29 Dec 2022 11:55:32 +0100 Subject: [PATCH 118/249] Removal of the example [Taproot Key Spend] There was the link to the page that doesn't exist: [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.md) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 77498c73a..a8de46778 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,6 @@ The below examples are implemented as integration tests, they should be very eas Otherwise, pull requests are appreciated. Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). -- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.md) - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) From d4957655c948e6c87a850e907f806ee71a56df70 Mon Sep 17 00:00:00 2001 From: Vodopyanov Egor Date: Thu, 29 Dec 2022 23:04:40 +0800 Subject: [PATCH 119/249] chore: initialise TAGGED_HASH_PREFIXES on-demand --- src/crypto.js | 15 +++++++++------ ts_src/crypto.ts | 17 +++++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/crypto.js b/src/crypto.js index b84f2701e..9a3c1a157 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -49,13 +49,16 @@ const TAGS = [ 'KeyAgg coefficient', ]; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ -const TAGGED_HASH_PREFIXES = Object.fromEntries( - TAGS.map(tag => { - const tagHash = sha256(Buffer.from(tag)); - return [tag, Buffer.concat([tagHash, tagHash])]; - }), -); +let TAGGED_HASH_PREFIXES = undefined; function taggedHash(prefix, data) { + if (!TAGGED_HASH_PREFIXES) { + TAGGED_HASH_PREFIXES = Object.fromEntries( + TAGS.map(tag => { + const tagHash = sha256(Buffer.from(tag)); + return [tag, Buffer.concat([tagHash, tagHash])]; + }), + ); + } return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data])); } exports.taggedHash = taggedHash; diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index 5af3bfbbe..47ac48313 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -42,13 +42,18 @@ const TAGS = [ ] as const; export type TaggedHashPrefix = typeof TAGS[number]; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ -const TAGGED_HASH_PREFIXES = Object.fromEntries( - TAGS.map(tag => { - const tagHash = sha256(Buffer.from(tag)); - return [tag, Buffer.concat([tagHash, tagHash])]; - }), -) as { [k in TaggedHashPrefix]: Buffer }; +let TAGGED_HASH_PREFIXES = undefined as + | { [k in TaggedHashPrefix]: Buffer } + | undefined; export function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer { + if (!TAGGED_HASH_PREFIXES) { + TAGGED_HASH_PREFIXES = Object.fromEntries( + TAGS.map(tag => { + const tagHash = sha256(Buffer.from(tag)); + return [tag, Buffer.concat([tagHash, tagHash])]; + }), + ) as { [k in TaggedHashPrefix]: Buffer }; + } return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data])); } From 6c886dd8d86e3b2e39d5e9231436aacf2a8ce91d Mon Sep 17 00:00:00 2001 From: Vodopyanov Egor Date: Thu, 5 Jan 2023 17:51:59 +0800 Subject: [PATCH 120/249] refactor: store pre-computed prefixes --- package.json | 4 ++- scripts/generate-tagged-hash-prefixes.js | 26 +++++++++++++++++++ src/crypto.d.ts | 3 +-- src/crypto.js | 27 +++++-------------- src/tagged-hash-prefixes.d.ts | 5 ++++ src/tagged-hash-prefixes.js | 23 +++++++++++++++++ src/tags.d.ts | 1 + src/tags.js | 14 ++++++++++ test/crypto.spec.ts | 20 ++++++++++++++ ts_src/crypto.ts | 33 +++++++----------------- ts_src/tagged-hash-prefixes.ts | 23 +++++++++++++++++ ts_src/tags.ts | 11 ++++++++ 12 files changed, 144 insertions(+), 46 deletions(-) create mode 100644 scripts/generate-tagged-hash-prefixes.js create mode 100644 src/tagged-hash-prefixes.d.ts create mode 100644 src/tagged-hash-prefixes.js create mode 100644 src/tags.d.ts create mode 100644 src/tags.js create mode 100644 ts_src/tagged-hash-prefixes.ts create mode 100644 ts_src/tags.ts diff --git a/package.json b/package.json index b973d3d3f..771af161b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "gitdiff:ci": "npm run build && git diff --exit-code", "integration": "npm run build && npm run nobuild:integration", "lint": "eslint ts_src/** src/**/*.js", + "lint:fix": "eslint --fix ts_src/** src/**/*.js", "lint:tests": "eslint test/**/*.spec.ts", "mocha:ts": "mocha --recursive --require test/ts-node-register", "nobuild:coverage-report": "nyc report --reporter=lcov", @@ -39,7 +40,8 @@ "prettier": "prettier \"ts_src/**/*.ts\" \"test/**/*.ts\" --ignore-path ./.prettierignore", "prettierjs": "prettier \"src/**/*.js\" --ignore-path ./.prettierignore", "test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", - "unit": "npm run build && npm run nobuild:unit" + "unit": "npm run build && npm run nobuild:unit", + "generate:prefixes": "node scripts/generate-tagged-hash-prefixes.js && yarn prettierjs && yarn lint:fix" }, "repository": { "type": "git", diff --git a/scripts/generate-tagged-hash-prefixes.js b/scripts/generate-tagged-hash-prefixes.js new file mode 100644 index 000000000..e582f5014 --- /dev/null +++ b/scripts/generate-tagged-hash-prefixes.js @@ -0,0 +1,26 @@ +const { sha256 } = require('../src/crypto'); +const { TAGS } = require('../src/tags'); +const fs = require('fs'); +const path = require('path'); + +const taggedHashPrefixes = Object.fromEntries( + TAGS.map(tag => { + const tagHash = sha256(Buffer.from(tag)); + return [tag, Buffer.concat([tagHash, tagHash]).toString('hex')]; + }), +); + +let content = ` +interface StringMap { + [key: string]: string; +} +export const TAGGED_HASH_PREFIXES_HEX: StringMap = ${JSON.stringify( + taggedHashPrefixes, +)} +`; + +fs.writeFileSync( + path.resolve(__dirname, '../ts_src', 'tagged-hash-prefixes.ts'), + content, + { encoding: 'utf8', flag: 'w' }, +); diff --git a/src/crypto.d.ts b/src/crypto.d.ts index 4d88c2f39..e3cf463a9 100644 --- a/src/crypto.d.ts +++ b/src/crypto.d.ts @@ -1,10 +1,9 @@ /// +import { TAGS } from './tags'; export declare function ripemd160(buffer: Buffer): Buffer; export declare function sha1(buffer: Buffer): Buffer; export declare function sha256(buffer: Buffer): Buffer; export declare function hash160(buffer: Buffer): Buffer; export declare function hash256(buffer: Buffer): Buffer; -declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"]; export type TaggedHashPrefix = typeof TAGS[number]; export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer; -export {}; diff --git a/src/crypto.js b/src/crypto.js index 9a3c1a157..783a22900 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -9,6 +9,7 @@ exports.taggedHash = void 0; const createHash = require('create-hash'); const RipeMd160 = require('ripemd160'); +const tagged_hash_prefixes_1 = require('./tagged-hash-prefixes'); function ripemd160(buffer) { try { return createHash('rmd160').update(buffer).digest(); @@ -37,28 +38,14 @@ function hash256(buffer) { return sha256(sha256(buffer)); } exports.hash256 = hash256; -const TAGS = [ - 'BIP0340/challenge', - 'BIP0340/aux', - 'BIP0340/nonce', - 'TapLeaf', - 'TapBranch', - 'TapSighash', - 'TapTweak', - 'KeyAgg list', - 'KeyAgg coefficient', -]; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ -let TAGGED_HASH_PREFIXES = undefined; +const TAGGED_HASH_PREFIXES = Object.fromEntries( + Object.keys(tagged_hash_prefixes_1.TAGGED_HASH_PREFIXES_HEX).map(tag => [ + tag, + Buffer.from(tagged_hash_prefixes_1.TAGGED_HASH_PREFIXES_HEX[tag], 'hex'), + ]), +); function taggedHash(prefix, data) { - if (!TAGGED_HASH_PREFIXES) { - TAGGED_HASH_PREFIXES = Object.fromEntries( - TAGS.map(tag => { - const tagHash = sha256(Buffer.from(tag)); - return [tag, Buffer.concat([tagHash, tagHash])]; - }), - ); - } return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data])); } exports.taggedHash = taggedHash; diff --git a/src/tagged-hash-prefixes.d.ts b/src/tagged-hash-prefixes.d.ts new file mode 100644 index 000000000..c74a8da53 --- /dev/null +++ b/src/tagged-hash-prefixes.d.ts @@ -0,0 +1,5 @@ +interface StringMap { + [key: string]: string; +} +export declare const TAGGED_HASH_PREFIXES_HEX: StringMap; +export {}; diff --git a/src/tagged-hash-prefixes.js b/src/tagged-hash-prefixes.js new file mode 100644 index 000000000..1ab6f8704 --- /dev/null +++ b/src/tagged-hash-prefixes.js @@ -0,0 +1,23 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.TAGGED_HASH_PREFIXES_HEX = void 0; +exports.TAGGED_HASH_PREFIXES_HEX = { + 'BIP0340/challenge': + '7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', + 'BIP0340/aux': + 'f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90', + 'BIP0340/nonce': + '07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f', + TapLeaf: + 'aeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9eeaeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9ee', + TapBranch: + '1941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a0151941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a015', + TapSighash: + 'f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031', + TapTweak: + 'e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9', + 'KeyAgg list': + '481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0', + 'KeyAgg coefficient': + 'bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981', +}; diff --git a/src/tags.d.ts b/src/tags.d.ts new file mode 100644 index 000000000..a6138f822 --- /dev/null +++ b/src/tags.d.ts @@ -0,0 +1 @@ +export declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"]; diff --git a/src/tags.js b/src/tags.js new file mode 100644 index 000000000..96e529ec7 --- /dev/null +++ b/src/tags.js @@ -0,0 +1,14 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.TAGS = void 0; +exports.TAGS = [ + 'BIP0340/challenge', + 'BIP0340/aux', + 'BIP0340/nonce', + 'TapLeaf', + 'TapBranch', + 'TapSighash', + 'TapTweak', + 'KeyAgg list', + 'KeyAgg coefficient', +]; diff --git a/test/crypto.spec.ts b/test/crypto.spec.ts index 0482ec9ec..6f3a21f00 100644 --- a/test/crypto.spec.ts +++ b/test/crypto.spec.ts @@ -2,6 +2,9 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; import { crypto as bcrypto, TaggedHashPrefix } from '..'; import * as fixtures from './fixtures/crypto.json'; +import { sha256 } from '../src/crypto'; +import { TAGS } from '../src/tags'; +import { TAGGED_HASH_PREFIXES_HEX } from '../src/tagged-hash-prefixes'; describe('crypto', () => { ['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => { @@ -30,4 +33,21 @@ describe('crypto', () => { }); }); }); + + describe('TAGGED_HASH_PREFIXES', () => { + const taggedHashPrefixes = Object.fromEntries( + TAGS.map(tag => { + const tagHash = sha256(Buffer.from(tag)); + return [tag, Buffer.concat([tagHash, tagHash])]; + }), + ); + it('stored the result of operation', () => { + Object.keys(taggedHashPrefixes).forEach(tag => { + assert.strictEqual( + TAGGED_HASH_PREFIXES_HEX[tag], + taggedHashPrefixes[tag].toString('hex'), + ); + }); + }); + }); }); diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index 47ac48313..ba85b260b 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,6 +1,7 @@ import * as createHash from 'create-hash'; import * as RipeMd160 from 'ripemd160'; - +import { TAGGED_HASH_PREFIXES_HEX } from './tagged-hash-prefixes'; +import { TAGS } from './tags'; export function ripemd160(buffer: Buffer): Buffer { try { return createHash('rmd160').update(buffer).digest(); @@ -29,31 +30,17 @@ export function hash256(buffer: Buffer): Buffer { return sha256(sha256(buffer)); } -const TAGS = [ - 'BIP0340/challenge', - 'BIP0340/aux', - 'BIP0340/nonce', - 'TapLeaf', - 'TapBranch', - 'TapSighash', - 'TapTweak', - 'KeyAgg list', - 'KeyAgg coefficient', -] as const; export type TaggedHashPrefix = typeof TAGS[number]; + /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ -let TAGGED_HASH_PREFIXES = undefined as - | { [k in TaggedHashPrefix]: Buffer } - | undefined; + +const TAGGED_HASH_PREFIXES = Object.fromEntries( + Object.keys(TAGGED_HASH_PREFIXES_HEX).map((tag: string) => [ + tag, + Buffer.from(TAGGED_HASH_PREFIXES_HEX[tag], 'hex'), + ]), +) as { [k in TaggedHashPrefix]: Buffer }; export function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer { - if (!TAGGED_HASH_PREFIXES) { - TAGGED_HASH_PREFIXES = Object.fromEntries( - TAGS.map(tag => { - const tagHash = sha256(Buffer.from(tag)); - return [tag, Buffer.concat([tagHash, tagHash])]; - }), - ) as { [k in TaggedHashPrefix]: Buffer }; - } return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data])); } diff --git a/ts_src/tagged-hash-prefixes.ts b/ts_src/tagged-hash-prefixes.ts new file mode 100644 index 000000000..4f800cbc3 --- /dev/null +++ b/ts_src/tagged-hash-prefixes.ts @@ -0,0 +1,23 @@ +interface StringMap { + [key: string]: string; +} +export const TAGGED_HASH_PREFIXES_HEX: StringMap = { + 'BIP0340/challenge': + '7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', + 'BIP0340/aux': + 'f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90', + 'BIP0340/nonce': + '07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f', + TapLeaf: + 'aeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9eeaeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9ee', + TapBranch: + '1941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a0151941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a015', + TapSighash: + 'f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031', + TapTweak: + 'e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9', + 'KeyAgg list': + '481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0', + 'KeyAgg coefficient': + 'bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981', +}; diff --git a/ts_src/tags.ts b/ts_src/tags.ts new file mode 100644 index 000000000..5480c2818 --- /dev/null +++ b/ts_src/tags.ts @@ -0,0 +1,11 @@ +export const TAGS = [ + 'BIP0340/challenge', + 'BIP0340/aux', + 'BIP0340/nonce', + 'TapLeaf', + 'TapBranch', + 'TapSighash', + 'TapTweak', + 'KeyAgg list', + 'KeyAgg coefficient', +] as const; From c462e1dbdd5c4cf30ac0e299037903ee64592b97 Mon Sep 17 00:00:00 2001 From: Vodopyanov Egor Date: Mon, 9 Jan 2023 14:03:40 +0800 Subject: [PATCH 121/249] refactor: hardcoded prefixes --- package.json | 4 +- scripts/generate-tagged-hash-prefixes.js | 26 -------- src/crypto.d.ts | 7 ++- src/crypto.js | 76 ++++++++++++++++++++--- src/tagged-hash-prefixes.d.ts | 5 -- src/tagged-hash-prefixes.js | 23 ------- src/tags.d.ts | 1 - src/tags.js | 14 ----- test/crypto.spec.ts | 16 ++--- ts_src/crypto.ts | 79 +++++++++++++++++++++--- ts_src/tagged-hash-prefixes.ts | 23 ------- ts_src/tags.ts | 11 ---- 12 files changed, 152 insertions(+), 133 deletions(-) delete mode 100644 scripts/generate-tagged-hash-prefixes.js delete mode 100644 src/tagged-hash-prefixes.d.ts delete mode 100644 src/tagged-hash-prefixes.js delete mode 100644 src/tags.d.ts delete mode 100644 src/tags.js delete mode 100644 ts_src/tagged-hash-prefixes.ts delete mode 100644 ts_src/tags.ts diff --git a/package.json b/package.json index 771af161b..b973d3d3f 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "gitdiff:ci": "npm run build && git diff --exit-code", "integration": "npm run build && npm run nobuild:integration", "lint": "eslint ts_src/** src/**/*.js", - "lint:fix": "eslint --fix ts_src/** src/**/*.js", "lint:tests": "eslint test/**/*.spec.ts", "mocha:ts": "mocha --recursive --require test/ts-node-register", "nobuild:coverage-report": "nyc report --reporter=lcov", @@ -40,8 +39,7 @@ "prettier": "prettier \"ts_src/**/*.ts\" \"test/**/*.ts\" --ignore-path ./.prettierignore", "prettierjs": "prettier \"src/**/*.js\" --ignore-path ./.prettierignore", "test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", - "unit": "npm run build && npm run nobuild:unit", - "generate:prefixes": "node scripts/generate-tagged-hash-prefixes.js && yarn prettierjs && yarn lint:fix" + "unit": "npm run build && npm run nobuild:unit" }, "repository": { "type": "git", diff --git a/scripts/generate-tagged-hash-prefixes.js b/scripts/generate-tagged-hash-prefixes.js deleted file mode 100644 index e582f5014..000000000 --- a/scripts/generate-tagged-hash-prefixes.js +++ /dev/null @@ -1,26 +0,0 @@ -const { sha256 } = require('../src/crypto'); -const { TAGS } = require('../src/tags'); -const fs = require('fs'); -const path = require('path'); - -const taggedHashPrefixes = Object.fromEntries( - TAGS.map(tag => { - const tagHash = sha256(Buffer.from(tag)); - return [tag, Buffer.concat([tagHash, tagHash]).toString('hex')]; - }), -); - -let content = ` -interface StringMap { - [key: string]: string; -} -export const TAGGED_HASH_PREFIXES_HEX: StringMap = ${JSON.stringify( - taggedHashPrefixes, -)} -`; - -fs.writeFileSync( - path.resolve(__dirname, '../ts_src', 'tagged-hash-prefixes.ts'), - content, - { encoding: 'utf8', flag: 'w' }, -); diff --git a/src/crypto.d.ts b/src/crypto.d.ts index e3cf463a9..770d3bbb5 100644 --- a/src/crypto.d.ts +++ b/src/crypto.d.ts @@ -1,9 +1,14 @@ /// -import { TAGS } from './tags'; export declare function ripemd160(buffer: Buffer): Buffer; export declare function sha1(buffer: Buffer): Buffer; export declare function sha256(buffer: Buffer): Buffer; export declare function hash160(buffer: Buffer): Buffer; export declare function hash256(buffer: Buffer): Buffer; +export declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"]; export type TaggedHashPrefix = typeof TAGS[number]; +export type TaggedHashPrefixes = { + [key in TaggedHashPrefix]: Buffer; +}; +/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ +export declare const TAGGED_HASH_PREFIXES: TaggedHashPrefixes; export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer; diff --git a/src/crypto.js b/src/crypto.js index 783a22900..25f71bad2 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,6 +1,8 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.taggedHash = + exports.TAGGED_HASH_PREFIXES = + exports.TAGS = exports.hash256 = exports.hash160 = exports.sha256 = @@ -9,7 +11,6 @@ exports.taggedHash = void 0; const createHash = require('create-hash'); const RipeMd160 = require('ripemd160'); -const tagged_hash_prefixes_1 = require('./tagged-hash-prefixes'); function ripemd160(buffer) { try { return createHash('rmd160').update(buffer).digest(); @@ -38,14 +39,75 @@ function hash256(buffer) { return sha256(sha256(buffer)); } exports.hash256 = hash256; +exports.TAGS = [ + 'BIP0340/challenge', + 'BIP0340/aux', + 'BIP0340/nonce', + 'TapLeaf', + 'TapBranch', + 'TapSighash', + 'TapTweak', + 'KeyAgg list', + 'KeyAgg coefficient', +]; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ -const TAGGED_HASH_PREFIXES = Object.fromEntries( - Object.keys(tagged_hash_prefixes_1.TAGGED_HASH_PREFIXES_HEX).map(tag => [ - tag, - Buffer.from(tagged_hash_prefixes_1.TAGGED_HASH_PREFIXES_HEX[tag], 'hex'), +exports.TAGGED_HASH_PREFIXES = { + 'BIP0340/challenge': Buffer.from([ + 123, 181, 45, 122, 159, 239, 88, 50, 62, 177, 191, 122, 64, 125, 179, 130, + 210, 243, 242, 216, 27, 177, 34, 79, 73, 254, 81, 143, 109, 72, 211, 124, + 123, 181, 45, 122, 159, 239, 88, 50, 62, 177, 191, 122, 64, 125, 179, 130, + 210, 243, 242, 216, 27, 177, 34, 79, 73, 254, 81, 143, 109, 72, 211, 124, ]), -); + 'BIP0340/aux': Buffer.from([ + 241, 239, 78, 94, 192, 99, 202, 218, 109, 148, 202, 250, 157, 152, 126, 160, + 105, 38, 88, 57, 236, 193, 31, 151, 45, 119, 165, 46, 216, 193, 204, 144, + 241, 239, 78, 94, 192, 99, 202, 218, 109, 148, 202, 250, 157, 152, 126, 160, + 105, 38, 88, 57, 236, 193, 31, 151, 45, 119, 165, 46, 216, 193, 204, 144, + ]), + 'BIP0340/nonce': Buffer.from([ + 7, 73, 119, 52, 167, 155, 203, 53, 91, 155, 140, 125, 3, 79, 18, 28, 244, + 52, 215, 62, 247, 45, 218, 25, 135, 0, 97, 251, 82, 191, 235, 47, 7, 73, + 119, 52, 167, 155, 203, 53, 91, 155, 140, 125, 3, 79, 18, 28, 244, 52, 215, + 62, 247, 45, 218, 25, 135, 0, 97, 251, 82, 191, 235, 47, + ]), + TapLeaf: Buffer.from([ + 174, 234, 143, 220, 66, 8, 152, 49, 5, 115, 75, 88, 8, 29, 30, 38, 56, 211, + 95, 28, 181, 64, 8, 212, 211, 87, 202, 3, 190, 120, 233, 238, 174, 234, 143, + 220, 66, 8, 152, 49, 5, 115, 75, 88, 8, 29, 30, 38, 56, 211, 95, 28, 181, + 64, 8, 212, 211, 87, 202, 3, 190, 120, 233, 238, + ]), + TapBranch: Buffer.from([ + 25, 65, 161, 242, 229, 110, 185, 95, 162, 169, 241, 148, 190, 92, 1, 247, + 33, 111, 51, 237, 130, 176, 145, 70, 52, 144, 208, 91, 245, 22, 160, 21, 25, + 65, 161, 242, 229, 110, 185, 95, 162, 169, 241, 148, 190, 92, 1, 247, 33, + 111, 51, 237, 130, 176, 145, 70, 52, 144, 208, 91, 245, 22, 160, 21, + ]), + TapSighash: Buffer.from([ + 244, 10, 72, 223, 75, 42, 112, 200, 180, 146, 75, 242, 101, 70, 97, 237, 61, + 149, 253, 102, 163, 19, 235, 135, 35, 117, 151, 198, 40, 228, 160, 49, 244, + 10, 72, 223, 75, 42, 112, 200, 180, 146, 75, 242, 101, 70, 97, 237, 61, 149, + 253, 102, 163, 19, 235, 135, 35, 117, 151, 198, 40, 228, 160, 49, + ]), + TapTweak: Buffer.from([ + 232, 15, 225, 99, 156, 156, 160, 80, 227, 175, 27, 57, 193, 67, 198, 62, 66, + 156, 188, 235, 21, 217, 64, 251, 181, 197, 161, 244, 175, 87, 197, 233, 232, + 15, 225, 99, 156, 156, 160, 80, 227, 175, 27, 57, 193, 67, 198, 62, 66, 156, + 188, 235, 21, 217, 64, 251, 181, 197, 161, 244, 175, 87, 197, 233, + ]), + 'KeyAgg list': Buffer.from([ + 72, 28, 151, 28, 60, 11, 70, 215, 240, 178, 117, 174, 89, 141, 78, 44, 126, + 215, 49, 156, 89, 74, 92, 110, 199, 158, 160, 212, 153, 2, 148, 240, 72, 28, + 151, 28, 60, 11, 70, 215, 240, 178, 117, 174, 89, 141, 78, 44, 126, 215, 49, + 156, 89, 74, 92, 110, 199, 158, 160, 212, 153, 2, 148, 240, + ]), + 'KeyAgg coefficient': Buffer.from([ + 191, 201, 4, 3, 77, 28, 136, 232, 200, 14, 34, 229, 61, 36, 86, 109, 100, + 130, 78, 214, 66, 114, 129, 192, 145, 0, 249, 77, 205, 82, 201, 129, 191, + 201, 4, 3, 77, 28, 136, 232, 200, 14, 34, 229, 61, 36, 86, 109, 100, 130, + 78, 214, 66, 114, 129, 192, 145, 0, 249, 77, 205, 82, 201, 129, + ]), +}; function taggedHash(prefix, data) { - return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data])); + return sha256(Buffer.concat([exports.TAGGED_HASH_PREFIXES[prefix], data])); } exports.taggedHash = taggedHash; diff --git a/src/tagged-hash-prefixes.d.ts b/src/tagged-hash-prefixes.d.ts deleted file mode 100644 index c74a8da53..000000000 --- a/src/tagged-hash-prefixes.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface StringMap { - [key: string]: string; -} -export declare const TAGGED_HASH_PREFIXES_HEX: StringMap; -export {}; diff --git a/src/tagged-hash-prefixes.js b/src/tagged-hash-prefixes.js deleted file mode 100644 index 1ab6f8704..000000000 --- a/src/tagged-hash-prefixes.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -exports.TAGGED_HASH_PREFIXES_HEX = void 0; -exports.TAGGED_HASH_PREFIXES_HEX = { - 'BIP0340/challenge': - '7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', - 'BIP0340/aux': - 'f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90', - 'BIP0340/nonce': - '07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f', - TapLeaf: - 'aeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9eeaeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9ee', - TapBranch: - '1941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a0151941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a015', - TapSighash: - 'f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031', - TapTweak: - 'e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9', - 'KeyAgg list': - '481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0', - 'KeyAgg coefficient': - 'bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981', -}; diff --git a/src/tags.d.ts b/src/tags.d.ts deleted file mode 100644 index a6138f822..000000000 --- a/src/tags.d.ts +++ /dev/null @@ -1 +0,0 @@ -export declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"]; diff --git a/src/tags.js b/src/tags.js deleted file mode 100644 index 96e529ec7..000000000 --- a/src/tags.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -exports.TAGS = void 0; -exports.TAGS = [ - 'BIP0340/challenge', - 'BIP0340/aux', - 'BIP0340/nonce', - 'TapLeaf', - 'TapBranch', - 'TapSighash', - 'TapTweak', - 'KeyAgg list', - 'KeyAgg coefficient', -]; diff --git a/test/crypto.spec.ts b/test/crypto.spec.ts index 6f3a21f00..b7273568f 100644 --- a/test/crypto.spec.ts +++ b/test/crypto.spec.ts @@ -2,9 +2,7 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; import { crypto as bcrypto, TaggedHashPrefix } from '..'; import * as fixtures from './fixtures/crypto.json'; -import { sha256 } from '../src/crypto'; -import { TAGS } from '../src/tags'; -import { TAGGED_HASH_PREFIXES_HEX } from '../src/tagged-hash-prefixes'; +import { sha256, TAGS, TAGGED_HASH_PREFIXES } from '../src/crypto'; describe('crypto', () => { ['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => { @@ -36,18 +34,16 @@ describe('crypto', () => { describe('TAGGED_HASH_PREFIXES', () => { const taggedHashPrefixes = Object.fromEntries( - TAGS.map(tag => { + TAGS.map((tag: TaggedHashPrefix) => { const tagHash = sha256(Buffer.from(tag)); return [tag, Buffer.concat([tagHash, tagHash])]; }), ); it('stored the result of operation', () => { - Object.keys(taggedHashPrefixes).forEach(tag => { - assert.strictEqual( - TAGGED_HASH_PREFIXES_HEX[tag], - taggedHashPrefixes[tag].toString('hex'), - ); - }); + assert.strictEqual( + JSON.stringify(TAGGED_HASH_PREFIXES), + JSON.stringify(taggedHashPrefixes), + ); }); }); }); diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index ba85b260b..686cdcd9e 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,7 +1,6 @@ import * as createHash from 'create-hash'; import * as RipeMd160 from 'ripemd160'; -import { TAGGED_HASH_PREFIXES_HEX } from './tagged-hash-prefixes'; -import { TAGS } from './tags'; + export function ripemd160(buffer: Buffer): Buffer { try { return createHash('rmd160').update(buffer).digest(); @@ -30,16 +29,78 @@ export function hash256(buffer: Buffer): Buffer { return sha256(sha256(buffer)); } +export const TAGS = [ + 'BIP0340/challenge', + 'BIP0340/aux', + 'BIP0340/nonce', + 'TapLeaf', + 'TapBranch', + 'TapSighash', + 'TapTweak', + 'KeyAgg list', + 'KeyAgg coefficient', +] as const; export type TaggedHashPrefix = typeof TAGS[number]; - +type TaggedHashPrefixes = { + [key in TaggedHashPrefix]: Buffer; +}; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ - -const TAGGED_HASH_PREFIXES = Object.fromEntries( - Object.keys(TAGGED_HASH_PREFIXES_HEX).map((tag: string) => [ - tag, - Buffer.from(TAGGED_HASH_PREFIXES_HEX[tag], 'hex'), +export const TAGGED_HASH_PREFIXES: TaggedHashPrefixes = { + 'BIP0340/challenge': Buffer.from([ + 123, 181, 45, 122, 159, 239, 88, 50, 62, 177, 191, 122, 64, 125, 179, 130, + 210, 243, 242, 216, 27, 177, 34, 79, 73, 254, 81, 143, 109, 72, 211, 124, + 123, 181, 45, 122, 159, 239, 88, 50, 62, 177, 191, 122, 64, 125, 179, 130, + 210, 243, 242, 216, 27, 177, 34, 79, 73, 254, 81, 143, 109, 72, 211, 124, + ]), + 'BIP0340/aux': Buffer.from([ + 241, 239, 78, 94, 192, 99, 202, 218, 109, 148, 202, 250, 157, 152, 126, 160, + 105, 38, 88, 57, 236, 193, 31, 151, 45, 119, 165, 46, 216, 193, 204, 144, + 241, 239, 78, 94, 192, 99, 202, 218, 109, 148, 202, 250, 157, 152, 126, 160, + 105, 38, 88, 57, 236, 193, 31, 151, 45, 119, 165, 46, 216, 193, 204, 144, + ]), + 'BIP0340/nonce': Buffer.from([ + 7, 73, 119, 52, 167, 155, 203, 53, 91, 155, 140, 125, 3, 79, 18, 28, 244, + 52, 215, 62, 247, 45, 218, 25, 135, 0, 97, 251, 82, 191, 235, 47, 7, 73, + 119, 52, 167, 155, 203, 53, 91, 155, 140, 125, 3, 79, 18, 28, 244, 52, 215, + 62, 247, 45, 218, 25, 135, 0, 97, 251, 82, 191, 235, 47, + ]), + TapLeaf: Buffer.from([ + 174, 234, 143, 220, 66, 8, 152, 49, 5, 115, 75, 88, 8, 29, 30, 38, 56, 211, + 95, 28, 181, 64, 8, 212, 211, 87, 202, 3, 190, 120, 233, 238, 174, 234, 143, + 220, 66, 8, 152, 49, 5, 115, 75, 88, 8, 29, 30, 38, 56, 211, 95, 28, 181, + 64, 8, 212, 211, 87, 202, 3, 190, 120, 233, 238, + ]), + TapBranch: Buffer.from([ + 25, 65, 161, 242, 229, 110, 185, 95, 162, 169, 241, 148, 190, 92, 1, 247, + 33, 111, 51, 237, 130, 176, 145, 70, 52, 144, 208, 91, 245, 22, 160, 21, 25, + 65, 161, 242, 229, 110, 185, 95, 162, 169, 241, 148, 190, 92, 1, 247, 33, + 111, 51, 237, 130, 176, 145, 70, 52, 144, 208, 91, 245, 22, 160, 21, + ]), + TapSighash: Buffer.from([ + 244, 10, 72, 223, 75, 42, 112, 200, 180, 146, 75, 242, 101, 70, 97, 237, 61, + 149, 253, 102, 163, 19, 235, 135, 35, 117, 151, 198, 40, 228, 160, 49, 244, + 10, 72, 223, 75, 42, 112, 200, 180, 146, 75, 242, 101, 70, 97, 237, 61, 149, + 253, 102, 163, 19, 235, 135, 35, 117, 151, 198, 40, 228, 160, 49, + ]), + TapTweak: Buffer.from([ + 232, 15, 225, 99, 156, 156, 160, 80, 227, 175, 27, 57, 193, 67, 198, 62, 66, + 156, 188, 235, 21, 217, 64, 251, 181, 197, 161, 244, 175, 87, 197, 233, 232, + 15, 225, 99, 156, 156, 160, 80, 227, 175, 27, 57, 193, 67, 198, 62, 66, 156, + 188, 235, 21, 217, 64, 251, 181, 197, 161, 244, 175, 87, 197, 233, + ]), + 'KeyAgg list': Buffer.from([ + 72, 28, 151, 28, 60, 11, 70, 215, 240, 178, 117, 174, 89, 141, 78, 44, 126, + 215, 49, 156, 89, 74, 92, 110, 199, 158, 160, 212, 153, 2, 148, 240, 72, 28, + 151, 28, 60, 11, 70, 215, 240, 178, 117, 174, 89, 141, 78, 44, 126, 215, 49, + 156, 89, 74, 92, 110, 199, 158, 160, 212, 153, 2, 148, 240, + ]), + 'KeyAgg coefficient': Buffer.from([ + 191, 201, 4, 3, 77, 28, 136, 232, 200, 14, 34, 229, 61, 36, 86, 109, 100, + 130, 78, 214, 66, 114, 129, 192, 145, 0, 249, 77, 205, 82, 201, 129, 191, + 201, 4, 3, 77, 28, 136, 232, 200, 14, 34, 229, 61, 36, 86, 109, 100, 130, + 78, 214, 66, 114, 129, 192, 145, 0, 249, 77, 205, 82, 201, 129, ]), -) as { [k in TaggedHashPrefix]: Buffer }; +}; export function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer { return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data])); diff --git a/ts_src/tagged-hash-prefixes.ts b/ts_src/tagged-hash-prefixes.ts deleted file mode 100644 index 4f800cbc3..000000000 --- a/ts_src/tagged-hash-prefixes.ts +++ /dev/null @@ -1,23 +0,0 @@ -interface StringMap { - [key: string]: string; -} -export const TAGGED_HASH_PREFIXES_HEX: StringMap = { - 'BIP0340/challenge': - '7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', - 'BIP0340/aux': - 'f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90', - 'BIP0340/nonce': - '07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f', - TapLeaf: - 'aeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9eeaeea8fdc4208983105734b58081d1e2638d35f1cb54008d4d357ca03be78e9ee', - TapBranch: - '1941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a0151941a1f2e56eb95fa2a9f194be5c01f7216f33ed82b091463490d05bf516a015', - TapSighash: - 'f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031', - TapTweak: - 'e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9', - 'KeyAgg list': - '481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0481c971c3c0b46d7f0b275ae598d4e2c7ed7319c594a5c6ec79ea0d4990294f0', - 'KeyAgg coefficient': - 'bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981bfc904034d1c88e8c80e22e53d24566d64824ed6427281c09100f94dcd52c981', -}; diff --git a/ts_src/tags.ts b/ts_src/tags.ts deleted file mode 100644 index 5480c2818..000000000 --- a/ts_src/tags.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const TAGS = [ - 'BIP0340/challenge', - 'BIP0340/aux', - 'BIP0340/nonce', - 'TapLeaf', - 'TapBranch', - 'TapSighash', - 'TapTweak', - 'KeyAgg list', - 'KeyAgg coefficient', -] as const; From 5707a04937c5774f83a2fc08696966e9119308ad Mon Sep 17 00:00:00 2001 From: Vodopyanov Egor Date: Thu, 19 Jan 2023 18:27:48 +0800 Subject: [PATCH 122/249] chore: upgrade json5 --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6de61ff9..1007efae0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2725,9 +2725,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -6437,9 +6437,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "levn": { From cc1b23e74ee52c362908b3e4ceda6a3ff95a6760 Mon Sep 17 00:00:00 2001 From: Vodopyanov Egor Date: Thu, 19 Jan 2023 18:28:47 +0800 Subject: [PATCH 123/249] fix: commit crypto.d.ts --- src/crypto.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crypto.d.ts b/src/crypto.d.ts index 770d3bbb5..1a465dad1 100644 --- a/src/crypto.d.ts +++ b/src/crypto.d.ts @@ -6,9 +6,10 @@ export declare function hash160(buffer: Buffer): Buffer; export declare function hash256(buffer: Buffer): Buffer; export declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"]; export type TaggedHashPrefix = typeof TAGS[number]; -export type TaggedHashPrefixes = { +type TaggedHashPrefixes = { [key in TaggedHashPrefix]: Buffer; }; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ export declare const TAGGED_HASH_PREFIXES: TaggedHashPrefixes; export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer; +export {}; From e3c16e6be31851b101b28aad6a6c29623d08d642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Landabaso=20D=C3=ADaz?= Date: Mon, 23 Jan 2023 10:16:49 +0100 Subject: [PATCH 124/249] This commit aims to prevent the creation of unspendable scripts in bitcoinjs-lib by implementing checks for resource limitations. Specifically, it addresses the following issues: * Scripts over 520 bytes are invalid by consensus (P2SH). * Scripts over 3600 bytes are invalid by standardness (P2WSH, P2SH-P2WSH). * Scripts where the total number of non-push opcodes plus the number of keys participating in all executed multis, is above 201, are invalid by consensus. However, it is important to note that this fix only checks for non-push opcodes and may not cover all cases of unspendable scripts, especially in some scenarios, such as a script with two spending branches, one using multi and another one not using it. It is the responsibility of the user to account for those cases. Read more: [https://bitcoin.sipa.be/miniscript/](https://bitcoin.sipa.be/miniscript/), section Resource limitations. See also: MAX_OPS_PER_SCRIPT, MAX_SCRIPT_ELEMENT_SIZE in [https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp](https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp) MAX_STANDARD_P2WSH_SCRIPT_SIZE in [https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp](https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp) --- src/payments/p2sh.js | 8 ++++++++ src/payments/p2wsh.js | 13 +++++++++++-- src/script.d.ts | 1 + src/script.js | 5 +++++ test/fixtures/p2sh.json | 8 ++++++++ test/fixtures/p2wsh.json | 16 ++++++++++++++++ ts_src/payments/p2sh.ts | 8 ++++++++ ts_src/payments/p2wsh.ts | 13 +++++++++++-- ts_src/script.ts | 4 ++++ 9 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 48edf304f..5061edc95 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -138,6 +138,14 @@ function p2sh(a, opts) { const decompile = bscript.decompile(redeem.output); if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short'); + if (redeem.output.byteLength > 520) + throw new TypeError( + 'Redeem.output unspendable if larger than 520 bytes', + ); + if (bscript.countNonPushOnlyOPs(decompile) > 201) + throw new TypeError( + 'Redeem.output unspendable with more than 201 non-push ops', + ); // match hash against other sources const hash2 = bcrypto.hash160(redeem.output); if (hash.length > 0 && !hash.equals(hash2)) diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index 66ee1da02..2ad9387e9 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -166,10 +166,19 @@ function p2wsh(a, opts) { a.redeem.witness.length > 0 ) throw new TypeError('Ambiguous witness source'); - // is the redeem output non-empty? + // is the redeem output non-empty/valid? if (a.redeem.output) { - if (bscript.decompile(a.redeem.output).length === 0) + const decompile = bscript.decompile(a.redeem.output); + if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output is invalid'); + if (a.redeem.output.byteLength > 3600) + throw new TypeError( + 'Redeem.output unspendable if larger than 3600 bytes', + ); + if (bscript.countNonPushOnlyOPs(decompile) > 201) + throw new TypeError( + 'Redeem.output unspendable with more than 201 non-push ops', + ); // match hash against other sources const hash2 = bcrypto.sha256(a.redeem.output); if (hash.length > 0 && !hash.equals(hash2)) diff --git a/src/script.d.ts b/src/script.d.ts index 261ecf4af..fd5964b89 100644 --- a/src/script.d.ts +++ b/src/script.d.ts @@ -5,6 +5,7 @@ import * as scriptNumber from './script_number'; import * as scriptSignature from './script_signature'; export { OPS }; export declare function isPushOnly(value: Stack): boolean; +export declare function countNonPushOnlyOPs(value: Stack): number; export declare function compile(chunks: Buffer | Stack): Buffer; export declare function decompile(buffer: Buffer | Array): Array | null; export declare function toASM(chunks: Buffer | Array): string; diff --git a/src/script.js b/src/script.js index 5dff149f3..6ed7ba20a 100644 --- a/src/script.js +++ b/src/script.js @@ -10,6 +10,7 @@ exports.signature = exports.toASM = exports.decompile = exports.compile = + exports.countNonPushOnlyOPs = exports.isPushOnly = exports.OPS = void 0; @@ -42,6 +43,10 @@ function isPushOnly(value) { return types.Array(value) && value.every(isPushOnlyChunk); } exports.isPushOnly = isPushOnly; +function countNonPushOnlyOPs(value) { + return value.length - value.filter(isPushOnlyChunk).length; +} +exports.countNonPushOnlyOPs = countNonPushOnlyOPs; function asMinimalOP(buffer) { if (buffer.length === 0) return ops_1.OPS.OP_0; if (buffer.length !== 1) return; diff --git a/test/fixtures/p2sh.json b/test/fixtures/p2sh.json index b222de52b..32f108c91 100644 --- a/test/fixtures/p2sh.json +++ b/test/fixtures/p2sh.json @@ -294,6 +294,14 @@ "input": "OP_0 OP_0" } }, + { + "exception": "Redeem.output unspendable if larger than 520 bytes", + "arguments": { + "redeem": { + "output": "OP_16 03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0 0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600 0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8 0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8 02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8 0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286 0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009 02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d 03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9 02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af 02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd 036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24 02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc 02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3 02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06 0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232 OP_16 OP_CHECKMULTISIG" + } + } + }, { "exception": "Input is invalid", "arguments": { diff --git a/test/fixtures/p2wsh.json b/test/fixtures/p2wsh.json index 03fb01d0f..effe4d148 100644 --- a/test/fixtures/p2wsh.json +++ b/test/fixtures/p2wsh.json @@ -264,6 +264,22 @@ } } }, + { + "exception": "Redeem.output unspendable with more than 201 non-push ops", + "arguments": { + "redeem": { + "output": "OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP OP_0 OP_0 OP_2DROP 02c56aef124aa09836590fb858ce3517bf9d4b27e9d3fc81c2b00845f98ef87d9e OP_CHECKSIG" + } + } + }, + { + "exception": "Redeem.output unspendable if larger than 3600 bytes", + "arguments": { + "redeem": { + "output": "02dfec67b1047f5ee7fcfc470565e07ce346fb89013f8e56b9e48a9af572f6cc9e OP_CHECKSIGVERIFY 034896ca85df092ac577480101193980c02b96a01533e7096858e696789c55b032 OP_CHECKSIGVERIFY 03365a5bd77cb355a5edefe0b47b64adf466d37c826c42332c8ea3bd2cb0fd2c0f OP_CHECKSIGVERIFY 0206c24e25bc5048bbb4723d701532db94366aa12bd080a8cf2cd35b41ea16b7e2 OP_CHECKSIGVERIFY 02e55fb5431a1c654cce06255befb4232600a94c4db57b4ab218a4ce04259c1145 OP_CHECKSIGVERIFY 02b4af93ac624e0f45e8577d828d0de343c595b9328958005baf6ab21a650ad39a OP_CHECKSIGVERIFY 03311744aa2a0d9b0af1c10f2eb4f4dd98b672fd61a575720298f334ebff3633fb OP_CHECKSIGVERIFY 0355b2ca92e66c250fafd45f3177b4a0fb48befc8b5911a37cdf3de5acc23b6e48 OP_CHECKSIGVERIFY 023e24b1d99ee02f08451af9e7e5d08db66db3e4ba04238bb4a484fc1bd66e4b89 OP_CHECKSIGVERIFY 03e0ece48ceb3c3bb566acbfda433662d2ce14f0b23bda0d0f065c5e0589ec8f1d OP_CHECKSIGVERIFY 02426aed062674081fe6e71ed130fc9b29329cef01204fe26b5ace638648b8d846 OP_CHECKSIGVERIFY 022960bc5bb274d8c963dbe6f77ef2cf59644f93f2dc3bf0d0036903656deaf0f3 OP_CHECKSIGVERIFY 03460e475689d3222710af5d3b8079bad3a12b248f61b300657b026c44c2b392b2 OP_CHECKSIGVERIFY 029d23671b573cbfb6272f5d197bbb88afa8fda9947affb5686c16a3e1f7592bb4 OP_CHECKSIGVERIFY 038bd4adeddfe560f8d9b1d1b7fc655c74fe605de09195c3f71e2fb8f68ecc4ca0 OP_CHECKSIGVERIFY 03a531c8795353c2a10f0e1619db468e5a87fd3303091662193c2701cfc1b1c4b0 OP_CHECKSIGVERIFY 02900a11b6b8ccc281dd7f1ab84b18358bfe2fef972967f0ca0ff1c71c5c72a8cb OP_CHECKSIGVERIFY 025e8d845e3f4d0d3afccb9c3a8368ccdcac14ff56b20706f534d7f81655715605 OP_CHECKSIGVERIFY 02646cce7f0159537815819139aad2e6d33f4398f8f502a0391491c7bf25bca4de OP_CHECKSIGVERIFY 03e852e9b853a26d7199a8772382bec56b9da7478b8037112c7d16d80d7c683568 OP_CHECKSIGVERIFY 0308d04709baf88bc6f5670d57c332ca872a8707207f3a5a59272940eb9ebc2936 OP_CHECKSIGVERIFY 0393cf1028583df1323f3b712edeb68078d412fabd1e5ccde839a500ddf506885a OP_CHECKSIGVERIFY 020edb22b296434cfe44b51f8dfc33579dd587e57a5f147470564ba9f817a586c8 OP_CHECKSIGVERIFY 0233de8260c26cba2851efc0ca2f695c37008929a2dc9244298c1990b36990083a OP_CHECKSIGVERIFY 02bcae3f6aecfd066ca029dfc833fc5f9f799760ab4baed6ef8c5ff94c002a7bc6 OP_CHECKSIGVERIFY 0285bb427b63bc8ed0890132d91a9283c7b82aa3f6d7fa7b3b27a74e7ffa41373c OP_CHECKSIGVERIFY 024e4c4f5ad562855256ba6380aba4bc93d7699939ace07c7c53fbecce044836c1 OP_CHECKSIGVERIFY 030895c63aebb5ce9e1ea2b3f30ebfd23f55fed7698757c1ec03154f1d581f0531 OP_CHECKSIGVERIFY 02070a7d5b57bf8c67ba34ed35108ba26579dbb84703c043886641e6482be0ae4a OP_CHECKSIGVERIFY 029c6ce3700ffe9b47b008c4cd849d4cd6ed1264eedebc0a4e1165dcbf5db503c6 OP_CHECKSIGVERIFY 02b1aa852b32f35e73a6aa070db64bc70329ec34f3ec13081b3859d45bb19e83b9 OP_CHECKSIGVERIFY 0375b9b9b287208cc57e49e15511f2c853bbb5562ed9fdd7b35e561d7e429018fc OP_CHECKSIGVERIFY 0355b3f1730e8eee06687bc128a86e3724dbe4da8a26fd3bfa54482ca25b247a99 OP_CHECKSIGVERIFY 03e9960ff1268abaff7190ba9f34f480b1279a6a52f96608313324c79214287bd1 OP_CHECKSIGVERIFY 036c8755595e5f3cfe45585237cbbec13737c9dab16ef90feca4b59cc20a3a5535 OP_CHECKSIGVERIFY 0228adcffece12cc99217f1d763aac469754fe3e49894a16a813443e29532e9651 OP_CHECKSIGVERIFY 03a0ea1dc9043e2f67450bb8ddccaa3af31964077a25b2baef62099fe0515fbd21 OP_CHECKSIGVERIFY 037de9ef6bee93474f6e02e377721543f8f4684e7074b45584fd751881aee96cf7 OP_CHECKSIGVERIFY 032874661d4c3802ad9913852a0271cb45cc90d981e662d831bd5290f2d6fe138d OP_CHECKSIGVERIFY 03e9242d1c7ed3ba76f8eba3e2fb4c5d33d667425222b95407de9209790e1b4bd3 OP_CHECKSIGVERIFY 02473773327552dc9f3652b513c68e133cc4d39202b348bbed359bc7b00f91bade OP_CHECKSIGVERIFY 03cebb99348541461de36106a68b0c7063bd4321021a21afbe90fa05d90a3444f0 OP_CHECKSIGVERIFY 03544a4accbdebb1bc0c62dfd3055dea9bd3c610c161a10afd53b4646d736a17db OP_CHECKSIGVERIFY 02baa91fc638c57fff9211334dc9c98b9937d75b7c534bdaeefcdeb56ca8f997e3 OP_CHECKSIGVERIFY 03bd8ea1948d869143f1fc0604ec8b54c95dd6cfc6cce11800bbe82b92ae9b5b74 OP_CHECKSIGVERIFY 0319ab36ae3727162c6699d6cfa76389a49d06a4bd4d4dad47606f8deec0376e75 OP_CHECKSIGVERIFY 0293003a1bca4ad83ac3bf5a2224ef782c8d7290f4f492e3588e644ffe02b3b35a OP_CHECKSIGVERIFY 0395248d8852d32e11f3288fa4f39a36fcc8225da9289fc3d0ad65f409821c4e17 OP_CHECKSIGVERIFY 02e3bee6809b08ab3b9fdbb4a7fcaa64cb602fdae173c6ce1c4042ca36f60e90fd OP_CHECKSIGVERIFY 034b74d1136c5422239d8c4a869e6cff7985204d9dedb2c3cca0b0b9afd589a35f OP_CHECKSIGVERIFY 0318e3f8941cf82c788d5dce82256bfe9522dd87f63a467e01c5004fec6667f0fe OP_CHECKSIGVERIFY 023fcda99ba2c461c65a3d95d03ab251d29ae9bd7b4d90a485aa6d075e35460e93 OP_CHECKSIGVERIFY 0329f7b047301f8f29725a9dd93e4742460e54b4c2ac9c08cfa9c4cd6a5e14a4ed OP_CHECKSIGVERIFY 0343a0c3f0fbbfd4ed0d1677c1a0cb3bb2a59dfcecdbc4af4386a6542cf349f437 OP_CHECKSIGVERIFY 02eae41bb436873f562629380ffdaf0fe5353cb5be07bd95343adf32ee984ef0ce OP_CHECKSIGVERIFY 02d71aa051ea95ba899d3e721f12a8ede4aebdd265a2ff31081128033bdd66befc OP_CHECKSIGVERIFY 029faa4411b3bc3a2778d6d3236396d2a81afefc792d07909e212de58e0765fa07 OP_CHECKSIGVERIFY 03eb3b7d5a44fcc754383ceca274a250b9a48a348ef166fad19981d5f37781d5dc OP_CHECKSIGVERIFY 03f3ccef99da37185f238f7643b7ff553e4f62fa376cf1852a49cd80cd856e444b OP_CHECKSIGVERIFY 03ba7b6f4ea978f9c5277c97d733ae0a2f55062555997971d6ed4b37d399b4f130 OP_CHECKSIGVERIFY 0217209004fa5e8b2277767caeac19a9c26c71655c917cf61aad6817f0717fcba0 OP_CHECKSIGVERIFY 0363f2e57f2ea1a49a39ed0542e1fdbc83e2d2317a2d29b058c7d82233f3c54e8a OP_CHECKSIGVERIFY 03dfd044cc08b325121055155e90f2955771f33f0e4d5462967dbeae431ac34e4b OP_CHECKSIGVERIFY 02aa29194b8666b1526b32e7b54cebe8305637837c7821399577ee95cf7bca6a36 OP_CHECKSIGVERIFY 02e11f095aa2730bfa6ab6a6d088d015e92204a70ad36c5fe6a6a97cf4be8fdc34 OP_CHECKSIGVERIFY 02d8a0b231ca61468d86abd8bf5169bd231243a4f75184f64a6946abb8eee17b9b OP_CHECKSIGVERIFY 034653d87fbdc8cff324a5f6e3220150749cff56f39f1bb378ae73301c15caca05 OP_CHECKSIGVERIFY 02b0e8830c020a01c31d8d211708ad1d04e7309bb584b8489d35fbcf12c09c84eb OP_CHECKSIGVERIFY 030b94559a4b54fc895f861bb2b5669736a709994ade45b08f9f6148ef25dc4eca OP_CHECKSIGVERIFY 02c1d4e3bd6651e273030a6d1f436a2455fa51b50390ba045c5e78bbef03eeb78f OP_CHECKSIGVERIFY 02b7d7f60abf61f50bcbaa0435f9a31a231b8f26def0f172578d0838091f7906eb OP_CHECKSIGVERIFY 02d410fbd6d899b21c3f6d380aeafea56a2e4e0e3b70f46734a716134fee793054 OP_CHECKSIGVERIFY 02bac3d71349f3dd83bc59c15c661d1520f43475f5fc0c38698b37de978419248b OP_CHECKSIGVERIFY 0277b4f24cc552577374909a853c1d570404e9072368d48310ed3ee597c9579e9d OP_CHECKSIGVERIFY 037ab3bd19731038c03bf4eec27e4e4788a8634b52c41e364dea45130b3ac8874b OP_CHECKSIGVERIFY 03102af5eb78a65b542b878e4e3eaa29f83f2efdae8838afe75cf2f85017b3f683 OP_CHECKSIGVERIFY 02452e8e88d61c0b206b9e4a319dc262662f4f98a5737e13b7abe2ceeae2bb8dcf OP_CHECKSIGVERIFY 0360d3d51bef5d6c5932e61b2ab44c7eea23a2d03cfed2422656ed1c873286a8e1 OP_CHECKSIGVERIFY 021eadd72a30b0ff9c2bc0f0b2815352fc789b85bf958ba36bde39092967968094 OP_CHECKSIGVERIFY 02631804ca57f694c27885d2d8c5e31e1f3b741a23d8c2e95a1ccd9ac8392c3c25 OP_CHECKSIGVERIFY 021ec0824b1594998ff91ddd2a7b53099ba60de84d447b13fddebf34533d111a3e OP_CHECKSIGVERIFY 039b386361ac56bd2b73186b66b77cb6de2fffbe05326dd2abf23d6b9d26b629ea OP_CHECKSIGVERIFY 0262efb44256f6ae1f1965aa24b57847e13f80a4283cb44a257572993845439a0e OP_CHECKSIGVERIFY 0283f818f966acc4894b54197eb7e94221851acfb6c986a7862ad86f961ab50a57 OP_CHECKSIGVERIFY 03a418b8eb7da20d694e257de6a9fc493676e1f98fef9f8165f69cd8d0f8099439 OP_CHECKSIGVERIFY 03697922030495b328ea3c101a1852ffd7818aa10fbedaf6c3a88b6fa381694d4b OP_CHECKSIGVERIFY 0266a13940d79ce8a272da6094df36875fd9c337c83d4ef491e0fa2a47ab3ffc57 OP_CHECKSIGVERIFY 03747ae0c8012281475a00085795b8bb0d595ebf2322f57ebd7c6b1edbbab56f92 OP_CHECKSIGVERIFY 02e357b1c82274aea4357e36278f912338e3adf1c17d8829edf394a1cc2bedc185 OP_CHECKSIGVERIFY 02dcc75706face7293b47b2e60af164971f9920118986ebf5ea147d077722e316f OP_CHECKSIGVERIFY 0347cdc7f774f1291bc45c7cb1d86eb7ea66a24622e387ccc7ac580b750f1ad9c3 OP_CHECKSIGVERIFY 02e650efb11ecd8ab320b3e5bec0d5852db1cddc1d67ede952f3c042f90dae1e43 OP_CHECKSIGVERIFY 039c24916f37a11398d4186a634414ea371003595e6444c92d01ace02bab5267ea OP_CHECKSIGVERIFY 038a5dbb9595503741fafe92f60187bfb2aad442dda5f9e6c17e5be6ec4b5521aa OP_CHECKSIGVERIFY 0291909a41d7114fc99e81224e37c030efc0c73b33e6b0eedcd8a53bfee276f108 OP_CHECKSIGVERIFY 02da51f2cc61ba9522869d2f9a34a21288cc8d017e4cf0060c85f2fff3ceacaf28 OP_CHECKSIGVERIFY 03237812d3a5ec0c49d8a60b15c2ac676cffc7eabf459dc9ef0ae215a1cbd26bb7 OP_CHECKSIGVERIFY 020e33b6b74b7f7190b0966b4f295199244ed33cea6e324c2f39a227486d87e059 OP_CHECKSIGVERIFY 02d2c96fd98d5da619ef2033892026b7f7595fd03667ab1f0b39a59d2e45a58c98 OP_CHECKSIGVERIFY 03df7ed4e582c8997b3a4c1138fac76144c02a052c7ac2c40e4e34d50e308efb42 OP_CHECKSIGVERIFY 028f343af70f46773a48bd8fcbeeb34a15f3cfee8427b62750006da2ae8a0745fc OP_CHECKSIGVERIFY 027e2f0c4f3d8d90ea0b426c82a6d41226e7b7c579724302034f844fde9a34bf96 OP_CHECKSIGVERIFY 03abdfa8a173a9be3a2669346430dcf5cbf5e2bb2321392bd661b9fa88be82677b OP_CHECKSIG" + } + } + }, { "exception": "Non push-only scriptSig", "arguments": { diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index 272d094a3..67e5f38dd 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -159,6 +159,14 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { const decompile = bscript.decompile(redeem.output); if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short'); + if (redeem.output.byteLength > 520) + throw new TypeError( + 'Redeem.output unspendable if larger than 520 bytes', + ); + if (bscript.countNonPushOnlyOPs(decompile) > 201) + throw new TypeError( + 'Redeem.output unspendable with more than 201 non-push ops', + ); // match hash against other sources const hash2 = bcrypto.hash160(redeem.output); diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index 00860e0b9..1ba384924 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -180,10 +180,19 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { ) throw new TypeError('Ambiguous witness source'); - // is the redeem output non-empty? + // is the redeem output non-empty/valid? if (a.redeem.output) { - if (bscript.decompile(a.redeem.output)!.length === 0) + const decompile = bscript.decompile(a.redeem.output); + if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output is invalid'); + if (a.redeem.output.byteLength > 3600) + throw new TypeError( + 'Redeem.output unspendable if larger than 3600 bytes', + ); + if (bscript.countNonPushOnlyOPs(decompile) > 201) + throw new TypeError( + 'Redeem.output unspendable with more than 201 non-push ops', + ); // match hash against other sources const hash2 = bcrypto.sha256(a.redeem.output); diff --git a/ts_src/script.ts b/ts_src/script.ts index ca2c557f5..4f246fe7a 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -27,6 +27,10 @@ export function isPushOnly(value: Stack): boolean { return types.Array(value) && value.every(isPushOnlyChunk); } +export function countNonPushOnlyOPs(value: Stack): number { + return value.length - value.filter(isPushOnlyChunk).length; +} + function asMinimalOP(buffer: Buffer): number | void { if (buffer.length === 0) return OPS.OP_0; if (buffer.length !== 1) return; From 8bb53b40b2a22e6af400a7c4da8c610965690357 Mon Sep 17 00:00:00 2001 From: Hyunhum Cho Date: Fri, 3 Feb 2023 14:58:55 +0900 Subject: [PATCH 125/249] Add new Taproot example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a8de46778..e1d7af69f 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ Otherwise, pull requests are appreciated. Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). +- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts) - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) From 35634ceee62d3523620232c106f6096fc58182cf Mon Sep 17 00:00:00 2001 From: Hyunhum Cho Date: Thu, 9 Feb 2023 11:41:36 +0900 Subject: [PATCH 126/249] "feat: remove privateAdd & privateNegate for tinysecp256k1" --- ts_src/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/ts_src/types.ts b/ts_src/types.ts index d4f4ac774..2457ec240 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -105,8 +105,6 @@ 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 ac3f01b773a58885f0041d56ba4327b0ac4ca098 Mon Sep 17 00:00:00 2001 From: Hyunhum Cho Date: Thu, 9 Feb 2023 11:43:37 +0900 Subject: [PATCH 127/249] "feat: remove privateAdd & privateNegate in tinysecp256k1 declaration" --- src/types.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types.d.ts b/src/types.d.ts index 035d73efc..0b8a02f9c 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -30,8 +30,6 @@ export declare function isTaptree(scriptTree: any): scriptTree is Taptree; 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; From f1a4b9d55526c756da99381b01581a0e114ec633 Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 14 Feb 2023 18:10:03 -0700 Subject: [PATCH 128/249] Add example using BIP86 vector to verify the sending to and from a BIP86 generated taproot address --- test/integration/taproot.spec.ts | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index ec870b761..550a34608 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -1,4 +1,6 @@ +import * as assert from 'assert'; import BIP32Factory from 'bip32'; +import * as bip39 from 'bip39'; import ECPairFactory from 'ecpair'; import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; @@ -17,6 +19,78 @@ const bip32 = BIP32Factory(ecc); const ECPair = ECPairFactory(ecc); describe('bitcoinjs-lib (transaction with taproot)', () => { + it('can verify the BIP86 HD wallet vectors for taproot single sig (& sending example)', async () => { + // Values taken from BIP86 document + const mnemonic = + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; + const xprv = + 'xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu'; + const path = `m/86'/0'/0'/0/0`; // Path to first child of receiving wallet on first account + const internalPubkey = Buffer.from( + 'cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115', + 'hex', + ); + const expectedAddress = + 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr'; + + // Verify the above (Below is no different than other HD wallets) + const seed = await bip39.mnemonicToSeed(mnemonic); + const rootKey = bip32.fromSeed(seed); + assert.strictEqual(rootKey.toBase58(), xprv); + const childNode = rootKey.derivePath(path); + // Since internalKey is an xOnly pubkey, we drop the DER header byte + const childNodeXOnlyPubkey = childNode.publicKey.slice(1, 33); + assert.deepEqual(childNodeXOnlyPubkey, internalPubkey); + + // This is new for taproot + // Note: we are using mainnet here to get the correct address + // The output is the same no matter what the network is. + const { address, output } = bitcoin.payments.p2tr({ + internalPubkey, + }); + assert(output); + assert.strictEqual(address, expectedAddress); + // Used for signing, since the output and address are using a tweaked key + // We must tweak the signer in the same way. + const tweakedChildNode = childNode.tweak( + bitcoin.crypto.taggedHash('TapTweak', childNodeXOnlyPubkey), + ); + + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // Send some sats to the address via faucet. Get the hash and index. (txid/vout) + const { txId: hash, vout: index } = await regtestUtils.faucetComplex( + output, + amount, + ); + // Sent 420000 sats to taproot address + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput({ + hash, + index, + witnessUtxo: { value: amount, script: output }, + tapInternalKey: childNodeXOnlyPubkey, + }) + .addOutput({ + value: sendAmount, + address: regtestUtils.RANDOM_ADDRESS, + }) + .signInput(0, tweakedChildNode) + .finalizeAllInputs(); + + const tx = psbt.extractTransaction(); + await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: sendAmount, + }); + }); + it('can create (and broadcast via 3PBP) a taproot key-path spend Transaction', async () => { const internalKey = bip32.fromSeed(rng(64), regtest); const p2pkhKey = bip32.fromSeed(rng(64), regtest); From 8c504c423ba81c16afaaf236a103318e6b3adc5b Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Fri, 17 Feb 2023 20:43:17 +0000 Subject: [PATCH 129/249] Switch from create-hash, ripemd160 to noble-hashes --- package-lock.json | 57 ++++++++++++++--------------------------------- package.json | 5 +---- src/crypto.js | 27 ++++++++++------------ ts_src/crypto.ts | 23 +++++++------------ 4 files changed, 38 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1007efae0..0ce902e7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,10 @@ "version": "6.1.0", "license": "MIT", "dependencies": { + "@noble/hashes": "^1.2.0", "bech32": "^2.0.0", "bip174": "^2.1.0", "bs58check": "^2.1.2", - "create-hash": "^1.1.0", - "ripemd160": "^2.0.2", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" @@ -21,12 +20,10 @@ "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/ripemd160": "^2.0.0", "@types/wif": "^2.0.2", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", @@ -682,6 +679,17 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -735,15 +743,6 @@ "@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/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -777,15 +776,6 @@ "@types/node": "*" } }, - "node_modules/@types/ripemd160": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", - "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -4934,6 +4924,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4978,15 +4973,6 @@ "@types/node": "*" } }, - "@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, - "requires": { - "@types/node": "*" - } - }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -5020,15 +5006,6 @@ "@types/node": "*" } }, - "@types/ripemd160": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", - "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", diff --git a/package.json b/package.json index b973d3d3f..6194306c3 100644 --- a/package.json +++ b/package.json @@ -49,11 +49,10 @@ "src" ], "dependencies": { + "@noble/hashes": "^1.2.0", "bech32": "^2.0.0", "bip174": "^2.1.0", "bs58check": "^2.1.2", - "create-hash": "^1.1.0", - "ripemd160": "^2.0.2", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" @@ -61,12 +60,10 @@ "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/ripemd160": "^2.0.0", "@types/wif": "^2.0.2", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", diff --git a/src/crypto.js b/src/crypto.js index 25f71bad2..af1224d25 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -9,34 +9,31 @@ exports.taggedHash = exports.sha1 = exports.ripemd160 = void 0; -const createHash = require('create-hash'); -const RipeMd160 = require('ripemd160'); +const ripemd160_1 = require('@noble/hashes/ripemd160'); +const sha1_1 = require('@noble/hashes/sha1'); +const sha256_1 = require('@noble/hashes/sha256'); function ripemd160(buffer) { - try { - return createHash('rmd160').update(buffer).digest(); - } catch (err) { - try { - return createHash('ripemd160').update(buffer).digest(); - } catch (err2) { - return new RipeMd160().update(buffer).digest(); - } - } + return Buffer.from((0, ripemd160_1.ripemd160)(Uint8Array.from(buffer))); } exports.ripemd160 = ripemd160; function sha1(buffer) { - return createHash('sha1').update(buffer).digest(); + return Buffer.from((0, sha1_1.sha1)(Uint8Array.from(buffer))); } exports.sha1 = sha1; function sha256(buffer) { - return createHash('sha256').update(buffer).digest(); + return Buffer.from((0, sha256_1.sha256)(Uint8Array.from(buffer))); } exports.sha256 = sha256; function hash160(buffer) { - return ripemd160(sha256(buffer)); + return Buffer.from( + (0, ripemd160_1.ripemd160)((0, sha256_1.sha256)(Uint8Array.from(buffer))), + ); } exports.hash160 = hash160; function hash256(buffer) { - return sha256(sha256(buffer)); + return Buffer.from( + (0, sha256_1.sha256)((0, sha256_1.sha256)(Uint8Array.from(buffer))), + ); } exports.hash256 = hash256; exports.TAGS = [ diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index 686cdcd9e..e3b876961 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,32 +1,25 @@ -import * as createHash from 'create-hash'; -import * as RipeMd160 from 'ripemd160'; +import { ripemd160 as _ripemd160 } from '@noble/hashes/ripemd160'; +import { sha1 as _sha1 } from '@noble/hashes/sha1'; +import { sha256 as _sha256 } from '@noble/hashes/sha256'; export function ripemd160(buffer: Buffer): Buffer { - try { - return createHash('rmd160').update(buffer).digest(); - } catch (err) { - try { - return createHash('ripemd160').update(buffer).digest(); - } catch (err2) { - return new RipeMd160().update(buffer).digest(); - } - } + return Buffer.from(_ripemd160(Uint8Array.from(buffer))); } export function sha1(buffer: Buffer): Buffer { - return createHash('sha1').update(buffer).digest(); + return Buffer.from(_sha1(Uint8Array.from(buffer))); } export function sha256(buffer: Buffer): Buffer { - return createHash('sha256').update(buffer).digest(); + return Buffer.from(_sha256(Uint8Array.from(buffer))); } export function hash160(buffer: Buffer): Buffer { - return ripemd160(sha256(buffer)); + return Buffer.from(_ripemd160(_sha256(Uint8Array.from(buffer)))); } export function hash256(buffer: Buffer): Buffer { - return sha256(sha256(buffer)); + return Buffer.from(_sha256(_sha256(Uint8Array.from(buffer)))); } export const TAGS = [ From adc8a33a19091c19bc552d02bb98f1d6f52bb971 Mon Sep 17 00:00:00 2001 From: jafri Date: Mon, 20 Feb 2023 22:17:39 -0700 Subject: [PATCH 130/249] Handle custom signature hash type - sig length --- src/payments/p2tr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index d2cbc02a8..df31662cc 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -31,7 +31,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(types_1.typeforce.BufferN(64)), + signature: types_1.typeforce.maybe(types_1.typeforce.anyOf[types_1.typeforce.BufferN(64), types_1.typeforce.BufferN(65)]), witness: types_1.typeforce.maybe( types_1.typeforce.arrayOf(types_1.typeforce.Buffer), ), From 599f16b7e3811981dc71dca492d9a55f7fd2f900 Mon Sep 17 00:00:00 2001 From: jafri Date: Mon, 20 Feb 2023 22:57:48 -0700 Subject: [PATCH 131/249] Fix format --- src/payments/p2tr.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index df31662cc..e39c6aaf7 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -31,7 +31,11 @@ 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(types_1.typeforce.anyOf[types_1.typeforce.BufferN(64), types_1.typeforce.BufferN(65)]), + signature: types_1.typeforce.maybe( + types_1.typeforce.anyOf[ + (types_1.typeforce.BufferN(64), types_1.typeforce.BufferN(65)) + ], + ), witness: types_1.typeforce.maybe( types_1.typeforce.arrayOf(types_1.typeforce.Buffer), ), From d28e9dd7a67fd839b71eb0ceb0217a9c72070ce0 Mon Sep 17 00:00:00 2001 From: jafri Date: Tue, 21 Feb 2023 02:55:18 -0700 Subject: [PATCH 132/249] add .ts --- ts_src/payments/p2tr.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 231a263fc..54d1e13cc 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -40,7 +40,9 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { internalPubkey: 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)), + signature: typef.maybe( + typef.anyOf[(typef.BufferN(64), typef.BufferN(65))], + ), witness: typef.maybe(typef.arrayOf(typef.Buffer)), scriptTree: typef.maybe(isTaptree), redeem: typef.maybe({ From 9bf12190b0ed2f4f2d0500709e71c42e275aeb11 Mon Sep 17 00:00:00 2001 From: jafri Date: Tue, 21 Feb 2023 12:44:35 -0700 Subject: [PATCH 133/249] fix tests --- src/payments/p2tr.js | 7 ++++--- ts_src/payments/p2tr.ts | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index e39c6aaf7..83080b434 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -32,9 +32,10 @@ function p2tr(a, opts) { 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( - types_1.typeforce.anyOf[ - (types_1.typeforce.BufferN(64), types_1.typeforce.BufferN(65)) - ], + types_1.typeforce.anyOf( + types_1.typeforce.BufferN(64), + types_1.typeforce.BufferN(65), + ), ), witness: types_1.typeforce.maybe( types_1.typeforce.arrayOf(types_1.typeforce.Buffer), diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 54d1e13cc..53f1b40f9 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -40,9 +40,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { internalPubkey: 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.anyOf[(typef.BufferN(64), typef.BufferN(65))], - ), + signature: typef.maybe(typef.anyOf(typef.BufferN(64), typef.BufferN(65))), witness: typef.maybe(typef.arrayOf(typef.Buffer)), scriptTree: typef.maybe(isTaptree), redeem: typef.maybe({ From 7be0c0bf992b0d301e901ef88325d8a1b5ca0a49 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Sat, 25 Feb 2023 08:35:15 +0000 Subject: [PATCH 134/249] Bump bip32, bip39 --- package-lock.json | 249 ++++++++++++++++++++++++---------------------- package.json | 6 +- 2 files changed, 132 insertions(+), 123 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ce902e7f..b042e30fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@noble/hashes": "^1.2.0", "bech32": "^2.0.0", "bip174": "^2.1.0", - "bs58check": "^2.1.2", + "bs58check": "^3.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" @@ -28,8 +28,8 @@ "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "better-npm-audit": "^3.7.3", - "bip32": "^3.0.1", - "bip39": "^3.0.2", + "bip32": "^4.0.0", + "bip39": "^3.1.0", "bip65": "^1.0.1", "bip68": "^1.0.3", "bs58": "^4.0.0", @@ -725,6 +725,18 @@ "node": ">= 8" } }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, "node_modules/@types/bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", @@ -1177,15 +1189,13 @@ } }, "node_modules/bip32": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.1.0.tgz", - "integrity": "sha512-eoeajYEzJ4d6yyVtby8C+XkCeKItiC4Mx56a0M9VaqTMC73SWOm4xVZG7SaR8e/yp4eSyky2XcBpH3DApPdu7Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-4.0.0.tgz", + "integrity": "sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ==", "dev": true, "dependencies": { - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "ripemd160": "^2.0.2", + "@noble/hashes": "^1.2.0", + "@scure/base": "^1.1.1", "typeforce": "^1.11.5", "wif": "^2.0.6" }, @@ -1194,23 +1204,14 @@ } }, "node_modules/bip39": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", - "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", "dev": true, "dependencies": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" + "@noble/hashes": "^1.2.0" } }, - "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", @@ -1300,13 +1301,25 @@ } }, "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/bs58check/node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/bs58check/node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" } }, "node_modules/buffer-from": { @@ -1505,20 +1518,6 @@ "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", @@ -3370,22 +3369,6 @@ "node": ">=8" } }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "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/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -3575,9 +3558,9 @@ } }, "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3622,6 +3605,17 @@ "randombytes": "^2.1.0" } }, + "node_modules/regtest-client/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -4289,6 +4283,16 @@ "bs58check": "<3.0.0" } }, + "node_modules/wif/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/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -4955,6 +4959,12 @@ "fastq": "^1.6.0" } }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "dev": true + }, "@types/bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", @@ -5267,37 +5277,24 @@ "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==" }, "bip32": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.1.0.tgz", - "integrity": "sha512-eoeajYEzJ4d6yyVtby8C+XkCeKItiC4Mx56a0M9VaqTMC73SWOm4xVZG7SaR8e/yp4eSyky2XcBpH3DApPdu7Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-4.0.0.tgz", + "integrity": "sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ==", "dev": true, "requires": { - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "ripemd160": "^2.0.2", + "@noble/hashes": "^1.2.0", + "@scure/base": "^1.1.1", "typeforce": "^1.11.5", "wif": "^2.0.6" } }, "bip39": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", - "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", "dev": true, "requires": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - }, - "dependencies": { - "@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 - } + "@noble/hashes": "^1.2.0" } }, "bip65": { @@ -5364,13 +5361,27 @@ } }, "bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + }, + "dependencies": { + "base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "requires": { + "base-x": "^4.0.0" + } + } } }, "buffer-from": { @@ -5523,20 +5534,6 @@ "sha.js": "^2.4.0" } }, - "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, - "requires": { - "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" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -6923,19 +6920,6 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "pbkdf2": { - "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", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7068,9 +7052,9 @@ } }, "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7101,6 +7085,19 @@ "bs58check": "^2.1.2", "dhttp": "^3.0.3", "randombytes": "^2.1.0" + }, + "dependencies": { + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + } } }, "release-zalgo": { @@ -7574,6 +7571,18 @@ "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", "requires": { "bs58check": "<3.0.0" + }, + "dependencies": { + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + } } }, "word-wrap": { diff --git a/package.json b/package.json index 6194306c3..556850d46 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@noble/hashes": "^1.2.0", "bech32": "^2.0.0", "bip174": "^2.1.0", - "bs58check": "^2.1.2", + "bs58check": "^3.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" @@ -68,8 +68,8 @@ "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "better-npm-audit": "^3.7.3", - "bip32": "^3.0.1", - "bip39": "^3.0.2", + "bip32": "^4.0.0", + "bip39": "^3.1.0", "bip65": "^1.0.1", "bip68": "^1.0.3", "bs58": "^4.0.0", From 4bf8a84b8e4118dadc2b4e9685faf7abcea3445d Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Sat, 25 Feb 2023 15:12:03 +0000 Subject: [PATCH 135/249] Remove wif. Use new bs58check --- package-lock.json | 58 +++++++++++++++++++++++----------------- package.json | 4 +-- src/address.js | 4 +-- src/payments/p2pkh.js | 2 +- src/payments/p2sh.js | 2 +- ts_src/address.ts | 4 +-- ts_src/payments/p2pkh.ts | 2 +- ts_src/payments/p2sh.ts | 2 +- 8 files changed, 42 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index b042e30fb..4c0d1da60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,7 @@ "bip174": "^2.1.0", "bs58check": "^3.0.1", "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.1.2", - "wif": "^2.0.1" + "varuint-bitcoin": "^1.1.2" }, "devDependencies": { "@types/bs58": "^4.0.0", @@ -24,7 +23,6 @@ "@types/node": "^16.11.7", "@types/proxyquire": "^1.3.28", "@types/randombytes": "^2.0.0", - "@types/wif": "^2.0.2", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "better-npm-audit": "^3.7.3", @@ -794,15 +792,6 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, - "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/@typescript-eslint/eslint-plugin": { "version": "5.45.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", @@ -1144,6 +1133,7 @@ "version": "3.0.9", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -1296,6 +1286,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, "dependencies": { "base-x": "^3.0.2" } @@ -1436,6 +1427,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -1510,6 +1502,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -2295,6 +2288,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, "dependencies": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -2406,7 +2400,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -2839,6 +2834,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -3561,6 +3557,7 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3704,6 +3701,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -3785,6 +3783,7 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3916,6 +3915,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -4235,7 +4235,8 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "node_modules/uuid": { "version": "8.3.2", @@ -4279,6 +4280,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dev": true, "dependencies": { "bs58check": "<3.0.0" } @@ -4287,6 +4289,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", @@ -5022,15 +5025,6 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, - "@types/wif": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", - "integrity": "sha512-IiIuBeJzlh4LWJ7kVTrX0nwB60OG0vvGTaWC/SgSbVFw7uYUTF6gEuvDZ1goWkeirekJDD58Y8g7NljQh2fNkA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@typescript-eslint/eslint-plugin": { "version": "5.45.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", @@ -5244,6 +5238,7 @@ "version": "3.0.9", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -5356,6 +5351,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, "requires": { "base-x": "^3.0.2" } @@ -5461,6 +5457,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -5526,6 +5523,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -6101,6 +6099,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, "requires": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -6184,7 +6183,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-binary-path": { "version": "2.1.0", @@ -6505,6 +6505,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -7055,6 +7056,7 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7163,6 +7165,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -7210,6 +7213,7 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -7310,6 +7314,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "requires": { "safe-buffer": "~5.2.0" } @@ -7534,7 +7539,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "uuid": { "version": "8.3.2", @@ -7569,6 +7575,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dev": true, "requires": { "bs58check": "<3.0.0" }, @@ -7577,6 +7584,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, "requires": { "bs58": "^4.0.0", "create-hash": "^1.1.0", diff --git a/package.json b/package.json index 556850d46..12ded6971 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,7 @@ "bip174": "^2.1.0", "bs58check": "^3.0.1", "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.1.2", - "wif": "^2.0.1" + "varuint-bitcoin": "^1.1.2" }, "devDependencies": { "@types/bs58": "^4.0.0", @@ -64,7 +63,6 @@ "@types/node": "^16.11.7", "@types/proxyquire": "^1.3.28", "@types/randombytes": "^2.0.0", - "@types/wif": "^2.0.2", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "better-npm-audit": "^3.7.3", diff --git a/src/address.js b/src/address.js index d58c9a69e..2712168a1 100644 --- a/src/address.js +++ b/src/address.js @@ -42,11 +42,11 @@ function _toFutureSegwitAddress(output, network) { return toBech32(data, version, network.bech32); } function fromBase58Check(address) { - const payload = bs58check.decode(address); + const payload = Buffer.from(bs58check.decode(address)); // TODO: 4.0.0, move to "toOutputScript" if (payload.length < 21) throw new TypeError(address + ' is too short'); if (payload.length > 21) throw new TypeError(address + ' is too long'); - const version = payload.readUInt8(0); + const version = payload.readUint8(0); const hash = payload.slice(1); return { version, hash }; } diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index 8edc8ba9e..16e293d59 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -27,7 +27,7 @@ function p2pkh(a, opts) { a, ); const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); + const payload = Buffer.from(bs58check.decode(a.address)); const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 5061edc95..b280fcafa 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -48,7 +48,7 @@ function p2sh(a, opts) { } const o = { network }; const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); + const payload = Buffer.from(bs58check.decode(a.address)); const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; diff --git a/ts_src/address.ts b/ts_src/address.ts index 8004b2668..a9c0e4103 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -53,13 +53,13 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string { } export function fromBase58Check(address: string): Base58CheckResult { - const payload = bs58check.decode(address); + const payload = Buffer.from(bs58check.decode(address)); // TODO: 4.0.0, move to "toOutputScript" if (payload.length < 21) throw new TypeError(address + ' is too short'); if (payload.length > 21) throw new TypeError(address + ' is too long'); - const version = payload.readUInt8(0); + const version = payload.readUint8(0); const hash = payload.slice(1); return { version, hash }; diff --git a/ts_src/payments/p2pkh.ts b/ts_src/payments/p2pkh.ts index 3b5bd6f79..4947c832a 100644 --- a/ts_src/payments/p2pkh.ts +++ b/ts_src/payments/p2pkh.ts @@ -29,7 +29,7 @@ export function p2pkh(a: Payment, opts?: PaymentOpts): Payment { ); const _address = lazy.value(() => { - const payload = bs58check.decode(a.address!); + const payload = Buffer.from(bs58check.decode(a.address!)); const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index 67e5f38dd..ebff7853f 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -57,7 +57,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { const o: Payment = { network }; const _address = lazy.value(() => { - const payload = bs58check.decode(a.address!); + const payload = Buffer.from(bs58check.decode(a.address!)); const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; From 1c18fe6e2c925f94c99e6311783e28c2799eca5a Mon Sep 17 00:00:00 2001 From: fboucquez Date: Sun, 26 Feb 2023 20:41:03 -0300 Subject: [PATCH 136/249] fix: android `TypedArray.of requires its this argument to subclass a TypedArray constructor` --- src/transaction.js | 2 +- ts_src/transaction.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 6f1382cfa..4b149c0b8 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -389,7 +389,7 @@ class Transaction { // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19 return bcrypto.taggedHash( 'TapSighash', - Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]), + Buffer.concat([Buffer.alloc(1), sigMsgWriter.end()]), ); } hashForWitnessV0(inIndex, prevOutScript, value, hashType) { diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 416f20efe..52d224a11 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -490,7 +490,7 @@ export class Transaction { // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19 return bcrypto.taggedHash( 'TapSighash', - Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]), + Buffer.concat([Buffer.alloc(1), sigMsgWriter.end()]), ); } From 3541220e7065959044065c534562c52c0fd4236e Mon Sep 17 00:00:00 2001 From: fboucquez Date: Mon, 27 Feb 2023 11:42:20 -0300 Subject: [PATCH 137/249] using Buffer.from([0x00] --- src/transaction.js | 2 +- ts_src/transaction.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 4b149c0b8..ade4582cf 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -389,7 +389,7 @@ class Transaction { // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19 return bcrypto.taggedHash( 'TapSighash', - Buffer.concat([Buffer.alloc(1), sigMsgWriter.end()]), + Buffer.concat([Buffer.from([0x00]), sigMsgWriter.end()]), ); } hashForWitnessV0(inIndex, prevOutScript, value, hashType) { diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 52d224a11..39b975545 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -490,7 +490,7 @@ export class Transaction { // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19 return bcrypto.taggedHash( 'TapSighash', - Buffer.concat([Buffer.alloc(1), sigMsgWriter.end()]), + Buffer.concat([Buffer.from([0x00]), sigMsgWriter.end()]), ); } From 640a2a781c9c9af63688d7bdb1760fa7b9d9caa3 Mon Sep 17 00:00:00 2001 From: habibitcoin Date: Fri, 14 Apr 2023 19:10:22 -0400 Subject: [PATCH 138/249] Include test for taproot custom signature types --- test/fixtures/psbt.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index ec6eda328..1ee38388b 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -767,6 +767,15 @@ "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" } }, + { + "description": "checks taproot signing works when using custom signature type of 65 bytes", + "shouldSign": { + "psbt": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBAwSDAAAAARcgWzC4qnD37J3WaDEbZPRihBXdI0gN68BGutJykDcHR6wBGCDlDrX1cnzwZvmcyLBH8M6NiS9lk7JVwM58wZZVOzmuMwAA", + "inputToCheck": 0, + "WIF": "KypUz2y1jdgzM8HGDUx9DYLmyzd8EWhruvLX2J5iSL7MiAcc7dBG", + "result": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBAwSDAAAAARNBqgjqjSQVTf41zgZ1H9Lq3CKQt0nq1APST8FpwGifNjyUHMS0MbFnIxA70SXTEOoSJePyOXTW+u59fzLpxekL2oMBFyBbMLiqcPfsndZoMRtk9GKEFd0jSA3rwEa60nKQNwdHrAEYIOUOtfVyfPBm+ZzIsEfwzo2JL2WTslXAznzBllU7Oa4zAAA=" + } + }, { "description": "checks taproot key-path signer (tweaked key) matches internal tap key", "isTaproot": true, From 3c5faec85db846748d7eb0af0d7763677e58aa63 Mon Sep 17 00:00:00 2001 From: habibitcoin <114780316+habibitcoin@users.noreply.github.com> Date: Thu, 27 Apr 2023 22:03:12 -0400 Subject: [PATCH 139/249] Update test to specify sighashTypes --- test/fixtures/psbt.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index 1ee38388b..c3bcde380 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -771,6 +771,9 @@ "description": "checks taproot signing works when using custom signature type of 65 bytes", "shouldSign": { "psbt": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBAwSDAAAAARcgWzC4qnD37J3WaDEbZPRihBXdI0gN68BGutJykDcHR6wBGCDlDrX1cnzwZvmcyLBH8M6NiS9lk7JVwM58wZZVOzmuMwAA", + "sighashTypes": [ + 3, 128 + ], "inputToCheck": 0, "WIF": "KypUz2y1jdgzM8HGDUx9DYLmyzd8EWhruvLX2J5iSL7MiAcc7dBG", "result": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBAwSDAAAAARNBqgjqjSQVTf41zgZ1H9Lq3CKQt0nq1APST8FpwGifNjyUHMS0MbFnIxA70SXTEOoSJePyOXTW+u59fzLpxekL2oMBFyBbMLiqcPfsndZoMRtk9GKEFd0jSA3rwEa60nKQNwdHrAEYIOUOtfVyfPBm+ZzIsEfwzo2JL2WTslXAznzBllU7Oa4zAAA=" @@ -917,4 +920,4 @@ "clone": { "psbt": "cHNidP8BAKYCAAAAAlwKQ3suPWwEJ/zQ9sZsIioOcHKU1KoLMxlMNSXVIkEWAAAAAAD/////YYDJMap+mYgbTrCNAdpWHN+EkKvl+XYao/6co/EQfwMAAAAAAP////8CkF8BAAAAAAAWABRnPBAmVHz2HL+8/1U+QG5L2thjmjhKAAAAAAAAIgAg700yfFRyhWzQnPHIUb/XQqsjlpf4A0uw682pCVWuQ8IAAAAAAAEBKzB1AAAAAAAAIgAgth9oE4cDfC5aV58VgkW5CptHsIxppYzJV8C5kT6aTo8iAgNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06UgwRQIhALO0xRpuqP3aVkm+DPykrtqe6fPNSgNblp9K9MAwmtHJAiAHV5ZvZN8Vi49n/o9ISFyvtHsPXXPKqBxC9m2m2HlpYgEBBSMhA182CcWcQ9L2zg9j8jlt7OlaIATNDgNFMBKB1J14wzTprAABASuAOAEAAAAAACIAILYfaBOHA3wuWlefFYJFuQqbR7CMaaWMyVfAuZE+mk6PIgIDXzYJxZxD0vbOD2PyOW3s6VogBM0OA0UwEoHUnXjDNOlIMEUCIQC6XN6tpo9mYlZej4BXSSh5D1K6aILBfQ4WBWXUrISx6wIgVaxFUsz8y59xJ1V4sZ1qarHX9pZ+MJmLKbl2ZSadisoBAQUjIQNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06awAAAA=" } -} \ No newline at end of file +} From 5ffda410db14cf109e65d0a69ae3148c2e552b20 Mon Sep 17 00:00:00 2001 From: habibitcoin Date: Tue, 23 May 2023 22:47:51 -0400 Subject: [PATCH 140/249] Update test to specify isTaproot --- test/fixtures/psbt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index c3bcde380..31645c86d 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -769,10 +769,11 @@ }, { "description": "checks taproot signing works when using custom signature type of 65 bytes", + "isTaproot": true, "shouldSign": { "psbt": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBAwSDAAAAARcgWzC4qnD37J3WaDEbZPRihBXdI0gN68BGutJykDcHR6wBGCDlDrX1cnzwZvmcyLBH8M6NiS9lk7JVwM58wZZVOzmuMwAA", "sighashTypes": [ - 3, 128 + 131 ], "inputToCheck": 0, "WIF": "KypUz2y1jdgzM8HGDUx9DYLmyzd8EWhruvLX2J5iSL7MiAcc7dBG", From f230cc96ceaf82b151b6d5e9fb0061dd8eaad4ce Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 25 May 2023 19:06:29 +0300 Subject: [PATCH 141/249] chore: version bump to `6.1.1` and changelog --- CHANGELOG.md | 16 ++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d11209c..f22ccbfdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# 6.1.1 +__added__ +- add example using BIP86 vector to verify the sending to and from a BIP86 generated taproot address + +__fixed__ +- support for 65 byte taproot signature +- prevent the creation of unspendable scripts in bitcoinjs-lib by implementing checks for resource limitations +- use `Buffer.from()` instead of `Buffer.of()` + +__changed__ +- performance: precompute the taproot hashes +- performance: switch from `create-hash` and `ripemd160` to noble-hashes + +__removed__ +- types: removed unused methods `privateAdd` and `privateNegate` from `TinySecp256k1Interface` + # 6.1.0 __added__ - taproot support for payments (p2tr) and PSBT. See taproot.spec.ts integration test for examples. (#1742) diff --git a/package-lock.json b/package-lock.json index 4c0d1da60..24fd6410d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bitcoinjs-lib", - "version": "6.1.0", + "version": "6.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bitcoinjs-lib", - "version": "6.1.0", + "version": "6.1.1", "license": "MIT", "dependencies": { "@noble/hashes": "^1.2.0", diff --git a/package.json b/package.json index 12ded6971..bf93b2901 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.0", + "version": "6.1.1", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From 85c5c6a3354ac2473f55976a9fb0e581a1bbd907 Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 2 Jun 2023 20:36:38 -0700 Subject: [PATCH 142/249] Fix: Find hashes WITHOUT leafHash instead --- src/psbt.js | 14 ++++++++++---- test/integration/taproot.spec.ts | 31 +++++++++++++++++++++++++++++++ ts_src/psbt.ts | 15 +++++++++++---- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 9aa36686a..d4b78093b 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -468,14 +468,16 @@ class Psbt { this.__CACHE, ); if (!allHashses.length) throw new Error('No signatures for this pubkey'); - const tapKeyHash = allHashses.find(h => !!h.leafHash); + const tapKeyHash = allHashses.find(h => !h.leafHash); + let validationResultCount = 0; if (tapKeySig && tapKeyHash) { const isValidTapkeySig = validator( tapKeyHash.pubkey, tapKeyHash.hash, - tapKeySig, + trimTaprootSig(tapKeySig), ); if (!isValidTapkeySig) return false; + validationResultCount++; } if (tapScriptSig) { for (const tapSig of tapScriptSig) { @@ -484,13 +486,14 @@ class Psbt { const isValidTapScriptSig = validator( tapSig.pubkey, tapSigHash.hash, - tapSig.signature, + trimTaprootSig(tapSig.signature), ); if (!isValidTapScriptSig) return false; + validationResultCount++; } } } - return true; + return validationResultCount > 0; } signAllInputsHD( hdKeyPair, @@ -1292,6 +1295,9 @@ function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) { ); return allHashes.flat(); } +function trimTaprootSig(signature) { + return signature.length === 64 ? signature : signature.subarray(1); +} function getTaprootHashesForSig( inputIndex, input, diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 550a34608..87cfaedb9 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -598,6 +598,37 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); } }); + + it('should fail validating invalid signatures for taproot (See issue #1931)', () => { + const schnorrValidator = ( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer, + ) => { + return ecc.verifySchnorr(msghash, pubkey, signature); + }; + + const psbtBase64 = + `cHNidP8BAFICAAAAAe1h73A6zedruNERV6JU7Ty1IlYZh2KO1cBklZqCMEy8AAAAAAD/////ARA + nAAAAAAAAFgAUS0GlfqWSeEWIpwPwrvRIjBbJQroAAAAAAAEA/TgBAQAAAAABAnGJ6st1FIvYLEV + bJMQaZ3HSOJnkw5C+ViCuJYiFEYosAAAAAAD9////xuZd0xArNSaBuElLX3nzjwtZW95O7L/wbz9 + 4v+v0vuYAAAAAAP3///8CECcAAAAAAAAiUSAVbMSHgwYVdyBgfNy0syr6TMaFOGhFjXJYuQcRLlp + DS8hgBwAAAAAAIlEgthWGz3o2R7WpgjIK52ODoEaA/0HcImSUjVk6agZgghwBQIP9WWErMfeBBYy + uHuSZS7MdXVICtlFgNveDrvuXeQGSZl1gGG6/r3Aw7h9TifGtoA+7JwYBjLMcEG6hbeyQGXIBQNS + qKH1p/NFzO9bxe9vpvBZQIaX5Qa9SY2NfNCgSRNabmX5EiaihWcLC+ALgchm7DUfYrAmi1r4uSI/ + YaQ1lq8gAAAAAAQErECcAAAAAAAAiUSAVbMSHgwYVdyBgfNy0syr6TMaFOGhFjXJYuQcRLlpDSwE + DBIMAAAABCEMBQZUpv6e1Hwfpi/PpglkkK/Rx40vZIIHwtJ7dXWFZ5TcZUEelCnfKOAWZ4xWjauY + M2y+JcgFcVsuPzPuiM+z5AH+DARNBlSm/p7UfB+mL8+mCWSQr9HHjS9kggfC0nt1dYVnlNxlQR6U + Kd8o4BZnjFaNq5gzbL4lyAVxWy4/M+6Iz7PkAf4MBFyC6ZCT2zZVrEbkw/T1fyS8eLKQaP2MH6rz + dlMauGvQzLQAA`.replace(/\s+/g, ''); + + const psbt = bitcoin.Psbt.fromBase64(psbtBase64); + + assert( + !psbt.validateSignaturesOfAllInputs(schnorrValidator), + 'Should fail validation', + ); + }); }); function buildLeafIndexFinalizer( diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index e7ca28976..84eb89c33 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -606,14 +606,16 @@ export class Psbt { if (!allHashses.length) throw new Error('No signatures for this pubkey'); - const tapKeyHash = allHashses.find(h => !!h.leafHash); + const tapKeyHash = allHashses.find(h => !h.leafHash); + let validationResultCount = 0; if (tapKeySig && tapKeyHash) { const isValidTapkeySig = validator( tapKeyHash.pubkey, tapKeyHash.hash, - tapKeySig, + trimTaprootSig(tapKeySig), ); if (!isValidTapkeySig) return false; + validationResultCount++; } if (tapScriptSig) { @@ -623,14 +625,15 @@ export class Psbt { const isValidTapScriptSig = validator( tapSig.pubkey, tapSigHash.hash, - tapSig.signature, + trimTaprootSig(tapSig.signature), ); if (!isValidTapScriptSig) return false; + validationResultCount++; } } } - return true; + return validationResultCount > 0; } signAllInputsHD( @@ -1714,6 +1717,10 @@ function getAllTaprootHashesForSig( return allHashes.flat(); } +function trimTaprootSig(signature: Buffer): Buffer { + return signature.length === 64 ? signature : signature.subarray(1); +} + function getTaprootHashesForSig( inputIndex: number, input: PsbtInput, From f8cfd7f679054afe18834a7acee1fbaa318ab701 Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 5 Jun 2023 07:35:29 -0700 Subject: [PATCH 143/249] 6.1.2 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f22ccbfdd..93fb78d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 6.1.2 +__fixed__ +- validateSignaturesOfInput for taproot inputs returned true for invalid signatures in specific cases. (#1932) + # 6.1.1 __added__ - add example using BIP86 vector to verify the sending to and from a BIP86 generated taproot address diff --git a/package-lock.json b/package-lock.json index 24fd6410d..f3a34063d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.1", + "version": "6.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index bf93b2901..89566f8e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.1", + "version": "6.1.2", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From b5fd4d644c4b7a9dfc6f6fd4046626c37596241b Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 6 Jun 2023 22:51:45 -0700 Subject: [PATCH 144/249] Fix: Taproot signature validation was incorrect again. Output keys are not guaranteed to be tweaked against their pubkey's hash. That is only a convention decided upon for NUMS properties. --- package.json | 2 +- src/psbt.js | 15 +++++++++++---- test/fixtures/psbt.json | 6 +++--- test/integration/taproot.spec.ts | 29 +++++++++++++++++++++++++++++ ts_src/psbt.ts | 22 +++++++++++++++++----- 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 89566f8e3..bf6ac269c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "mocha:ts": "mocha --recursive --require test/ts-node-register", "nobuild:coverage-report": "nyc report --reporter=lcov", "nobuild:coverage-html": "nyc report --reporter=html", - "nobuild:coverage": "npm run build:tests && nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha && npm run clean:jstests", + "nobuild:coverage": "npm run build:tests && nyc --check-coverage --branches 85 --functions 90 --lines 90 mocha && npm run clean:jstests", "nobuild:integration": "npm run mocha:ts -- --timeout 50000 'test/integration/*.ts'", "nobuild:unit": "npm run mocha:ts -- 'test/*.ts'", "prettier": "prettier \"ts_src/**/*.ts\" \"test/**/*.ts\" --ignore-path ./.prettierignore", diff --git a/src/psbt.js b/src/psbt.js index d4b78093b..71c3589f0 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1283,8 +1283,10 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) { const allPublicKeys = []; if (input.tapInternalKey) { - const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input); - allPublicKeys.push(outputKey); + const key = getPrevoutTaprootKey(inputIndex, input, cache); + if (key) { + allPublicKeys.push(key); + } } if (input.tapScriptSig) { const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey); @@ -1295,8 +1297,12 @@ function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) { ); return allHashes.flat(); } +function getPrevoutTaprootKey(inputIndex, input, cache) { + const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); + return (0, psbtutils_1.isP2TR)(script) ? script.subarray(2, 34) : null; +} function trimTaprootSig(signature) { - return signature.length === 64 ? signature : signature.subarray(1); + return signature.length === 64 ? signature : signature.subarray(0, 64); } function getTaprootHashesForSig( inputIndex, @@ -1318,7 +1324,8 @@ function getTaprootHashesForSig( const values = prevOuts.map(o => o.value); const hashes = []; if (input.tapInternalKey && !tapLeafHashToSign) { - const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input); + const outputKey = + getPrevoutTaprootKey(inputIndex, input, cache) || Buffer.from([]); if ((0, bip371_1.toXOnly)(pubkey).equals(outputKey)) { const tapKeyHash = unsignedTx.hashForWitnessV1( inputIndex, diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index 31645c86d..ce8988fa3 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -310,7 +310,7 @@ }, { "description": "Sign PSBT with 3 inputs [P2PKH, P2TR (key-path), 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+p90ZfGGstEZ83LyfTLDFcfi2OlxAyuARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMgAAAA==", + "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAABASu4BQEAAAAAACJRIE/vXFFjvqaak+dKWWcrvrCBg3B3y5TPpuSBpc8A2KsYARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMgAAAA==", "isTaproot": true, "keys": [ { @@ -326,7 +326,7 @@ "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+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==" + "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEgT+9cUWO+ppqT50pZZyu+sIGDcHfLlM+m5IGlzwDYqxgBE0B0eYK4chVhtLT9WMi14T8ZknZSdTe1pMdIvaq6tIfwqY2xQ9YlcTTy0jWU9utItw/rHQ2c1FplbF9bRvZ6RLQSARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==" }, { "description": "Sign PSBT with 1 input [P2TR] (script-path, 3-of-3) and one output [P2TR]", @@ -896,7 +896,7 @@ "nonExistantIndex": 42 }, "validateSignaturesOfTapKeyInput": { - "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==", + "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEgT+9cUWO+ppqT50pZZyu+sIGDcHfLlM+m5IGlzwDYqxgBE0B0eYK4chVhtLT9WMi14T8ZknZSdTe1pMdIvaq6tIfwqY2xQ9YlcTTy0jWU9utItw/rHQ2c1FplbF9bRvZ6RLQSARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==", "index": 1, "pubkey": "Buffer.from('024fef5c5163bea69a93e74a59672bbeb081837077cb94cfa6e481a5cf00d8ab18', 'hex')", "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')" diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 87cfaedb9..9fee629c3 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -629,6 +629,35 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { 'Should fail validation', ); }); + + it('should succeed validating valid signatures for taproot (See issue #1934)', () => { + const schnorrValidator = ( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer, + ) => { + return ecc.verifySchnorr(msghash, pubkey, signature); + }; + + const psbtBase64 = + `cHNidP8BAF4CAAAAAU6UzYPa7tES0HoS+obnRJuXX41Ob64Zs59qDEyKsu1ZAAAAAAD/////AYA + zAjsAAAAAIlEgIlIzfR+flIWYTyewD9v+1N84IubZ/7qg6oHlYLzv1aYAAAAAAAEAXgEAAAAB8f+ + afEJBun7sRQLFE1Olc/gK9LBaduUpz3vB4fjXVF0AAAAAAP3///8BECcAAAAAAAAiUSAiUjN9H5+ + UhZhPJ7AP2/7U3zgi5tn/uqDqgeVgvO/VpgAAAAABASsQJwAAAAAAACJRICJSM30fn5SFmE8nsA/ + b/tTfOCLm2f+6oOqB5WC879WmAQMEgwAAAAETQWQwNOao3RMOBWPuAQ9Iph7Qzk47MvroTHbJR49 + MxKJmQ6hfhZa5wVVrdKYea5BW/loqa7al2pYYZMlGvdS06wODARcgjuYXxIpyOMVTYEvl35gDidC + m/vUICZyuNNZKaPz9dxAAAQUgjuYXxIpyOMVTYEvl35gDidCm/vUICZyuNNZKaPz9dxAA`.replace( + /\s+/g, + '', + ); + + const psbt = bitcoin.Psbt.fromBase64(psbtBase64); + + assert( + psbt.validateSignaturesOfAllInputs(schnorrValidator), + 'Should succeed validation', + ); + }); }); function buildLeafIndexFinalizer( diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 84eb89c33..a69dc1e08 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -29,7 +29,6 @@ import { isTaprootInput, checkTaprootInputFields, checkTaprootOutputFields, - tweakInternalPubKey, checkTaprootInputForSigs, } from './psbt/bip371'; import { @@ -42,6 +41,7 @@ import { isP2WPKH, isP2WSHScript, isP2SHScript, + isP2TR, } from './psbt/psbtutils'; export interface TransactionInput { @@ -1701,8 +1701,10 @@ function getAllTaprootHashesForSig( ): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] { const allPublicKeys = []; if (input.tapInternalKey) { - const outputKey = tweakInternalPubKey(inputIndex, input); - allPublicKeys.push(outputKey); + const key = getPrevoutTaprootKey(inputIndex, input, cache); + if (key) { + allPublicKeys.push(key); + } } if (input.tapScriptSig) { @@ -1717,8 +1719,17 @@ function getAllTaprootHashesForSig( return allHashes.flat(); } +function getPrevoutTaprootKey( + inputIndex: number, + input: PsbtInput, + cache: PsbtCache, +): Buffer | null { + const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); + return isP2TR(script) ? script.subarray(2, 34) : null; +} + function trimTaprootSig(signature: Buffer): Buffer { - return signature.length === 64 ? signature : signature.subarray(1); + return signature.length === 64 ? signature : signature.subarray(0, 64); } function getTaprootHashesForSig( @@ -1743,7 +1754,8 @@ function getTaprootHashesForSig( const hashes = []; if (input.tapInternalKey && !tapLeafHashToSign) { - const outputKey = tweakInternalPubKey(inputIndex, input); + const outputKey = + getPrevoutTaprootKey(inputIndex, input, cache) || Buffer.from([]); if (toXOnly(pubkey).equals(outputKey)) { const tapKeyHash = unsignedTx.hashForWitnessV1( inputIndex, From 8d2d7dbe4391a8de30f2e692c5b19d1167f28333 Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 6 Jun 2023 22:52:58 -0700 Subject: [PATCH 145/249] 6.1.3 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93fb78d0c..fc7dd6138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 6.1.3 +__fixed__ +- validateSignaturesOfInput for taproot inputs returned false for valid signatures in specific cases. (#1934) + # 6.1.2 __fixed__ - validateSignaturesOfInput for taproot inputs returned true for invalid signatures in specific cases. (#1932) diff --git a/package-lock.json b/package-lock.json index f3a34063d..9ba7c2727 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.2", + "version": "6.1.3", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index bf6ac269c..747c8f0f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.2", + "version": "6.1.3", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From e80d22bafcfdb0c4c5134c6fe2233de15a9bb4c7 Mon Sep 17 00:00:00 2001 From: Jonathan Underwood Date: Wed, 19 Jul 2023 11:15:59 +0900 Subject: [PATCH 146/249] Update README.md Include section about the Matrix rooms. --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1d7af69f..01a45733b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ Released under the terms of the [MIT LICENSE](LICENSE). If you are thinking of using the *master* branch of this library in production, **stop**. Master is not stable; it is our development branch, and [only tagged releases may be classified as stable](https://github.com/bitcoinjs/bitcoinjs-lib/tags). - ## Can I trust this code? > Don't trust. Verify. @@ -23,12 +22,24 @@ Mistakes and bugs happen, but with your help in resolving and reporting [issues] - Standardized, using [prettier](https://github.com/prettier/prettier) and Node `Buffer`'s throughout, and - Friendly, with a strong and helpful community, ready to answer questions. - ## Documentation Presently, we do not have any formal documentation other than our [examples](#examples), please [ask for help](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new) if our examples aren't enough to guide you. You can find a [Web UI](https://bitcoincore.tech/apps/bitcoinjs-ui/index.html) that covers most of the `psbt.ts`, `transaction.ts` and `p2*.ts` APIs [here](https://bitcoincore.tech/apps/bitcoinjs-ui/index.html). +## How can I contact the developers outside of Github? +**Most of the time, this is not appropriate. Creating issues and pull requests in the open will help others with similar issues, so please try to use public issues and pull requests for communication.** + +That said, sometimes developers might be open to taking things off the record (ie. You want to share code that you don't want public to get help with it). In that case, please negotiate on the public issues as to where you will contact. + +We have created public rooms on IRC (`#bitcoinjs` on `libera.chat`) and Matrix (`#bitcoinjs-dev:matrix.org`). These two channels have been joined together in a Matrix "Space" which has the Matrix room AND an IRC bridge room that can converse with the IRC room. The "Space" is `#bitcoinjs-space:matrix.org`. + +Matrix and IRC both have functions for direct messaging, but IRC is not end to end encrypted, so Matrix is recommended for most communication. The official Matrix client maintained by the Matrix core team is called "Element" and can be downloaded here: https://element.io/download (Account creation is free on the matrix.org server, which is the default setting for Element.) + +We used to have a Slack. It is dead. If you find it, no one will answer you most likely. + +No we will not make a Discord. + ## Installation ``` bash npm install bitcoinjs-lib From bcf1bf899324058ad9c70e8fa5a82f79acda0a13 Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 28 Aug 2023 13:27:42 -0700 Subject: [PATCH 147/249] Bump package-lock.json version --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 9ba7c2727..7543b1571 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "bitcoinjs-lib", - "version": "6.1.1", + "version": "6.1.3", "license": "MIT", "dependencies": { "@noble/hashes": "^1.2.0", From d60ba49a3c92c8ce2a2d350f17cbe66417f85e8c Mon Sep 17 00:00:00 2001 From: Waqas Ahmed Date: Thu, 14 Sep 2023 22:12:45 +0500 Subject: [PATCH 148/249] Fixes issue#1974 Wrong method call fixed for the issue https://github.com/bitcoinjs/bitcoinjs-lib/issues/1974 --- src/address.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/address.js b/src/address.js index 2712168a1..ada8042af 100644 --- a/src/address.js +++ b/src/address.js @@ -46,7 +46,7 @@ function fromBase58Check(address) { // TODO: 4.0.0, move to "toOutputScript" if (payload.length < 21) throw new TypeError(address + ' is too short'); if (payload.length > 21) throw new TypeError(address + ' is too long'); - const version = payload.readUint8(0); + const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; } From 1cd80e5fe8737e8c92d6e2e9ca1e813804d522bd Mon Sep 17 00:00:00 2001 From: junderw Date: Thu, 14 Sep 2023 11:12:58 -0700 Subject: [PATCH 149/249] Fix TS as well --- test/bufferutils.spec.ts | 2 +- ts_src/address.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/bufferutils.spec.ts b/test/bufferutils.spec.ts index 0f1f1a901..6895f7e50 100644 --- a/test/bufferutils.spec.ts +++ b/test/bufferutils.spec.ts @@ -316,7 +316,7 @@ describe('bufferutils', () => { } } - it('readUint8', () => { + it('readUInt8', () => { const values = [0, 1, 0xfe, 0xff]; const buffer = Buffer.from([0, 1, 0xfe, 0xff]); const bufferReader = new BufferReader(buffer); diff --git a/ts_src/address.ts b/ts_src/address.ts index a9c0e4103..ce224fd11 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -59,7 +59,7 @@ export function fromBase58Check(address: string): Base58CheckResult { if (payload.length < 21) throw new TypeError(address + ' is too short'); if (payload.length > 21) throw new TypeError(address + ' is too long'); - const version = payload.readUint8(0); + const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; From 105625fb0ae0a56ef50d567308f32292208d2a63 Mon Sep 17 00:00:00 2001 From: junderw Date: Thu, 14 Sep 2023 11:27:50 -0700 Subject: [PATCH 150/249] 6.1.4 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc7dd6138..0cffcca3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 6.1.4 +__changed__ +- Changed internal usage of the Buffer API to match with newer broken bundlers that don't follow spec. The new usage is still compatible with older versions of Buffer, so there shouldn't be any breakage. The public API interface was not changed. (#1975) + # 6.1.3 __fixed__ - validateSignaturesOfInput for taproot inputs returned false for valid signatures in specific cases. (#1934) diff --git a/package-lock.json b/package-lock.json index 7543b1571..839c2724a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.3", + "version": "6.1.4", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 747c8f0f6..5ea1fbc6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.3", + "version": "6.1.4", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From 26ada310ae45747c0fa4689474138457b7cfcfda Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Mon, 18 Sep 2023 13:33:28 -0700 Subject: [PATCH 151/249] Upgrade bip174 to 2.1.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ea1fbc6f..e8fd47ae9 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "dependencies": { "@noble/hashes": "^1.2.0", "bech32": "^2.0.0", - "bip174": "^2.1.0", + "bip174": "^2.1.1", "bs58check": "^3.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" From 8f5f244b0c86cd8a3b92e9451102e5c3c273ede6 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Mon, 18 Sep 2023 14:44:49 -0700 Subject: [PATCH 152/249] Update lockfile. --- package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 839c2724a..2d7a35da7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,12 +6,12 @@ "packages": { "": { "name": "bitcoinjs-lib", - "version": "6.1.3", + "version": "6.1.4", "license": "MIT", "dependencies": { "@noble/hashes": "^1.2.0", "bech32": "^2.0.0", - "bip174": "^2.1.0", + "bip174": "^2.1.1", "bs58check": "^3.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" @@ -1171,9 +1171,9 @@ } }, "node_modules/bip174": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.0.tgz", - "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", "engines": { "node": ">=8.0.0" } @@ -5267,9 +5267,9 @@ "dev": true }, "bip174": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.0.tgz", - "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==" }, "bip32": { "version": "4.0.0", From 4af931720c164f680cad68cd0cd6faeeba77c85c Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 18 Sep 2023 16:25:28 -0700 Subject: [PATCH 153/249] 6.1.5 --- CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cffcca3e..d0485f0e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 6.1.5 +__fixed__ +- Updated bip174 dependency to fix issue with unknownKeyVals. (#1979) + # 6.1.4 __changed__ - Changed internal usage of the Buffer API to match with newer broken bundlers that don't follow spec. The new usage is still compatible with older versions of Buffer, so there shouldn't be any breakage. The public API interface was not changed. (#1975) diff --git a/package-lock.json b/package-lock.json index 2d7a35da7..cb00c5608 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bitcoinjs-lib", - "version": "6.1.4", + "version": "6.1.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bitcoinjs-lib", - "version": "6.1.4", + "version": "6.1.5", "license": "MIT", "dependencies": { "@noble/hashes": "^1.2.0", diff --git a/package.json b/package.json index e8fd47ae9..485afdbf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.4", + "version": "6.1.5", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From 995021373525e055482173d03dfe84b251895a5f Mon Sep 17 00:00:00 2001 From: youssef Date: Tue, 26 Sep 2023 19:34:54 +0000 Subject: [PATCH 154/249] cleaned and refactored --- test/integration/taproot.spec.ts | 64 +++++++++----------------------- 1 file changed, 17 insertions(+), 47 deletions(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 9fee629c3..9708af138 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -1,22 +1,20 @@ import * as assert from 'assert'; import BIP32Factory from 'bip32'; import * as bip39 from 'bip39'; -import ECPairFactory from 'ecpair'; import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; -import { PsbtInput, TapLeafScript } from 'bip174/src/lib/interfaces'; +import { PsbtInput, TapLeaf, TapLeafScript } from 'bip174/src/lib/interfaces'; import { regtestUtils } from './_regtest'; import * as bitcoin from '../..'; import { Taptree } from '../../src/types'; +import { LEAF_VERSION_TAPSCRIPT } from '../../src/payments/bip341'; import { toXOnly, tapTreeToList, tapTreeFromList } from '../../src/psbt/bip371'; import { witnessStackToScriptWitness } from '../../src/psbt/psbtutils'; -import { TapLeaf } from 'bip174/src/lib/interfaces'; const rng = require('randombytes'); const regtest = regtestUtils.network; bitcoin.initEccLib(ecc); const bip32 = BIP32Factory(ecc); -const ECPair = ECPairFactory(ecc); describe('bitcoinjs-lib (transaction with taproot)', () => { it('can verify the BIP86 HD wallet vectors for taproot single sig (& sending example)', async () => { @@ -39,7 +37,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { assert.strictEqual(rootKey.toBase58(), xprv); const childNode = rootKey.derivePath(path); // Since internalKey is an xOnly pubkey, we drop the DER header byte - const childNodeXOnlyPubkey = childNode.publicKey.slice(1, 33); + const childNodeXOnlyPubkey = toXOnly(childNode.publicKey); assert.deepEqual(childNodeXOnlyPubkey, internalPubkey); // This is new for taproot @@ -139,7 +137,9 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { tapInternalKey: sendPubKey, }); - const tweakedSigner = tweakSigner(internalKey!, { network: regtest }); + const tweakedSigner = internalKey.tweak( + bitcoin.crypto.taggedHash('TapTweak', toXOnly(internalKey.publicKey)), + ); await psbt.signInputAsync(0, tweakedSigner); await psbt.signInputAsync(1, p2pkhKey); @@ -194,10 +194,12 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); psbt.addOutput({ value: sendAmount, address: address! }); - const tweakedSigner = tweakSigner(internalKey!, { - tweakHash: hash, - network: regtest, - }); + const tweakedSigner = internalKey.tweak( + bitcoin.crypto.taggedHash( + 'TapTweak', + Buffer.concat([toXOnly(internalKey.publicKey), hash!]), + ), + ); psbt.signInput(0, tweakedSigner); psbt.finalizeAllInputs(); @@ -271,7 +273,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { ]; const redeem = { output: leafScript, - redeemVersion: 192, + redeemVersion: LEAF_VERSION_TAPSCRIPT, }; const { output, witness } = bitcoin.payments.p2tr({ @@ -361,7 +363,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { ]; const redeem = { output: leafScript, - redeemVersion: 192, + redeemVersion: LEAF_VERSION_TAPSCRIPT, }; const { output, witness } = bitcoin.payments.p2tr({ @@ -472,7 +474,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { ]; const redeem = { output: leafScript, - redeemVersion: 192, + redeemVersion: LEAF_VERSION_TAPSCRIPT, }; const { output, address, witness } = bitcoin.payments.p2tr({ @@ -532,7 +534,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { (_, index) => ({ depth: 3, - leafVersion: 192, + leafVersion: LEAF_VERSION_TAPSCRIPT, script: bitcoin.script.fromASM(`OP_ADD OP_${index * 2} OP_EQUAL`), } as TapLeaf), ); @@ -541,7 +543,7 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { for (let leafIndex = 1; leafIndex < leafCount; leafIndex++) { const redeem = { output: bitcoin.script.fromASM(`OP_ADD OP_${leafIndex * 2} OP_EQUAL`), - redeemVersion: 192, + redeemVersion: LEAF_VERSION_TAPSCRIPT, }; const internalKey = bip32.fromSeed(rng(64), regtest); @@ -691,35 +693,3 @@ function buildLeafIndexFinalizer( } }; } - -// This logic will be extracted to ecpair -function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @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]), - ); -} From 5b8f4fc296905b9581428f4e52fd6c2387d391de Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 29 Sep 2023 17:21:31 +0800 Subject: [PATCH 155/249] =?UTF-8?q?=E2=9C=A8feature:=20add=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main_ci.yml | 30 ++++++ package-lock.json | 167 ++++++++++++++++++++++++++++++++-- package.json | 4 +- typedoc.json | 17 ++++ 4 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 typedoc.json diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index e9ba67bb2..360ab725c 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -8,6 +8,9 @@ on: permissions: contents: read + pages: write + id-token: write + jobs: ################## @@ -124,3 +127,30 @@ jobs: cache: 'npm' - run: npm ci - run: npm run lint:tests + + build-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 + - uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # v3 + with: + node-version: 'lts/*' + registry-url: https://registry.npmjs.org/ + cache: 'npm' + - run: npm ci + - run: npm run doc + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: ./docs + + deploy-doc: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build-doc + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/package-lock.json b/package-lock.json index cb00c5608..f3c2ef43c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", + "typedoc": "^0.25.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, @@ -1047,6 +1048,11 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1126,8 +1132,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base-x": { "version": "3.0.9", @@ -2720,6 +2725,11 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2800,6 +2810,11 @@ "node": ">=10" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmmirror.com/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -2830,6 +2845,17 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -3813,6 +3839,17 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "0.14.4", + "resolved": "https://registry.npmmirror.com/shiki/-/shiki-0.14.4.tgz", + "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4170,6 +4207,45 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedoc": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/typedoc/-/typedoc-0.25.1.tgz", + "integrity": "sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==", + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/typeforce": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", @@ -4179,7 +4255,6 @@ "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4255,6 +4330,16 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5170,6 +5255,11 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, + "ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -5231,8 +5321,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base-x": { "version": "3.0.9", @@ -6416,6 +6505,11 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6478,6 +6572,11 @@ "yallist": "^4.0.0" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmmirror.com/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6501,6 +6600,11 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -7234,6 +7338,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shiki": { + "version": "0.14.4", + "resolved": "https://registry.npmmirror.com/shiki/-/shiki-0.14.4.tgz", + "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", + "requires": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -7500,6 +7615,35 @@ "is-typedarray": "^1.0.0" } }, + "typedoc": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/typedoc/-/typedoc-0.25.1.tgz", + "integrity": "sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==", + "requires": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "typeforce": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", @@ -7508,8 +7652,7 @@ "typescript": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", - "dev": true + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==" }, "uint8array-tools": { "version": "0.0.7", @@ -7556,6 +7699,16 @@ "safe-buffer": "^5.1.1" } }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 485afdbf8..b9cec28a4 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "prettier": "prettier \"ts_src/**/*.ts\" \"test/**/*.ts\" --ignore-path ./.prettierignore", "prettierjs": "prettier \"src/**/*.js\" --ignore-path ./.prettierignore", "test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", - "unit": "npm run build && npm run nobuild:unit" + "unit": "npm run build && npm run nobuild:unit", + "doc": "npx typedoc" }, "repository": { "type": "git", @@ -53,6 +54,7 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", + "typedoc": "^0.25.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 000000000..041e85042 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "name": "bitcoinjs-lib", + "entryPoints": ["./ts_src"], + "out": "docs", + "searchCategoryBoosts": { + "Component": 2, + "Model": 1.2 + }, + "searchGroupBoosts": { + "Classes": 1.5 + }, + "visibilityFilters": {}, + "hideGenerator": true, + "excludePrivate": true, + "includeVersion": true +} \ No newline at end of file From bc0ef13081740d3c4d8383758a97a32799e5cc9b Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 29 Sep 2023 19:01:30 +0800 Subject: [PATCH 156/249] =?UTF-8?q?=F0=9F=90=9E=20fix:=20update=20package.?= =?UTF-8?q?json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 58 +++++++++++++++++++++++++++++++++-------------- package.json | 6 ++--- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3c2ef43c..8c03ee2eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", - "typedoc": "^0.25.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, @@ -48,6 +47,7 @@ "rimraf": "^2.6.3", "tiny-secp256k1": "^2.2.0", "ts-node": "^8.3.0", + "typedoc": "^0.25.1", "typescript": "^4.4.4" }, "engines": { @@ -1051,7 +1051,8 @@ "node_modules/ansi-sequence-parser": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true }, "node_modules/ansi-styles": { "version": "4.3.0", @@ -1132,7 +1133,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base-x": { "version": "3.0.9", @@ -2728,7 +2730,8 @@ "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true }, "node_modules/levn": { "version": "0.4.1", @@ -2813,7 +2816,8 @@ "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmmirror.com/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true }, "node_modules/make-dir": { "version": "3.1.0", @@ -2849,6 +2853,7 @@ "version": "4.3.0", "resolved": "https://registry.npmmirror.com/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, "bin": { "marked": "bin/marked.js" }, @@ -3843,6 +3848,7 @@ "version": "0.14.4", "resolved": "https://registry.npmmirror.com/shiki/-/shiki-0.14.4.tgz", "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", + "dev": true, "dependencies": { "ansi-sequence-parser": "^1.1.0", "jsonc-parser": "^3.2.0", @@ -4209,8 +4215,9 @@ }, "node_modules/typedoc": { "version": "0.25.1", - "resolved": "https://registry.npmmirror.com/typedoc/-/typedoc-0.25.1.tgz", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.1.tgz", "integrity": "sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==", + "dev": true, "dependencies": { "lunr": "^2.3.9", "marked": "^4.3.0", @@ -4231,6 +4238,7 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -4239,6 +4247,7 @@ "version": "9.0.3", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4255,6 +4264,7 @@ "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4333,12 +4343,14 @@ "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmmirror.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true }, "node_modules/vscode-textmate": { "version": "8.0.0", "resolved": "https://registry.npmmirror.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true }, "node_modules/which": { "version": "2.0.2", @@ -5258,7 +5270,8 @@ "ansi-sequence-parser": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true }, "ansi-styles": { "version": "4.3.0", @@ -5321,7 +5334,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "base-x": { "version": "3.0.9", @@ -6508,7 +6522,8 @@ "jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true }, "levn": { "version": "0.4.1", @@ -6575,7 +6590,8 @@ "lunr": { "version": "2.3.9", "resolved": "https://registry.npmmirror.com/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true }, "make-dir": { "version": "3.1.0", @@ -6603,7 +6619,8 @@ "marked": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==" + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true }, "md5.js": { "version": "1.3.5", @@ -7342,6 +7359,7 @@ "version": "0.14.4", "resolved": "https://registry.npmmirror.com/shiki/-/shiki-0.14.4.tgz", "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", + "dev": true, "requires": { "ansi-sequence-parser": "^1.1.0", "jsonc-parser": "^3.2.0", @@ -7617,8 +7635,9 @@ }, "typedoc": { "version": "0.25.1", - "resolved": "https://registry.npmmirror.com/typedoc/-/typedoc-0.25.1.tgz", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.1.tgz", "integrity": "sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==", + "dev": true, "requires": { "lunr": "^2.3.9", "marked": "^4.3.0", @@ -7630,6 +7649,7 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "requires": { "balanced-match": "^1.0.0" } @@ -7638,6 +7658,7 @@ "version": "9.0.3", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -7652,7 +7673,8 @@ "typescript": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==" + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true }, "uint8array-tools": { "version": "0.0.7", @@ -7702,12 +7724,14 @@ "vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmmirror.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true }, "vscode-textmate": { "version": "8.0.0", "resolved": "https://registry.npmmirror.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true }, "which": { "version": "2.0.2", diff --git a/package.json b/package.json index b9cec28a4..13c845713 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "coverage-report": "npm run build && npm run nobuild:coverage-report", "coverage-html": "npm run build && npm run nobuild:coverage-html", "coverage": "npm run build && npm run nobuild:coverage", + "doc": "typedoc", "format": "npm run prettier -- --write", "formatjs": "npm run prettierjs -- --write", "format:ci": "npm run prettier -- --check && npm run prettierjs -- --check", @@ -39,8 +40,7 @@ "prettier": "prettier \"ts_src/**/*.ts\" \"test/**/*.ts\" --ignore-path ./.prettierignore", "prettierjs": "prettier \"src/**/*.js\" --ignore-path ./.prettierignore", "test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", - "unit": "npm run build && npm run nobuild:unit", - "doc": "npx typedoc" + "unit": "npm run build && npm run nobuild:unit" }, "repository": { "type": "git", @@ -54,7 +54,6 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", - "typedoc": "^0.25.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, @@ -89,6 +88,7 @@ "rimraf": "^2.6.3", "tiny-secp256k1": "^2.2.0", "ts-node": "^8.3.0", + "typedoc": "^0.25.1", "typescript": "^4.4.4" }, "license": "MIT" From b76e645581775ad8b7a858c66d699ffbb9ac96be Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Sun, 1 Oct 2023 00:11:30 +0800 Subject: [PATCH 157/249] =?UTF-8?q?=E2=9C=A8feature:=20update=20deploy-doc?= =?UTF-8?q?=20ci=20only=20trigger=20on=20master=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main_ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index 360ab725c..2e197b4e6 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -145,6 +145,7 @@ jobs: path: ./docs deploy-doc: + if: github.ref == 'refs/heads/master' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} From 5382260bda7e49547501200e1396ab3a53d97775 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Thu, 5 Oct 2023 18:26:59 +0800 Subject: [PATCH 158/249] =?UTF-8?q?=F0=9F=93=84=20docs:=20comment=20addres?= =?UTF-8?q?s=20namespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ts_src/address.ts | 154 ++++++++++++++++++++++++++++++++++++++++++++++ ts_src/index.ts | 5 +- ts_src/psbt.ts | 5 ++ 3 files changed, 163 insertions(+), 1 deletion(-) diff --git a/ts_src/address.ts b/ts_src/address.ts index ce224fd11..4c674cad9 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -1,3 +1,12 @@ +/** + * bitcoin address decode and encode tools, include base58、bech32 and output script + * + * networks support bitcoin、litecoin、bitcoin testnet、litecoin testnet、bitcoin regtest、litecoin regtest and so on + * + * addresses support P2PKH、P2SH、P2WPKH、P2WSH、P2TR and so on + * + * @packageDocumentation + */ import { Network } from './networks'; import * as networks from './networks'; import * as payments from './payments'; @@ -5,14 +14,22 @@ import * as bscript from './script'; import { typeforce, tuple, Hash160bit, UInt8 } from './types'; import { bech32, bech32m } from 'bech32'; import * as bs58check from 'bs58check'; + +/** base58check decode result */ export interface Base58CheckResult { + /** address hash */ hash: Buffer; + /** address version: 0x00 for P2PKH, 0x05 for P2SH */ version: number; } +/** bech32 decode result */ export interface Bech32Result { + /** address version: 0x00 for P2WPKH、P2WSH, 0x01 for P2TR*/ version: number; + /** address prefix: bc for P2WPKH、P2WSH、P2TR */ prefix: string; + /** address data:20 bytes for P2WPKH, 32 bytes for P2WSH、P2TR */ data: Buffer; } @@ -52,6 +69,23 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string { return toBech32(data, version, network.bech32); } +/** + * decode address with base58 specification, return address version and address hash if valid + * @example + * ```ts + * // valid case + * fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') + * // => {version: 0, hash: } + * + * // invalid case: address is too short + * fromBase58Check('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx') + * // => throw new TypeError('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx is too short') + * + * // invalid case: address is too long + * fromBase58Check('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe') + * // => throw new TypeError('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe is too long') + * ``` + */ export function fromBase58Check(address: string): Base58CheckResult { const payload = Buffer.from(bs58check.decode(address)); @@ -65,6 +99,43 @@ export function fromBase58Check(address: string): Base58CheckResult { return { version, hash }; } +/** + * decode address with bech32 specification, return address version、address prefix and address data if valid + * @example + * ```ts + * // valid case + * fromBech32('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') + * // => {version: 0, prefix: 'bc', data: } + * + * // invalid case + * fromBase58Check('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5') + * // => Invalid checksum + * + * // invalid case + * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7') + * // => Mixed-case string + * + * // invalid case + * fromBase58Check('tb1pw508d6qejxtdg4y5r3zarquvzkan') + * // => Excess padding + * + * // invalid case + * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') + * // => Excess padding + * + * // invalid case + * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') + * // => Non-zero padding + * + * // invalid case + * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv') + * // => uses wrong encoding + * + * // invalid case + * fromBase58Check('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd') + * // => uses wrong encoding + * ``` + */ export function fromBech32(address: string): Bech32Result { let result; let version; @@ -90,6 +161,16 @@ export function fromBech32(address: string): Bech32Result { }; } +/** + * encode address hash to base58 address with version + * + * @example + * ```ts + * // valid case + * toBase58Check('751e76e8199196d454941c45d1b3a323f1433bd6', 0) + * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH + * ``` + */ export function toBase58Check(hash: Buffer, version: number): string { typeforce(tuple(Hash160bit, UInt8), arguments); @@ -100,6 +181,16 @@ export function toBase58Check(hash: Buffer, version: number): string { return bs58check.encode(payload); } +/** + * encode address hash to bech32 address with version and prefix + * + * @example + * ```ts + * // valid case + * toBech32('000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 0, 'tb) + * // => tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy + * ``` + */ export function toBech32( data: Buffer, version: number, @@ -113,6 +204,48 @@ export function toBech32( : bech32m.encode(prefix, words); } +/** + * decode address from output script with network, return address if matched + * @example + * ```ts + * // valid case + * fromOutputScript('OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG', 'bicoin) + * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH + * + * // invalid case + * fromOutputScript('031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_0 75', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 75', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', undefined) + * // => has no matching Address + * + * ``` + */ export function fromOutputScript(output: Buffer, network?: Network): string { // TODO: Network network = network || networks.bitcoin; @@ -139,6 +272,27 @@ export function fromOutputScript(output: Buffer, network?: Network): string { throw new Error(bscript.toASM(output) + ' has no matching Address'); } +/** + * encodes address to output script with network, return output script if address matched + * @example + * ```ts + * // valid case + * toOutputScript('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 'bicoin) + * // => OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG + * + * // invalid case + * toOutputScript('24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE', undefined) + * // => has no matching Script + * + * // invalid case + * toOutputScript('BC1SW50QGDZ25J', { "bech32": "foo" }) + * // => has an invalid prefix + * + * // invalid case + * toOutputScript('bc1rw5uspcuh', undefined) + * // => has no matching Script + * ``` + */ export function toOutputScript(address: string, network?: Network): Buffer { network = network || networks.bitcoin; diff --git a/ts_src/index.ts b/ts_src/index.ts index 64c4294c2..4b4041152 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -7,6 +7,7 @@ import * as script from './script'; export { address, crypto, networks, payments, script }; export { Block } from './block'; +/** @hidden */ export { TaggedHashPrefix } from './crypto'; export { Psbt, @@ -17,10 +18,12 @@ export { HDSigner, HDSignerAsync, } from './psbt'; +/** @hidden */ export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; - +/** @hidden */ export { Network } from './networks'; +/** @hidden */ export { Payment, PaymentCreator, diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index a69dc1e08..66bf91194 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -92,6 +92,7 @@ const DEFAULT_OPTS: PsbtOpts = { * There are 6 roles that this class fulfills. (Explained in BIP174) * * Creator: This can be done with `new Psbt()` + * * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, @@ -102,20 +103,24 @@ const DEFAULT_OPTS: PsbtOpts = { * data for updateOutput. * For a list of what attributes should be what types. Check the bip174 library. * Also, check the integration tests for some examples of usage. + * * Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input * information for your pubkey or pubkeyhash, and only sign inputs where it finds * your info. Or you can explicitly sign a specific input with signInput and * signInputAsync. For the async methods you can create a SignerAsync object * and use something like a hardware wallet to sign with. (You must implement this) + * * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` * the psbt calling combine will always have precedence when a conflict occurs. * Combine checks if the internal bitcoin transaction is the same, so be sure that * all sequences, version, locktime, etc. are the same before combining. + * * Input Finalizer: This role is fairly important. Not only does it need to construct * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. * Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()` * Running any finalize method will delete any data in the input(s) that are no longer * needed due to the finalized scripts containing the information. + * * Transaction Extractor: This role will perform some checks before returning a * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ From 6463bbcf171fd944f939547a5bc93b2cb1e736f7 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Thu, 5 Oct 2023 18:33:41 +0800 Subject: [PATCH 159/249] =?UTF-8?q?=F0=9F=93=84=20docs:=20build=20src=20af?= =?UTF-8?q?ter=20comment=20address=20namespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/address.d.ts | 153 +++++++++++++++++++++++++++++++++++++++++++++++ src/address.js | 137 ++++++++++++++++++++++++++++++++++++++++++ src/index.d.ts | 4 ++ src/index.js | 1 + src/psbt.d.ts | 5 ++ src/psbt.js | 5 ++ 6 files changed, 305 insertions(+) diff --git a/src/address.d.ts b/src/address.d.ts index be0e00a61..132537497 100644 --- a/src/address.d.ts +++ b/src/address.d.ts @@ -1,17 +1,170 @@ /// +/** + * bitcoin address decode and encode tools, include base58、bech32 and output script + * + * networks support bitcoin、litecoin、bitcoin testnet、litecoin testnet、bitcoin regtest、litecoin regtest and so on + * + * addresses support P2PKH、P2SH、P2WPKH、P2WSH、P2TR and so on + * + * @packageDocumentation + */ import { Network } from './networks'; +/** base58check decode result */ export interface Base58CheckResult { + /** address hash */ hash: Buffer; + /** address version: 0x00 for P2PKH, 0x05 for P2SH */ version: number; } +/** bech32 decode result */ export interface Bech32Result { + /** address version: 0x00 for P2WPKH、P2WSH, 0x01 for P2TR*/ version: number; + /** address prefix: bc for P2WPKH、P2WSH、P2TR */ prefix: string; + /** address data:20 bytes for P2WPKH, 32 bytes for P2WSH、P2TR */ data: Buffer; } +/** + * decode address with base58 specification, return address version and address hash if valid + * @example + * ```ts + * // valid case + * fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') + * // => {version: 0, hash: } + * + * // invalid case: address is too short + * fromBase58Check('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx') + * // => throw new TypeError('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx is too short') + * + * // invalid case: address is too long + * fromBase58Check('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe') + * // => throw new TypeError('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe is too long') + * ``` + */ export declare function fromBase58Check(address: string): Base58CheckResult; +/** + * decode address with bech32 specification, return address version、address prefix and address data if valid + * @example + * ```ts + * // valid case + * fromBech32('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') + * // => {version: 0, prefix: 'bc', data: } + * + * // invalid case + * fromBase58Check('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5') + * // => Invalid checksum + * + * // invalid case + * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7') + * // => Mixed-case string + * + * // invalid case + * fromBase58Check('tb1pw508d6qejxtdg4y5r3zarquvzkan') + * // => Excess padding + * + * // invalid case + * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') + * // => Excess padding + * + * // invalid case + * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') + * // => Non-zero padding + * + * // invalid case + * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv') + * // => uses wrong encoding + * + * // invalid case + * fromBase58Check('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd') + * // => uses wrong encoding + * ``` + */ export declare function fromBech32(address: string): Bech32Result; +/** + * encode address hash to base58 address with version + * + * @example + * ```ts + * // valid case + * toBase58Check('751e76e8199196d454941c45d1b3a323f1433bd6', 0) + * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH + * ``` + */ export declare function toBase58Check(hash: Buffer, version: number): string; +/** + * encode address hash to bech32 address with version and prefix + * + * @example + * ```ts + * // valid case + * toBech32('000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 0, 'tb) + * // => tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy + * ``` + */ export declare function toBech32(data: Buffer, version: number, prefix: string): string; +/** + * decode address from output script with network, return address if matched + * @example + * ```ts + * // valid case + * fromOutputScript('OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG', 'bicoin) + * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH + * + * // invalid case + * fromOutputScript('031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_0 75', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 75', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', undefined) + * // => has no matching Address + * + * ``` + */ export declare function fromOutputScript(output: Buffer, network?: Network): string; +/** + * encodes address to output script with network, return output script if address matched + * @example + * ```ts + * // valid case + * toOutputScript('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 'bicoin) + * // => OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG + * + * // invalid case + * toOutputScript('24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE', undefined) + * // => has no matching Script + * + * // invalid case + * toOutputScript('BC1SW50QGDZ25J', { "bech32": "foo" }) + * // => has an invalid prefix + * + * // invalid case + * toOutputScript('bc1rw5uspcuh', undefined) + * // => has no matching Script + * ``` + */ export declare function toOutputScript(address: string, network?: Network): Buffer; diff --git a/src/address.js b/src/address.js index ada8042af..b1b199188 100644 --- a/src/address.js +++ b/src/address.js @@ -41,6 +41,23 @@ function _toFutureSegwitAddress(output, network) { console.warn(FUTURE_SEGWIT_VERSION_WARNING); return toBech32(data, version, network.bech32); } +/** + * decode address with base58 specification, return address version and address hash if valid + * @example + * ```ts + * // valid case + * fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') + * // => {version: 0, hash: } + * + * // invalid case: address is too short + * fromBase58Check('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx') + * // => throw new TypeError('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx is too short') + * + * // invalid case: address is too long + * fromBase58Check('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe') + * // => throw new TypeError('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe is too long') + * ``` + */ function fromBase58Check(address) { const payload = Buffer.from(bs58check.decode(address)); // TODO: 4.0.0, move to "toOutputScript" @@ -51,6 +68,43 @@ function fromBase58Check(address) { return { version, hash }; } exports.fromBase58Check = fromBase58Check; +/** + * decode address with bech32 specification, return address version、address prefix and address data if valid + * @example + * ```ts + * // valid case + * fromBech32('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') + * // => {version: 0, prefix: 'bc', data: } + * + * // invalid case + * fromBase58Check('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5') + * // => Invalid checksum + * + * // invalid case + * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7') + * // => Mixed-case string + * + * // invalid case + * fromBase58Check('tb1pw508d6qejxtdg4y5r3zarquvzkan') + * // => Excess padding + * + * // invalid case + * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') + * // => Excess padding + * + * // invalid case + * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') + * // => Non-zero padding + * + * // invalid case + * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv') + * // => uses wrong encoding + * + * // invalid case + * fromBase58Check('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd') + * // => uses wrong encoding + * ``` + */ function fromBech32(address) { let result; let version; @@ -73,6 +127,16 @@ function fromBech32(address) { }; } exports.fromBech32 = fromBech32; +/** + * encode address hash to base58 address with version + * + * @example + * ```ts + * // valid case + * toBase58Check('751e76e8199196d454941c45d1b3a323f1433bd6', 0) + * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH + * ``` + */ function toBase58Check(hash, version) { (0, types_1.typeforce)( (0, types_1.tuple)(types_1.Hash160bit, types_1.UInt8), @@ -84,6 +148,16 @@ function toBase58Check(hash, version) { return bs58check.encode(payload); } exports.toBase58Check = toBase58Check; +/** + * encode address hash to bech32 address with version and prefix + * + * @example + * ```ts + * // valid case + * toBech32('000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 0, 'tb) + * // => tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy + * ``` + */ function toBech32(data, version, prefix) { const words = bech32_1.bech32.toWords(data); words.unshift(version); @@ -92,6 +166,48 @@ function toBech32(data, version, prefix) { : bech32_1.bech32m.encode(prefix, words); } exports.toBech32 = toBech32; +/** + * decode address from output script with network, return address if matched + * @example + * ```ts + * // valid case + * fromOutputScript('OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG', 'bicoin) + * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH + * + * // invalid case + * fromOutputScript('031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_0 75', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 75', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) + * // => has no matching Address + * + * // invalid case + * fromOutputScript('OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', undefined) + * // => has no matching Address + * + * ``` + */ function fromOutputScript(output, network) { // TODO: Network network = network || networks.bitcoin; @@ -116,6 +232,27 @@ function fromOutputScript(output, network) { throw new Error(bscript.toASM(output) + ' has no matching Address'); } exports.fromOutputScript = fromOutputScript; +/** + * encodes address to output script with network, return output script if address matched + * @example + * ```ts + * // valid case + * toOutputScript('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 'bicoin) + * // => OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG + * + * // invalid case + * toOutputScript('24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE', undefined) + * // => has no matching Script + * + * // invalid case + * toOutputScript('BC1SW50QGDZ25J', { "bech32": "foo" }) + * // => has an invalid prefix + * + * // invalid case + * toOutputScript('bc1rw5uspcuh', undefined) + * // => has no matching Script + * ``` + */ function toOutputScript(address, network) { network = network || networks.bitcoin; let decodeBase58; diff --git a/src/index.d.ts b/src/index.d.ts index 420979ffe..3655f4560 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -5,11 +5,15 @@ import * as payments from './payments'; import * as script from './script'; export { address, crypto, networks, payments, script }; export { Block } from './block'; +/** @hidden */ export { TaggedHashPrefix } from './crypto'; export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt'; +/** @hidden */ export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; +/** @hidden */ export { Network } from './networks'; +/** @hidden */ 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 4159064bd..c742ceef6 100644 --- a/src/index.js +++ b/src/index.js @@ -35,6 +35,7 @@ Object.defineProperty(exports, 'Psbt', { return psbt_1.Psbt; }, }); +/** @hidden */ var ops_1 = require('./ops'); Object.defineProperty(exports, 'opcodes', { enumerable: true, diff --git a/src/psbt.d.ts b/src/psbt.d.ts index de7bbb3d4..d350ca12b 100644 --- a/src/psbt.d.ts +++ b/src/psbt.d.ts @@ -24,6 +24,7 @@ export type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: B * There are 6 roles that this class fulfills. (Explained in BIP174) * * Creator: This can be done with `new Psbt()` + * * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, @@ -34,20 +35,24 @@ export type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: B * data for updateOutput. * For a list of what attributes should be what types. Check the bip174 library. * Also, check the integration tests for some examples of usage. + * * Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input * information for your pubkey or pubkeyhash, and only sign inputs where it finds * your info. Or you can explicitly sign a specific input with signInput and * signInputAsync. For the async methods you can create a SignerAsync object * and use something like a hardware wallet to sign with. (You must implement this) + * * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` * the psbt calling combine will always have precedence when a conflict occurs. * Combine checks if the internal bitcoin transaction is the same, so be sure that * all sequences, version, locktime, etc. are the same before combining. + * * Input Finalizer: This role is fairly important. Not only does it need to construct * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. * Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()` * Running any finalize method will delete any data in the input(s) that are no longer * needed due to the finalized scripts containing the information. + * * Transaction Extractor: This role will perform some checks before returning a * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ diff --git a/src/psbt.js b/src/psbt.js index 71c3589f0..5477cd919 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -34,6 +34,7 @@ const DEFAULT_OPTS = { * There are 6 roles that this class fulfills. (Explained in BIP174) * * Creator: This can be done with `new Psbt()` + * * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, @@ -44,20 +45,24 @@ const DEFAULT_OPTS = { * data for updateOutput. * For a list of what attributes should be what types. Check the bip174 library. * Also, check the integration tests for some examples of usage. + * * Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input * information for your pubkey or pubkeyhash, and only sign inputs where it finds * your info. Or you can explicitly sign a specific input with signInput and * signInputAsync. For the async methods you can create a SignerAsync object * and use something like a hardware wallet to sign with. (You must implement this) + * * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` * the psbt calling combine will always have precedence when a conflict occurs. * Combine checks if the internal bitcoin transaction is the same, so be sure that * all sequences, version, locktime, etc. are the same before combining. + * * Input Finalizer: This role is fairly important. Not only does it need to construct * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. * Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()` * Running any finalize method will delete any data in the input(s) that are no longer * needed due to the finalized scripts containing the information. + * * Transaction Extractor: This role will perform some checks before returning a * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ From c4dbe6f9a8a228fb357f4a9bffb2f26dc15ace72 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Thu, 12 Oct 2023 14:51:07 +0800 Subject: [PATCH 160/249] remove redundant code 1. extract stacksEqual 2. decode P2TR address with fromBech32 3. convergence hashType judgment to isDefinedHashType --- src/payments/embed.js | 8 +------- src/payments/p2ms.js | 10 ++-------- src/payments/p2sh.js | 8 +------- src/payments/p2tr.js | 18 +++--------------- src/payments/p2wsh.js | 8 +------- src/script_signature.js | 9 +++++---- src/types.d.ts | 1 + src/types.js | 8 ++++++++ ts_src/payments/embed.ts | 10 +--------- ts_src/payments/p2ms.ts | 10 +--------- ts_src/payments/p2sh.ts | 10 +--------- ts_src/payments/p2tr.ts | 25 ++++++++----------------- ts_src/payments/p2wsh.ts | 10 +--------- ts_src/script_signature.ts | 9 +++++---- ts_src/types.ts | 8 ++++++++ 15 files changed, 47 insertions(+), 105 deletions(-) diff --git a/src/payments/embed.js b/src/payments/embed.js index 4b7218f36..a465c0815 100644 --- a/src/payments/embed.js +++ b/src/payments/embed.js @@ -6,12 +6,6 @@ const bscript = require('../script'); const types_1 = require('../types'); const lazy = require('./lazy'); const OPS = bscript.OPS; -function stacksEqual(a, b) { - if (a.length !== b.length) return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); -} // output: OP_RETURN ... function p2data(a, opts) { if (!a.data && !a.output) throw new TypeError('Not enough data'); @@ -43,7 +37,7 @@ function p2data(a, opts) { if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid'); if (!chunks.slice(1).every(types_1.typeforce.Buffer)) throw new TypeError('Output is invalid'); - if (a.data && !stacksEqual(a.data, o.data)) + if (a.data && !(0, types_1.stacksEqual)(a.data, o.data)) throw new TypeError('Data mismatch'); } } diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js index 0b7e72d9a..0fb57bb41 100644 --- a/src/payments/p2ms.js +++ b/src/payments/p2ms.js @@ -7,12 +7,6 @@ const types_1 = require('../types'); const lazy = require('./lazy'); const OPS = bscript.OPS; const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 -function stacksEqual(a, b) { - if (a.length !== b.length) return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); -} // input: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG function p2ms(a, opts) { @@ -117,7 +111,7 @@ function p2ms(a, opts) { throw new TypeError('Output is invalid'); if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch'); if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch'); - if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) + if (a.pubkeys && !(0, types_1.stacksEqual)(a.pubkeys, o.pubkeys)) throw new TypeError('Pubkeys mismatch'); } if (a.pubkeys) { @@ -139,7 +133,7 @@ function p2ms(a, opts) { !o.signatures.every(isAcceptableSignature) ) throw new TypeError('Input has invalid signature(s)'); - if (a.signatures && !stacksEqual(a.signatures, o.signatures)) + if (a.signatures && !(0, types_1.stacksEqual)(a.signatures, o.signatures)) throw new TypeError('Signature mismatch'); if (a.m !== undefined && a.m !== a.signatures.length) throw new TypeError('Signature count mismatch'); diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index b280fcafa..66e56b6c8 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -8,12 +8,6 @@ const types_1 = require('../types'); const lazy = require('./lazy'); const bs58check = require('bs58check'); const OPS = bscript.OPS; -function stacksEqual(a, b) { - if (a.length !== b.length) return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); -} // input: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL @@ -188,7 +182,7 @@ function p2sh(a, opts) { if ( a.redeem && a.redeem.witness && - !stacksEqual(a.redeem.witness, a.witness) + !(0, types_1.stacksEqual)(a.redeem.witness, a.witness) ) throw new TypeError('Witness and redeem.witness mismatch'); } diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 83080b434..bc0ab45c7 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -9,6 +9,7 @@ const ecc_lib_1 = require('../ecc_lib'); const bip341_1 = require('./bip341'); const lazy = require('./lazy'); const bech32_1 = require('bech32'); +const address_1 = require('../address'); const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; @@ -53,14 +54,7 @@ function p2tr(a, opts) { 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), - }; + return (0, address_1.fromBech32)(a.address); }); // remove annex if present, ignored by taproot const _witness = lazy.value(() => { @@ -237,7 +231,7 @@ function p2tr(a, opts) { if (a.redeem.witness) { if ( o.redeem.witness && - !stacksEqual(a.redeem.witness, o.redeem.witness) + !(0, types_1.stacksEqual)(a.redeem.witness, o.redeem.witness) ) throw new TypeError('Redeem.witness and witness mismatch'); } @@ -289,9 +283,3 @@ function p2tr(a, opts) { return Object.assign(o, a); } exports.p2tr = p2tr; -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/p2wsh.js b/src/payments/p2wsh.js index 2ad9387e9..b4adebd8e 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -9,12 +9,6 @@ const lazy = require('./lazy'); const bech32_1 = require('bech32'); const OPS = bscript.OPS; const EMPTY_BUFFER = Buffer.alloc(0); -function stacksEqual(a, b) { - if (a.length !== b.length) return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); -} function chunkHasUncompressedPubkey(chunk) { if ( Buffer.isBuffer(chunk) && @@ -190,7 +184,7 @@ function p2wsh(a, opts) { if ( a.witness && a.redeem.witness && - !stacksEqual(a.witness, a.redeem.witness) + !(0, types_1.stacksEqual)(a.witness, a.redeem.witness) ) throw new TypeError('Witness and redeem.witness mismatch'); if ( diff --git a/src/script_signature.js b/src/script_signature.js index 638e5f299..eb252ceb4 100644 --- a/src/script_signature.js +++ b/src/script_signature.js @@ -2,6 +2,7 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.encode = exports.decode = void 0; const bip66 = require('./bip66'); +const script_1 = require('./script'); const types = require('./types'); const { typeforce } = types; const ZERO = Buffer.alloc(1, 0); @@ -23,9 +24,9 @@ function fromDER(x) { // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) function decode(buffer) { const hashType = buffer.readUInt8(buffer.length - 1); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) + if (!(0, script_1.isDefinedHashType)(hashType)) { throw new Error('Invalid hashType ' + hashType); + } const decoded = bip66.decode(buffer.slice(0, -1)); const r = fromDER(decoded.r); const s = fromDER(decoded.s); @@ -41,9 +42,9 @@ function encode(signature, hashType) { }, { signature, hashType }, ); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) + if (!(0, script_1.isDefinedHashType)(hashType)) { throw new Error('Invalid hashType ' + hashType); + } const hashTypeBuffer = Buffer.allocUnsafe(1); hashTypeBuffer.writeUInt8(hashType, 0); const r = toDER(signature.slice(0, 32)); diff --git a/src/types.d.ts b/src/types.d.ts index 0b8a02f9c..5b32ab9de 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,5 +1,6 @@ /// export declare const typeforce: any; +export declare function stacksEqual(a: Buffer[], b: Buffer[]): boolean; 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 acb51ad0b..337badd34 100644 --- a/src/types.js +++ b/src/types.js @@ -27,6 +27,7 @@ exports.oneOf = exports.BIP32Path = exports.UInt31 = exports.isPoint = + exports.stacksEqual = exports.typeforce = void 0; const buffer_1 = require('buffer'); @@ -36,6 +37,13 @@ const EC_P = buffer_1.Buffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); +function stacksEqual(a, b) { + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); +} +exports.stacksEqual = stacksEqual; function isPoint(p) { if (!buffer_1.Buffer.isBuffer(p)) return false; if (p.length < 33) return false; diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index c479b899a..1d9a82807 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -1,19 +1,11 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { typeforce as typef } from '../types'; +import { typeforce as typef, stacksEqual } from '../types'; import { Payment, PaymentOpts, Stack } from './index'; import * as lazy from './lazy'; const OPS = bscript.OPS; -function stacksEqual(a: Buffer[], b: Buffer[]): boolean { - if (a.length !== b.length) return false; - - return a.every((x, i) => { - return x.equals(b[i]); - }); -} - // output: OP_RETURN ... export function p2data(a: Payment, opts?: PaymentOpts): Payment { if (!a.data && !a.output) throw new TypeError('Not enough data'); diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index eaa144069..8ba33479c 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -1,20 +1,12 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { isPoint, typeforce as typef } from '../types'; +import { isPoint, typeforce as typef, stacksEqual } from '../types'; import { Payment, PaymentOpts, Stack } from './index'; import * as lazy from './lazy'; const OPS = bscript.OPS; const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 -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: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG export function p2ms(a: Payment, opts?: PaymentOpts): Payment { diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index ebff7853f..f4a07c55d 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -1,7 +1,7 @@ import * as bcrypto from '../crypto'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { typeforce as typef } from '../types'; +import { typeforce as typef, stacksEqual } from '../types'; import { Payment, PaymentFunction, @@ -13,14 +13,6 @@ import * as lazy from './lazy'; import * as bs58check from 'bs58check'; const OPS = bscript.OPS; -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: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 53f1b40f9..b92d76cc3 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -1,7 +1,12 @@ import { Buffer as NBuffer } from 'buffer'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { typeforce as typef, isTaptree, TAPLEAF_VERSION_MASK } from '../types'; +import { + typeforce as typef, + isTaptree, + TAPLEAF_VERSION_MASK, + stacksEqual, +} from '../types'; import { getEccLib } from '../ecc_lib'; import { toHashTree, @@ -14,6 +19,7 @@ import { import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; import { bech32m } from 'bech32'; +import { fromBech32 } from '../address'; const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; @@ -54,14 +60,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { ); 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), - }; + return fromBech32(a.address!); }); // remove annex if present, ignored by taproot @@ -314,11 +313,3 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { return Object.assign(o, a); } - -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/p2wsh.ts b/ts_src/payments/p2wsh.ts index 1ba384924..41cefe686 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -1,7 +1,7 @@ import * as bcrypto from '../crypto'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; -import { isPoint, typeforce as typef } from '../types'; +import { isPoint, typeforce as typef, stacksEqual } from '../types'; import { Payment, PaymentOpts, StackElement, StackFunction } from './index'; import * as lazy from './lazy'; import { bech32 } from 'bech32'; @@ -9,14 +9,6 @@ const OPS = bscript.OPS; const EMPTY_BUFFER = Buffer.alloc(0); -function stacksEqual(a: Buffer[], b: Buffer[]): boolean { - if (a.length !== b.length) return false; - - return a.every((x, i) => { - return x.equals(b[i]); - }); -} - function chunkHasUncompressedPubkey(chunk: StackElement): boolean { if ( Buffer.isBuffer(chunk) && diff --git a/ts_src/script_signature.ts b/ts_src/script_signature.ts index df206e813..f10ed86b7 100644 --- a/ts_src/script_signature.ts +++ b/ts_src/script_signature.ts @@ -1,4 +1,5 @@ import * as bip66 from './bip66'; +import { isDefinedHashType } from './script'; import * as types from './types'; const { typeforce } = types; @@ -28,9 +29,9 @@ interface ScriptSignature { // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) export function decode(buffer: Buffer): ScriptSignature { const hashType = buffer.readUInt8(buffer.length - 1); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) + if (!isDefinedHashType(hashType)) { throw new Error('Invalid hashType ' + hashType); + } const decoded = bip66.decode(buffer.slice(0, -1)); const r = fromDER(decoded.r); @@ -49,9 +50,9 @@ export function encode(signature: Buffer, hashType: number): Buffer { { signature, hashType }, ); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) + if (!isDefinedHashType(hashType)) { throw new Error('Invalid hashType ' + hashType); + } const hashTypeBuffer = Buffer.allocUnsafe(1); hashTypeBuffer.writeUInt8(hashType, 0); diff --git a/ts_src/types.ts b/ts_src/types.ts index 2457ec240..937805617 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -8,6 +8,14 @@ const EC_P = NBuffer.from( 'hex', ); +export function stacksEqual(a: Buffer[], b: Buffer[]): boolean { + if (a.length !== b.length) return false; + + return a.every((x, i) => { + return x.equals(b[i]); + }); +} + export function isPoint(p: Buffer | number | undefined | null): boolean { if (!NBuffer.isBuffer(p)) return false; if (p.length < 33) return false; From 63f84d45c3645d3d143d3583ee08417b4e7b62e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 03:19:24 +0000 Subject: [PATCH 161/249] chore(deps-dev): bump @babel/traverse from 7.20.5 to 7.23.2 Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.5 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 381 +++++++++++++++++++++++++++++++--------------- 1 file changed, 257 insertions(+), 124 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb00c5608..caa0fb630 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,17 +67,89 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/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": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/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/@babel/compat-data": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", @@ -127,13 +199,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -182,34 +255,34 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -259,30 +332,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -312,13 +385,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -397,9 +470,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -409,33 +482,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -453,13 +526,13 @@ } }, "node_modules/@babel/types": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", - "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -4461,12 +4534,71 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "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, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { @@ -4507,13 +4639,14 @@ } }, "@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "dependencies": { @@ -4551,28 +4684,28 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { @@ -4610,24 +4743,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -4648,13 +4781,13 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -4717,36 +4850,36 @@ } }, "@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -4760,13 +4893,13 @@ } }, "@babel/types": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", - "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, From a52503fcbf4f7f86b33e8e6d3aa19f93add2b5c8 Mon Sep 17 00:00:00 2001 From: Jonathan Underwood Date: Sun, 29 Oct 2023 18:09:20 +0900 Subject: [PATCH 162/249] Update communication channels in CONTRIBUTING --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1df5e090..7797c92d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,8 +14,8 @@ We are always accepting of pull requests, but we do adhere to specific standards GitHub is the preferred method of communication between members. Otherwise, in order of preference: -* bitcoinjs.slack.com -* #bitcoinjs-dev on Freenode IRC +* #bitcoinjs-dev:matrix.org on Matrix (A part of the #bitcoinjs-space:matrix.org "Space") +* #bitcoinjs on libera.chat IRC ## Workflow From caea9606bf38596f5fee3c95a1c6acf50d023bbc Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Thu, 2 Nov 2023 14:21:08 +0800 Subject: [PATCH 163/249] =?UTF-8?q?=E2=9C=A8feature:=20add=20custom=20tag?= =?UTF-8?q?=20@case=20to=20create=20a=20runnable=20environment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + package-lock.json | 100 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + ts_src/address.ts | 17 +++----- typedoc.json | 1 + 5 files changed, 104 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index d6ceb6b58..c1733a6e0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ npm-debug.log test/*.js test/integration/*.js !test/ts-node-register.js +docs diff --git a/package-lock.json b/package-lock.json index 8c03ee2eb..55189abba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", + "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, @@ -2676,8 +2677,7 @@ "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 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -2801,6 +2801,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3584,6 +3595,29 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, "node_modules/readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -3780,6 +3814,14 @@ } ] }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -4234,6 +4276,15 @@ "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" } }, + "node_modules/typedoc-plugin-bitcoinjs-runcase": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-bitcoinjs-runcase/-/typedoc-plugin-bitcoinjs-runcase-1.0.1.tgz", + "integrity": "sha512-tqbnCCP2ku2egGjwn0G0bRNeSH/lh3FT7khrpyYdx1kDu8rjKlyqJX7yPgEj4biBHJATJnQGxp3cKmYG0wv5uw==", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/typedoc/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -6483,8 +6534,7 @@ "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 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "4.1.0", @@ -6578,6 +6628,14 @@ "is-unicode-supported": "^0.1.0" } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7173,6 +7231,23 @@ "safe-buffer": "^5.1.0" } }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, "readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -7306,6 +7381,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -7665,6 +7748,15 @@ } } }, + "typedoc-plugin-bitcoinjs-runcase": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-bitcoinjs-runcase/-/typedoc-plugin-bitcoinjs-runcase-1.0.1.tgz", + "integrity": "sha512-tqbnCCP2ku2egGjwn0G0bRNeSH/lh3FT7khrpyYdx1kDu8rjKlyqJX7yPgEj4biBHJATJnQGxp3cKmYG0wv5uw==", + "requires": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "typeforce": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", diff --git a/package.json b/package.json index 13c845713..2c057e38e 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", + "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, diff --git a/ts_src/address.ts b/ts_src/address.ts index 4c674cad9..50326c859 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -71,19 +71,12 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string { /** * decode address with base58 specification, return address version and address hash if valid - * @example + * @case * ```ts - * // valid case - * fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') - * // => {version: 0, hash: } - * - * // invalid case: address is too short - * fromBase58Check('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx') - * // => throw new TypeError('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx is too short') - * - * // invalid case: address is too long - * fromBase58Check('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe') - * // => throw new TypeError('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe is too long') + * // You can test it here and find more case in test/address.spec.ts + * const result = address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') + * console.log(JSON.stringify(result)) + * // => {"version":0,"hash":{"type":"Buffer","data":[117,30,118,232,25,145,150,212,84,148,28,69,209,179,163,35,241,67,59,214]}} * ``` */ export function fromBase58Check(address: string): Base58CheckResult { diff --git a/typedoc.json b/typedoc.json index 041e85042..c731be6c8 100644 --- a/typedoc.json +++ b/typedoc.json @@ -10,6 +10,7 @@ "searchGroupBoosts": { "Classes": 1.5 }, + "plugin": ["typedoc-plugin-bitcoinjs-runcase"], "visibilityFilters": {}, "hideGenerator": true, "excludePrivate": true, From 19b31eb01be0fa9f20b2d6676d92aac77d00b0e4 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Thu, 2 Nov 2023 14:25:30 +0800 Subject: [PATCH 164/249] =?UTF-8?q?=F0=9F=90=9E=20fix:=20build=20src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/address.d.ts | 17 +++++------------ src/address.js | 17 +++++------------ 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/address.d.ts b/src/address.d.ts index 132537497..5f3af8a78 100644 --- a/src/address.d.ts +++ b/src/address.d.ts @@ -27,19 +27,12 @@ export interface Bech32Result { } /** * decode address with base58 specification, return address version and address hash if valid - * @example + * @case * ```ts - * // valid case - * fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') - * // => {version: 0, hash: } - * - * // invalid case: address is too short - * fromBase58Check('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx') - * // => throw new TypeError('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx is too short') - * - * // invalid case: address is too long - * fromBase58Check('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe') - * // => throw new TypeError('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe is too long') + * // You can test it here and find more case in test/address.spec.ts + * const result = address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') + * console.log(JSON.stringify(result)) + * // => {"version":0,"hash":{"type":"Buffer","data":[117,30,118,232,25,145,150,212,84,148,28,69,209,179,163,35,241,67,59,214]}} * ``` */ export declare function fromBase58Check(address: string): Base58CheckResult; diff --git a/src/address.js b/src/address.js index b1b199188..11cba0686 100644 --- a/src/address.js +++ b/src/address.js @@ -43,19 +43,12 @@ function _toFutureSegwitAddress(output, network) { } /** * decode address with base58 specification, return address version and address hash if valid - * @example + * @case * ```ts - * // valid case - * fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') - * // => {version: 0, hash: } - * - * // invalid case: address is too short - * fromBase58Check('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx') - * // => throw new TypeError('7SeEnXWPaCCALbVrTnszCVGfRU8cGfx is too short') - * - * // invalid case: address is too long - * fromBase58Check('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe') - * // => throw new TypeError('j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe is too long') + * // You can test it here and find more case in test/address.spec.ts + * const result = address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') + * console.log(JSON.stringify(result)) + * // => {"version":0,"hash":{"type":"Buffer","data":[117,30,118,232,25,145,150,212,84,148,28,69,209,179,163,35,241,67,59,214]}} * ``` */ function fromBase58Check(address) { From f5995bd07b476fe138f7327c362a68813e8010ab Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Sun, 5 Nov 2023 10:38:11 +0800 Subject: [PATCH 165/249] =?UTF-8?q?=F0=9F=90=9E=20fix:=20move=20typedoc-pl?= =?UTF-8?q?ugin-bitcoinjs-runcase=20to=20devDependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 18 +++++++++++++++--- package.json | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55189abba..eb48abc49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", - "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, @@ -49,6 +48,7 @@ "tiny-secp256k1": "^2.2.0", "ts-node": "^8.3.0", "typedoc": "^0.25.1", + "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typescript": "^4.4.4" }, "engines": { @@ -2677,7 +2677,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==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -2805,6 +2806,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -3599,6 +3601,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3610,6 +3613,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -3818,6 +3822,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -4280,6 +4285,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/typedoc-plugin-bitcoinjs-runcase/-/typedoc-plugin-bitcoinjs-runcase-1.0.1.tgz", "integrity": "sha512-tqbnCCP2ku2egGjwn0G0bRNeSH/lh3FT7khrpyYdx1kDu8rjKlyqJX7yPgEj4biBHJATJnQGxp3cKmYG0wv5uw==", + "dev": true, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" @@ -6534,7 +6540,8 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "4.1.0", @@ -6632,6 +6639,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -7235,6 +7243,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -7243,6 +7252,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -7385,6 +7395,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -7752,6 +7763,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/typedoc-plugin-bitcoinjs-runcase/-/typedoc-plugin-bitcoinjs-runcase-1.0.1.tgz", "integrity": "sha512-tqbnCCP2ku2egGjwn0G0bRNeSH/lh3FT7khrpyYdx1kDu8rjKlyqJX7yPgEj4biBHJATJnQGxp3cKmYG0wv5uw==", + "dev": true, "requires": { "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/package.json b/package.json index 2c057e38e..997318f1e 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "bech32": "^2.0.0", "bip174": "^2.1.1", "bs58check": "^3.0.1", - "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" }, @@ -90,6 +89,7 @@ "tiny-secp256k1": "^2.2.0", "ts-node": "^8.3.0", "typedoc": "^0.25.1", + "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typescript": "^4.4.4" }, "license": "MIT" From 4a1913ea243a704d77ad579960bf312b76773944 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 1 Mar 2024 01:46:59 +0800 Subject: [PATCH 166/249] =?UTF-8?q?=E2=8F=AA=20rollback:=20separate=20type?= =?UTF-8?q?doc-plugin-bitcoinjs-runcase=20to=20another=20PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - typedoc.json | 1 - 2 files changed, 2 deletions(-) diff --git a/package.json b/package.json index 997318f1e..13c845713 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "tiny-secp256k1": "^2.2.0", "ts-node": "^8.3.0", "typedoc": "^0.25.1", - "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typescript": "^4.4.4" }, "license": "MIT" diff --git a/typedoc.json b/typedoc.json index c731be6c8..041e85042 100644 --- a/typedoc.json +++ b/typedoc.json @@ -10,7 +10,6 @@ "searchGroupBoosts": { "Classes": 1.5 }, - "plugin": ["typedoc-plugin-bitcoinjs-runcase"], "visibilityFilters": {}, "hideGenerator": true, "excludePrivate": true, From 5bf4367ed615dddfab6ad059175c6cd8984f5fbb Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 1 Mar 2024 01:48:18 +0800 Subject: [PATCH 167/249] =?UTF-8?q?=E2=8F=AA=20rollback:=20remove=20runcas?= =?UTF-8?q?e=20of=20address=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ts_src/address.ts | 114 +--------------------------------------------- 1 file changed, 1 insertion(+), 113 deletions(-) diff --git a/ts_src/address.ts b/ts_src/address.ts index 50326c859..cbd03da1f 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -1,7 +1,7 @@ /** * bitcoin address decode and encode tools, include base58、bech32 and output script * - * networks support bitcoin、litecoin、bitcoin testnet、litecoin testnet、bitcoin regtest、litecoin regtest and so on + * networks support bitcoin、bitcoin testnet and bitcoin regtest * * addresses support P2PKH、P2SH、P2WPKH、P2WSH、P2TR and so on * @@ -71,13 +71,6 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string { /** * decode address with base58 specification, return address version and address hash if valid - * @case - * ```ts - * // You can test it here and find more case in test/address.spec.ts - * const result = address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') - * console.log(JSON.stringify(result)) - * // => {"version":0,"hash":{"type":"Buffer","data":[117,30,118,232,25,145,150,212,84,148,28,69,209,179,163,35,241,67,59,214]}} - * ``` */ export function fromBase58Check(address: string): Base58CheckResult { const payload = Buffer.from(bs58check.decode(address)); @@ -94,40 +87,6 @@ export function fromBase58Check(address: string): Base58CheckResult { /** * decode address with bech32 specification, return address version、address prefix and address data if valid - * @example - * ```ts - * // valid case - * fromBech32('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') - * // => {version: 0, prefix: 'bc', data: } - * - * // invalid case - * fromBase58Check('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5') - * // => Invalid checksum - * - * // invalid case - * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7') - * // => Mixed-case string - * - * // invalid case - * fromBase58Check('tb1pw508d6qejxtdg4y5r3zarquvzkan') - * // => Excess padding - * - * // invalid case - * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') - * // => Excess padding - * - * // invalid case - * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') - * // => Non-zero padding - * - * // invalid case - * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv') - * // => uses wrong encoding - * - * // invalid case - * fromBase58Check('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd') - * // => uses wrong encoding - * ``` */ export function fromBech32(address: string): Bech32Result { let result; @@ -156,13 +115,6 @@ export function fromBech32(address: string): Bech32Result { /** * encode address hash to base58 address with version - * - * @example - * ```ts - * // valid case - * toBase58Check('751e76e8199196d454941c45d1b3a323f1433bd6', 0) - * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH - * ``` */ export function toBase58Check(hash: Buffer, version: number): string { typeforce(tuple(Hash160bit, UInt8), arguments); @@ -176,13 +128,6 @@ export function toBase58Check(hash: Buffer, version: number): string { /** * encode address hash to bech32 address with version and prefix - * - * @example - * ```ts - * // valid case - * toBech32('000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 0, 'tb) - * // => tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy - * ``` */ export function toBech32( data: Buffer, @@ -199,45 +144,6 @@ export function toBech32( /** * decode address from output script with network, return address if matched - * @example - * ```ts - * // valid case - * fromOutputScript('OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG', 'bicoin) - * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH - * - * // invalid case - * fromOutputScript('031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_0 75', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 75', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', undefined) - * // => has no matching Address - * - * ``` */ export function fromOutputScript(output: Buffer, network?: Network): string { // TODO: Network @@ -267,24 +173,6 @@ export function fromOutputScript(output: Buffer, network?: Network): string { /** * encodes address to output script with network, return output script if address matched - * @example - * ```ts - * // valid case - * toOutputScript('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 'bicoin) - * // => OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG - * - * // invalid case - * toOutputScript('24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE', undefined) - * // => has no matching Script - * - * // invalid case - * toOutputScript('BC1SW50QGDZ25J', { "bech32": "foo" }) - * // => has an invalid prefix - * - * // invalid case - * toOutputScript('bc1rw5uspcuh', undefined) - * // => has no matching Script - * ``` */ export function toOutputScript(address: string, network?: Network): Buffer { network = network || networks.bitcoin; From 6cc676d4b579b59e23622a6925d82d6c23879205 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 1 Mar 2024 01:49:00 +0800 Subject: [PATCH 168/249] =?UTF-8?q?=F0=9F=93=84=20docs:=20add=20docs=20for?= =?UTF-8?q?=20payments=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ts_src/payments/bip341.ts | 7 +++++++ ts_src/payments/embed.ts | 7 +++++++ ts_src/payments/index.ts | 7 +++++++ ts_src/payments/p2ms.ts | 7 +++++++ ts_src/payments/p2pk.ts | 8 ++++++++ ts_src/payments/p2pkh.ts | 8 ++++++++ ts_src/payments/p2sh.ts | 8 ++++++++ ts_src/payments/p2tr.ts | 8 ++++++++ ts_src/payments/p2wpkh.ts | 8 ++++++++ ts_src/payments/p2wsh.ts | 8 ++++++++ 10 files changed, 76 insertions(+) diff --git a/ts_src/payments/bip341.ts b/ts_src/payments/bip341.ts index 40793779f..af9b1f171 100644 --- a/ts_src/payments/bip341.ts +++ b/ts_src/payments/bip341.ts @@ -34,6 +34,13 @@ const isHashBranch = (ht: HashTree): ht is HashBranch => */ export type HashTree = HashLeaf | HashBranch; +/** + * Calculates the root hash from a given control block and leaf hash. + * @param controlBlock - The control block buffer. + * @param leafHash - The leaf hash buffer. + * @returns The root hash buffer. + * @throws {TypeError} If the control block length is less than 33. + */ export function rootHashFromPath( controlBlock: Buffer, leafHash: Buffer, diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index c479b899a..84843333c 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -15,6 +15,13 @@ function stacksEqual(a: Buffer[], b: Buffer[]): boolean { } // output: OP_RETURN ... +/** + * Embeds data in a Bitcoin payment. + * @param a - The payment object. + * @param opts - Optional payment options. + * @returns The modified payment object. + * @throws {TypeError} If there is not enough data or if the output is invalid. + */ export function p2data(a: Payment, opts?: PaymentOpts): Payment { if (!a.data && !a.output) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index ca72f72cb..ac539a6ff 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,3 +1,10 @@ +/** + * Represents a payment object, which is used to create a payment. + * + * Supports P2PKH、P2SH、P2WPKH、P2WSH、P2TR and so on + * + * @packageDocumentation + */ import { Network } from '../networks'; import { Taptree } from '../types'; import { p2data as embed } from './embed'; diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index eaa144069..536b02913 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -17,6 +17,13 @@ function stacksEqual(a: Buffer[], b: Buffer[]): boolean { // input: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG +/** + * Represents a function that creates a Pay-to-Multisig (P2MS) payment object. + * @param a - The payment object. + * @param opts - Optional payment options. + * @returns The created payment object. + * @throws {TypeError} If the provided data is not valid. + */ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { if ( !a.input && diff --git a/ts_src/payments/p2pk.ts b/ts_src/payments/p2pk.ts index 7273f53fb..097e72b3c 100644 --- a/ts_src/payments/p2pk.ts +++ b/ts_src/payments/p2pk.ts @@ -7,6 +7,14 @@ const OPS = bscript.OPS; // input: {signature} // output: {pubKey} OP_CHECKSIG +/** + * Creates a pay-to-public-key (P2PK) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2PK payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ export function p2pk(a: Payment, opts?: PaymentOpts): Payment { if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) throw new TypeError('Not enough data'); diff --git a/ts_src/payments/p2pkh.ts b/ts_src/payments/p2pkh.ts index 4947c832a..2873a6ebe 100644 --- a/ts_src/payments/p2pkh.ts +++ b/ts_src/payments/p2pkh.ts @@ -9,6 +9,14 @@ const OPS = bscript.OPS; // input: {signature} {pubkey} // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG +/** + * Creates a Pay-to-Public-Key-Hash (P2PKH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2PKH payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ export function p2pkh(a: Payment, opts?: PaymentOpts): Payment { if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) throw new TypeError('Not enough data'); diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index ebff7853f..9bf841073 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -24,6 +24,14 @@ function stacksEqual(a: Buffer[], b: Buffer[]): boolean { // input: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL +/** + * Creates a Pay-to-Script-Hash (P2SH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2SH payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) throw new TypeError('Not enough data'); diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index 53f1b40f9..b288666c4 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -19,6 +19,14 @@ const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; +/** + * Creates a Pay-to-Taproot (P2TR) payment object. + * + * @param a - The payment object containing the necessary data for P2TR. + * @param opts - Optional payment options. + * @returns The P2TR payment object. + * @throws {TypeError} If the provided data is invalid or insufficient. + */ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { if ( !a.address && diff --git a/ts_src/payments/p2wpkh.ts b/ts_src/payments/p2wpkh.ts index a4497fece..997cb6292 100644 --- a/ts_src/payments/p2wpkh.ts +++ b/ts_src/payments/p2wpkh.ts @@ -12,6 +12,14 @@ const EMPTY_BUFFER = Buffer.alloc(0); // witness: {signature} {pubKey} // input: <> // output: OP_0 {pubKeyHash} +/** + * Creates a pay-to-witness-public-key-hash (p2wpkh) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The p2wpkh payment object. + * @throws {TypeError} If the required data is missing or invalid. + */ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) throw new TypeError('Not enough data'); diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index 1ba384924..b5162f5e1 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -33,6 +33,14 @@ function chunkHasUncompressedPubkey(chunk: StackElement): boolean { // input: <> // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} +/** + * Creates a Pay-to-Witness-Script-Hash (P2WSH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2WSH payment object. + * @throws {TypeError} If the required data is missing or invalid. + */ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) throw new TypeError('Not enough data'); From 94039aeed91182dac9d66653a122f35a4bb5adc1 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 1 Mar 2024 01:49:26 +0800 Subject: [PATCH 169/249] =?UTF-8?q?=F0=9F=93=84=20docs:=20add=20docs=20fro?= =?UTF-8?q?=20script=20namespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ts_src/script.ts | 29 +++++++++++++++++++++++++++++ ts_src/script_number.ts | 16 ++++++++++++++++ ts_src/script_signature.ts | 25 +++++++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/ts_src/script.ts b/ts_src/script.ts index 4f246fe7a..329831497 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -1,3 +1,7 @@ +/** + * Script tools, including decompile, compile, toASM, fromASM, toStack, isCanonicalPubKey, isCanonicalScriptSignature + * @packageDocumentation + */ import * as bip66 from './bip66'; import { OPS, REVERSE_OPS } from './ops'; import { Stack } from './payments'; @@ -7,6 +11,7 @@ import * as scriptSignature from './script_signature'; import * as types from './types'; const { typeforce } = types; + const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 export { OPS }; @@ -50,6 +55,13 @@ function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { return Buffer.isBuffer(buf); } +/** + * Compiles an array of chunks into a Buffer. + * + * @param chunks - The array of chunks to compile. + * @returns The compiled Buffer. + * @throws Error if the compilation fails. + */ export function compile(chunks: Buffer | Stack): Buffer { // TODO: remove me if (chunksIsBuffer(chunks)) return chunks; @@ -147,6 +159,12 @@ export function decompile( return chunks; } +/** + * Converts the given chunks into an ASM (Assembly) string representation. + * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. + * @param chunks - The chunks to convert into ASM. + * @returns The ASM string representation of the chunks. + */ export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks) as Stack; @@ -167,6 +185,11 @@ export function toASM(chunks: Buffer | Array): string { .join(' '); } +/** + * Converts an ASM string to a Buffer. + * @param asm The ASM string to convert. + * @returns The converted Buffer. + */ export function fromASM(asm: string): Buffer { typeforce(types.String, asm); @@ -182,6 +205,12 @@ export function fromASM(asm: string): Buffer { ); } +/** + * Converts the given chunks into a stack of buffers. + * + * @param chunks - The chunks to convert. + * @returns The stack of buffers. + */ export function toStack(chunks: Buffer | Array): Buffer[] { chunks = decompile(chunks) as Stack; typeforce(isPushOnly, chunks); diff --git a/ts_src/script_number.ts b/ts_src/script_number.ts index a4c502fc9..19e522f54 100644 --- a/ts_src/script_number.ts +++ b/ts_src/script_number.ts @@ -1,3 +1,13 @@ +/** + * Decodes a script number from a buffer. + * + * @param buffer - The buffer containing the script number. + * @param maxLength - The maximum length of the script number. Defaults to 4. + * @param minimal - Whether the script number should be minimal. Defaults to true. + * @returns The decoded script number. + * @throws {TypeError} If the script number overflows the maximum length. + * @throws {Error} If the script number is not minimally encoded when minimal is true. + */ export function decode( buffer: Buffer, maxLength?: number, @@ -50,6 +60,12 @@ function scriptNumSize(i: number): number { : 0; } +/** + * Encodes a number into a Buffer using a specific format. + * + * @param _number - The number to encode. + * @returns The encoded number as a Buffer. + */ export function encode(_number: number): Buffer { let value = Math.abs(_number); const size = scriptNumSize(value); diff --git a/ts_src/script_signature.ts b/ts_src/script_signature.ts index df206e813..74e17051e 100644 --- a/ts_src/script_signature.ts +++ b/ts_src/script_signature.ts @@ -3,6 +3,11 @@ import * as types from './types'; const { typeforce } = types; const ZERO = Buffer.alloc(1, 0); +/** + * Converts a buffer to a DER-encoded buffer. + * @param x - The buffer to be converted. + * @returns The DER-encoded buffer. + */ function toDER(x: Buffer): Buffer { let i = 0; while (x[i] === 0) ++i; @@ -12,6 +17,13 @@ function toDER(x: Buffer): Buffer { return x; } +/** + * Converts a DER-encoded signature to a buffer. + * If the first byte of the input buffer is 0x00, it is skipped. + * The resulting buffer is 32 bytes long, filled with zeros if necessary. + * @param x - The DER-encoded signature. + * @returns The converted buffer. + */ function fromDER(x: Buffer): Buffer { if (x[0] === 0x00) x = x.slice(1); const buffer = Buffer.alloc(32, 0); @@ -26,6 +38,12 @@ interface ScriptSignature { } // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) +/** + * Decodes a buffer into a ScriptSignature object. + * @param buffer - The buffer to decode. + * @returns The decoded ScriptSignature object. + * @throws Error if the hashType is invalid. + */ export function decode(buffer: Buffer): ScriptSignature { const hashType = buffer.readUInt8(buffer.length - 1); const hashTypeMod = hashType & ~0x80; @@ -40,6 +58,13 @@ export function decode(buffer: Buffer): ScriptSignature { return { signature, hashType }; } +/** + * Encodes a signature and hash type into a buffer. + * @param signature - The signature to encode. + * @param hashType - The hash type to encode. + * @returns The encoded buffer. + * @throws Error if the hashType is invalid. + */ export function encode(signature: Buffer, hashType: number): Buffer { typeforce( { From 3dc81f76a5a59fdc8f7336fb6c82910d7025fd31 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 1 Mar 2024 01:50:28 +0800 Subject: [PATCH 170/249] =?UTF-8?q?=F0=9F=93=84=20docs:=20add=20other=20do?= =?UTF-8?q?cs=20and=20build=20src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/address.d.ts | 114 +------------------------------------- src/address.js | 112 ------------------------------------- src/bufferutils.d.ts | 13 +++++ src/bufferutils.js | 13 +++++ src/crypto.d.ts | 3 + src/crypto.js | 9 +++ src/ecc_lib.d.ts | 14 +++++ src/ecc_lib.js | 19 +++++++ src/merkle.d.ts | 8 +++ src/merkle.js | 8 +++ src/networks.d.ts | 14 +++++ src/networks.js | 35 ++++++++++++ src/payments/bip341.d.ts | 7 +++ src/payments/bip341.js | 7 +++ src/payments/embed.d.ts | 7 +++ src/payments/embed.js | 7 +++ src/payments/index.d.ts | 7 +++ src/payments/p2ms.d.ts | 7 +++ src/payments/p2ms.js | 7 +++ src/payments/p2pk.d.ts | 8 +++ src/payments/p2pk.js | 8 +++ src/payments/p2pkh.d.ts | 8 +++ src/payments/p2pkh.js | 8 +++ src/payments/p2sh.d.ts | 8 +++ src/payments/p2sh.js | 8 +++ src/payments/p2tr.d.ts | 8 +++ src/payments/p2tr.js | 8 +++ src/payments/p2wpkh.d.ts | 8 +++ src/payments/p2wpkh.js | 8 +++ src/payments/p2wsh.d.ts | 8 +++ src/payments/p2wsh.js | 8 +++ src/psbt/bip371.js | 41 ++++++++++++++ src/psbt/psbtutils.d.ts | 36 ++++++++++++ src/psbt/psbtutils.js | 54 ++++++++++++++++++ src/push_data.d.ts | 21 +++++++ src/push_data.js | 21 +++++++ src/script.d.ts | 24 ++++++++ src/script.js | 28 ++++++++++ src/script_number.d.ts | 16 ++++++ src/script_number.js | 16 ++++++ src/script_signature.d.ts | 13 +++++ src/script_signature.js | 25 +++++++++ src/transaction.d.ts | 3 + src/transaction.js | 3 + src/types.d.ts | 5 ++ src/types.js | 5 ++ ts_src/bufferutils.ts | 13 +++++ ts_src/crypto.ts | 9 +++ ts_src/ecc_lib.ts | 19 +++++++ ts_src/merkle.ts | 8 +++ ts_src/networks.ts | 39 +++++++++++++ ts_src/psbt/bip371.ts | 41 ++++++++++++++ ts_src/psbt/psbtutils.ts | 54 ++++++++++++++++++ ts_src/push_data.ts | 21 +++++++ ts_src/transaction.ts | 3 + ts_src/types.ts | 5 ++ 56 files changed, 805 insertions(+), 225 deletions(-) diff --git a/src/address.d.ts b/src/address.d.ts index 5f3af8a78..6b5bc9c2a 100644 --- a/src/address.d.ts +++ b/src/address.d.ts @@ -2,7 +2,7 @@ /** * bitcoin address decode and encode tools, include base58、bech32 and output script * - * networks support bitcoin、litecoin、bitcoin testnet、litecoin testnet、bitcoin regtest、litecoin regtest and so on + * networks support bitcoin、bitcoin testnet and bitcoin regtest * * addresses support P2PKH、P2SH、P2WPKH、P2WSH、P2TR and so on * @@ -27,137 +27,25 @@ export interface Bech32Result { } /** * decode address with base58 specification, return address version and address hash if valid - * @case - * ```ts - * // You can test it here and find more case in test/address.spec.ts - * const result = address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') - * console.log(JSON.stringify(result)) - * // => {"version":0,"hash":{"type":"Buffer","data":[117,30,118,232,25,145,150,212,84,148,28,69,209,179,163,35,241,67,59,214]}} - * ``` */ export declare function fromBase58Check(address: string): Base58CheckResult; /** * decode address with bech32 specification, return address version、address prefix and address data if valid - * @example - * ```ts - * // valid case - * fromBech32('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') - * // => {version: 0, prefix: 'bc', data: } - * - * // invalid case - * fromBase58Check('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5') - * // => Invalid checksum - * - * // invalid case - * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7') - * // => Mixed-case string - * - * // invalid case - * fromBase58Check('tb1pw508d6qejxtdg4y5r3zarquvzkan') - * // => Excess padding - * - * // invalid case - * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') - * // => Excess padding - * - * // invalid case - * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') - * // => Non-zero padding - * - * // invalid case - * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv') - * // => uses wrong encoding - * - * // invalid case - * fromBase58Check('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd') - * // => uses wrong encoding - * ``` */ export declare function fromBech32(address: string): Bech32Result; /** * encode address hash to base58 address with version - * - * @example - * ```ts - * // valid case - * toBase58Check('751e76e8199196d454941c45d1b3a323f1433bd6', 0) - * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH - * ``` */ export declare function toBase58Check(hash: Buffer, version: number): string; /** * encode address hash to bech32 address with version and prefix - * - * @example - * ```ts - * // valid case - * toBech32('000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 0, 'tb) - * // => tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy - * ``` */ export declare function toBech32(data: Buffer, version: number, prefix: string): string; /** * decode address from output script with network, return address if matched - * @example - * ```ts - * // valid case - * fromOutputScript('OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG', 'bicoin) - * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH - * - * // invalid case - * fromOutputScript('031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_0 75', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 75', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', undefined) - * // => has no matching Address - * - * ``` */ export declare function fromOutputScript(output: Buffer, network?: Network): string; /** * encodes address to output script with network, return output script if address matched - * @example - * ```ts - * // valid case - * toOutputScript('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 'bicoin) - * // => OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG - * - * // invalid case - * toOutputScript('24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE', undefined) - * // => has no matching Script - * - * // invalid case - * toOutputScript('BC1SW50QGDZ25J', { "bech32": "foo" }) - * // => has an invalid prefix - * - * // invalid case - * toOutputScript('bc1rw5uspcuh', undefined) - * // => has no matching Script - * ``` */ export declare function toOutputScript(address: string, network?: Network): Buffer; diff --git a/src/address.js b/src/address.js index 11cba0686..ad3ceeb1a 100644 --- a/src/address.js +++ b/src/address.js @@ -43,13 +43,6 @@ function _toFutureSegwitAddress(output, network) { } /** * decode address with base58 specification, return address version and address hash if valid - * @case - * ```ts - * // You can test it here and find more case in test/address.spec.ts - * const result = address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') - * console.log(JSON.stringify(result)) - * // => {"version":0,"hash":{"type":"Buffer","data":[117,30,118,232,25,145,150,212,84,148,28,69,209,179,163,35,241,67,59,214]}} - * ``` */ function fromBase58Check(address) { const payload = Buffer.from(bs58check.decode(address)); @@ -63,40 +56,6 @@ function fromBase58Check(address) { exports.fromBase58Check = fromBase58Check; /** * decode address with bech32 specification, return address version、address prefix and address data if valid - * @example - * ```ts - * // valid case - * fromBech32('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') - * // => {version: 0, prefix: 'bc', data: } - * - * // invalid case - * fromBase58Check('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5') - * // => Invalid checksum - * - * // invalid case - * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7') - * // => Mixed-case string - * - * // invalid case - * fromBase58Check('tb1pw508d6qejxtdg4y5r3zarquvzkan') - * // => Excess padding - * - * // invalid case - * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') - * // => Excess padding - * - * // invalid case - * fromBase58Check('bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7') - * // => Non-zero padding - * - * // invalid case - * fromBase58Check('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv') - * // => uses wrong encoding - * - * // invalid case - * fromBase58Check('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd') - * // => uses wrong encoding - * ``` */ function fromBech32(address) { let result; @@ -122,13 +81,6 @@ function fromBech32(address) { exports.fromBech32 = fromBech32; /** * encode address hash to base58 address with version - * - * @example - * ```ts - * // valid case - * toBase58Check('751e76e8199196d454941c45d1b3a323f1433bd6', 0) - * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH - * ``` */ function toBase58Check(hash, version) { (0, types_1.typeforce)( @@ -143,13 +95,6 @@ function toBase58Check(hash, version) { exports.toBase58Check = toBase58Check; /** * encode address hash to bech32 address with version and prefix - * - * @example - * ```ts - * // valid case - * toBech32('000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 0, 'tb) - * // => tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy - * ``` */ function toBech32(data, version, prefix) { const words = bech32_1.bech32.toWords(data); @@ -161,45 +106,6 @@ function toBech32(data, version, prefix) { exports.toBech32 = toBech32; /** * decode address from output script with network, return address if matched - * @example - * ```ts - * // valid case - * fromOutputScript('OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG', 'bicoin) - * // => 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH - * - * // invalid case - * fromOutputScript('031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_0 75', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 75', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675', undefined) - * // => has no matching Address - * - * // invalid case - * fromOutputScript('OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', undefined) - * // => has no matching Address - * - * ``` */ function fromOutputScript(output, network) { // TODO: Network @@ -227,24 +133,6 @@ function fromOutputScript(output, network) { exports.fromOutputScript = fromOutputScript; /** * encodes address to output script with network, return output script if address matched - * @example - * ```ts - * // valid case - * toOutputScript('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 'bicoin) - * // => OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG - * - * // invalid case - * toOutputScript('24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE', undefined) - * // => has no matching Script - * - * // invalid case - * toOutputScript('BC1SW50QGDZ25J', { "bech32": "foo" }) - * // => has an invalid prefix - * - * // invalid case - * toOutputScript('bc1rw5uspcuh', undefined) - * // => has no matching Script - * ``` */ function toOutputScript(address, network) { network = network || networks.bitcoin; diff --git a/src/bufferutils.d.ts b/src/bufferutils.d.ts index b1d89665b..b76bae0e3 100644 --- a/src/bufferutils.d.ts +++ b/src/bufferutils.d.ts @@ -2,7 +2,20 @@ import * as varuint from 'varuint-bitcoin'; export { varuint }; export declare function readUInt64LE(buffer: Buffer, offset: number): number; +/** + * Writes a 64-bit unsigned integer in little-endian format to the specified buffer at the given offset. + * + * @param buffer - The buffer to write the value to. + * @param value - The 64-bit unsigned integer value to write. + * @param offset - The offset in the buffer where the value should be written. + * @returns The new offset after writing the value. + */ export declare function writeUInt64LE(buffer: Buffer, value: number, offset: number): number; +/** + * Reverses the order of bytes in a buffer. + * @param buffer - The buffer to reverse. + * @returns A new buffer with the bytes reversed. + */ export declare function reverseBuffer(buffer: Buffer): Buffer; export declare function cloneBuffer(buffer: Buffer): Buffer; /** diff --git a/src/bufferutils.js b/src/bufferutils.js index 0f7fd10f4..cf11f02b4 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -30,6 +30,14 @@ function readUInt64LE(buffer, offset) { return b + a; } exports.readUInt64LE = readUInt64LE; +/** + * Writes a 64-bit unsigned integer in little-endian format to the specified buffer at the given offset. + * + * @param buffer - The buffer to write the value to. + * @param value - The 64-bit unsigned integer value to write. + * @param offset - The offset in the buffer where the value should be written. + * @returns The new offset after writing the value. + */ function writeUInt64LE(buffer, value, offset) { verifuint(value, 0x001fffffffffffff); buffer.writeInt32LE(value & -1, offset); @@ -37,6 +45,11 @@ function writeUInt64LE(buffer, value, offset) { return offset + 8; } exports.writeUInt64LE = writeUInt64LE; +/** + * Reverses the order of bytes in a buffer. + * @param buffer - The buffer to reverse. + * @returns A new buffer with the bytes reversed. + */ function reverseBuffer(buffer) { if (buffer.length < 1) return buffer; let j = buffer.length - 1; diff --git a/src/crypto.d.ts b/src/crypto.d.ts index 1a465dad1..6692df1ee 100644 --- a/src/crypto.d.ts +++ b/src/crypto.d.ts @@ -10,6 +10,9 @@ type TaggedHashPrefixes = { [key in TaggedHashPrefix]: Buffer; }; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ +/** + * Defines the tagged hash prefixes used in the crypto module. + */ export declare const TAGGED_HASH_PREFIXES: TaggedHashPrefixes; export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer; export {}; diff --git a/src/crypto.js b/src/crypto.js index af1224d25..a7a5936d9 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -9,6 +9,12 @@ exports.taggedHash = exports.sha1 = exports.ripemd160 = void 0; +/** + * A module for hashing functions. + * include ripemd160、sha1、sha256、hash160、hash256、taggedHash + * + * @packageDocumentation + */ const ripemd160_1 = require('@noble/hashes/ripemd160'); const sha1_1 = require('@noble/hashes/sha1'); const sha256_1 = require('@noble/hashes/sha256'); @@ -48,6 +54,9 @@ exports.TAGS = [ 'KeyAgg coefficient', ]; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ +/** + * Defines the tagged hash prefixes used in the crypto module. + */ exports.TAGGED_HASH_PREFIXES = { 'BIP0340/challenge': Buffer.from([ 123, 181, 45, 122, 159, 239, 88, 50, 62, 177, 191, 122, 64, 125, 179, 130, diff --git a/src/ecc_lib.d.ts b/src/ecc_lib.d.ts index 201ebb5cf..abb750a32 100644 --- a/src/ecc_lib.d.ts +++ b/src/ecc_lib.d.ts @@ -1,3 +1,17 @@ import { TinySecp256k1Interface } from './types'; +/** + * Initializes the ECC library with the provided instance. + * If `eccLib` is `undefined`, the library will be cleared. + * If `eccLib` is a new instance, it will be verified before setting it as the active library. + * + * @param eccLib The instance of the ECC library to initialize. + */ export declare function initEccLib(eccLib: TinySecp256k1Interface | undefined): void; +/** + * Retrieves the ECC Library instance. + * Throws an error if the ECC Library is not provided. + * You must call initEccLib() with a valid TinySecp256k1Interface instance before calling this function. + * @returns The ECC Library instance. + * @throws Error if the ECC Library is not provided. + */ export declare function getEccLib(): TinySecp256k1Interface; diff --git a/src/ecc_lib.js b/src/ecc_lib.js index eaa8a5327..22202da98 100644 --- a/src/ecc_lib.js +++ b/src/ecc_lib.js @@ -2,6 +2,13 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.getEccLib = exports.initEccLib = void 0; const _ECCLIB_CACHE = {}; +/** + * Initializes the ECC library with the provided instance. + * If `eccLib` is `undefined`, the library will be cleared. + * If `eccLib` is a new instance, it will be verified before setting it as the active library. + * + * @param eccLib The instance of the ECC library to initialize. + */ function initEccLib(eccLib) { if (!eccLib) { // allow clearing the library @@ -13,6 +20,13 @@ function initEccLib(eccLib) { } } exports.initEccLib = initEccLib; +/** + * Retrieves the ECC Library instance. + * Throws an error if the ECC Library is not provided. + * You must call initEccLib() with a valid TinySecp256k1Interface instance before calling this function. + * @returns The ECC Library instance. + * @throws Error if the ECC Library is not provided. + */ function getEccLib() { if (!_ECCLIB_CACHE.eccLib) throw new Error( @@ -22,6 +36,11 @@ function getEccLib() { } exports.getEccLib = getEccLib; const h = hex => Buffer.from(hex, 'hex'); +/** + * Verifies the ECC functionality. + * + * @param ecc - The TinySecp256k1Interface object. + */ function verifyEcc(ecc) { assert(typeof ecc.isXOnlyPoint === 'function'); assert( diff --git a/src/merkle.d.ts b/src/merkle.d.ts index d602201b9..a911ca525 100644 --- a/src/merkle.d.ts +++ b/src/merkle.d.ts @@ -1,2 +1,10 @@ /// +/** + * Calculates the Merkle root of an array of buffers using a specified digest function. + * + * @param values - The array of buffers. + * @param digestFn - The digest function used to calculate the hash of the concatenated buffers. + * @returns The Merkle root as a buffer. + * @throws {TypeError} If the values parameter is not an array or the digestFn parameter is not a function. + */ export declare function fastMerkleRoot(values: Buffer[], digestFn: (b: Buffer) => Buffer): Buffer; diff --git a/src/merkle.js b/src/merkle.js index e93f9cab6..c326c53af 100644 --- a/src/merkle.js +++ b/src/merkle.js @@ -1,6 +1,14 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.fastMerkleRoot = void 0; +/** + * Calculates the Merkle root of an array of buffers using a specified digest function. + * + * @param values - The array of buffers. + * @param digestFn - The digest function used to calculate the hash of the concatenated buffers. + * @returns The Merkle root as a buffer. + * @throws {TypeError} If the values parameter is not an array or the digestFn parameter is not a function. + */ function fastMerkleRoot(values, digestFn) { if (!Array.isArray(values)) throw TypeError('Expected values Array'); if (typeof digestFn !== 'function') diff --git a/src/networks.d.ts b/src/networks.d.ts index d5590fd1b..f9b020d9e 100644 --- a/src/networks.d.ts +++ b/src/networks.d.ts @@ -1,3 +1,8 @@ +/** + * Represents a Bitcoin network configuration,including messagePrefix, bech32, bip32, pubKeyHash, scriptHash, wif. + * Support bitcoin、bitcoin testnet and bitcoin regtest. + * @packageDocumentation + */ export interface Network { messagePrefix: string; bech32: string; @@ -10,7 +15,16 @@ interface Bip32 { public: number; private: number; } +/** + * Represents the Bitcoin network configuration. + */ export declare const bitcoin: Network; +/** + * Represents the regtest network configuration. + */ export declare const regtest: Network; +/** + * Represents the testnet network configuration. + */ export declare const testnet: Network; export {}; diff --git a/src/networks.js b/src/networks.js index ea710f8a2..75cdf30cc 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,17 +1,49 @@ 'use strict'; +// https://en.bitcoin.it/wiki/List_of_address_prefixes +// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 Object.defineProperty(exports, '__esModule', { value: true }); exports.testnet = exports.regtest = exports.bitcoin = void 0; +/** + * Represents the Bitcoin network configuration. + */ exports.bitcoin = { + /** + * The message prefix used for signing Bitcoin messages. + */ messagePrefix: '\x18Bitcoin Signed Message:\n', + /** + * The Bech32 prefix used for Bitcoin addresses. + */ bech32: 'bc', + /** + * The BIP32 key prefixes for Bitcoin. + */ bip32: { + /** + * The public key prefix for BIP32 extended public keys. + */ public: 0x0488b21e, + /** + * The private key prefix for BIP32 extended private keys. + */ private: 0x0488ade4, }, + /** + * The prefix for Bitcoin public key hashes. + */ pubKeyHash: 0x00, + /** + * The prefix for Bitcoin script hashes. + */ scriptHash: 0x05, + /** + * The prefix for Bitcoin Wallet Import Format (WIF) private keys. + */ wif: 0x80, }; +/** + * Represents the regtest network configuration. + */ exports.regtest = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bcrt', @@ -23,6 +55,9 @@ exports.regtest = { scriptHash: 0xc4, wif: 0xef, }; +/** + * Represents the testnet network configuration. + */ exports.testnet = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'tb', diff --git a/src/payments/bip341.d.ts b/src/payments/bip341.d.ts index fe9ecf0d9..676021e4c 100644 --- a/src/payments/bip341.d.ts +++ b/src/payments/bip341.d.ts @@ -21,6 +21,13 @@ interface TweakedPublicKey { * and calculating merkle inclusion proofs when constructing a control block. */ export type HashTree = HashLeaf | HashBranch; +/** + * Calculates the root hash from a given control block and leaf hash. + * @param controlBlock - The control block buffer. + * @param leafHash - The leaf hash buffer. + * @returns The root hash buffer. + * @throws {TypeError} If the control block length is less than 33. + */ export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer; /** * Build a hash tree of merkle nodes from the scripts binary tree. diff --git a/src/payments/bip341.js b/src/payments/bip341.js index 35609d788..926af6bf2 100644 --- a/src/payments/bip341.js +++ b/src/payments/bip341.js @@ -17,6 +17,13 @@ const types_1 = require('../types'); exports.LEAF_VERSION_TAPSCRIPT = 0xc0; exports.MAX_TAPTREE_DEPTH = 128; const isHashBranch = ht => 'left' in ht && 'right' in ht; +/** + * Calculates the root hash from a given control block and leaf hash. + * @param controlBlock - The control block buffer. + * @param leafHash - The leaf hash buffer. + * @returns The root hash buffer. + * @throws {TypeError} If the control block length is less than 33. + */ function rootHashFromPath(controlBlock, leafHash) { if (controlBlock.length < 33) throw new TypeError( diff --git a/src/payments/embed.d.ts b/src/payments/embed.d.ts index 76a9ed28f..d5f5785e1 100644 --- a/src/payments/embed.d.ts +++ b/src/payments/embed.d.ts @@ -1,2 +1,9 @@ import { Payment, PaymentOpts } from './index'; +/** + * Embeds data in a Bitcoin payment. + * @param a - The payment object. + * @param opts - Optional payment options. + * @returns The modified payment object. + * @throws {TypeError} If there is not enough data or if the output is invalid. + */ export declare function p2data(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/embed.js b/src/payments/embed.js index 4b7218f36..6d84c5de8 100644 --- a/src/payments/embed.js +++ b/src/payments/embed.js @@ -13,6 +13,13 @@ function stacksEqual(a, b) { }); } // output: OP_RETURN ... +/** + * Embeds data in a Bitcoin payment. + * @param a - The payment object. + * @param opts - Optional payment options. + * @returns The modified payment object. + * @throws {TypeError} If there is not enough data or if the output is invalid. + */ function p2data(a, opts) { if (!a.data && !a.output) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts index 57a0369b8..8eafc319e 100644 --- a/src/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -1,4 +1,11 @@ /// +/** + * Represents a payment object, which is used to create a payment. + * + * Supports P2PKH、P2SH、P2WPKH、P2WSH、P2TR and so on + * + * @packageDocumentation + */ import { Network } from '../networks'; import { Taptree } from '../types'; import { p2data as embed } from './embed'; diff --git a/src/payments/p2ms.d.ts b/src/payments/p2ms.d.ts index 199e02915..ff761c1fc 100644 --- a/src/payments/p2ms.d.ts +++ b/src/payments/p2ms.d.ts @@ -1,2 +1,9 @@ import { Payment, PaymentOpts } from './index'; +/** + * Represents a function that creates a Pay-to-Multisig (P2MS) payment object. + * @param a - The payment object. + * @param opts - Optional payment options. + * @returns The created payment object. + * @throws {TypeError} If the provided data is not valid. + */ export declare function p2ms(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js index 0b7e72d9a..cbdf2e194 100644 --- a/src/payments/p2ms.js +++ b/src/payments/p2ms.js @@ -15,6 +15,13 @@ function stacksEqual(a, b) { } // input: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG +/** + * Represents a function that creates a Pay-to-Multisig (P2MS) payment object. + * @param a - The payment object. + * @param opts - Optional payment options. + * @returns The created payment object. + * @throws {TypeError} If the provided data is not valid. + */ function p2ms(a, opts) { if ( !a.input && diff --git a/src/payments/p2pk.d.ts b/src/payments/p2pk.d.ts index d7e824d6c..862ea90df 100644 --- a/src/payments/p2pk.d.ts +++ b/src/payments/p2pk.d.ts @@ -1,2 +1,10 @@ import { Payment, PaymentOpts } from './index'; +/** + * Creates a pay-to-public-key (P2PK) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2PK payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ export declare function p2pk(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js index 284953045..cd6702f53 100644 --- a/src/payments/p2pk.js +++ b/src/payments/p2pk.js @@ -8,6 +8,14 @@ const lazy = require('./lazy'); const OPS = bscript.OPS; // input: {signature} // output: {pubKey} OP_CHECKSIG +/** + * Creates a pay-to-public-key (P2PK) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2PK payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ function p2pk(a, opts) { if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) throw new TypeError('Not enough data'); diff --git a/src/payments/p2pkh.d.ts b/src/payments/p2pkh.d.ts index a33eeb030..1c5b76b5b 100644 --- a/src/payments/p2pkh.d.ts +++ b/src/payments/p2pkh.d.ts @@ -1,2 +1,10 @@ import { Payment, PaymentOpts } from './index'; +/** + * Creates a Pay-to-Public-Key-Hash (P2PKH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2PKH payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ export declare function p2pkh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index 16e293d59..bc35e878e 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -10,6 +10,14 @@ const bs58check = require('bs58check'); const OPS = bscript.OPS; // input: {signature} {pubkey} // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG +/** + * Creates a Pay-to-Public-Key-Hash (P2PKH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2PKH payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ function p2pkh(a, opts) { if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) throw new TypeError('Not enough data'); diff --git a/src/payments/p2sh.d.ts b/src/payments/p2sh.d.ts index bb76772e7..c82d040cc 100644 --- a/src/payments/p2sh.d.ts +++ b/src/payments/p2sh.d.ts @@ -1,2 +1,10 @@ import { Payment, PaymentOpts } from './index'; +/** + * Creates a Pay-to-Script-Hash (P2SH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2SH payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ export declare function p2sh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index b280fcafa..3be14fb89 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -17,6 +17,14 @@ function stacksEqual(a, b) { // input: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL +/** + * Creates a Pay-to-Script-Hash (P2SH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2SH payment object. + * @throws {TypeError} If the required data is not provided or if the data is invalid. + */ function p2sh(a, opts) { if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) throw new TypeError('Not enough data'); diff --git a/src/payments/p2tr.d.ts b/src/payments/p2tr.d.ts index 350ed0ffc..97b2e0eb2 100644 --- a/src/payments/p2tr.d.ts +++ b/src/payments/p2tr.d.ts @@ -1,2 +1,10 @@ import { Payment, PaymentOpts } from './index'; +/** + * Creates a Pay-to-Taproot (P2TR) payment object. + * + * @param a - The payment object containing the necessary data for P2TR. + * @param opts - Optional payment options. + * @returns The P2TR payment object. + * @throws {TypeError} If the provided data is invalid or insufficient. + */ export declare function p2tr(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 83080b434..d3969c323 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -12,6 +12,14 @@ const bech32_1 = require('bech32'); const OPS = bscript.OPS; const TAPROOT_WITNESS_VERSION = 0x01; const ANNEX_PREFIX = 0x50; +/** + * Creates a Pay-to-Taproot (P2TR) payment object. + * + * @param a - The payment object containing the necessary data for P2TR. + * @param opts - Optional payment options. + * @returns The P2TR payment object. + * @throws {TypeError} If the provided data is invalid or insufficient. + */ function p2tr(a, opts) { if ( !a.address && diff --git a/src/payments/p2wpkh.d.ts b/src/payments/p2wpkh.d.ts index 360939119..5108e2912 100644 --- a/src/payments/p2wpkh.d.ts +++ b/src/payments/p2wpkh.d.ts @@ -1,2 +1,10 @@ import { Payment, PaymentOpts } from './index'; +/** + * Creates a pay-to-witness-public-key-hash (p2wpkh) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The p2wpkh payment object. + * @throws {TypeError} If the required data is missing or invalid. + */ export declare function p2wpkh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js index 168e08f19..1c6073e9b 100644 --- a/src/payments/p2wpkh.js +++ b/src/payments/p2wpkh.js @@ -12,6 +12,14 @@ const EMPTY_BUFFER = Buffer.alloc(0); // witness: {signature} {pubKey} // input: <> // output: OP_0 {pubKeyHash} +/** + * Creates a pay-to-witness-public-key-hash (p2wpkh) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The p2wpkh payment object. + * @throws {TypeError} If the required data is missing or invalid. + */ function p2wpkh(a, opts) { if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) throw new TypeError('Not enough data'); diff --git a/src/payments/p2wsh.d.ts b/src/payments/p2wsh.d.ts index d9ae925e8..4169a30de 100644 --- a/src/payments/p2wsh.d.ts +++ b/src/payments/p2wsh.d.ts @@ -1,2 +1,10 @@ import { Payment, PaymentOpts } from './index'; +/** + * Creates a Pay-to-Witness-Script-Hash (P2WSH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2WSH payment object. + * @throws {TypeError} If the required data is missing or invalid. + */ export declare function p2wsh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index 2ad9387e9..dd1cde2d1 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -30,6 +30,14 @@ function chunkHasUncompressedPubkey(chunk) { // input: <> // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} +/** + * Creates a Pay-to-Witness-Script-Hash (P2WSH) payment object. + * + * @param a - The payment object containing the necessary data. + * @param opts - Optional payment options. + * @returns The P2WSH payment object. + * @throws {TypeError} If the required data is missing or invalid. + */ function p2wsh(a, opts) { if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) throw new TypeError('Not enough data'); diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 61196ead2..2b53171e0 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -265,6 +265,14 @@ function checkMixedTaprootAndNonTaprootOutputFields( `Cannot use both taproot and non-taproot fields.`, ); } +/** + * Checks if the tap leaf is part of the tap tree for the given input data. + * Throws an error if the tap leaf is not part of the tap tree. + * @param inputData - The original PsbtInput data. + * @param newInputData - The new PsbtInput data. + * @param action - The action being performed. + * @throws {Error} - If the tap leaf is not part of the tap tree. + */ function checkIfTapLeafInTree(inputData, newInputData, action) { if (newInputData.tapMerkleRoot) { const newLeafsInTree = (newInputData.tapLeafScript || []).every(l => @@ -287,6 +295,12 @@ function checkIfTapLeafInTree(inputData, newInputData, action) { ); } } +/** + * Checks if a TapLeafScript is present in a Merkle tree. + * @param tapLeaf The TapLeafScript to check. + * @param merkleRoot The Merkle root of the tree. If not provided, the function assumes the TapLeafScript is present. + * @returns A boolean indicating whether the TapLeafScript is present in the tree. + */ function isTapLeafInTree(tapLeaf, merkleRoot) { if (!merkleRoot) return true; const leafHash = (0, bip341_1.tapleafHash)({ @@ -299,6 +313,13 @@ function isTapLeafInTree(tapLeaf, merkleRoot) { ); return rootHash.equals(merkleRoot); } +/** + * Sorts the signatures in the input's tapScriptSig array based on their position in the tapLeaf script. + * + * @param input - The PsbtInput object. + * @param tapLeaf - The TapLeafScript object. + * @returns An array of sorted signatures as Buffers. + */ function sortSignatures(input, tapLeaf) { const leafHash = (0, bip341_1.tapleafHash)({ output: tapLeaf.script, @@ -310,6 +331,12 @@ function sortSignatures(input, tapLeaf) { .sort((t1, t2) => t2.positionInScript - t1.positionInScript) .map(t => t.signature); } +/** + * Adds the position of a public key in a script to a TapScriptSig object. + * @param script The script in which to find the position of the public key. + * @param tss The TapScriptSig object to add the position to. + * @returns A TapScriptSigWitPosition object with the added position. + */ function addPubkeyPositionInScript(script, tss) { return Object.assign( { @@ -340,6 +367,14 @@ function findTapLeafToFinalize(input, inputIndex, leafHashToFinalize) { ); return tapLeaf; } +/** + * Determines whether a TapLeafScript can be finalized. + * + * @param leaf - The TapLeafScript to check. + * @param tapScriptSig - The array of TapScriptSig objects. + * @param hash - The optional hash to compare with the leaf hash. + * @returns A boolean indicating whether the TapLeafScript can be finalized. + */ function canFinalizeLeaf(leaf, tapScriptSig, hash) { const leafHash = (0, bip341_1.tapleafHash)({ output: leaf.script, @@ -351,6 +386,12 @@ function canFinalizeLeaf(leaf, tapScriptSig, hash) { tapScriptSig.find(tss => tss.leafHash.equals(leafHash)) !== undefined ); } +/** + * Checks if the given PsbtInput or PsbtOutput has non-taproot fields. + * Non-taproot fields include redeemScript, witnessScript, and bip32Derivation. + * @param io The PsbtInput or PsbtOutput to check. + * @returns A boolean indicating whether the given input or output has non-taproot fields. + */ function hasNonTaprootFields(io) { return ( io && diff --git a/src/psbt/psbtutils.d.ts b/src/psbt/psbtutils.d.ts index db5f0b516..3e7b2a393 100644 --- a/src/psbt/psbtutils.d.ts +++ b/src/psbt/psbtutils.d.ts @@ -7,13 +7,49 @@ export declare const isP2WPKH: (script: Buffer) => boolean; export declare const isP2WSHScript: (script: Buffer) => boolean; export declare const isP2SHScript: (script: Buffer) => boolean; export declare const isP2TR: (script: Buffer) => boolean; +/** + * Converts a witness stack to a script witness. + * @param witness The witness stack to convert. + * @returns The script witness as a Buffer. + */ +/** + * Converts a witness stack to a script witness. + * @param witness The witness stack to convert. + * @returns The converted script witness. + */ export declare function witnessStackToScriptWitness(witness: Buffer[]): Buffer; +/** + * Finds the position of a public key in a script. + * @param pubkey The public key to search for. + * @param script The script to search in. + * @returns The index of the public key in the script, or -1 if not found. + * @throws {Error} If there is an unknown script error. + */ export declare function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number; +/** + * Checks if a public key is present in a script. + * @param pubkey The public key to check. + * @param script The script to search in. + * @returns A boolean indicating whether the public key is present in the script. + */ export declare function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean; +/** + * Checks if an input contains a signature for a specific action. + * @param input - The input to check. + * @param action - The action to check for. + * @returns A boolean indicating whether the input contains a signature for the specified action. + */ export declare function checkInputForSig(input: PsbtInput, action: string): boolean; type SignatureDecodeFunc = (buffer: Buffer) => { signature: Buffer; hashType: number; }; +/** + * Determines if a given action is allowed for a signature block. + * @param signature - The signature block. + * @param signatureDecodeFn - The function used to decode the signature. + * @param action - The action to be checked. + * @returns True if the action is allowed, false otherwise. + */ export declare function signatureBlocksAction(signature: Buffer, signatureDecodeFn: SignatureDecodeFunc, action: string): boolean; export {}; diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index 2e8bd435a..ea5f1d719 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -35,6 +35,16 @@ exports.isP2WPKH = isPaymentFactory(payments.p2wpkh); exports.isP2WSHScript = isPaymentFactory(payments.p2wsh); exports.isP2SHScript = isPaymentFactory(payments.p2sh); exports.isP2TR = isPaymentFactory(payments.p2tr); +/** + * Converts a witness stack to a script witness. + * @param witness The witness stack to convert. + * @returns The script witness as a Buffer. + */ +/** + * Converts a witness stack to a script witness. + * @param witness The witness stack to convert. + * @returns The converted script witness. + */ function witnessStackToScriptWitness(witness) { let buffer = Buffer.allocUnsafe(0); function writeSlice(slice) { @@ -58,6 +68,13 @@ function witnessStackToScriptWitness(witness) { return buffer; } exports.witnessStackToScriptWitness = witnessStackToScriptWitness; +/** + * Finds the position of a public key in a script. + * @param pubkey The public key to search for. + * @param script The script to search in. + * @returns The index of the public key in the script, or -1 if not found. + * @throws {Error} If there is an unknown script error. + */ function pubkeyPositionInScript(pubkey, script) { const pubkeyHash = (0, crypto_1.hash160)(pubkey); const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? @@ -73,10 +90,22 @@ function pubkeyPositionInScript(pubkey, script) { }); } exports.pubkeyPositionInScript = pubkeyPositionInScript; +/** + * Checks if a public key is present in a script. + * @param pubkey The public key to check. + * @param script The script to search in. + * @returns A boolean indicating whether the public key is present in the script. + */ function pubkeyInScript(pubkey, script) { return pubkeyPositionInScript(pubkey, script) !== -1; } exports.pubkeyInScript = pubkeyInScript; +/** + * Checks if an input contains a signature for a specific action. + * @param input - The input to check. + * @param action - The action to check for. + * @returns A boolean indicating whether the input contains a signature for the specified action. + */ function checkInputForSig(input, action) { const pSigs = extractPartialSigs(input); return pSigs.some(pSig => @@ -84,6 +113,13 @@ function checkInputForSig(input, action) { ); } exports.checkInputForSig = checkInputForSig; +/** + * Determines if a given action is allowed for a signature block. + * @param signature - The signature block. + * @param signatureDecodeFn - The function used to decode the signature. + * @param action - The action to be checked. + * @returns True if the action is allowed, false otherwise. + */ function signatureBlocksAction(signature, signatureDecodeFn, action) { const { hashType } = signatureDecodeFn(signature); const whitelist = []; @@ -106,6 +142,16 @@ function signatureBlocksAction(signature, signatureDecodeFn, action) { return false; } exports.signatureBlocksAction = signatureBlocksAction; +/** + * Extracts the signatures from a PsbtInput object. + * If the input has partial signatures, it returns an array of the signatures. + * If the input does not have partial signatures, it checks if it has a finalScriptSig or finalScriptWitness. + * If it does, it extracts the signatures from the final scripts and returns them. + * If none of the above conditions are met, it returns an empty array. + * + * @param input - The PsbtInput object from which to extract the signatures. + * @returns An array of signatures extracted from the PsbtInput object. + */ function extractPartialSigs(input) { let pSigs = []; if ((input.partialSig || []).length === 0) { @@ -116,6 +162,14 @@ function extractPartialSigs(input) { } return pSigs.map(p => p.signature); } +/** + * Retrieves the partial signatures (Psigs) from the input's final scripts. + * Psigs are extracted from both the final scriptSig and final scriptWitness of the input. + * Only canonical script signatures are considered. + * + * @param input - The PsbtInput object representing the input. + * @returns An array of PartialSig objects containing the extracted Psigs. + */ function getPsigsFromInputFinalScripts(input) { const scriptItems = !input.finalScriptSig ? [] diff --git a/src/push_data.d.ts b/src/push_data.d.ts index 07c2f918d..068456148 100644 --- a/src/push_data.d.ts +++ b/src/push_data.d.ts @@ -1,6 +1,27 @@ /// +/** + * Calculates the encoding length of a number used for push data in Bitcoin transactions. + * @param i The number to calculate the encoding length for. + * @returns The encoding length of the number. + */ export declare function encodingLength(i: number): number; +/** + * Encodes a number into a buffer using a variable-length encoding scheme. + * The encoded buffer is written starting at the specified offset. + * Returns the size of the encoded buffer. + * + * @param buffer - The buffer to write the encoded data into. + * @param num - The number to encode. + * @param offset - The offset at which to start writing the encoded buffer. + * @returns The size of the encoded buffer. + */ export declare function encode(buffer: Buffer, num: number, offset: number): number; +/** + * Decodes a buffer and returns information about the opcode, number, and size. + * @param buffer - The buffer to decode. + * @param offset - The offset within the buffer to start decoding. + * @returns An object containing the opcode, number, and size, or null if decoding fails. + */ export declare function decode(buffer: Buffer, offset: number): { opcode: number; number: number; diff --git a/src/push_data.js b/src/push_data.js index 16b6147b5..2d3683769 100644 --- a/src/push_data.js +++ b/src/push_data.js @@ -2,10 +2,25 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.decode = exports.encode = exports.encodingLength = void 0; const ops_1 = require('./ops'); +/** + * Calculates the encoding length of a number used for push data in Bitcoin transactions. + * @param i The number to calculate the encoding length for. + * @returns The encoding length of the number. + */ function encodingLength(i) { return i < ops_1.OPS.OP_PUSHDATA1 ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5; } exports.encodingLength = encodingLength; +/** + * Encodes a number into a buffer using a variable-length encoding scheme. + * The encoded buffer is written starting at the specified offset. + * Returns the size of the encoded buffer. + * + * @param buffer - The buffer to write the encoded data into. + * @param num - The number to encode. + * @param offset - The offset at which to start writing the encoded buffer. + * @returns The size of the encoded buffer. + */ function encode(buffer, num, offset) { const size = encodingLength(num); // ~6 bit @@ -27,6 +42,12 @@ function encode(buffer, num, offset) { return size; } exports.encode = encode; +/** + * Decodes a buffer and returns information about the opcode, number, and size. + * @param buffer - The buffer to decode. + * @param offset - The offset within the buffer to start decoding. + * @returns An object containing the opcode, number, and size, or null if decoding fails. + */ function decode(buffer, offset) { const opcode = buffer.readUInt8(offset); let num; diff --git a/src/script.d.ts b/src/script.d.ts index fd5964b89..ffc8c89bb 100644 --- a/src/script.d.ts +++ b/src/script.d.ts @@ -6,10 +6,34 @@ import * as scriptSignature from './script_signature'; export { OPS }; export declare function isPushOnly(value: Stack): boolean; export declare function countNonPushOnlyOPs(value: Stack): number; +/** + * Compiles an array of chunks into a Buffer. + * + * @param chunks - The array of chunks to compile. + * @returns The compiled Buffer. + * @throws Error if the compilation fails. + */ export declare function compile(chunks: Buffer | Stack): Buffer; export declare function decompile(buffer: Buffer | Array): Array | null; +/** + * Converts the given chunks into an ASM (Assembly) string representation. + * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. + * @param chunks - The chunks to convert into ASM. + * @returns The ASM string representation of the chunks. + */ export declare function toASM(chunks: Buffer | Array): string; +/** + * Converts an ASM string to a Buffer. + * @param asm The ASM string to convert. + * @returns The converted Buffer. + */ export declare function fromASM(asm: string): Buffer; +/** + * Converts the given chunks into a stack of buffers. + * + * @param chunks - The chunks to convert. + * @returns The stack of buffers. + */ export declare function toStack(chunks: Buffer | Array): Buffer[]; export declare function isCanonicalPubKey(buffer: Buffer): boolean; export declare function isDefinedHashType(hashType: number): boolean; diff --git a/src/script.js b/src/script.js index 6ed7ba20a..be2805190 100644 --- a/src/script.js +++ b/src/script.js @@ -14,6 +14,10 @@ exports.signature = exports.isPushOnly = exports.OPS = void 0; +/** + * Script tools, including decompile, compile, toASM, fromASM, toStack, isCanonicalPubKey, isCanonicalScriptSignature + * @packageDocumentation + */ const bip66 = require('./bip66'); const ops_1 = require('./ops'); Object.defineProperty(exports, 'OPS', { @@ -62,6 +66,13 @@ function chunksIsArray(buf) { function singleChunkIsBuffer(buf) { return Buffer.isBuffer(buf); } +/** + * Compiles an array of chunks into a Buffer. + * + * @param chunks - The array of chunks to compile. + * @returns The compiled Buffer. + * @throws Error if the compilation fails. + */ function compile(chunks) { // TODO: remove me if (chunksIsBuffer(chunks)) return chunks; @@ -137,6 +148,12 @@ function decompile(buffer) { return chunks; } exports.decompile = decompile; +/** + * Converts the given chunks into an ASM (Assembly) string representation. + * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. + * @param chunks - The chunks to convert into ASM. + * @returns The ASM string representation of the chunks. + */ function toASM(chunks) { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks); @@ -155,6 +172,11 @@ function toASM(chunks) { .join(' '); } exports.toASM = toASM; +/** + * Converts an ASM string to a Buffer. + * @param asm The ASM string to convert. + * @returns The converted Buffer. + */ function fromASM(asm) { typeforce(types.String, asm); return compile( @@ -168,6 +190,12 @@ function fromASM(asm) { ); } exports.fromASM = fromASM; +/** + * Converts the given chunks into a stack of buffers. + * + * @param chunks - The chunks to convert. + * @returns The stack of buffers. + */ function toStack(chunks) { chunks = decompile(chunks); typeforce(isPushOnly, chunks); diff --git a/src/script_number.d.ts b/src/script_number.d.ts index 015bb8943..90f09c55a 100644 --- a/src/script_number.d.ts +++ b/src/script_number.d.ts @@ -1,3 +1,19 @@ /// +/** + * Decodes a script number from a buffer. + * + * @param buffer - The buffer containing the script number. + * @param maxLength - The maximum length of the script number. Defaults to 4. + * @param minimal - Whether the script number should be minimal. Defaults to true. + * @returns The decoded script number. + * @throws {TypeError} If the script number overflows the maximum length. + * @throws {Error} If the script number is not minimally encoded when minimal is true. + */ export declare function decode(buffer: Buffer, maxLength?: number, minimal?: boolean): number; +/** + * Encodes a number into a Buffer using a specific format. + * + * @param _number - The number to encode. + * @returns The encoded number as a Buffer. + */ export declare function encode(_number: number): Buffer; diff --git a/src/script_number.js b/src/script_number.js index 8220a1043..2691b41e6 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -1,6 +1,16 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.encode = exports.decode = void 0; +/** + * Decodes a script number from a buffer. + * + * @param buffer - The buffer containing the script number. + * @param maxLength - The maximum length of the script number. Defaults to 4. + * @param minimal - Whether the script number should be minimal. Defaults to true. + * @returns The decoded script number. + * @throws {TypeError} If the script number overflows the maximum length. + * @throws {Error} If the script number is not minimally encoded when minimal is true. + */ function decode(buffer, maxLength, minimal) { maxLength = maxLength || 4; minimal = minimal === undefined ? true : minimal; @@ -43,6 +53,12 @@ function scriptNumSize(i) { ? 1 : 0; } +/** + * Encodes a number into a Buffer using a specific format. + * + * @param _number - The number to encode. + * @returns The encoded number as a Buffer. + */ function encode(_number) { let value = Math.abs(_number); const size = scriptNumSize(value); diff --git a/src/script_signature.d.ts b/src/script_signature.d.ts index 2057dd99f..b9034a816 100644 --- a/src/script_signature.d.ts +++ b/src/script_signature.d.ts @@ -3,6 +3,19 @@ interface ScriptSignature { signature: Buffer; hashType: number; } +/** + * Decodes a buffer into a ScriptSignature object. + * @param buffer - The buffer to decode. + * @returns The decoded ScriptSignature object. + * @throws Error if the hashType is invalid. + */ export declare function decode(buffer: Buffer): ScriptSignature; +/** + * Encodes a signature and hash type into a buffer. + * @param signature - The signature to encode. + * @param hashType - The hash type to encode. + * @returns The encoded buffer. + * @throws Error if the hashType is invalid. + */ export declare function encode(signature: Buffer, hashType: number): Buffer; export {}; diff --git a/src/script_signature.js b/src/script_signature.js index 638e5f299..0428cfab5 100644 --- a/src/script_signature.js +++ b/src/script_signature.js @@ -5,6 +5,11 @@ const bip66 = require('./bip66'); const types = require('./types'); const { typeforce } = types; const ZERO = Buffer.alloc(1, 0); +/** + * Converts a buffer to a DER-encoded buffer. + * @param x - The buffer to be converted. + * @returns The DER-encoded buffer. + */ function toDER(x) { let i = 0; while (x[i] === 0) ++i; @@ -13,6 +18,13 @@ function toDER(x) { if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length); return x; } +/** + * Converts a DER-encoded signature to a buffer. + * If the first byte of the input buffer is 0x00, it is skipped. + * The resulting buffer is 32 bytes long, filled with zeros if necessary. + * @param x - The DER-encoded signature. + * @returns The converted buffer. + */ function fromDER(x) { if (x[0] === 0x00) x = x.slice(1); const buffer = Buffer.alloc(32, 0); @@ -21,6 +33,12 @@ function fromDER(x) { return buffer; } // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) +/** + * Decodes a buffer into a ScriptSignature object. + * @param buffer - The buffer to decode. + * @returns The decoded ScriptSignature object. + * @throws Error if the hashType is invalid. + */ function decode(buffer) { const hashType = buffer.readUInt8(buffer.length - 1); const hashTypeMod = hashType & ~0x80; @@ -33,6 +51,13 @@ function decode(buffer) { return { signature, hashType }; } exports.decode = decode; +/** + * Encodes a signature and hash type into a buffer. + * @param signature - The signature to encode. + * @param hashType - The hash type to encode. + * @returns The encoded buffer. + * @throws Error if the hashType is invalid. + */ function encode(signature, hashType) { typeforce( { diff --git a/src/transaction.d.ts b/src/transaction.d.ts index 613706b67..118fa57f1 100644 --- a/src/transaction.d.ts +++ b/src/transaction.d.ts @@ -10,6 +10,9 @@ export interface Input { sequence: number; witness: Buffer[]; } +/** + * Represents a Bitcoin transaction. + */ export declare class Transaction { static readonly DEFAULT_SEQUENCE = 4294967295; static readonly SIGHASH_DEFAULT = 0; diff --git a/src/transaction.js b/src/transaction.js index ade4582cf..2b74f3788 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -38,6 +38,9 @@ const BLANK_OUTPUT = { function isOutput(out) { return out.value !== undefined; } +/** + * Represents a Bitcoin transaction. + */ class Transaction { constructor() { this.version = 1; diff --git a/src/types.d.ts b/src/types.d.ts index 0b8a02f9c..4c7cb0559 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,5 +1,10 @@ /// export declare const typeforce: any; +/** + * Checks if the given value is a valid elliptic curve point. + * @param p - The value to check. + * @returns True if the value is a valid elliptic curve point, false otherwise. + */ 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 acb51ad0b..8e1f0e7c0 100644 --- a/src/types.js +++ b/src/types.js @@ -36,6 +36,11 @@ const EC_P = buffer_1.Buffer.from( 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex', ); +/** + * Checks if the given value is a valid elliptic curve point. + * @param p - The value to check. + * @returns True if the value is a valid elliptic curve point, false otherwise. + */ function isPoint(p) { if (!buffer_1.Buffer.isBuffer(p)) return false; if (p.length < 33) return false; diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts index 901d72a48..f56cf4093 100644 --- a/ts_src/bufferutils.ts +++ b/ts_src/bufferutils.ts @@ -23,6 +23,14 @@ export function readUInt64LE(buffer: Buffer, offset: number): number { return b + a; } +/** + * Writes a 64-bit unsigned integer in little-endian format to the specified buffer at the given offset. + * + * @param buffer - The buffer to write the value to. + * @param value - The 64-bit unsigned integer value to write. + * @param offset - The offset in the buffer where the value should be written. + * @returns The new offset after writing the value. + */ export function writeUInt64LE( buffer: Buffer, value: number, @@ -35,6 +43,11 @@ export function writeUInt64LE( return offset + 8; } +/** + * Reverses the order of bytes in a buffer. + * @param buffer - The buffer to reverse. + * @returns A new buffer with the bytes reversed. + */ export function reverseBuffer(buffer: Buffer): Buffer { if (buffer.length < 1) return buffer; let j = buffer.length - 1; diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index e3b876961..8d8491d42 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,3 +1,9 @@ +/** + * A module for hashing functions. + * include ripemd160、sha1、sha256、hash160、hash256、taggedHash + * + * @packageDocumentation + */ import { ripemd160 as _ripemd160 } from '@noble/hashes/ripemd160'; import { sha1 as _sha1 } from '@noble/hashes/sha1'; import { sha256 as _sha256 } from '@noble/hashes/sha256'; @@ -38,6 +44,9 @@ type TaggedHashPrefixes = { [key in TaggedHashPrefix]: Buffer; }; /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ +/** + * Defines the tagged hash prefixes used in the crypto module. + */ export const TAGGED_HASH_PREFIXES: TaggedHashPrefixes = { 'BIP0340/challenge': Buffer.from([ 123, 181, 45, 122, 159, 239, 88, 50, 62, 177, 191, 122, 64, 125, 179, 130, diff --git a/ts_src/ecc_lib.ts b/ts_src/ecc_lib.ts index eb4c59eeb..a176d09c6 100644 --- a/ts_src/ecc_lib.ts +++ b/ts_src/ecc_lib.ts @@ -2,6 +2,13 @@ import { TinySecp256k1Interface } from './types'; const _ECCLIB_CACHE: { eccLib?: TinySecp256k1Interface } = {}; +/** + * Initializes the ECC library with the provided instance. + * If `eccLib` is `undefined`, the library will be cleared. + * If `eccLib` is a new instance, it will be verified before setting it as the active library. + * + * @param eccLib The instance of the ECC library to initialize. + */ export function initEccLib(eccLib: TinySecp256k1Interface | undefined): void { if (!eccLib) { // allow clearing the library @@ -13,6 +20,13 @@ export function initEccLib(eccLib: TinySecp256k1Interface | undefined): void { } } +/** + * Retrieves the ECC Library instance. + * Throws an error if the ECC Library is not provided. + * You must call initEccLib() with a valid TinySecp256k1Interface instance before calling this function. + * @returns The ECC Library instance. + * @throws Error if the ECC Library is not provided. + */ export function getEccLib(): TinySecp256k1Interface { if (!_ECCLIB_CACHE.eccLib) throw new Error( @@ -23,6 +37,11 @@ export function getEccLib(): TinySecp256k1Interface { const h = (hex: string): Buffer => Buffer.from(hex, 'hex'); +/** + * Verifies the ECC functionality. + * + * @param ecc - The TinySecp256k1Interface object. + */ function verifyEcc(ecc: TinySecp256k1Interface): void { assert(typeof ecc.isXOnlyPoint === 'function'); assert( diff --git a/ts_src/merkle.ts b/ts_src/merkle.ts index 8ff8c3f8c..47b161e4e 100644 --- a/ts_src/merkle.ts +++ b/ts_src/merkle.ts @@ -1,3 +1,11 @@ +/** + * Calculates the Merkle root of an array of buffers using a specified digest function. + * + * @param values - The array of buffers. + * @param digestFn - The digest function used to calculate the hash of the concatenated buffers. + * @returns The Merkle root as a buffer. + * @throws {TypeError} If the values parameter is not an array or the digestFn parameter is not a function. + */ export function fastMerkleRoot( values: Buffer[], digestFn: (b: Buffer) => Buffer, diff --git a/ts_src/networks.ts b/ts_src/networks.ts index e66b08c30..24d913db9 100644 --- a/ts_src/networks.ts +++ b/ts_src/networks.ts @@ -1,5 +1,11 @@ // https://en.bitcoin.it/wiki/List_of_address_prefixes // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 + +/** + * Represents a Bitcoin network configuration,including messagePrefix, bech32, bip32, pubKeyHash, scriptHash, wif. + * Support bitcoin、bitcoin testnet and bitcoin regtest. + * @packageDocumentation + */ export interface Network { messagePrefix: string; bech32: string; @@ -14,17 +20,47 @@ interface Bip32 { private: number; } +/** + * Represents the Bitcoin network configuration. + */ export const bitcoin: Network = { + /** + * The message prefix used for signing Bitcoin messages. + */ messagePrefix: '\x18Bitcoin Signed Message:\n', + /** + * The Bech32 prefix used for Bitcoin addresses. + */ bech32: 'bc', + /** + * The BIP32 key prefixes for Bitcoin. + */ bip32: { + /** + * The public key prefix for BIP32 extended public keys. + */ public: 0x0488b21e, + /** + * The private key prefix for BIP32 extended private keys. + */ private: 0x0488ade4, }, + /** + * The prefix for Bitcoin public key hashes. + */ pubKeyHash: 0x00, + /** + * The prefix for Bitcoin script hashes. + */ scriptHash: 0x05, + /** + * The prefix for Bitcoin Wallet Import Format (WIF) private keys. + */ wif: 0x80, }; +/** + * Represents the regtest network configuration. + */ export const regtest: Network = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bcrt', @@ -36,6 +72,9 @@ export const regtest: Network = { scriptHash: 0xc4, wif: 0xef, }; +/** + * Represents the testnet network configuration. + */ export const testnet: Network = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'tb', diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 375d35cc6..678c1637a 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -337,6 +337,14 @@ function checkMixedTaprootAndNonTaprootOutputFields( ); } +/** + * Checks if the tap leaf is part of the tap tree for the given input data. + * Throws an error if the tap leaf is not part of the tap tree. + * @param inputData - The original PsbtInput data. + * @param newInputData - The new PsbtInput data. + * @param action - The action being performed. + * @throws {Error} - If the tap leaf is not part of the tap tree. + */ function checkIfTapLeafInTree( inputData: PsbtInput, newInputData: PsbtInput, @@ -364,6 +372,12 @@ function checkIfTapLeafInTree( } } +/** + * Checks if a TapLeafScript is present in a Merkle tree. + * @param tapLeaf The TapLeafScript to check. + * @param merkleRoot The Merkle root of the tree. If not provided, the function assumes the TapLeafScript is present. + * @returns A boolean indicating whether the TapLeafScript is present in the tree. + */ function isTapLeafInTree(tapLeaf: TapLeafScript, merkleRoot?: Buffer): boolean { if (!merkleRoot) return true; @@ -376,6 +390,13 @@ function isTapLeafInTree(tapLeaf: TapLeafScript, merkleRoot?: Buffer): boolean { return rootHash.equals(merkleRoot); } +/** + * Sorts the signatures in the input's tapScriptSig array based on their position in the tapLeaf script. + * + * @param input - The PsbtInput object. + * @param tapLeaf - The TapLeafScript object. + * @returns An array of sorted signatures as Buffers. + */ function sortSignatures(input: PsbtInput, tapLeaf: TapLeafScript): Buffer[] { const leafHash = tapleafHash({ output: tapLeaf.script, @@ -389,6 +410,12 @@ function sortSignatures(input: PsbtInput, tapLeaf: TapLeafScript): Buffer[] { .map(t => t.signature) as Buffer[]; } +/** + * Adds the position of a public key in a script to a TapScriptSig object. + * @param script The script in which to find the position of the public key. + * @param tss The TapScriptSig object to add the position to. + * @returns A TapScriptSigWitPosition object with the added position. + */ function addPubkeyPositionInScript( script: Buffer, tss: TapScriptSig, @@ -427,6 +454,14 @@ function findTapLeafToFinalize( return tapLeaf; } +/** + * Determines whether a TapLeafScript can be finalized. + * + * @param leaf - The TapLeafScript to check. + * @param tapScriptSig - The array of TapScriptSig objects. + * @param hash - The optional hash to compare with the leaf hash. + * @returns A boolean indicating whether the TapLeafScript can be finalized. + */ function canFinalizeLeaf( leaf: TapLeafScript, tapScriptSig: TapScriptSig[], @@ -443,6 +478,12 @@ function canFinalizeLeaf( ); } +/** + * Checks if the given PsbtInput or PsbtOutput has non-taproot fields. + * Non-taproot fields include redeemScript, witnessScript, and bip32Derivation. + * @param io The PsbtInput or PsbtOutput to check. + * @returns A boolean indicating whether the given input or output has non-taproot fields. + */ function hasNonTaprootFields(io: PsbtInput | PsbtOutput): boolean { return ( io && diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 173e0215c..7639e5211 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -23,6 +23,16 @@ export const isP2WSHScript = isPaymentFactory(payments.p2wsh); export const isP2SHScript = isPaymentFactory(payments.p2sh); export const isP2TR = isPaymentFactory(payments.p2tr); +/** + * Converts a witness stack to a script witness. + * @param witness The witness stack to convert. + * @returns The script witness as a Buffer. + */ +/** + * Converts a witness stack to a script witness. + * @param witness The witness stack to convert. + * @returns The converted script witness. + */ export function witnessStackToScriptWitness(witness: Buffer[]): Buffer { let buffer = Buffer.allocUnsafe(0); @@ -53,6 +63,13 @@ export function witnessStackToScriptWitness(witness: Buffer[]): Buffer { return buffer; } +/** + * Finds the position of a public key in a script. + * @param pubkey The public key to search for. + * @param script The script to search in. + * @returns The index of the public key in the script, or -1 if not found. + * @throws {Error} If there is an unknown script error. + */ export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { const pubkeyHash = hash160(pubkey); const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? @@ -70,10 +87,22 @@ export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { }); } +/** + * Checks if a public key is present in a script. + * @param pubkey The public key to check. + * @param script The script to search in. + * @returns A boolean indicating whether the public key is present in the script. + */ export function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean { return pubkeyPositionInScript(pubkey, script) !== -1; } +/** + * Checks if an input contains a signature for a specific action. + * @param input - The input to check. + * @param action - The action to check for. + * @returns A boolean indicating whether the input contains a signature for the specified action. + */ export function checkInputForSig(input: PsbtInput, action: string): boolean { const pSigs = extractPartialSigs(input); return pSigs.some(pSig => @@ -85,6 +114,13 @@ type SignatureDecodeFunc = (buffer: Buffer) => { signature: Buffer; hashType: number; }; +/** + * Determines if a given action is allowed for a signature block. + * @param signature - The signature block. + * @param signatureDecodeFn - The function used to decode the signature. + * @param action - The action to be checked. + * @returns True if the action is allowed, false otherwise. + */ export function signatureBlocksAction( signature: Buffer, signatureDecodeFn: SignatureDecodeFunc, @@ -110,6 +146,16 @@ export function signatureBlocksAction( return false; } +/** + * Extracts the signatures from a PsbtInput object. + * If the input has partial signatures, it returns an array of the signatures. + * If the input does not have partial signatures, it checks if it has a finalScriptSig or finalScriptWitness. + * If it does, it extracts the signatures from the final scripts and returns them. + * If none of the above conditions are met, it returns an empty array. + * + * @param input - The PsbtInput object from which to extract the signatures. + * @returns An array of signatures extracted from the PsbtInput object. + */ function extractPartialSigs(input: PsbtInput): Buffer[] { let pSigs: PartialSig[] = []; if ((input.partialSig || []).length === 0) { @@ -121,6 +167,14 @@ function extractPartialSigs(input: PsbtInput): Buffer[] { return pSigs.map(p => p.signature); } +/** + * Retrieves the partial signatures (Psigs) from the input's final scripts. + * Psigs are extracted from both the final scriptSig and final scriptWitness of the input. + * Only canonical script signatures are considered. + * + * @param input - The PsbtInput object representing the input. + * @returns An array of PartialSig objects containing the extracted Psigs. + */ function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { const scriptItems = !input.finalScriptSig ? [] diff --git a/ts_src/push_data.ts b/ts_src/push_data.ts index 56bb02ab2..ade5f82f9 100644 --- a/ts_src/push_data.ts +++ b/ts_src/push_data.ts @@ -1,9 +1,24 @@ import { OPS } from './ops'; +/** + * Calculates the encoding length of a number used for push data in Bitcoin transactions. + * @param i The number to calculate the encoding length for. + * @returns The encoding length of the number. + */ export function encodingLength(i: number): number { return i < OPS.OP_PUSHDATA1 ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5; } +/** + * Encodes a number into a buffer using a variable-length encoding scheme. + * The encoded buffer is written starting at the specified offset. + * Returns the size of the encoded buffer. + * + * @param buffer - The buffer to write the encoded data into. + * @param num - The number to encode. + * @param offset - The offset at which to start writing the encoded buffer. + * @returns The size of the encoded buffer. + */ export function encode(buffer: Buffer, num: number, offset: number): number { const size = encodingLength(num); @@ -30,6 +45,12 @@ export function encode(buffer: Buffer, num: number, offset: number): number { return size; } +/** + * Decodes a buffer and returns information about the opcode, number, and size. + * @param buffer - The buffer to decode. + * @param offset - The offset within the buffer to start decoding. + * @returns An object containing the opcode, number, and size, or null if decoding fails. + */ export function decode( buffer: Buffer, offset: number, diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 39b975545..665583ec5 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -60,6 +60,9 @@ export interface Input { witness: Buffer[]; } +/** + * Represents a Bitcoin transaction. + */ export class Transaction { static readonly DEFAULT_SEQUENCE = 0xffffffff; static readonly SIGHASH_DEFAULT = 0x00; diff --git a/ts_src/types.ts b/ts_src/types.ts index 2457ec240..757cd26d0 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -8,6 +8,11 @@ const EC_P = NBuffer.from( 'hex', ); +/** + * Checks if the given value is a valid elliptic curve point. + * @param p - The value to check. + * @returns True if the value is a valid elliptic curve point, false otherwise. + */ export function isPoint(p: Buffer | number | undefined | null): boolean { if (!NBuffer.isBuffer(p)) return false; if (p.length < 33) return false; From 4ef1bdc759933e0126a0c0f60694d1a6c2d9a42a Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 1 Mar 2024 02:01:55 +0800 Subject: [PATCH 171/249] =?UTF-8?q?=F0=9F=90=9E=20fix:=20fix=20romat:ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ts_src/bufferutils.ts | 2 +- ts_src/crypto.ts | 4 ++-- ts_src/ecc_lib.ts | 4 ++-- ts_src/merkle.ts | 2 +- ts_src/networks.ts | 2 +- ts_src/payments/index.ts | 2 +- ts_src/payments/p2pk.ts | 2 +- ts_src/payments/p2wsh.ts | 2 +- ts_src/psbt/bip371.ts | 4 ++-- ts_src/psbt/psbtutils.ts | 4 ++-- ts_src/script.ts | 5 ++--- ts_src/script_number.ts | 4 ++-- ts_src/types.ts | 1 - 13 files changed, 18 insertions(+), 20 deletions(-) diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts index f56cf4093..b73ce1502 100644 --- a/ts_src/bufferutils.ts +++ b/ts_src/bufferutils.ts @@ -25,7 +25,7 @@ export function readUInt64LE(buffer: Buffer, offset: number): number { /** * Writes a 64-bit unsigned integer in little-endian format to the specified buffer at the given offset. - * + * * @param buffer - The buffer to write the value to. * @param value - The 64-bit unsigned integer value to write. * @param offset - The offset in the buffer where the value should be written. diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index 8d8491d42..223305a11 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,7 +1,7 @@ /** - * A module for hashing functions. + * A module for hashing functions. * include ripemd160、sha1、sha256、hash160、hash256、taggedHash - * + * * @packageDocumentation */ import { ripemd160 as _ripemd160 } from '@noble/hashes/ripemd160'; diff --git a/ts_src/ecc_lib.ts b/ts_src/ecc_lib.ts index a176d09c6..fbd1fcdb8 100644 --- a/ts_src/ecc_lib.ts +++ b/ts_src/ecc_lib.ts @@ -6,7 +6,7 @@ const _ECCLIB_CACHE: { eccLib?: TinySecp256k1Interface } = {}; * Initializes the ECC library with the provided instance. * If `eccLib` is `undefined`, the library will be cleared. * If `eccLib` is a new instance, it will be verified before setting it as the active library. - * + * * @param eccLib The instance of the ECC library to initialize. */ export function initEccLib(eccLib: TinySecp256k1Interface | undefined): void { @@ -39,7 +39,7 @@ const h = (hex: string): Buffer => Buffer.from(hex, 'hex'); /** * Verifies the ECC functionality. - * + * * @param ecc - The TinySecp256k1Interface object. */ function verifyEcc(ecc: TinySecp256k1Interface): void { diff --git a/ts_src/merkle.ts b/ts_src/merkle.ts index 47b161e4e..206f8fcd0 100644 --- a/ts_src/merkle.ts +++ b/ts_src/merkle.ts @@ -1,6 +1,6 @@ /** * Calculates the Merkle root of an array of buffers using a specified digest function. - * + * * @param values - The array of buffers. * @param digestFn - The digest function used to calculate the hash of the concatenated buffers. * @returns The Merkle root as a buffer. diff --git a/ts_src/networks.ts b/ts_src/networks.ts index 24d913db9..9aa465d70 100644 --- a/ts_src/networks.ts +++ b/ts_src/networks.ts @@ -2,7 +2,7 @@ // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 /** - * Represents a Bitcoin network configuration,including messagePrefix, bech32, bip32, pubKeyHash, scriptHash, wif. + * Represents a Bitcoin network configuration,including messagePrefix, bech32, bip32, pubKeyHash, scriptHash, wif. * Support bitcoin、bitcoin testnet and bitcoin regtest. * @packageDocumentation */ diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index ac539a6ff..bd1ac0d84 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,6 +1,6 @@ /** * Represents a payment object, which is used to create a payment. - * + * * Supports P2PKH、P2SH、P2WPKH、P2WSH、P2TR and so on * * @packageDocumentation diff --git a/ts_src/payments/p2pk.ts b/ts_src/payments/p2pk.ts index 097e72b3c..c6c83b35d 100644 --- a/ts_src/payments/p2pk.ts +++ b/ts_src/payments/p2pk.ts @@ -9,7 +9,7 @@ const OPS = bscript.OPS; // output: {pubKey} OP_CHECKSIG /** * Creates a pay-to-public-key (P2PK) payment object. - * + * * @param a - The payment object containing the necessary data. * @param opts - Optional payment options. * @returns The P2PK payment object. diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index 562790cb5..bd9339277 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -27,7 +27,7 @@ function chunkHasUncompressedPubkey(chunk: StackElement): boolean { // output: OP_0 {sha256(redeemScript)} /** * Creates a Pay-to-Witness-Script-Hash (P2WSH) payment object. - * + * * @param a - The payment object containing the necessary data. * @param opts - Optional payment options. * @returns The P2WSH payment object. diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 678c1637a..752265b0b 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -392,7 +392,7 @@ function isTapLeafInTree(tapLeaf: TapLeafScript, merkleRoot?: Buffer): boolean { /** * Sorts the signatures in the input's tapScriptSig array based on their position in the tapLeaf script. - * + * * @param input - The PsbtInput object. * @param tapLeaf - The TapLeafScript object. * @returns An array of sorted signatures as Buffers. @@ -456,7 +456,7 @@ function findTapLeafToFinalize( /** * Determines whether a TapLeafScript can be finalized. - * + * * @param leaf - The TapLeafScript to check. * @param tapScriptSig - The array of TapScriptSig objects. * @param hash - The optional hash to compare with the leaf hash. diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 7639e5211..19cf33e5b 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -152,7 +152,7 @@ export function signatureBlocksAction( * If the input does not have partial signatures, it checks if it has a finalScriptSig or finalScriptWitness. * If it does, it extracts the signatures from the final scripts and returns them. * If none of the above conditions are met, it returns an empty array. - * + * * @param input - The PsbtInput object from which to extract the signatures. * @returns An array of signatures extracted from the PsbtInput object. */ @@ -171,7 +171,7 @@ function extractPartialSigs(input: PsbtInput): Buffer[] { * Retrieves the partial signatures (Psigs) from the input's final scripts. * Psigs are extracted from both the final scriptSig and final scriptWitness of the input. * Only canonical script signatures are considered. - * + * * @param input - The PsbtInput object representing the input. * @returns An array of PartialSig objects containing the extracted Psigs. */ diff --git a/ts_src/script.ts b/ts_src/script.ts index 329831497..54ee98fde 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -11,7 +11,6 @@ import * as scriptSignature from './script_signature'; import * as types from './types'; const { typeforce } = types; - const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 export { OPS }; @@ -57,7 +56,7 @@ function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { /** * Compiles an array of chunks into a Buffer. - * + * * @param chunks - The array of chunks to compile. * @returns The compiled Buffer. * @throws Error if the compilation fails. @@ -207,7 +206,7 @@ export function fromASM(asm: string): Buffer { /** * Converts the given chunks into a stack of buffers. - * + * * @param chunks - The chunks to convert. * @returns The stack of buffers. */ diff --git a/ts_src/script_number.ts b/ts_src/script_number.ts index 19e522f54..1ad20e2ff 100644 --- a/ts_src/script_number.ts +++ b/ts_src/script_number.ts @@ -1,6 +1,6 @@ /** * Decodes a script number from a buffer. - * + * * @param buffer - The buffer containing the script number. * @param maxLength - The maximum length of the script number. Defaults to 4. * @param minimal - Whether the script number should be minimal. Defaults to true. @@ -62,7 +62,7 @@ function scriptNumSize(i: number): number { /** * Encodes a number into a Buffer using a specific format. - * + * * @param _number - The number to encode. * @returns The encoded number as a Buffer. */ diff --git a/ts_src/types.ts b/ts_src/types.ts index 90d1c5e76..971b42363 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -8,7 +8,6 @@ const EC_P = NBuffer.from( 'hex', ); - /** * Checks if two arrays of Buffers are equal. * @param a - The first array of Buffers. From 4554df37d4526d9421713f10097c8c3f03dd1f7d Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Wed, 13 Mar 2024 16:22:25 +0800 Subject: [PATCH 172/249] =?UTF-8?q?=F0=9F=93=84=20docs:=20add=20documentat?= =?UTF-8?q?ion=20link=20to=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01a45733b..ba3700948 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Mistakes and bugs happen, but with your help in resolving and reporting [issues] - Friendly, with a strong and helpful community, ready to answer questions. ## Documentation -Presently, we do not have any formal documentation other than our [examples](#examples), please [ask for help](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new) if our examples aren't enough to guide you. +Visit our [documentation](https://bitcoinjs.github.io/bitcoinjs-lib/) to explore the available resources. We're continually enhancing our documentation with additional features for an enriched experience. If you need further guidance beyond what our [examples](#examples) offer, don't hesitate to [ask for help](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new). We're here to assist you. You can find a [Web UI](https://bitcoincore.tech/apps/bitcoinjs-ui/index.html) that covers most of the `psbt.ts`, `transaction.ts` and `p2*.ts` APIs [here](https://bitcoincore.tech/apps/bitcoinjs-ui/index.html). From 98cdfced8be190ef1e57a0ba9020ebc41c7bd6d7 Mon Sep 17 00:00:00 2001 From: xinhangzhou Date: Wed, 13 Mar 2024 22:10:46 +0800 Subject: [PATCH 173/249] chore: fix some comments Signed-off-by: xinhangzhou --- CHANGELOG.md | 4 ++-- src/psbt.js | 4 ++-- test/integration/transactions.spec.ts | 2 +- test/transaction.spec.ts | 2 +- ts_src/psbt.ts | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0485f0e8..91ed0dae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -331,8 +331,8 @@ Ideally you shoud not have to directly access `HDNode` internals for general usa __added__ - `ECPair.prototype.getNetwork` -- `HDNode.prototype.getNetwork`, wraps the underyling keyPair's `getNetwork` method -- `HDNode.prototype.getPublicKeyBuffer`, wraps the underyling keyPair's `getPublicKeyBuffer` method +- `HDNode.prototype.getNetwork`, wraps the underlying keyPair's `getNetwork` method +- `HDNode.prototype.getPublicKeyBuffer`, wraps the underlying keyPair's `getPublicKeyBuffer` method - `HDNode.prototype.sign`, wraps the underlying keyPair's `sign` method - `HDNode.prototype.verify`, wraps the underlying keyPair's `verify` method diff --git a/src/psbt.js b/src/psbt.js index 5477cd919..227e29304 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -90,7 +90,7 @@ class Psbt { __NON_WITNESS_UTXO_BUF_CACHE: [], __TX_IN_CACHE: {}, __TX: this.data.globalMap.unsignedTx.tx, - // Psbt's predecesor (TransactionBuilder - now removed) behavior + // Psbt's predecessor (TransactionBuilder - now removed) behavior // was to not confirm input values before signing. // Even though we highly encourage people to get // the full parent transaction to verify values, the ability to @@ -1267,7 +1267,7 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { console.warn( 'Warning: Signing non-segwit inputs without the full parent transaction ' + 'means there is a chance that a miner could feed you incorrect information ' + - "to trick you into paying large fees. This behavior is the same as Psbt's predecesor " + + "to trick you into paying large fees. This behavior is the same as Psbt's predecessor " + '(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' + 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' + 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' + diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index 264d99a2f..2754ee226 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -126,7 +126,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee // Let's show a new feature with PSBT. - // We can have multiple signers sign in parrallel and combine them. + // We can have multiple signers sign in parallel and combine them. // (this is not necessary, but a nice feature) // encode to send out to the signers diff --git a/test/transaction.spec.ts b/test/transaction.spec.ts index bf62acedf..991557ba3 100644 --- a/test/transaction.spec.ts +++ b/test/transaction.spec.ts @@ -350,7 +350,7 @@ describe('Transaction', () => { }); describe('setWitness', () => { - it('only accepts a a witness stack (Array of Buffers)', () => { + it('only accepts a witness stack (Array of Buffers)', () => { assert.throws(() => { (new Transaction().setWitness as any)(0, 'foobar'); }, /Expected property "1" of type \[Buffer], got String "foobar"/); diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 66bf91194..a44646592 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -156,7 +156,7 @@ export class Psbt { __NON_WITNESS_UTXO_BUF_CACHE: [], __TX_IN_CACHE: {}, __TX: (this.data.globalMap.unsignedTx as PsbtTransaction).tx, - // Psbt's predecesor (TransactionBuilder - now removed) behavior + // Psbt's predecessor (TransactionBuilder - now removed) behavior // was to not confirm input values before signing. // Even though we highly encourage people to get // the full parent transaction to verify values, the ability to @@ -1678,7 +1678,7 @@ function getHashForSig( console.warn( 'Warning: Signing non-segwit inputs without the full parent transaction ' + 'means there is a chance that a miner could feed you incorrect information ' + - "to trick you into paying large fees. This behavior is the same as Psbt's predecesor " + + "to trick you into paying large fees. This behavior is the same as Psbt's predecessor " + '(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' + 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' + 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' + From 562f12372468173a0ca434808f48e484323c90a9 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Mon, 8 Apr 2024 14:34:28 +0800 Subject: [PATCH 174/249] =?UTF-8?q?=F0=9F=9A=85=20perfs:=20remove=20method?= =?UTF-8?q?s=20that=20neither=20used=20nor=20export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/types.d.ts | 8 -------- src/types.js | 43 ------------------------------------------- test/types.spec.ts | 37 ------------------------------------- ts_src/types.ts | 35 ----------------------------------- 5 files changed, 1 insertion(+), 123 deletions(-) diff --git a/.gitignore b/.gitignore index c1733a6e0..664a5520f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ test/*.js test/integration/*.js !test/ts-node-register.js docs +plugin \ No newline at end of file diff --git a/src/types.d.ts b/src/types.d.ts index b08a1fb6f..31e906a1a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -13,15 +13,7 @@ export declare function stacksEqual(a: Buffer[], b: Buffer[]): boolean; * @returns True if the value is a valid elliptic curve point, false otherwise. */ export declare function isPoint(p: Buffer | number | undefined | null): boolean; -export declare function UInt31(value: number): boolean; -export declare function BIP32Path(value: string): boolean; -export declare namespace BIP32Path { - var toJSON: () => string; -} -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 XOnlyPointAddTweakResult { parity: 1 | 0; xOnlyPubkey: Uint8Array; diff --git a/src/types.js b/src/types.js index 5bcd54c81..bcebef356 100644 --- a/src/types.js +++ b/src/types.js @@ -20,12 +20,7 @@ exports.oneOf = exports.isTaptree = exports.isTapleaf = exports.TAPLEAF_VERSION_MASK = - exports.Network = - exports.ECPoint = exports.Satoshi = - exports.Signer = - exports.BIP32Path = - exports.UInt31 = exports.isPoint = exports.stacksEqual = exports.typeforce = @@ -72,49 +67,11 @@ function isPoint(p) { return false; } exports.isPoint = isPoint; -const UINT31_MAX = Math.pow(2, 31) - 1; -function UInt31(value) { - return exports.typeforce.UInt32(value) && value <= UINT31_MAX; -} -exports.UInt31 = UInt31; -function BIP32Path(value) { - return ( - exports.typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) - ); -} -exports.BIP32Path = BIP32Path; -BIP32Path.toJSON = () => { - return 'BIP32 derivation path'; -}; -function Signer(obj) { - return ( - (exports.typeforce.Buffer(obj.publicKey) || - typeof obj.getPublicKey === 'function') && - typeof obj.sign === 'function' - ); -} -exports.Signer = Signer; const SATOSHI_MAX = 21 * 1e14; function Satoshi(value) { return exports.typeforce.UInt53(value) && value <= SATOSHI_MAX; } exports.Satoshi = Satoshi; -// external dependent types -exports.ECPoint = exports.typeforce.quacksLike('Point'); -// exposed, external API -exports.Network = exports.typeforce.compile({ - messagePrefix: exports.typeforce.oneOf( - exports.typeforce.Buffer, - exports.typeforce.String, - ), - bip32: { - public: exports.typeforce.UInt32, - private: exports.typeforce.UInt32, - }, - pubKeyHash: exports.typeforce.UInt8, - scriptHash: exports.typeforce.UInt8, - wif: exports.typeforce.UInt8, -}); exports.TAPLEAF_VERSION_MASK = 0xfe; function isTapleaf(o) { if (!o || !('output' in o)) return false; diff --git a/test/types.spec.ts b/test/types.spec.ts index 478fd997e..363b83c19 100644 --- a/test/types.spec.ts +++ b/test/types.spec.ts @@ -54,41 +54,4 @@ describe('types', () => { }); }); }); - - describe('UInt31', () => { - const UINT31_MAX = Math.pow(2, 31) - 1; - it('return true for valid values', () => { - assert.strictEqual(types.UInt31(0), true); - assert.strictEqual(types.UInt31(1000), true); - assert.strictEqual(types.UInt31(UINT31_MAX), true); - }); - - it('return false for negative values', () => { - assert.strictEqual(types.UInt31(-1), false); - assert.strictEqual(types.UInt31(-UINT31_MAX), false); - }); - - it(`return false for value > ${UINT31_MAX}`, () => { - assert.strictEqual(types.UInt31(UINT31_MAX + 1), false); - }); - }); - - describe('BIP32Path', () => { - it('return true for valid paths', () => { - assert.strictEqual(types.BIP32Path("m/0'/0'"), true); - assert.strictEqual(types.BIP32Path("m/0'/0"), true); - assert.strictEqual(types.BIP32Path("m/0'/1'/2'/3/4'"), true); - }); - - it('return false for invalid paths', () => { - assert.strictEqual(types.BIP32Path('m'), false); - assert.strictEqual(types.BIP32Path("n/0'/0'"), false); - assert.strictEqual(types.BIP32Path("m/0'/x"), false); - }); - - it('return "BIP32 derivation path" for JSON.strigify()', () => { - const toJsonValue = JSON.stringify(types.BIP32Path); - assert.equal(toJsonValue, '"BIP32 derivation path"'); - }); - }); }); diff --git a/ts_src/types.ts b/ts_src/types.ts index 971b42363..f4ce4875d 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -46,46 +46,11 @@ export function isPoint(p: Buffer | number | undefined | null): boolean { return false; } -const UINT31_MAX: number = Math.pow(2, 31) - 1; -export function UInt31(value: number): boolean { - return typeforce.UInt32(value) && value <= UINT31_MAX; -} - -export function BIP32Path(value: string): boolean { - return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); -} -BIP32Path.toJSON = (): string => { - return 'BIP32 derivation path'; -}; - -export function Signer(obj: any): boolean { - return ( - (typeforce.Buffer(obj.publicKey) || - typeof obj.getPublicKey === 'function') && - typeof obj.sign === 'function' - ); -} - const SATOSHI_MAX: number = 21 * 1e14; export function Satoshi(value: number): boolean { return typeforce.UInt53(value) && value <= SATOSHI_MAX; } -// external dependent types -export const ECPoint = typeforce.quacksLike('Point'); - -// exposed, external API -export const Network = typeforce.compile({ - messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), - bip32: { - public: typeforce.UInt32, - private: typeforce.UInt32, - }, - pubKeyHash: typeforce.UInt8, - scriptHash: typeforce.UInt8, - wif: typeforce.UInt8, -}); - export interface XOnlyPointAddTweakResult { parity: 1 | 0; xOnlyPubkey: Uint8Array; From 7f4bc4b1421df30ff14ffc18173c16c2924fec8a Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 12 Apr 2024 18:26:31 +0900 Subject: [PATCH 175/249] Remove unneeded package lock deps --- package-lock.json | 104 ---------------------------------------------- 1 file changed, 104 deletions(-) diff --git a/package-lock.json b/package-lock.json index c02f958ed..b4e1324a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,6 @@ "tiny-secp256k1": "^2.2.0", "ts-node": "^8.3.0", "typedoc": "^0.25.1", - "typedoc-plugin-bitcoinjs-runcase": "^1.0.1", "typescript": "^4.4.4" }, "engines": { @@ -2875,18 +2874,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3670,31 +3657,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, "node_modules/readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -3891,15 +3853,6 @@ } ] }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -4354,16 +4307,6 @@ "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" } }, - "node_modules/typedoc-plugin-bitcoinjs-runcase": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-bitcoinjs-runcase/-/typedoc-plugin-bitcoinjs-runcase-1.0.1.tgz", - "integrity": "sha512-tqbnCCP2ku2egGjwn0G0bRNeSH/lh3FT7khrpyYdx1kDu8rjKlyqJX7yPgEj4biBHJATJnQGxp3cKmYG0wv5uw==", - "dev": true, - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - } - }, "node_modules/typedoc/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -6768,15 +6711,6 @@ "is-unicode-supported": "^0.1.0" } }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7372,25 +7306,6 @@ "safe-buffer": "^5.1.0" } }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dev": true, - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, "readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -7524,15 +7439,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dev": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -7892,16 +7798,6 @@ } } }, - "typedoc-plugin-bitcoinjs-runcase": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-bitcoinjs-runcase/-/typedoc-plugin-bitcoinjs-runcase-1.0.1.tgz", - "integrity": "sha512-tqbnCCP2ku2egGjwn0G0bRNeSH/lh3FT7khrpyYdx1kDu8rjKlyqJX7yPgEj4biBHJATJnQGxp3cKmYG0wv5uw==", - "dev": true, - "requires": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - } - }, "typeforce": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", From 7e74ff28463c4716105423931ca7948fc075318f Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Tue, 30 Apr 2024 18:53:23 +0800 Subject: [PATCH 176/249] =?UTF-8?q?=F0=9F=90=9E=20fix:=20deepClone=20outpu?= =?UTF-8?q?tData=20to=20avoid=20mutate=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/psbt.js | 2 +- ts_src/psbt.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 227e29304..cc0e5c8ad 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -243,7 +243,7 @@ class Psbt { if (typeof address === 'string') { const { network } = this.opts; const script = (0, address_1.toOutputScript)(address, network); - outputData = Object.assign(outputData, { script }); + outputData = Object.assign({}, outputData, { script }); } (0, bip371_1.checkTaprootOutputFields)(outputData, outputData, 'addOutput'); const c = this.__CACHE; diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index a44646592..b2aa36f1f 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -331,7 +331,7 @@ export class Psbt { if (typeof address === 'string') { const { network } = this.opts; const script = toOutputScript(address, network); - outputData = Object.assign(outputData, { script }); + outputData = Object.assign({}, outputData, { script }); } checkTaprootOutputFields(outputData, outputData, 'addOutput'); From 6ec2822357e48cad2c9b1a31c0b97af0d6baad89 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 10 May 2024 12:08:16 +0800 Subject: [PATCH 177/249] perfs: optimize script.decomplie return type --- src/payments/p2sh.js | 2 +- src/payments/p2tr.js | 2 +- src/payments/p2wsh.js | 9 +++------ src/psbt.js | 6 +++--- src/psbt/psbtutils.js | 6 +++--- src/script.d.ts | 2 +- src/script.js | 5 ++--- test/script.spec.ts | 2 +- ts_src/payments/embed.ts | 7 +++---- ts_src/payments/p2ms.ts | 4 ++-- ts_src/payments/p2sh.ts | 9 +++------ ts_src/payments/p2tr.ts | 2 +- ts_src/payments/p2wsh.ts | 9 +++------ ts_src/psbt.ts | 6 +++--- ts_src/psbt/psbtutils.ts | 6 +++--- ts_src/script.ts | 13 +++++-------- ts_src/transaction.ts | 2 +- 17 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 1386966be..7a7bbd243 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -138,7 +138,7 @@ function p2sh(a, opts) { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (!decompile || decompile.length < 1) + if (decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 33fedb464..ccf803aa8 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -230,7 +230,7 @@ function p2tr(a, opts) { throw new TypeError('Redeem.redeemVersion and witness mismatch'); } if (a.redeem.output) { - if (bscript.decompile(a.redeem.output).length === 0) + if (!bscript.decompile(a.redeem.output).length) 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)) diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index a3422e50a..b45e3e6e3 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -171,8 +171,7 @@ function p2wsh(a, opts) { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile || decompile.length < 1) - throw new TypeError('Redeem.output is invalid'); + if (!decompile.length) throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -198,9 +197,7 @@ function p2wsh(a, opts) { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - (bscript.decompile(a.redeem.output) || []).some( - chunkHasUncompressedPubkey, - )) + bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -213,7 +210,7 @@ function p2wsh(a, opts) { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) + bscript.decompile(wScript).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/src/psbt.js b/src/psbt.js index cc0e5c8ad..b95bd8b9d 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1636,7 +1636,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { function redeemFromFinalScriptSig(finalScript) { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp) return; + if (!decomp.length) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -1645,7 +1645,7 @@ function redeemFromFinalScriptSig(finalScript) { ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } function redeemFromFinalWitnessScript(finalScript) { @@ -1654,7 +1654,7 @@ function redeemFromFinalWitnessScript(finalScript) { const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } function compressPubkey(pubkey) { diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index ea5f1d719..bf1cbd2d0 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -79,7 +79,7 @@ function pubkeyPositionInScript(pubkey, script) { const pubkeyHash = (0, crypto_1.hash160)(pubkey); const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (decompiled === null) throw new Error('Unknown script error'); + if (!decompiled.length) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; return ( @@ -173,10 +173,10 @@ function extractPartialSigs(input) { function getPsigsFromInputFinalScripts(input) { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig) || []; + : bscript.decompile(input.finalScriptSig); const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness) || []; + : bscript.decompile(input.finalScriptWitness); return scriptItems .concat(witnessItems) .filter(item => { diff --git a/src/script.d.ts b/src/script.d.ts index ffc8c89bb..e8563cf03 100644 --- a/src/script.d.ts +++ b/src/script.d.ts @@ -14,7 +14,7 @@ export declare function countNonPushOnlyOPs(value: Stack): number; * @throws Error if the compilation fails. */ export declare function compile(chunks: Buffer | Stack): Buffer; -export declare function decompile(buffer: Buffer | Array): Array | null; +export declare function decompile(buffer: Buffer | Array): Stack; /** * Converts the given chunks into an ASM (Assembly) string representation. * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. diff --git a/src/script.js b/src/script.js index be2805190..996d640d0 100644 --- a/src/script.js +++ b/src/script.js @@ -115,7 +115,6 @@ function compile(chunks) { } exports.compile = compile; function decompile(buffer) { - // TODO: remove me if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); const chunks = []; @@ -126,10 +125,10 @@ function decompile(buffer) { if (opcode > ops_1.OPS.OP_0 && opcode <= ops_1.OPS.OP_PUSHDATA4) { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return null; + if (d === null) return []; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return null; + if (i + d.number > buffer.length) return []; const data = buffer.slice(i, i + d.number); i += d.number; // decompile minimally diff --git a/test/script.spec.ts b/test/script.spec.ts index d593ab17d..570d1ccc0 100644 --- a/test/script.spec.ts +++ b/test/script.spec.ts @@ -158,7 +158,7 @@ describe('script', () => { () => { const chunks = bscript.decompile(Buffer.from(f.script, 'hex')); - assert.strictEqual(chunks, null); + assert.deepStrictEqual(chunks, []); }, ); }); diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index aef14e1b7..1acbab6ca 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -36,16 +36,15 @@ export function p2data(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'data', () => { if (!a.output) return; - return bscript.decompile(a.output)!.slice(1); + return bscript.decompile(a.output).slice(1); }); // extended validation if (opts.validate) { if (a.output) { const chunks = bscript.decompile(a.output); - if (chunks![0] !== OPS.OP_RETURN) - throw new TypeError('Output is invalid'); - if (!chunks!.slice(1).every(typef.Buffer)) + if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid'); + if (!chunks.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid'); if (a.data && !stacksEqual(a.data, o.data as Buffer[])) diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index ffbf0155b..c5d7d7ac3 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -55,7 +55,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { function decode(output: Buffer | Stack): void { if (decoded) return; decoded = true; - chunks = bscript.decompile(output) as Stack; + chunks = bscript.decompile(output); o.m = (chunks[0] as number) - OP_INT_BASE; o.n = (chunks[chunks.length - 2] as number) - OP_INT_BASE; o.pubkeys = chunks.slice(1, -2) as Buffer[]; @@ -90,7 +90,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'signatures', () => { if (!a.input) return; - return bscript.decompile(a.input)!.slice(1); + return bscript.decompile(a.input).slice(1); }); lazy.prop(o, 'input', () => { if (!a.signatures) return; diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index 2f5f936c6..fbb449450 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -105,10 +105,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'input', () => { if (!a.redeem || !a.redeem.input || !a.redeem.output) return; return bscript.compile( - ([] as Stack).concat( - bscript.decompile(a.redeem.input) as Stack, - a.redeem.output, - ), + ([] as Stack).concat(bscript.decompile(a.redeem.input), a.redeem.output), ); }); lazy.prop(o, 'witness', () => { @@ -157,7 +154,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (!decompile || decompile.length < 1) + if (decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( @@ -182,7 +179,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { if (hasInput && hasWitness) throw new TypeError('Input and witness provided'); if (hasInput) { - const richunks = bscript.decompile(redeem.input) as Stack; + const richunks = bscript.decompile(redeem.input); if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig'); } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index c1140b715..ac7e8bb92 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -252,7 +252,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.redeem.output) { - if (bscript.decompile(a.redeem.output)!.length === 0) + if (!bscript.decompile(a.redeem.output).length) throw new TypeError('Redeem.output is invalid'); // output redeem is constructed from the witness diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index bd9339277..1456a7008 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -183,8 +183,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile || decompile.length < 1) - throw new TypeError('Redeem.output is invalid'); + if (!decompile.length) throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -212,9 +211,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - (bscript.decompile(a.redeem.output) || []).some( - chunkHasUncompressedPubkey, - )) + bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -228,7 +225,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) + bscript.decompile(wScript).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index b2aa36f1f..2ed52418f 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -2148,7 +2148,7 @@ function redeemFromFinalScriptSig( ): Buffer | undefined { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp) return; + if (!decomp.length) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -2157,7 +2157,7 @@ function redeemFromFinalScriptSig( ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } @@ -2169,7 +2169,7 @@ function redeemFromFinalWitnessScript( const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 19cf33e5b..3b2fcedaf 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -75,7 +75,7 @@ export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (decompiled === null) throw new Error('Unknown script error'); + if (!decompiled.length) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; @@ -178,10 +178,10 @@ function extractPartialSigs(input: PsbtInput): Buffer[] { function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig) || []; + : bscript.decompile(input.finalScriptSig); const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness) || []; + : bscript.decompile(input.finalScriptWitness); return scriptItems .concat(witnessItems) .filter(item => { diff --git a/ts_src/script.ts b/ts_src/script.ts index 54ee98fde..f858c467d 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -111,10 +111,7 @@ export function compile(chunks: Buffer | Stack): Buffer { return buffer; } -export function decompile( - buffer: Buffer | Array, -): Array | null { - // TODO: remove me +export function decompile(buffer: Buffer | Array): Stack { if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); @@ -130,11 +127,11 @@ export function decompile( const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return null; + if (d === null) return []; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return null; + if (i + d.number > buffer.length) return []; const data = buffer.slice(i, i + d.number); i += d.number; @@ -166,7 +163,7 @@ export function decompile( */ export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { - chunks = decompile(chunks) as Stack; + chunks = decompile(chunks); } return chunks @@ -211,7 +208,7 @@ export function fromASM(asm: string): Buffer { * @returns The stack of buffers. */ export function toStack(chunks: Buffer | Array): Buffer[] { - chunks = decompile(chunks) as Stack; + chunks = decompile(chunks); typeforce(isPushOnly, chunks); return chunks.map(op => { diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 665583ec5..784f8b01c 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -284,7 +284,7 @@ export class Transaction { // ignore OP_CODESEPARATOR const ourScript = bscript.compile( - bscript.decompile(prevOutScript)!.filter(x => { + bscript.decompile(prevOutScript).filter(x => { return x !== opcodes.OP_CODESEPARATOR; }), ); From ac6a5b21b28c50d3d8d4af01011d915ca867ff8e Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 10 May 2024 19:11:01 +0800 Subject: [PATCH 178/249] Revert "perfs: optimize script.decomplie return type" This reverts commit 6ec2822357e48cad2c9b1a31c0b97af0d6baad89. --- src/payments/p2sh.js | 2 +- src/payments/p2tr.js | 2 +- src/payments/p2wsh.js | 9 ++++++--- src/psbt.js | 6 +++--- src/psbt/psbtutils.js | 6 +++--- src/script.d.ts | 2 +- src/script.js | 5 +++-- test/script.spec.ts | 2 +- ts_src/payments/embed.ts | 7 ++++--- ts_src/payments/p2ms.ts | 4 ++-- ts_src/payments/p2sh.ts | 9 ++++++--- ts_src/payments/p2tr.ts | 2 +- ts_src/payments/p2wsh.ts | 9 ++++++--- ts_src/psbt.ts | 6 +++--- ts_src/psbt/psbtutils.ts | 6 +++--- ts_src/script.ts | 13 ++++++++----- ts_src/transaction.ts | 2 +- 17 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 7a7bbd243..1386966be 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -138,7 +138,7 @@ function p2sh(a, opts) { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (decompile.length < 1) + if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index ccf803aa8..33fedb464 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -230,7 +230,7 @@ function p2tr(a, opts) { throw new TypeError('Redeem.redeemVersion and witness mismatch'); } if (a.redeem.output) { - if (!bscript.decompile(a.redeem.output).length) + 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)) diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index b45e3e6e3..a3422e50a 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -171,7 +171,8 @@ function p2wsh(a, opts) { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile.length) throw new TypeError('Redeem.output is invalid'); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -197,7 +198,9 @@ function p2wsh(a, opts) { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) + (bscript.decompile(a.redeem.output) || []).some( + chunkHasUncompressedPubkey, + )) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -210,7 +213,7 @@ function p2wsh(a, opts) { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - bscript.decompile(wScript).some(chunkHasUncompressedPubkey) + (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/src/psbt.js b/src/psbt.js index b95bd8b9d..cc0e5c8ad 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1636,7 +1636,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { function redeemFromFinalScriptSig(finalScript) { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp.length) return; + if (!decomp) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -1645,7 +1645,7 @@ function redeemFromFinalScriptSig(finalScript) { ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } function redeemFromFinalWitnessScript(finalScript) { @@ -1654,7 +1654,7 @@ function redeemFromFinalWitnessScript(finalScript) { const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } function compressPubkey(pubkey) { diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index bf1cbd2d0..ea5f1d719 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -79,7 +79,7 @@ function pubkeyPositionInScript(pubkey, script) { const pubkeyHash = (0, crypto_1.hash160)(pubkey); const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (!decompiled.length) throw new Error('Unknown script error'); + if (decompiled === null) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; return ( @@ -173,10 +173,10 @@ function extractPartialSigs(input) { function getPsigsFromInputFinalScripts(input) { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig); + : bscript.decompile(input.finalScriptSig) || []; const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness); + : bscript.decompile(input.finalScriptWitness) || []; return scriptItems .concat(witnessItems) .filter(item => { diff --git a/src/script.d.ts b/src/script.d.ts index e8563cf03..ffc8c89bb 100644 --- a/src/script.d.ts +++ b/src/script.d.ts @@ -14,7 +14,7 @@ export declare function countNonPushOnlyOPs(value: Stack): number; * @throws Error if the compilation fails. */ export declare function compile(chunks: Buffer | Stack): Buffer; -export declare function decompile(buffer: Buffer | Array): Stack; +export declare function decompile(buffer: Buffer | Array): Array | null; /** * Converts the given chunks into an ASM (Assembly) string representation. * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. diff --git a/src/script.js b/src/script.js index 996d640d0..be2805190 100644 --- a/src/script.js +++ b/src/script.js @@ -115,6 +115,7 @@ function compile(chunks) { } exports.compile = compile; function decompile(buffer) { + // TODO: remove me if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); const chunks = []; @@ -125,10 +126,10 @@ function decompile(buffer) { if (opcode > ops_1.OPS.OP_0 && opcode <= ops_1.OPS.OP_PUSHDATA4) { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return []; + if (d === null) return null; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return []; + if (i + d.number > buffer.length) return null; const data = buffer.slice(i, i + d.number); i += d.number; // decompile minimally diff --git a/test/script.spec.ts b/test/script.spec.ts index 570d1ccc0..d593ab17d 100644 --- a/test/script.spec.ts +++ b/test/script.spec.ts @@ -158,7 +158,7 @@ describe('script', () => { () => { const chunks = bscript.decompile(Buffer.from(f.script, 'hex')); - assert.deepStrictEqual(chunks, []); + assert.strictEqual(chunks, null); }, ); }); diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index 1acbab6ca..aef14e1b7 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -36,15 +36,16 @@ export function p2data(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'data', () => { if (!a.output) return; - return bscript.decompile(a.output).slice(1); + return bscript.decompile(a.output)!.slice(1); }); // extended validation if (opts.validate) { if (a.output) { const chunks = bscript.decompile(a.output); - if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid'); - if (!chunks.slice(1).every(typef.Buffer)) + if (chunks![0] !== OPS.OP_RETURN) + throw new TypeError('Output is invalid'); + if (!chunks!.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid'); if (a.data && !stacksEqual(a.data, o.data as Buffer[])) diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index c5d7d7ac3..ffbf0155b 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -55,7 +55,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { function decode(output: Buffer | Stack): void { if (decoded) return; decoded = true; - chunks = bscript.decompile(output); + chunks = bscript.decompile(output) as Stack; o.m = (chunks[0] as number) - OP_INT_BASE; o.n = (chunks[chunks.length - 2] as number) - OP_INT_BASE; o.pubkeys = chunks.slice(1, -2) as Buffer[]; @@ -90,7 +90,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'signatures', () => { if (!a.input) return; - return bscript.decompile(a.input).slice(1); + return bscript.decompile(a.input)!.slice(1); }); lazy.prop(o, 'input', () => { if (!a.signatures) return; diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index fbb449450..2f5f936c6 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -105,7 +105,10 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'input', () => { if (!a.redeem || !a.redeem.input || !a.redeem.output) return; return bscript.compile( - ([] as Stack).concat(bscript.decompile(a.redeem.input), a.redeem.output), + ([] as Stack).concat( + bscript.decompile(a.redeem.input) as Stack, + a.redeem.output, + ), ); }); lazy.prop(o, 'witness', () => { @@ -154,7 +157,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (decompile.length < 1) + if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( @@ -179,7 +182,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { if (hasInput && hasWitness) throw new TypeError('Input and witness provided'); if (hasInput) { - const richunks = bscript.decompile(redeem.input); + const richunks = bscript.decompile(redeem.input) as Stack; if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig'); } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index ac7e8bb92..c1140b715 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -252,7 +252,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.redeem.output) { - if (!bscript.decompile(a.redeem.output).length) + if (bscript.decompile(a.redeem.output)!.length === 0) throw new TypeError('Redeem.output is invalid'); // output redeem is constructed from the witness diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index 1456a7008..bd9339277 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -183,7 +183,8 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile.length) throw new TypeError('Redeem.output is invalid'); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -211,7 +212,9 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) + (bscript.decompile(a.redeem.output) || []).some( + chunkHasUncompressedPubkey, + )) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -225,7 +228,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - bscript.decompile(wScript).some(chunkHasUncompressedPubkey) + (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 2ed52418f..b2aa36f1f 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -2148,7 +2148,7 @@ function redeemFromFinalScriptSig( ): Buffer | undefined { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp.length) return; + if (!decomp) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -2157,7 +2157,7 @@ function redeemFromFinalScriptSig( ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } @@ -2169,7 +2169,7 @@ function redeemFromFinalWitnessScript( const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 3b2fcedaf..19cf33e5b 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -75,7 +75,7 @@ export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (!decompiled.length) throw new Error('Unknown script error'); + if (decompiled === null) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; @@ -178,10 +178,10 @@ function extractPartialSigs(input: PsbtInput): Buffer[] { function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig); + : bscript.decompile(input.finalScriptSig) || []; const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness); + : bscript.decompile(input.finalScriptWitness) || []; return scriptItems .concat(witnessItems) .filter(item => { diff --git a/ts_src/script.ts b/ts_src/script.ts index f858c467d..54ee98fde 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -111,7 +111,10 @@ export function compile(chunks: Buffer | Stack): Buffer { return buffer; } -export function decompile(buffer: Buffer | Array): Stack { +export function decompile( + buffer: Buffer | Array, +): Array | null { + // TODO: remove me if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); @@ -127,11 +130,11 @@ export function decompile(buffer: Buffer | Array): Stack { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return []; + if (d === null) return null; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return []; + if (i + d.number > buffer.length) return null; const data = buffer.slice(i, i + d.number); i += d.number; @@ -163,7 +166,7 @@ export function decompile(buffer: Buffer | Array): Stack { */ export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { - chunks = decompile(chunks); + chunks = decompile(chunks) as Stack; } return chunks @@ -208,7 +211,7 @@ export function fromASM(asm: string): Buffer { * @returns The stack of buffers. */ export function toStack(chunks: Buffer | Array): Buffer[] { - chunks = decompile(chunks); + chunks = decompile(chunks) as Stack; typeforce(isPushOnly, chunks); return chunks.map(op => { diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 784f8b01c..665583ec5 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -284,7 +284,7 @@ export class Transaction { // ignore OP_CODESEPARATOR const ourScript = bscript.compile( - bscript.decompile(prevOutScript).filter(x => { + bscript.decompile(prevOutScript)!.filter(x => { return x !== opcodes.OP_CODESEPARATOR; }), ); From bbd61bfd7afbdff2b860ad6548062240b562dfb2 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Mon, 13 May 2024 20:05:28 +0800 Subject: [PATCH 179/249] perfs: toASM throw error when receive invalid chunks --- src/script.js | 3 +++ ts_src/script.ts | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/script.js b/src/script.js index be2805190..0bc4475aa 100644 --- a/src/script.js +++ b/src/script.js @@ -158,6 +158,9 @@ function toASM(chunks) { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks); } + if (!chunks) { + throw new Error('convert invalid chunks to ASM'); + } return chunks .map(chunk => { // data? diff --git a/ts_src/script.ts b/ts_src/script.ts index 54ee98fde..455dce3d3 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -168,7 +168,9 @@ export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks) as Stack; } - + if (!chunks) { + throw new Error('convert invalid chunks to ASM'); + } return chunks .map(chunk => { // data? From 7efa3f9d6ee6e2d9bb90c10211d5b14f99fba9a9 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 17 May 2024 12:07:46 +0800 Subject: [PATCH 180/249] fix: update toASM error tips --- src/script.js | 2 +- ts_src/script.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script.js b/src/script.js index 0bc4475aa..c95f0cc67 100644 --- a/src/script.js +++ b/src/script.js @@ -159,7 +159,7 @@ function toASM(chunks) { chunks = decompile(chunks); } if (!chunks) { - throw new Error('convert invalid chunks to ASM'); + throw new Error('Could not convert invalid chunks to ASM'); } return chunks .map(chunk => { diff --git a/ts_src/script.ts b/ts_src/script.ts index 455dce3d3..c2a033569 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -169,7 +169,7 @@ export function toASM(chunks: Buffer | Array): string { chunks = decompile(chunks) as Stack; } if (!chunks) { - throw new Error('convert invalid chunks to ASM'); + throw new Error('Could not convert invalid chunks to ASM'); } return chunks .map(chunk => { From 1d87e578fcb7649171d991f9542f3d83c061bc61 Mon Sep 17 00:00:00 2001 From: Long Li Date: Sun, 9 Jun 2024 13:55:16 +0900 Subject: [PATCH 181/249] sign tapLeaf with sighashType --- ts_src/psbt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index b2aa36f1f..c73574035 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1789,7 +1789,7 @@ function getTaprootHashesForSig( inputIndex, signingScripts, values, - Transaction.SIGHASH_DEFAULT, + sighashType, tapLeaf.hash, ); From 26f3eda4c6af8b33ee03bcd2fe822de2000f3b80 Mon Sep 17 00:00:00 2001 From: Jonathan Underwood Date: Sun, 9 Jun 2024 19:19:15 +0900 Subject: [PATCH 182/249] Update JS files to match TS files --- src/psbt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psbt.js b/src/psbt.js index cc0e5c8ad..b071f374f 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1358,7 +1358,7 @@ function getTaprootHashesForSig( inputIndex, signingScripts, values, - transaction_1.Transaction.SIGHASH_DEFAULT, + sighashType, tapLeaf.hash, ); return { From 57078a246493fd35ea2af2e19cefbc528e65a335 Mon Sep 17 00:00:00 2001 From: junderw Date: Sun, 9 Jun 2024 19:35:12 +0900 Subject: [PATCH 183/249] v6.1.6 --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ed0dae5..6fdcdca38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 6.1.6 +__fixed__ +- Fix sighash treatment when signing taproot script sign scripts using Psbt (#2104) +- Fix error for invalid scripts in toASM (#2097) +- Fix mutation of input to addOutput method on Psbt (#2091) + # 6.1.5 __fixed__ - Updated bip174 dependency to fix issue with unknownKeyVals. (#1979) diff --git a/package-lock.json b/package-lock.json index b4e1324a9..e0a146dbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bitcoinjs-lib", - "version": "6.1.5", + "version": "6.1.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bitcoinjs-lib", - "version": "6.1.5", + "version": "6.1.6", "license": "MIT", "dependencies": { "@noble/hashes": "^1.2.0", diff --git a/package.json b/package.json index 13c845713..c061f5881 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.1.5", + "version": "6.1.6", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From cfd61e66ed660b2d7a81cf3786444c76ac2f29e9 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Thu, 13 Jun 2024 23:45:51 +0800 Subject: [PATCH 184/249] docs: update README with alternatives for ECC Library --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index ba3700948..597181431 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,22 @@ Which you can then import as an ESM module: ```` +**Using Taproot:** +When utilizing Taproot features with bitcoinjs-lib, you may need to include an additional ECC (Elliptic Curve Cryptography) library. The commonly used tiny-secp256k1 library, however, might lead to compatibility issues due to its reliance on WASM (WebAssembly). + +If you encounter the following error: + +``` +Uncaught TypeError: (0 , fs_1.readFileSync) is not a function +``` +This indicates that tiny-secp256k1's WASM implementation is not fully compatible with your browser environment. +**Alternatives for ECC Library:** +1. **@bitcoinjs-lib/tiny-secp256k1-asmjs** + A version compiled to ASM.js, potentially better supported in browsers. +2. **@bitcoinerlab/secp256k1** + Another alternative library for ECC functionality. +For advantages and detailed comparison of these libraries, visit: [tiny-secp256k1 GitHub page](https://github.com/bitcoinjs/tiny-secp256k1). + **NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](https://babeljs.io/docs/plugins/preset-es2015/) preset). **WARNING**: iOS devices have [problems](https://github.com/feross/buffer/issues/136), use at least [buffer@5.0.5](https://github.com/feross/buffer/pull/155) or greater, and enforce the test suites (for `Buffer`, and any other dependency) pass before use. From 1a868e8ce4e2302a465b53bf47cc68bae55b113a Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 14 Jun 2024 22:08:22 +0800 Subject: [PATCH 185/249] =?UTF-8?q?=F0=9F=94=B5=20other:=20update=20audit?= =?UTF-8?q?=20ci=20to=20ignore=201097496?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index 2e197b4e6..dcb5a369f 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -71,7 +71,7 @@ jobs: registry-url: https://registry.npmjs.org/ cache: 'npm' - run: npm ci - - run: npm run audit + - run: npm run audit -- -x 1097496 coverage: runs-on: ubuntu-latest steps: From 24e13b4f0856cca8e93d12875ed9d6a65e160d38 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 14 Jun 2024 23:30:50 +0800 Subject: [PATCH 186/249] docs: backticks around tiny-secp256k1 --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 597181431..f6ff73c52 100644 --- a/README.md +++ b/README.md @@ -103,14 +103,15 @@ Which you can then import as an ESM module: ```` **Using Taproot:** -When utilizing Taproot features with bitcoinjs-lib, you may need to include an additional ECC (Elliptic Curve Cryptography) library. The commonly used tiny-secp256k1 library, however, might lead to compatibility issues due to its reliance on WASM (WebAssembly). +When utilizing Taproot features with bitcoinjs-lib, you may need to include an additional ECC (Elliptic Curve Cryptography) library. The commonly used `tiny-secp256k1` library, however, might lead to compatibility issues due to its reliance on WASM (WebAssembly). If you encounter the following error: ``` Uncaught TypeError: (0 , fs_1.readFileSync) is not a function ``` -This indicates that tiny-secp256k1's WASM implementation is not fully compatible with your browser environment. +This indicates that tiny-secp256k1's WASM implementation is not fully compatible with your browser environment. + **Alternatives for ECC Library:** 1. **@bitcoinjs-lib/tiny-secp256k1-asmjs** A version compiled to ASM.js, potentially better supported in browsers. From 2a17aec7a6ace265bcbf5645610526042c18be7a Mon Sep 17 00:00:00 2001 From: Jonathan Underwood Date: Sat, 15 Jun 2024 13:16:41 +0900 Subject: [PATCH 187/249] Fix audit Ignore the audit vulnerability since it won't affect us. --- .github/workflows/main_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index 2e197b4e6..dcb5a369f 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -71,7 +71,7 @@ jobs: registry-url: https://registry.npmjs.org/ cache: 'npm' - run: npm ci - - run: npm run audit + - run: npm run audit -- -x 1097496 coverage: runs-on: ubuntu-latest steps: From 4802803b4bbf2b6160bd5f99f2ffee0d90accfa0 Mon Sep 17 00:00:00 2001 From: gomezdn Date: Fri, 14 Jun 2024 02:43:48 -0300 Subject: [PATCH 188/249] fix: added error with clear message when signing a tx without outputs --- src/transaction.js | 2 ++ ts_src/transaction.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/transaction.js b/src/transaction.js index 2b74f3788..ea0179bd4 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -318,6 +318,8 @@ class Transaction { hashSequences = bcrypto.sha256(bufferWriter.end()); } if (!(isNone || isSingle)) { + if (!this.outs.length) + throw new Error('Add outputs to the transaction before signing.'); const txOutsSize = this.outs .map(output => 8 + varSliceSize(output.script)) .reduce((a, b) => a + b); diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 665583ec5..48f68e54f 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -414,6 +414,8 @@ export class Transaction { } if (!(isNone || isSingle)) { + if (!this.outs.length) + throw new Error('Add outputs to the transaction before signing.'); const txOutsSize = this.outs .map(output => 8 + varSliceSize(output.script)) .reduce((a, b) => a + b); From 230138f57af02e710fa2497fdff730b3230857c4 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 15 Jun 2024 11:43:31 +0900 Subject: [PATCH 189/249] Add taproot multisig with verified unspendable internalPubkey example --- test/integration/taproot.spec.ts | 146 ++++++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 1 deletion(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 9708af138..9214ca7df 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -7,7 +7,7 @@ import { PsbtInput, TapLeaf, TapLeafScript } from 'bip174/src/lib/interfaces'; import { regtestUtils } from './_regtest'; import * as bitcoin from '../..'; import { Taptree } from '../../src/types'; -import { LEAF_VERSION_TAPSCRIPT } from '../../src/payments/bip341'; +import { LEAF_VERSION_TAPSCRIPT, tapleafHash } from '../../src/payments/bip341'; import { toXOnly, tapTreeToList, tapTreeFromList } from '../../src/psbt/bip371'; import { witnessStackToScriptWitness } from '../../src/psbt/psbtutils'; @@ -528,6 +528,107 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { }); }); + it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIGADD (2-of-3) and verify unspendable internalKey', async () => { + 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')); + } + + // This is just a visual way of creating the script for educational purposes. + // In a production application there's no need to bother with this step, creating the binary + // directly from buffers is fine. + const leafScriptAsm = `${leafPubkeys[2]} OP_CHECKSIG ${leafPubkeys[1]} OP_CHECKSIGADD ${leafPubkeys[0]} OP_CHECKSIGADD OP_2 OP_GREATERTHANOREQUAL`; + const leafScript = bitcoin.script.fromASM(leafScriptAsm); + + // Taptree can also be a single TapLeaf + // Since we only have one script, it's all we need. + const scriptTree: Taptree = { + output: leafScript, + }; + const redeem = { + output: leafScript, + redeemVersion: LEAF_VERSION_TAPSCRIPT, + }; + + // We don't pass in a shared nonce because our wallet doesn't care. + // See the helper function's comments to understand why you might want to use a shared nonce. + // All signers should verify that the internalPubkey of the script they're signing is unspendable. + // Otherwise the person who made the script could be hiding a secret master key (for one-key-only spending). + // If a nonce is used, that nonce should be shared among all signers. + const internalPubkey = makeUnspendableInternalKey(); + + const { output, address, witness } = bitcoin.payments.p2tr({ + internalPubkey, + scriptTree, + redeem, + network: regtest, + }); + + // 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({ network: regtest }); + psbt.addInput({ + hash: unspent.txId, + index: 0, + witnessUtxo: { value: amount, script: output! }, + }); + psbt.updateInput(0, { + tapLeafScript: [ + { + leafVersion: redeem.redeemVersion, + script: redeem.output, + controlBlock: witness![witness!.length - 1], + }, + ], + }); + + psbt.addOutput({ value: sendAmount, address: address! }); + + // random order for signers + psbt.signInput(0, leafKeys[2]); + psbt.signInput(0, leafKeys[0]); + + // Before finalizing, every key that did not sign must have an empty signature + // in place where their signature would be. + // In order to do this currently we need to construct a dummy signature manually. + const noSignatureKeyDummySig = { + // This can be reused for each dummy signature + leafHash: tapleafHash({ + output: leafScript, + version: LEAF_VERSION_TAPSCRIPT, + }), + // This is the pubkey that didn't sign + pubkey: toXOnly(leafKeys[1].publicKey), + // This must be an empty Buffer. + signature: Buffer.from([]), + }; + + // We know that the first input exists and we have added tapScriptSigs + // so the tapScriptSig must exist. + psbt.data.inputs[0].tapScriptSig!.push(noSignatureKeyDummySig); + + psbt.finalizeInput(0); + 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 - custom finalizer', async () => { const leafCount = 8; const leaves = Array.from({ length: leafCount }).map( @@ -693,3 +794,46 @@ function buildLeafIndexFinalizer( } }; } + +function makeUnspendableInternalKey(provableNonce?: Buffer): Buffer { + // This is the generator point of secp256k1. Private key is known (equal to 1) + const G = Buffer.from( + '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', + 'hex', + ); + // This is the hash of the uncompressed generator point. + // It is also a valid X value on the curve, but we don't know what the private key is. + // Since we know this X value (a fake "public key") is made from a hash of a well known value, + // We can prove that the internalKey is unspendable. + const Hx = bitcoin.crypto.sha256(G); + + // This "Nothing Up My Sleeve" value is mentioned in BIP341 so we verify it here: + assert.strictEqual( + Hx.toString('hex'), + '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0', + ); + + if (provableNonce) { + // Using a shared random value, we create an unspendable internalKey + // P = H + int(hash_taptweak(provableNonce))*G + // Since we don't know H's private key (see explanation above), we can't know P's private key + if (provableNonce.length !== 32) { + throw new Error( + 'provableNonce must be a 32 byte random value shared between script holders', + ); + } + const ret = ecc.xOnlyPointAddTweak(Hx, provableNonce); + if (!ret) { + throw new Error( + 'provableNonce produced an invalid key when tweaking the G hash', + ); + } + return Buffer.from(ret.xOnlyPubkey); + } else { + // The downside to using no shared provable nonce is that anyone viewing a spend + // on the blockchain can KNOW that you CAN'T use key spend. + // Most people would be ok with this being public, but some wallets (exchanges etc) + // might not want ANY details about how their wallet works public. + return Hx; + } +} From 96cb42751dccbf91f0875e4f60bd920f79bdb55e Mon Sep 17 00:00:00 2001 From: Jonathan Underwood Date: Sun, 16 Jun 2024 00:35:58 +0900 Subject: [PATCH 190/249] Minor fixes to README --- README.md | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f6ff73c52..5a895d57a 100644 --- a/README.md +++ b/README.md @@ -102,21 +102,14 @@ Which you can then import as an ESM module: ```` -**Using Taproot:** -When utilizing Taproot features with bitcoinjs-lib, you may need to include an additional ECC (Elliptic Curve Cryptography) library. The commonly used `tiny-secp256k1` library, however, might lead to compatibility issues due to its reliance on WASM (WebAssembly). - -If you encounter the following error: - -``` -Uncaught TypeError: (0 , fs_1.readFileSync) is not a function -``` -This indicates that tiny-secp256k1's WASM implementation is not fully compatible with your browser environment. - -**Alternatives for ECC Library:** -1. **@bitcoinjs-lib/tiny-secp256k1-asmjs** - A version compiled to ASM.js, potentially better supported in browsers. -2. **@bitcoinerlab/secp256k1** - Another alternative library for ECC functionality. +#### Using Taproot: +When utilizing Taproot features with bitcoinjs-lib, you may need to include an additional ECC (Elliptic Curve Cryptography) library. The commonly used `tiny-secp256k1` library, however, might lead to compatibility issues due to its reliance on WASM (WebAssembly). The following alternatives may be used instead, though they may be significantly slower for high volume of signing and pubkey deriving operations. + +#### Alternatives for ECC Library: +1. `@bitcoinjs-lib/tiny-secp256k1-asmjs` + A version of `tiny-secp256k1` compiled to ASM.js directly from the WASM version, potentially better supported in browsers. This is the slowest option. +2. `@bitcoinerlab/secp256k1` + Another alternative library for ECC functionality. This requires access to the global `BigInt` primitive. For advantages and detailed comparison of these libraries, visit: [tiny-secp256k1 GitHub page](https://github.com/bitcoinjs/tiny-secp256k1). **NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](https://babeljs.io/docs/plugins/preset-es2015/) preset). From c4025dfe60ab02d31dff24417e79cb685393e069 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 15 Jun 2024 16:51:05 +0900 Subject: [PATCH 191/249] Use a taproot multisig wallet class --- test/integration/taproot.spec.ts | 338 ++++++++++++++++++++++++------- 1 file changed, 269 insertions(+), 69 deletions(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 9214ca7df..7e5f4657a 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -530,92 +530,54 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIGADD (2-of-3) and verify unspendable internalKey', async () => { const leafKeys = []; - const leafPubkeys = []; + const leafPubkeys: Buffer[] = []; for (let i = 0; i < 3; i++) { const leafKey = bip32.fromSeed(rng(64), regtest); leafKeys.push(leafKey); - leafPubkeys.push(toXOnly(leafKey.publicKey).toString('hex')); + leafPubkeys.push(toXOnly(leafKey.publicKey)); } - // This is just a visual way of creating the script for educational purposes. - // In a production application there's no need to bother with this step, creating the binary - // directly from buffers is fine. - const leafScriptAsm = `${leafPubkeys[2]} OP_CHECKSIG ${leafPubkeys[1]} OP_CHECKSIGADD ${leafPubkeys[0]} OP_CHECKSIGADD OP_2 OP_GREATERTHANOREQUAL`; - const leafScript = bitcoin.script.fromASM(leafScriptAsm); - - // Taptree can also be a single TapLeaf - // Since we only have one script, it's all we need. - const scriptTree: Taptree = { - output: leafScript, - }; - const redeem = { - output: leafScript, - redeemVersion: LEAF_VERSION_TAPSCRIPT, - }; - - // We don't pass in a shared nonce because our wallet doesn't care. - // See the helper function's comments to understand why you might want to use a shared nonce. - // All signers should verify that the internalPubkey of the script they're signing is unspendable. - // Otherwise the person who made the script could be hiding a secret master key (for one-key-only spending). - // If a nonce is used, that nonce should be shared among all signers. - const internalPubkey = makeUnspendableInternalKey(); - - const { output, address, witness } = bitcoin.payments.p2tr({ - internalPubkey, - scriptTree, - redeem, - network: regtest, - }); + // The only thing that differs between the wallets is the private key. + // So we will use the first wallet for all the Psbt stuff. + const [wallet, wallet2, wallet3] = leafKeys.map(key => + new TaprootMultisigWallet( + leafPubkeys, + 2, // Number of required signatures + key.privateKey!, + LEAF_VERSION_TAPSCRIPT, + ).setNetwork(regtest), + ); // amount from faucet const amount = 42e4; // amount to send const sendAmount = amount - 1e4; // get faucet - const unspent = await regtestUtils.faucetComplex(output!, amount); + const unspent = await regtestUtils.faucetComplex(wallet.output, amount); const psbt = new bitcoin.Psbt({ network: regtest }); - psbt.addInput({ - hash: unspent.txId, - index: 0, - witnessUtxo: { value: amount, script: output! }, - }); - psbt.updateInput(0, { - tapLeafScript: [ - { - leafVersion: redeem.redeemVersion, - script: redeem.output, - controlBlock: witness![witness!.length - 1], - }, - ], - }); - psbt.addOutput({ value: sendAmount, address: address! }); + // Adding an input is a bit special in this case, + // So we contain it in the wallet class + // Any wallet can do this, wallet2 or wallet3 could be used. + wallet.addInput(psbt, unspent.txId, unspent.vout, unspent.value); - // random order for signers - psbt.signInput(0, leafKeys[2]); - psbt.signInput(0, leafKeys[0]); + psbt.addOutput({ value: sendAmount, address: wallet.address }); - // Before finalizing, every key that did not sign must have an empty signature - // in place where their signature would be. - // In order to do this currently we need to construct a dummy signature manually. - const noSignatureKeyDummySig = { - // This can be reused for each dummy signature - leafHash: tapleafHash({ - output: leafScript, - version: LEAF_VERSION_TAPSCRIPT, - }), - // This is the pubkey that didn't sign - pubkey: toXOnly(leafKeys[1].publicKey), - // This must be an empty Buffer. - signature: Buffer.from([]), - }; + // Sign with at least 2 of the 3 wallets. + // Verify that there is a matching leaf script + // (which includes the unspendable internalPubkey, + // so we verify that no one can key-spend it) + wallet3.verifyInputScript(psbt, 0); + wallet2.verifyInputScript(psbt, 0); + psbt.signInput(0, wallet3); + psbt.signInput(0, wallet2); - // We know that the first input exists and we have added tapScriptSigs - // so the tapScriptSig must exist. - psbt.data.inputs[0].tapScriptSig!.push(noSignatureKeyDummySig); + // Before finalizing, we need to add dummy signatures for all that did not sign. + // Any wallet can do this, wallet2 or wallet3 could be used. + wallet.addDummySigs(psbt); - psbt.finalizeInput(0); + psbt.finalizeAllInputs(); const tx = psbt.extractTransaction(); const rawTx = tx.toBuffer(); const hex = rawTx.toString('hex'); @@ -623,7 +585,8 @@ describe('bitcoinjs-lib (transaction with taproot)', () => { await regtestUtils.broadcast(hex); await regtestUtils.verify({ txId: tx.getId(), - address: address!, + // Any wallet can do this, wallet2 or wallet3 could be used. + address: wallet.address, vout: 0, value: sendAmount, }); @@ -837,3 +800,240 @@ function makeUnspendableInternalKey(provableNonce?: Buffer): Buffer { return Hx; } } + +class TaprootMultisigWallet { + private leafScriptCache: Buffer | null = null; + private internalPubkeyCache: Buffer | null = null; + private paymentCache: bitcoin.Payment | null = null; + private readonly publicKeyCache: Buffer; + network: bitcoin.Network; + + constructor( + /** + * A list of all the (x-only) pubkeys in the multisig + */ + private readonly pubkeys: Buffer[], + /** + * The number of required signatures + */ + private readonly requiredSigs: number, + /** + * The private key you hold. + */ + private readonly privateKey: Buffer, + /** + * leaf version (0xc0 currently) + */ + readonly leafVersion: number, + /** + * Optional shared nonce. This should be used in wallets where + * the fact that key-spend is unspendable should not be public, + * BUT each signer must verify that it is unspendable to be safe. + */ + private readonly sharedNonce?: Buffer, + ) { + this.network = bitcoin.networks.bitcoin; + assert(pubkeys.length > 0, 'Need pubkeys'); + assert( + pubkeys.every(p => p.length === 32), + 'Pubkeys must be 32 bytes (x-only)', + ); + assert( + requiredSigs > 0 && requiredSigs <= pubkeys.length, + 'Invalid requiredSigs', + ); + + assert( + leafVersion <= 0xff && (leafVersion & 1) === 0, + 'Invalid leafVersion', + ); + + if (sharedNonce) { + assert( + sharedNonce.length === 32 && ecc.isPrivate(sharedNonce), + 'Invalid sharedNonce', + ); + } + + const pubkey = ecc.pointFromScalar(privateKey); + assert(pubkey, 'Invalid pubkey'); + + this.publicKeyCache = Buffer.from(pubkey); + assert( + pubkeys.some(p => p.equals(toXOnly(this.publicKeyCache))), + 'At least one pubkey must match your private key', + ); + + // IMPORTANT: Make sure the pubkeys are sorted (To prevent ordering issues between wallet signers) + this.pubkeys.sort((a, b) => a.compare(b)); + } + + setNetwork(network: bitcoin.Network): this { + this.network = network; + return this; + } + + // Required for Signer interface. + // Prevent setting by using a getter. + get publicKey(): Buffer { + return this.publicKeyCache; + } + + /** + * Lazily build the leafScript. A 2 of 3 would look like: + * key1 OP_CHECKSIG key2 OP_CHECKSIGADD key3 OP_CHECKSIGADD OP_2 OP_GREATERTHANOREQUAL + */ + get leafScript(): Buffer { + if (this.leafScriptCache) { + return this.leafScriptCache; + } + const ops = []; + this.pubkeys.forEach(pubkey => { + if (ops.length === 0) { + ops.push(pubkey); + ops.push(bitcoin.opcodes.OP_CHECKSIG); + } else { + ops.push(pubkey); + ops.push(bitcoin.opcodes.OP_CHECKSIGADD); + } + }); + if (this.requiredSigs > 16) { + ops.push(bitcoin.script.number.encode(this.requiredSigs)); + } else { + ops.push(bitcoin.opcodes.OP_1 - 1 + this.requiredSigs); + } + ops.push(bitcoin.opcodes.OP_GREATERTHANOREQUAL); + + this.leafScriptCache = bitcoin.script.compile(ops); + return this.leafScriptCache; + } + + get internalPubkey(): Buffer { + if (this.internalPubkeyCache) { + return this.internalPubkeyCache; + } + // See the helper function for explanation + this.internalPubkeyCache = makeUnspendableInternalKey(this.sharedNonce); + return this.internalPubkeyCache; + } + + get scriptTree(): Taptree { + // If more complicated, maybe it should be cached. + // (ie. if other scripts are created only to create the tree + // and will only be stored in the tree.) + return { + output: this.leafScript, + }; + } + + get redeem(): { + output: Buffer; + redeemVersion: number; + } { + return { + output: this.leafScript, + redeemVersion: this.leafVersion, + }; + } + + private get payment(): bitcoin.Payment { + if (this.paymentCache) { + return this.paymentCache; + } + this.paymentCache = bitcoin.payments.p2tr({ + internalPubkey: this.internalPubkey, + scriptTree: this.scriptTree, + redeem: this.redeem, + network: this.network, + }); + return this.paymentCache; + } + + get output(): Buffer { + return this.payment.output!; + } + + get address(): string { + return this.payment.address!; + } + + get controlBlock(): Buffer { + const witness = this.payment.witness!; + return witness[witness.length - 1]; + } + + verifyInputScript(psbt: bitcoin.Psbt, index: number) { + if (index >= psbt.data.inputs.length) + throw new Error('Invalid input index'); + const input = psbt.data.inputs[index]; + if (!input.tapLeafScript) throw new Error('Input has no tapLeafScripts'); + const hasMatch = + input.tapLeafScript.length === 1 && + input.tapLeafScript[0].leafVersion === this.leafVersion && + input.tapLeafScript[0].script.equals(this.leafScript) && + input.tapLeafScript[0].controlBlock.equals(this.controlBlock); + if (!hasMatch) + throw new Error( + 'No matching leafScript, or extra leaf script. Refusing to sign.', + ); + } + + addInput( + psbt: bitcoin.Psbt, + hash: string | Buffer, + index: number, + value: number, + ) { + psbt.addInput({ + hash, + index, + witnessUtxo: { value, script: this.output }, + }); + psbt.updateInput(psbt.inputCount - 1, { + tapLeafScript: [ + { + leafVersion: this.leafVersion, + script: this.leafScript, + controlBlock: this.controlBlock, + }, + ], + }); + } + + addDummySigs(psbt: bitcoin.Psbt) { + const leafHash = tapleafHash({ + output: this.leafScript, + version: this.leafVersion, + }); + for (const input of psbt.data.inputs) { + if (!input.tapScriptSig) continue; + const signedPubkeys = input.tapScriptSig + .filter(ts => ts.leafHash.equals(leafHash)) + .map(ts => ts.pubkey); + for (const pubkey of this.pubkeys) { + if (signedPubkeys.some(sPub => sPub.equals(pubkey))) continue; + // Before finalizing, every key that did not sign must have an empty signature + // in place where their signature would be. + // In order to do this currently we need to construct a dummy signature manually. + input.tapScriptSig.push({ + // This can be reused for each dummy signature + leafHash, + // This is the pubkey that didn't sign + pubkey, + // This must be an empty Buffer. + signature: Buffer.from([]), + }); + } + } + } + + // required for Signer interface + sign(hash: Buffer, _lowR?: boolean): Buffer { + return Buffer.from(ecc.sign(hash, this.privateKey)); + } + + // required for Signer interface + signSchnorr(hash: Buffer): Buffer { + return Buffer.from(ecc.signSchnorr(hash, this.privateKey)); + } +} From 791fa012a9d93ecdc6ee10e80b05342a0306f9a5 Mon Sep 17 00:00:00 2001 From: GoodDaisy <90915921+GoodDaisy@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:34:16 +0800 Subject: [PATCH 192/249] Fix typos (#2032) * Fix typo * Fix typo * Fix typo --------- Co-authored-by: Jonathan Underwood --- src/psbt/bip371.js | 2 +- test/fixtures/psbt.json | 2 +- ts_src/psbt/bip371.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 2b53171e0..52ab6ab43 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -97,7 +97,7 @@ function checkTaprootScriptPubkey(outputData, newOutputData) { const { script: scriptPubkey } = outputData; const script = getTaprootScripPubkey(tapInternalKey, tapTree); if (scriptPubkey && !scriptPubkey.equals(script)) - throw new Error('Error adding output. Script or address missmatch.'); + throw new Error('Error adding output. Script or address mismatch.'); } } function getTaprootScripPubkey(tapInternalKey, tapTree) { diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index ce8988fa3..33c599fec 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -615,7 +615,7 @@ "address": "bc1p3efq8ujsj0qr5xvms7mv89p8cz0crqdtuxe9ms6grqgxc9sgsntslthf6w", "value": 410000 }, - "exception": "Error adding output. Script or address missmatch." + "exception": "Error adding output. Script or address mismatch." }, { "description": "Adds taproot output with both taproot and non-taproot fields", diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 752265b0b..667bd1523 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -129,7 +129,7 @@ function checkTaprootScriptPubkey( const { script: scriptPubkey } = outputData as any; const script = getTaprootScripPubkey(tapInternalKey, tapTree); if (scriptPubkey && !scriptPubkey.equals(script)) - throw new Error('Error adding output. Script or address missmatch.'); + throw new Error('Error adding output. Script or address mismatch.'); } } From 18e39f09299a489ff7be105b6803e42f70a19778 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Mon, 1 Jul 2024 23:27:01 +0800 Subject: [PATCH 193/249] fix: fix typos --- CHANGELOG.md | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fdcdca38..86f630137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -333,7 +333,7 @@ __fixed__ # 2.1.0 From this release users should use the HDNode directly (compared to accessing `.keyPair`) when performing ECDSA operations such as `sign` or `verify`. -Ideally you shoud not have to directly access `HDNode` internals for general usage, as it can often be confusing and error prone. +Ideally you should not have to directly access `HDNode` internals for general usage, as it can often be confusing and error prone. __added__ - `ECPair.prototype.getNetwork` @@ -344,7 +344,7 @@ __added__ # 2.0.0 -In this release we have strived to simplify the API, [using native types](https://github.com/bitcoinjs/bitcoinjs-lib/issues/407) wherevever possible to encourage cross-compatibility with other open source community modules. +In this release we have strived to simplify the API, [using native types](https://github.com/bitcoinjs/bitcoinjs-lib/issues/407) wherever possible to encourage cross-compatibility with other open source community modules. The `ecdsa` module has been removed in lieu of using a new ECDSA module (for performance and safety reasons) during the `2.x.y` major release. Several other cumbersome modules have been removed, with their new independent modules recommended for usage instead for greater modularity in your projects. diff --git a/README.md b/README.md index 5a895d57a..cf4919c11 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ If you'd like to use a different (more modern) build tool than `browserify`, you ```sh $ npm install bitcoinjs-lib browserify -$ npx browserify --standalone bitcoin - -o bitcoinjs-lib.js <<<"module.exports = require('bitcoinjs-lib');" +$ npx browserify --standalone bitcoin -o bitcoinjs-lib.js <<< "module.exports = require('bitcoinjs-lib');" ``` Which you can then import as an ESM module: From 5909559b0f4ec7701260cc02686936251dfa35fb Mon Sep 17 00:00:00 2001 From: Jasonandjay <342690199@qq.com> Date: Thu, 11 Jul 2024 21:05:59 +0800 Subject: [PATCH 194/249] docs: add taproot multiSign to example list (#2125) * docs: add taproot multiSign to example list * feat: upgrade mocha & remove audit temporary solution * docs: remove line numbers of example --- .github/workflows/main_ci.yml | 2 +- README.md | 3 + package-lock.json | 384 ++++++++++++++++------------------ package.json | 2 +- 4 files changed, 182 insertions(+), 209 deletions(-) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index dcb5a369f..2e197b4e6 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -71,7 +71,7 @@ jobs: registry-url: https://registry.npmjs.org/ cache: 'npm' - run: npm ci - - run: npm run audit -- -x 1097496 + - run: npm run audit coverage: runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index cf4919c11..f527badff 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,9 @@ Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). - [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts) +- [Create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIG](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts) +- [Create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSEQUENCEVERIFY](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts) +- [Create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIGADD (3-of-3)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts) - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) diff --git a/package-lock.json b/package-lock.json index e0a146dbb..57c3a4b2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "eslint-plugin-prettier": "^4.2.1", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", - "mocha": "^10.0.0", + "mocha": "^10.6.0", "nyc": "^15.1.0", "prettier": "^2.8.0", "proxyquire": "^2.0.1", @@ -191,9 +191,9 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -247,9 +247,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -1104,9 +1104,9 @@ } }, "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "engines": { "node": ">=6" @@ -1317,12 +1317,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1612,9 +1612,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -1669,9 +1669,9 @@ } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -2091,9 +2091,9 @@ } }, "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==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2655,9 +2655,9 @@ } }, "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==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -2874,18 +2874,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmmirror.com/lunr/-/lunr-2.3.9.tgz", @@ -2908,9 +2896,9 @@ } }, "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==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -2996,32 +2984,31 @@ } }, "node_modules/mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", + "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -3029,10 +3016,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha/node_modules/brace-expansion": { @@ -3044,10 +3027,30 @@ "balanced-match": "^1.0.0" } }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3089,18 +3092,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3854,13 +3845,10 @@ ] }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3869,9 +3857,9 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -4467,18 +4455,18 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "node_modules/wrap-ansi": { @@ -4525,12 +4513,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -4550,9 +4532,9 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "engines": { "node": ">=10" @@ -4728,9 +4710,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -4773,9 +4755,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -5389,9 +5371,9 @@ } }, "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true }, "ansi-regex": { @@ -5557,12 +5539,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -5786,9 +5768,9 @@ "dev": true }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { "ms": "2.1.2" @@ -5825,9 +5807,9 @@ } }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, "dir-glob": { @@ -6143,9 +6125,9 @@ } }, "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==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -6543,9 +6525,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -6711,15 +6693,6 @@ "is-unicode-supported": "^0.1.0" } }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, "lunr": { "version": "2.3.9", "resolved": "https://registry.npmmirror.com/lunr/-/lunr-2.3.9.tgz", @@ -6736,9 +6709,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -6808,32 +6781,31 @@ } }, "mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", + "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "dependencies": { "brace-expansion": { @@ -6845,10 +6817,23 @@ "balanced-match": "^1.0.0" } }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -6883,12 +6868,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -7440,18 +7419,15 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -7904,15 +7880,15 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "wrap-ansi": { @@ -7950,12 +7926,6 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -7972,9 +7942,9 @@ } }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, "yargs-unparser": { diff --git a/package.json b/package.json index c061f5881..609f99b94 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "eslint-plugin-prettier": "^4.2.1", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", - "mocha": "^10.0.0", + "mocha": "^10.6.0", "nyc": "^15.1.0", "prettier": "^2.8.0", "proxyquire": "^2.0.1", From c184a965db2ea9be8191fbb81446677507d36bff Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 16 Jul 2024 19:53:51 +0900 Subject: [PATCH 195/249] Fix multisig taproot example's provable nonce calculation --- test/integration/taproot.spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts index 7e5f4657a..12448623e 100644 --- a/test/integration/taproot.spec.ts +++ b/test/integration/taproot.spec.ts @@ -777,15 +777,16 @@ function makeUnspendableInternalKey(provableNonce?: Buffer): Buffer { ); if (provableNonce) { - // Using a shared random value, we create an unspendable internalKey - // P = H + int(hash_taptweak(provableNonce))*G - // Since we don't know H's private key (see explanation above), we can't know P's private key if (provableNonce.length !== 32) { throw new Error( 'provableNonce must be a 32 byte random value shared between script holders', ); } - const ret = ecc.xOnlyPointAddTweak(Hx, provableNonce); + // Using a shared random value, we create an unspendable internalKey + // P = H + int(hash_taptweak(provableNonce))*G + // Since we don't know H's private key (see explanation above), we can't know P's private key + const tapHash = bitcoin.crypto.taggedHash('TapTweak', provableNonce); + const ret = ecc.xOnlyPointAddTweak(Hx, tapHash); if (!ret) { throw new Error( 'provableNonce produced an invalid key when tweaking the G hash', From f4b598218bde55a4d4ef1e4247b1b07b1ac9dbce Mon Sep 17 00:00:00 2001 From: IronMan <1341674619@qq.com> Date: Tue, 30 Jul 2024 18:30:34 +0800 Subject: [PATCH 196/249] docs: add Taproot related method annotations --- ts_src/psbt/bip371.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 667bd1523..51750d45e 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -27,6 +27,11 @@ import { p2tr } from '../payments'; import { signatureBlocksAction } from './psbtutils'; +/** + * Converts a public key to an X-only public key. + * @param pubKey The public key to convert. + * @returns The X-only public key. + */ export const toXOnly = (pubKey: Buffer) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); @@ -61,6 +66,12 @@ export function tapScriptFinalizer( } } +/** + * Serializes a taproot signature. + * @param sig The signature to serialize. + * @param sighashType The sighash type. Optional. + * @returns The serialized taproot signature. + */ export function serializeTaprootSignature( sig: Buffer, sighashType?: number, @@ -72,6 +83,11 @@ export function serializeTaprootSignature( return Buffer.concat([sig, sighashTypeByte]); } +/** + * Checks if a PSBT input is a taproot input. + * @param input The PSBT input to check. + * @returns True if the input is a taproot input, false otherwise. + */ export function isTaprootInput(input: PsbtInput): boolean { return ( input && @@ -85,6 +101,12 @@ export function isTaprootInput(input: PsbtInput): boolean { ); } +/** + * Checks if a PSBT output is a taproot output. + * @param output The PSBT output to check. + * @param script The script to check. Optional. + * @returns True if the output is a taproot output, false otherwise. + */ export function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean { return ( output && @@ -97,6 +119,13 @@ export function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean { ); } +/** + * Checks the taproot input fields for consistency. + * @param inputData The original input data. + * @param newInputData The new input data. + * @param action The action being performed. + * @throws Throws an error if the input fields are inconsistent. + */ export function checkTaprootInputFields( inputData: PsbtInput, newInputData: PsbtInput, @@ -106,6 +135,13 @@ export function checkTaprootInputFields( checkIfTapLeafInTree(inputData, newInputData, action); } +/** + * Checks the taproot output fields for consistency. + * @param outputData The original output data. + * @param newOutputData The new output data. + * @param action The action being performed. + * @throws Throws an error if the output fields are inconsistent. + */ export function checkTaprootOutputFields( outputData: PsbtOutput, newOutputData: PsbtOutput, From 39c5b82530c651dd7a259dcc5cc4ebba974d692e Mon Sep 17 00:00:00 2001 From: IronMan <1341674619@qq.com> Date: Tue, 30 Jul 2024 18:42:50 +0800 Subject: [PATCH 197/249] docs: add tapTree annotations --- ts_src/psbt/bip371.ts | 72 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index 51750d45e..573264ab9 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -169,6 +169,13 @@ function checkTaprootScriptPubkey( } } +/** + * Returns the Taproot script public key. + * + * @param tapInternalKey - The Taproot internal key. + * @param tapTree - The Taproot tree (optional). + * @returns The Taproot script public key. + */ function getTaprootScripPubkey( tapInternalKey: TapInternalKey, tapTree?: TapTree, @@ -181,6 +188,13 @@ function getTaprootScripPubkey( return output!; } +/** + * Tweak the internal public key for a specific input. + * @param inputIndex - The index of the input. + * @param input - The PsbtInput object representing the input. + * @returns The tweaked internal public key. + * @throws Error if the tap internal key cannot be tweaked. + */ export function tweakInternalPubKey( inputIndex: number, input: PsbtInput, @@ -232,6 +246,12 @@ export function tapTreeFromList(leaves: TapLeaf[] = []): Taptree { return instertLeavesInTree(leaves); } +/** + * Checks the taproot input for signatures. + * @param input The PSBT input to check. + * @param action The action being performed. + * @returns True if the input has taproot signatures, false otherwise. + */ export function checkTaprootInputForSigs( input: PsbtInput, action: string, @@ -242,6 +262,11 @@ export function checkTaprootInputForSigs( ); } +/** + * Decodes a Schnorr signature. + * @param signature The signature to decode. + * @returns The decoded Schnorr signature. + */ function decodeSchnorrSignature(signature: Buffer): { signature: Buffer; hashType: number; @@ -252,6 +277,11 @@ function decodeSchnorrSignature(signature: Buffer): { }; } +/** + * Extracts taproot signatures from a PSBT input. + * @param input The PSBT input to extract signatures from. + * @returns An array of taproot signatures. + */ function extractTaprootSigs(input: PsbtInput): Buffer[] { const sigs: Buffer[] = []; if (input.tapKeySig) sigs.push(input.tapKeySig); @@ -265,6 +295,11 @@ function extractTaprootSigs(input: PsbtInput): Buffer[] { return sigs; } +/** + * Gets the taproot signature from the witness. + * @param finalScriptWitness The final script witness. + * @returns The taproot signature, or undefined if not found. + */ function getTapKeySigFromWithness( finalScriptWitness?: Buffer, ): Buffer | undefined { @@ -274,6 +309,14 @@ function getTapKeySigFromWithness( if (witness.length === 64 || witness.length === 65) return witness; } +/** + * Converts a binary tree to a BIP371 type list. + * @param tree The binary tap tree. + * @param leaves A list of tapleaves. Optional. + * @param depth The current depth. Optional. + * @returns A list of BIP 371 tapleaves. + * @throws Throws an error if the taptree cannot be converted to a tapleaf list. + */ function _tapTreeToList( tree: Taptree, leaves: TapLeaf[] = [], @@ -299,6 +342,13 @@ type PartialTaptree = | [PartialTaptree | Tapleaf, PartialTaptree | Tapleaf] | Tapleaf | undefined; + +/** + * Inserts the tapleaves into the taproot tree. + * @param leaves The tapleaves to insert. + * @returns The taproot tree. + * @throws Throws an error if there is no room left to insert a tapleaf in the tree. + */ function instertLeavesInTree(leaves: TapLeaf[]): Taptree { let tree: PartialTaptree; for (const leaf of leaves) { @@ -309,6 +359,13 @@ function instertLeavesInTree(leaves: TapLeaf[]): Taptree { return tree as Taptree; } +/** + * Inserts a tapleaf into the taproot tree. + * @param leaf The tapleaf to insert. + * @param tree The taproot tree. + * @param depth The current depth. Optional. + * @returns The updated taproot tree. + */ function instertLeafInTree( leaf: TapLeaf, tree?: PartialTaptree, @@ -332,6 +389,13 @@ function instertLeafInTree( if (rightSide) return [tree && tree[0], rightSide]; } +/** + * Checks the input fields for mixed taproot and non-taproot fields. + * @param inputData The original input data. + * @param newInputData The new input data. + * @param action The action being performed. + * @throws Throws an error if the input fields are inconsistent. + */ function checkMixedTaprootAndNonTaprootInputFields( inputData: PsbtOutput, newInputData: PsbtInput, @@ -352,6 +416,14 @@ function checkMixedTaprootAndNonTaprootInputFields( `Cannot use both taproot and non-taproot fields.`, ); } + +/** + * Checks the output fields for mixed taproot and non-taproot fields. + * @param inputData The original output data. + * @param newInputData The new output data. + * @param action The action being performed. + * @throws Throws an error if the output fields are inconsistent. + */ function checkMixedTaprootAndNonTaprootOutputFields( inputData: PsbtOutput, newInputData: PsbtOutput, From 7dc2d802b8de98d3a41bc26aa7d9a1686f5bd67d Mon Sep 17 00:00:00 2001 From: IronMan <1341674619@qq.com> Date: Tue, 30 Jul 2024 18:44:37 +0800 Subject: [PATCH 198/249] docs: remove PSBT duplicate annotations --- src/psbt/bip371.d.ts | 49 ++++++++++++++++++ src/psbt/bip371.js | 106 +++++++++++++++++++++++++++++++++++++++ src/psbt/psbtutils.d.ts | 5 -- src/psbt/psbtutils.js | 10 ++-- ts_src/psbt/psbtutils.ts | 12 +++-- 5 files changed, 167 insertions(+), 15 deletions(-) diff --git a/src/psbt/bip371.d.ts b/src/psbt/bip371.d.ts index fae6e756b..c8755dbd0 100644 --- a/src/psbt/bip371.d.ts +++ b/src/psbt/bip371.d.ts @@ -1,6 +1,11 @@ /// import { Taptree } from '../types'; import { PsbtInput, PsbtOutput, TapLeaf } from 'bip174/src/lib/interfaces'; +/** + * Converts a public key to an X-only public key. + * @param pubKey The public key to convert. + * @returns The X-only public key. + */ export declare const toXOnly: (pubKey: Buffer) => Buffer; /** * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided. @@ -14,11 +19,49 @@ export declare const toXOnly: (pubKey: Buffer) => Buffer; export declare function tapScriptFinalizer(inputIndex: number, input: PsbtInput, tapLeafHashToFinalize?: Buffer): { finalScriptWitness: Buffer | undefined; }; +/** + * Serializes a taproot signature. + * @param sig The signature to serialize. + * @param sighashType The sighash type. Optional. + * @returns The serialized taproot signature. + */ export declare function serializeTaprootSignature(sig: Buffer, sighashType?: number): Buffer; +/** + * Checks if a PSBT input is a taproot input. + * @param input The PSBT input to check. + * @returns True if the input is a taproot input, false otherwise. + */ export declare function isTaprootInput(input: PsbtInput): boolean; +/** + * Checks if a PSBT output is a taproot output. + * @param output The PSBT output to check. + * @param script The script to check. Optional. + * @returns True if the output is a taproot output, false otherwise. + */ export declare function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean; +/** + * Checks the taproot input fields for consistency. + * @param inputData The original input data. + * @param newInputData The new input data. + * @param action The action being performed. + * @throws Throws an error if the input fields are inconsistent. + */ export declare function checkTaprootInputFields(inputData: PsbtInput, newInputData: PsbtInput, action: string): void; +/** + * Checks the taproot output fields for consistency. + * @param outputData The original output data. + * @param newOutputData The new output data. + * @param action The action being performed. + * @throws Throws an error if the output fields are inconsistent. + */ export declare function checkTaprootOutputFields(outputData: PsbtOutput, newOutputData: PsbtOutput, action: string): void; +/** + * Tweak the internal public key for a specific input. + * @param inputIndex - The index of the input. + * @param input - The PsbtInput object representing the input. + * @returns The tweaked internal public key. + * @throws Error if the tap internal key cannot be tweaked. + */ export declare function tweakInternalPubKey(inputIndex: number, input: PsbtInput): Buffer; /** * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371): @@ -38,4 +81,10 @@ export declare function tapTreeToList(tree: Taptree): TapLeaf[]; * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed */ export declare function tapTreeFromList(leaves?: TapLeaf[]): Taptree; +/** + * Checks the taproot input for signatures. + * @param input The PSBT input to check. + * @param action The action being performed. + * @returns True if the input has taproot signatures, false otherwise. + */ export declare function checkTaprootInputForSigs(input: PsbtInput, action: string): boolean; diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 52ab6ab43..0fe92721e 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -18,6 +18,11 @@ const psbtutils_1 = require('./psbtutils'); const bip341_1 = require('../payments/bip341'); const payments_1 = require('../payments'); const psbtutils_2 = require('./psbtutils'); +/** + * Converts a public key to an X-only public key. + * @param pubKey The public key to convert. + * @returns The X-only public key. + */ const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); exports.toXOnly = toXOnly; /** @@ -46,6 +51,12 @@ function tapScriptFinalizer(inputIndex, input, tapLeafHashToFinalize) { } } exports.tapScriptFinalizer = tapScriptFinalizer; +/** + * Serializes a taproot signature. + * @param sig The signature to serialize. + * @param sighashType The sighash type. Optional. + * @returns The serialized taproot signature. + */ function serializeTaprootSignature(sig, sighashType) { const sighashTypeByte = sighashType ? Buffer.from([sighashType]) @@ -53,6 +64,11 @@ function serializeTaprootSignature(sig, sighashType) { return Buffer.concat([sig, sighashTypeByte]); } exports.serializeTaprootSignature = serializeTaprootSignature; +/** + * Checks if a PSBT input is a taproot input. + * @param input The PSBT input to check. + * @returns True if the input is a taproot input, false otherwise. + */ function isTaprootInput(input) { return ( input && @@ -66,6 +82,12 @@ function isTaprootInput(input) { ); } exports.isTaprootInput = isTaprootInput; +/** + * Checks if a PSBT output is a taproot output. + * @param output The PSBT output to check. + * @param script The script to check. Optional. + * @returns True if the output is a taproot output, false otherwise. + */ function isTaprootOutput(output, script) { return ( output && @@ -78,11 +100,25 @@ function isTaprootOutput(output, script) { ); } exports.isTaprootOutput = isTaprootOutput; +/** + * Checks the taproot input fields for consistency. + * @param inputData The original input data. + * @param newInputData The new input data. + * @param action The action being performed. + * @throws Throws an error if the input fields are inconsistent. + */ function checkTaprootInputFields(inputData, newInputData, action) { checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action); checkIfTapLeafInTree(inputData, newInputData, action); } exports.checkTaprootInputFields = checkTaprootInputFields; +/** + * Checks the taproot output fields for consistency. + * @param outputData The original output data. + * @param newOutputData The new output data. + * @param action The action being performed. + * @throws Throws an error if the output fields are inconsistent. + */ function checkTaprootOutputFields(outputData, newOutputData, action) { checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action); checkTaprootScriptPubkey(outputData, newOutputData); @@ -100,6 +136,13 @@ function checkTaprootScriptPubkey(outputData, newOutputData) { throw new Error('Error adding output. Script or address mismatch.'); } } +/** + * Returns the Taproot script public key. + * + * @param tapInternalKey - The Taproot internal key. + * @param tapTree - The Taproot tree (optional). + * @returns The Taproot script public key. + */ function getTaprootScripPubkey(tapInternalKey, tapTree) { const scriptTree = tapTree && tapTreeFromList(tapTree.leaves); const { output } = (0, payments_1.p2tr)({ @@ -108,6 +151,13 @@ function getTaprootScripPubkey(tapInternalKey, tapTree) { }); return output; } +/** + * Tweak the internal public key for a specific input. + * @param inputIndex - The index of the input. + * @param input - The PsbtInput object representing the input. + * @returns The tweaked internal public key. + * @throws Error if the tap internal key cannot be tweaked. + */ function tweakInternalPubKey(inputIndex, input) { const tapInternalKey = input.tapInternalKey; const outputKey = @@ -155,6 +205,12 @@ function tapTreeFromList(leaves = []) { return instertLeavesInTree(leaves); } exports.tapTreeFromList = tapTreeFromList; +/** + * Checks the taproot input for signatures. + * @param input The PSBT input to check. + * @param action The action being performed. + * @returns True if the input has taproot signatures, false otherwise. + */ function checkTaprootInputForSigs(input, action) { const sigs = extractTaprootSigs(input); return sigs.some(sig => @@ -162,6 +218,11 @@ function checkTaprootInputForSigs(input, action) { ); } exports.checkTaprootInputForSigs = checkTaprootInputForSigs; +/** + * Decodes a Schnorr signature. + * @param signature The signature to decode. + * @returns The decoded Schnorr signature. + */ function decodeSchnorrSignature(signature) { return { signature: signature.slice(0, 64), @@ -169,6 +230,11 @@ function decodeSchnorrSignature(signature) { signature.slice(64)[0] || transaction_1.Transaction.SIGHASH_DEFAULT, }; } +/** + * Extracts taproot signatures from a PSBT input. + * @param input The PSBT input to extract signatures from. + * @returns An array of taproot signatures. + */ function extractTaprootSigs(input) { const sigs = []; if (input.tapKeySig) sigs.push(input.tapKeySig); @@ -180,12 +246,25 @@ function extractTaprootSigs(input) { } return sigs; } +/** + * Gets the taproot signature from the witness. + * @param finalScriptWitness The final script witness. + * @returns The taproot signature, or undefined if not found. + */ function getTapKeySigFromWithness(finalScriptWitness) { if (!finalScriptWitness) return; const witness = finalScriptWitness.slice(2); // todo: add schnorr signature validation if (witness.length === 64 || witness.length === 65) return witness; } +/** + * Converts a binary tree to a BIP371 type list. + * @param tree The binary tap tree. + * @param leaves A list of tapleaves. Optional. + * @param depth The current depth. Optional. + * @returns A list of BIP 371 tapleaves. + * @throws Throws an error if the taptree cannot be converted to a tapleaf list. + */ function _tapTreeToList(tree, leaves = [], depth = 0) { if (depth > bip341_1.MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); @@ -202,6 +281,12 @@ function _tapTreeToList(tree, leaves = [], depth = 0) { if (tree[1]) _tapTreeToList(tree[1], leaves, depth + 1); return leaves; } +/** + * Inserts the tapleaves into the taproot tree. + * @param leaves The tapleaves to insert. + * @returns The taproot tree. + * @throws Throws an error if there is no room left to insert a tapleaf in the tree. + */ function instertLeavesInTree(leaves) { let tree; for (const leaf of leaves) { @@ -210,6 +295,13 @@ function instertLeavesInTree(leaves) { } return tree; } +/** + * Inserts a tapleaf into the taproot tree. + * @param leaf The tapleaf to insert. + * @param tree The taproot tree. + * @param depth The current depth. Optional. + * @returns The updated taproot tree. + */ function instertLeafInTree(leaf, tree, depth = 0) { if (depth > bip341_1.MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); @@ -227,6 +319,13 @@ function instertLeafInTree(leaf, tree, depth = 0) { const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1); if (rightSide) return [tree && tree[0], rightSide]; } +/** + * Checks the input fields for mixed taproot and non-taproot fields. + * @param inputData The original input data. + * @param newInputData The new input data. + * @param action The action being performed. + * @throws Throws an error if the input fields are inconsistent. + */ function checkMixedTaprootAndNonTaprootInputFields( inputData, newInputData, @@ -246,6 +345,13 @@ function checkMixedTaprootAndNonTaprootInputFields( `Cannot use both taproot and non-taproot fields.`, ); } +/** + * Checks the output fields for mixed taproot and non-taproot fields. + * @param inputData The original output data. + * @param newInputData The new output data. + * @param action The action being performed. + * @throws Throws an error if the output fields are inconsistent. + */ function checkMixedTaprootAndNonTaprootOutputFields( inputData, newInputData, diff --git a/src/psbt/psbtutils.d.ts b/src/psbt/psbtutils.d.ts index 3e7b2a393..b6351cf55 100644 --- a/src/psbt/psbtutils.d.ts +++ b/src/psbt/psbtutils.d.ts @@ -12,11 +12,6 @@ export declare const isP2TR: (script: Buffer) => boolean; * @param witness The witness stack to convert. * @returns The script witness as a Buffer. */ -/** - * Converts a witness stack to a script witness. - * @param witness The witness stack to convert. - * @returns The converted script witness. - */ export declare function witnessStackToScriptWitness(witness: Buffer[]): Buffer; /** * Finds the position of a public key in a script. diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index ea5f1d719..8173e81d9 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -18,6 +18,11 @@ const bscript = require('../script'); const transaction_1 = require('../transaction'); const crypto_1 = require('../crypto'); const payments = require('../payments'); +/** + * Checks if a given payment factory can generate a payment script from a given script. + * @param payment The payment factory to check. + * @returns A function that takes a script and returns a boolean indicating whether the payment factory can generate a payment script from the script. + */ function isPaymentFactory(payment) { return script => { try { @@ -40,11 +45,6 @@ exports.isP2TR = isPaymentFactory(payments.p2tr); * @param witness The witness stack to convert. * @returns The script witness as a Buffer. */ -/** - * Converts a witness stack to a script witness. - * @param witness The witness stack to convert. - * @returns The converted script witness. - */ function witnessStackToScriptWitness(witness) { let buffer = Buffer.allocUnsafe(0); function writeSlice(slice) { diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 19cf33e5b..bc72db595 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -5,6 +5,11 @@ import { Transaction } from '../transaction'; import { hash160 } from '../crypto'; import * as payments from '../payments'; +/** + * Checks if a given payment factory can generate a payment script from a given script. + * @param payment The payment factory to check. + * @returns A function that takes a script and returns a boolean indicating whether the payment factory can generate a payment script from the script. + */ function isPaymentFactory(payment: any): (script: Buffer) => boolean { return (script: Buffer): boolean => { try { @@ -15,6 +20,7 @@ function isPaymentFactory(payment: any): (script: Buffer) => boolean { } }; } + export const isP2MS = isPaymentFactory(payments.p2ms); export const isP2PK = isPaymentFactory(payments.p2pk); export const isP2PKH = isPaymentFactory(payments.p2pkh); @@ -28,11 +34,6 @@ export const isP2TR = isPaymentFactory(payments.p2tr); * @param witness The witness stack to convert. * @returns The script witness as a Buffer. */ -/** - * Converts a witness stack to a script witness. - * @param witness The witness stack to convert. - * @returns The converted script witness. - */ export function witnessStackToScriptWitness(witness: Buffer[]): Buffer { let buffer = Buffer.allocUnsafe(0); @@ -114,6 +115,7 @@ type SignatureDecodeFunc = (buffer: Buffer) => { signature: Buffer; hashType: number; }; + /** * Determines if a given action is allowed for a signature block. * @param signature - The signature block. From d69b4282413e7214deb2c451bc39d1b932e1d4d0 Mon Sep 17 00:00:00 2001 From: TonyStark <343043880@qq.com> Date: Tue, 30 Jul 2024 21:29:14 +0800 Subject: [PATCH 199/249] docs: add bip341 annotation --- ts_src/payments/bip341.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ts_src/payments/bip341.ts b/ts_src/payments/bip341.ts index af9b1f171..69a093be2 100644 --- a/ts_src/payments/bip341.ts +++ b/ts_src/payments/bip341.ts @@ -107,6 +107,11 @@ export function findScriptPath( return undefined; } +/** + * Calculates the tapleaf hash for a given Tapleaf object. + * @param leaf - The Tapleaf object to calculate the hash for. + * @returns The tapleaf hash as a Buffer. + */ export function tapleafHash(leaf: Tapleaf): Buffer { const version = leaf.version || LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( @@ -115,6 +120,15 @@ export function tapleafHash(leaf: Tapleaf): Buffer { ); } +/** + * Computes the taproot tweak hash for a given public key and optional hash. + * If a hash is provided, the public key and hash are concatenated before computing the hash. + * If no hash is provided, only the public key is used to compute the hash. + * + * @param pubKey - The public key buffer. + * @param h - The optional hash buffer. + * @returns The taproot tweak hash. + */ export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { return bcrypto.taggedHash( 'TapTweak', @@ -122,6 +136,12 @@ export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { ); } +/** + * Tweak a public key with a given tweak hash. + * @param pubKey - The public key to be tweaked. + * @param h - The tweak hash. + * @returns The tweaked public key or null if the input is invalid. + */ export function tweakKey( pubKey: Buffer, h: Buffer | undefined, @@ -141,10 +161,23 @@ export function tweakKey( }; } +/** + * Computes the TapBranch hash by concatenating two buffers and applying the 'TapBranch' tagged hash algorithm. + * + * @param a - The first buffer. + * @param b - The second buffer. + * @returns The TapBranch hash of the concatenated buffers. + */ function tapBranchHash(a: Buffer, b: Buffer): Buffer { return bcrypto.taggedHash('TapBranch', NBuffer.concat([a, b])); } +/** + * Serializes a script by encoding its length as a varint and concatenating it with the script. + * + * @param s - The script to be serialized. + * @returns The serialized script as a Buffer. + */ function serializeScript(s: Buffer): Buffer { const varintLen = varuint.encodingLength(s.length); const buffer = NBuffer.allocUnsafe(varintLen); // better From fb20dcb96092b421e02d11abb558514fa833b069 Mon Sep 17 00:00:00 2001 From: TonyStark <343043880@qq.com> Date: Tue, 30 Jul 2024 21:32:04 +0800 Subject: [PATCH 200/249] docs: add annotation of bip66 --- ts_src/bip66.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ts_src/bip66.ts b/ts_src/bip66.ts index ab76a4fd3..1229ff474 100644 --- a/ts_src/bip66.ts +++ b/ts_src/bip66.ts @@ -2,6 +2,12 @@ // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] // NOTE: SIGHASH byte ignored AND restricted, truncate before use +/** + * Checks if the given buffer is a valid BIP66-encoded signature. + * + * @param buffer - The buffer to check. + * @returns A boolean indicating whether the buffer is a valid BIP66-encoded signature. + */ export function check(buffer: Buffer): boolean { if (buffer.length < 8) return false; if (buffer.length > 72) return false; @@ -27,6 +33,14 @@ export function check(buffer: Buffer): boolean { return true; } +/** + * Decodes a DER-encoded signature buffer and returns the R and S values. + * @param buffer - The DER-encoded signature buffer. + * @returns An object containing the R and S values. + * @throws {Error} If the DER sequence length is too short, too long, or invalid. + * @throws {Error} If the R or S length is zero or invalid. + * @throws {Error} If the R or S value is negative or excessively padded. + */ export function decode(buffer: Buffer): { r: Buffer; s: Buffer } { if (buffer.length < 8) throw new Error('DER sequence length is too short'); if (buffer.length > 72) throw new Error('DER sequence length is too long'); From db02d670fbfc645dbba43528c47eaec3331f63bc Mon Sep 17 00:00:00 2001 From: TonyStark <343043880@qq.com> Date: Tue, 30 Jul 2024 21:36:45 +0800 Subject: [PATCH 201/249] docs: update address annotation --- ts_src/address.ts | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/ts_src/address.ts b/ts_src/address.ts index cbd03da1f..409a54814 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -44,6 +44,13 @@ const FUTURE_SEGWIT_VERSION_WARNING: string = 'with caution. Wallets should verify the segwit version from the output of fromBech32, ' + 'then decide when it is safe to use which version of segwit.'; +/** + * Converts an output buffer to a future segwit address. + * @param output - The output buffer. + * @param network - The network object. + * @returns The future segwit address. + * @throws {TypeError} If the program length or version is invalid for segwit address. + */ function _toFutureSegwitAddress(output: Buffer, network: Network): string { const data = output.slice(2); @@ -70,7 +77,11 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string { } /** - * decode address with base58 specification, return address version and address hash if valid + * Decodes a base58check encoded Bitcoin address and returns the version and hash. + * + * @param address - The base58check encoded Bitcoin address to decode. + * @returns An object containing the version and hash of the decoded address. + * @throws {TypeError} If the address is too short or too long. */ export function fromBase58Check(address: string): Base58CheckResult { const payload = Buffer.from(bs58check.decode(address)); @@ -86,7 +97,10 @@ export function fromBase58Check(address: string): Base58CheckResult { } /** - * decode address with bech32 specification, return address version、address prefix and address data if valid + * Converts a Bech32 or Bech32m encoded address to its corresponding data representation. + * @param address - The Bech32 or Bech32m encoded address. + * @returns An object containing the version, prefix, and data of the address. + * @throws {TypeError} If the address uses the wrong encoding. */ export function fromBech32(address: string): Bech32Result { let result; @@ -114,7 +128,10 @@ export function fromBech32(address: string): Bech32Result { } /** - * encode address hash to base58 address with version + * Converts a hash to a Base58Check-encoded string. + * @param hash - The hash to be encoded. + * @param version - The version byte to be prepended to the encoded string. + * @returns The Base58Check-encoded string. */ export function toBase58Check(hash: Buffer, version: number): string { typeforce(tuple(Hash160bit, UInt8), arguments); @@ -127,7 +144,11 @@ export function toBase58Check(hash: Buffer, version: number): string { } /** - * encode address hash to bech32 address with version and prefix + * Converts a buffer to a Bech32 or Bech32m encoded string. + * @param data - The buffer to be encoded. + * @param version - The version number to be used in the encoding. + * @param prefix - The prefix string to be used in the encoding. + * @returns The Bech32 or Bech32m encoded string. */ export function toBech32( data: Buffer, @@ -143,7 +164,11 @@ export function toBech32( } /** - * decode address from output script with network, return address if matched + * Converts an output script to a Bitcoin address. + * @param output - The output script as a Buffer. + * @param network - The Bitcoin network (optional). + * @returns The Bitcoin address corresponding to the output script. + * @throws If the output script has no matching address. */ export function fromOutputScript(output: Buffer, network?: Network): string { // TODO: Network @@ -172,7 +197,11 @@ export function fromOutputScript(output: Buffer, network?: Network): string { } /** - * encodes address to output script with network, return output script if address matched + * Converts a Bitcoin address to its corresponding output script. + * @param address - The Bitcoin address to convert. + * @param network - The Bitcoin network to use. Defaults to the Bitcoin network. + * @returns The corresponding output script as a Buffer. + * @throws If the address has an invalid prefix or no matching script. */ export function toOutputScript(address: string, network?: Network): Buffer { network = network || networks.bitcoin; From b18500a9a2b885620878671403a1bb36b092da86 Mon Sep 17 00:00:00 2001 From: TonyStark <343043880@qq.com> Date: Tue, 30 Jul 2024 21:38:26 +0800 Subject: [PATCH 202/249] docs: build js --- src/address.d.ts | 34 +++++++++++++++++++++++++++------ src/address.js | 41 ++++++++++++++++++++++++++++++++++------ src/bip66.d.ts | 14 ++++++++++++++ src/bip66.js | 14 ++++++++++++++ src/payments/bip341.d.ts | 20 ++++++++++++++++++++ src/payments/bip341.js | 33 ++++++++++++++++++++++++++++++++ 6 files changed, 144 insertions(+), 12 deletions(-) diff --git a/src/address.d.ts b/src/address.d.ts index 6b5bc9c2a..5b42f17c5 100644 --- a/src/address.d.ts +++ b/src/address.d.ts @@ -26,26 +26,48 @@ export interface Bech32Result { data: Buffer; } /** - * decode address with base58 specification, return address version and address hash if valid + * Decodes a base58check encoded Bitcoin address and returns the version and hash. + * + * @param address - The base58check encoded Bitcoin address to decode. + * @returns An object containing the version and hash of the decoded address. + * @throws {TypeError} If the address is too short or too long. */ export declare function fromBase58Check(address: string): Base58CheckResult; /** - * decode address with bech32 specification, return address version、address prefix and address data if valid + * Converts a Bech32 or Bech32m encoded address to its corresponding data representation. + * @param address - The Bech32 or Bech32m encoded address. + * @returns An object containing the version, prefix, and data of the address. + * @throws {TypeError} If the address uses the wrong encoding. */ export declare function fromBech32(address: string): Bech32Result; /** - * encode address hash to base58 address with version + * Converts a hash to a Base58Check-encoded string. + * @param hash - The hash to be encoded. + * @param version - The version byte to be prepended to the encoded string. + * @returns The Base58Check-encoded string. */ export declare function toBase58Check(hash: Buffer, version: number): string; /** - * encode address hash to bech32 address with version and prefix + * Converts a buffer to a Bech32 or Bech32m encoded string. + * @param data - The buffer to be encoded. + * @param version - The version number to be used in the encoding. + * @param prefix - The prefix string to be used in the encoding. + * @returns The Bech32 or Bech32m encoded string. */ export declare function toBech32(data: Buffer, version: number, prefix: string): string; /** - * decode address from output script with network, return address if matched + * Converts an output script to a Bitcoin address. + * @param output - The output script as a Buffer. + * @param network - The Bitcoin network (optional). + * @returns The Bitcoin address corresponding to the output script. + * @throws If the output script has no matching address. */ export declare function fromOutputScript(output: Buffer, network?: Network): string; /** - * encodes address to output script with network, return output script if address matched + * Converts a Bitcoin address to its corresponding output script. + * @param address - The Bitcoin address to convert. + * @param network - The Bitcoin network to use. Defaults to the Bitcoin network. + * @returns The corresponding output script as a Buffer. + * @throws If the address has an invalid prefix or no matching script. */ export declare function toOutputScript(address: string, network?: Network): Buffer; diff --git a/src/address.js b/src/address.js index ad3ceeb1a..b9c8a88d2 100644 --- a/src/address.js +++ b/src/address.js @@ -23,6 +23,13 @@ const FUTURE_SEGWIT_VERSION_WARNING = 'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' + 'with caution. Wallets should verify the segwit version from the output of fromBech32, ' + 'then decide when it is safe to use which version of segwit.'; +/** + * Converts an output buffer to a future segwit address. + * @param output - The output buffer. + * @param network - The network object. + * @returns The future segwit address. + * @throws {TypeError} If the program length or version is invalid for segwit address. + */ function _toFutureSegwitAddress(output, network) { const data = output.slice(2); if ( @@ -42,7 +49,11 @@ function _toFutureSegwitAddress(output, network) { return toBech32(data, version, network.bech32); } /** - * decode address with base58 specification, return address version and address hash if valid + * Decodes a base58check encoded Bitcoin address and returns the version and hash. + * + * @param address - The base58check encoded Bitcoin address to decode. + * @returns An object containing the version and hash of the decoded address. + * @throws {TypeError} If the address is too short or too long. */ function fromBase58Check(address) { const payload = Buffer.from(bs58check.decode(address)); @@ -55,7 +66,10 @@ function fromBase58Check(address) { } exports.fromBase58Check = fromBase58Check; /** - * decode address with bech32 specification, return address version、address prefix and address data if valid + * Converts a Bech32 or Bech32m encoded address to its corresponding data representation. + * @param address - The Bech32 or Bech32m encoded address. + * @returns An object containing the version, prefix, and data of the address. + * @throws {TypeError} If the address uses the wrong encoding. */ function fromBech32(address) { let result; @@ -80,7 +94,10 @@ function fromBech32(address) { } exports.fromBech32 = fromBech32; /** - * encode address hash to base58 address with version + * Converts a hash to a Base58Check-encoded string. + * @param hash - The hash to be encoded. + * @param version - The version byte to be prepended to the encoded string. + * @returns The Base58Check-encoded string. */ function toBase58Check(hash, version) { (0, types_1.typeforce)( @@ -94,7 +111,11 @@ function toBase58Check(hash, version) { } exports.toBase58Check = toBase58Check; /** - * encode address hash to bech32 address with version and prefix + * Converts a buffer to a Bech32 or Bech32m encoded string. + * @param data - The buffer to be encoded. + * @param version - The version number to be used in the encoding. + * @param prefix - The prefix string to be used in the encoding. + * @returns The Bech32 or Bech32m encoded string. */ function toBech32(data, version, prefix) { const words = bech32_1.bech32.toWords(data); @@ -105,7 +126,11 @@ function toBech32(data, version, prefix) { } exports.toBech32 = toBech32; /** - * decode address from output script with network, return address if matched + * Converts an output script to a Bitcoin address. + * @param output - The output script as a Buffer. + * @param network - The Bitcoin network (optional). + * @returns The Bitcoin address corresponding to the output script. + * @throws If the output script has no matching address. */ function fromOutputScript(output, network) { // TODO: Network @@ -132,7 +157,11 @@ function fromOutputScript(output, network) { } exports.fromOutputScript = fromOutputScript; /** - * encodes address to output script with network, return output script if address matched + * Converts a Bitcoin address to its corresponding output script. + * @param address - The Bitcoin address to convert. + * @param network - The Bitcoin network to use. Defaults to the Bitcoin network. + * @returns The corresponding output script as a Buffer. + * @throws If the address has an invalid prefix or no matching script. */ function toOutputScript(address, network) { network = network || networks.bitcoin; diff --git a/src/bip66.d.ts b/src/bip66.d.ts index 547c57f78..59205ad69 100644 --- a/src/bip66.d.ts +++ b/src/bip66.d.ts @@ -1,5 +1,19 @@ /// +/** + * Checks if the given buffer is a valid BIP66-encoded signature. + * + * @param buffer - The buffer to check. + * @returns A boolean indicating whether the buffer is a valid BIP66-encoded signature. + */ export declare function check(buffer: Buffer): boolean; +/** + * Decodes a DER-encoded signature buffer and returns the R and S values. + * @param buffer - The DER-encoded signature buffer. + * @returns An object containing the R and S values. + * @throws {Error} If the DER sequence length is too short, too long, or invalid. + * @throws {Error} If the R or S length is zero or invalid. + * @throws {Error} If the R or S value is negative or excessively padded. + */ export declare function decode(buffer: Buffer): { r: Buffer; s: Buffer; diff --git a/src/bip66.js b/src/bip66.js index 0070f998c..da8ceda95 100644 --- a/src/bip66.js +++ b/src/bip66.js @@ -4,6 +4,12 @@ // NOTE: SIGHASH byte ignored AND restricted, truncate before use Object.defineProperty(exports, '__esModule', { value: true }); exports.encode = exports.decode = exports.check = void 0; +/** + * Checks if the given buffer is a valid BIP66-encoded signature. + * + * @param buffer - The buffer to check. + * @returns A boolean indicating whether the buffer is a valid BIP66-encoded signature. + */ function check(buffer) { if (buffer.length < 8) return false; if (buffer.length > 72) return false; @@ -25,6 +31,14 @@ function check(buffer) { return true; } exports.check = check; +/** + * Decodes a DER-encoded signature buffer and returns the R and S values. + * @param buffer - The DER-encoded signature buffer. + * @returns An object containing the R and S values. + * @throws {Error} If the DER sequence length is too short, too long, or invalid. + * @throws {Error} If the R or S length is zero or invalid. + * @throws {Error} If the R or S value is negative or excessively padded. + */ function decode(buffer) { if (buffer.length < 8) throw new Error('DER sequence length is too short'); if (buffer.length > 72) throw new Error('DER sequence length is too long'); diff --git a/src/payments/bip341.d.ts b/src/payments/bip341.d.ts index 676021e4c..86253290e 100644 --- a/src/payments/bip341.d.ts +++ b/src/payments/bip341.d.ts @@ -43,7 +43,27 @@ export declare function toHashTree(scriptTree: Taptree): HashTree; * path is found */ export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[] | undefined; +/** + * Calculates the tapleaf hash for a given Tapleaf object. + * @param leaf - The Tapleaf object to calculate the hash for. + * @returns The tapleaf hash as a Buffer. + */ export declare function tapleafHash(leaf: Tapleaf): Buffer; +/** + * Computes the taproot tweak hash for a given public key and optional hash. + * If a hash is provided, the public key and hash are concatenated before computing the hash. + * If no hash is provided, only the public key is used to compute the hash. + * + * @param pubKey - The public key buffer. + * @param h - The optional hash buffer. + * @returns The taproot tweak hash. + */ export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer; +/** + * Tweak a public key with a given tweak hash. + * @param pubKey - The public key to be tweaked. + * @param h - The tweak hash. + * @returns The tweaked public key or null if the input is invalid. + */ export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null; export {}; diff --git a/src/payments/bip341.js b/src/payments/bip341.js index 926af6bf2..a27698c9f 100644 --- a/src/payments/bip341.js +++ b/src/payments/bip341.js @@ -79,6 +79,11 @@ function findScriptPath(node, hash) { return undefined; } exports.findScriptPath = findScriptPath; +/** + * Calculates the tapleaf hash for a given Tapleaf object. + * @param leaf - The Tapleaf object to calculate the hash for. + * @returns The tapleaf hash as a Buffer. + */ function tapleafHash(leaf) { const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; return bcrypto.taggedHash( @@ -90,6 +95,15 @@ function tapleafHash(leaf) { ); } exports.tapleafHash = tapleafHash; +/** + * Computes the taproot tweak hash for a given public key and optional hash. + * If a hash is provided, the public key and hash are concatenated before computing the hash. + * If no hash is provided, only the public key is used to compute the hash. + * + * @param pubKey - The public key buffer. + * @param h - The optional hash buffer. + * @returns The taproot tweak hash. + */ function tapTweakHash(pubKey, h) { return bcrypto.taggedHash( 'TapTweak', @@ -97,6 +111,12 @@ function tapTweakHash(pubKey, h) { ); } exports.tapTweakHash = tapTweakHash; +/** + * Tweak a public key with a given tweak hash. + * @param pubKey - The public key to be tweaked. + * @param h - The tweak hash. + * @returns The tweaked public key or null if the input is invalid. + */ function tweakKey(pubKey, h) { if (!buffer_1.Buffer.isBuffer(pubKey)) return null; if (pubKey.length !== 32) return null; @@ -110,9 +130,22 @@ function tweakKey(pubKey, h) { }; } exports.tweakKey = tweakKey; +/** + * Computes the TapBranch hash by concatenating two buffers and applying the 'TapBranch' tagged hash algorithm. + * + * @param a - The first buffer. + * @param b - The second buffer. + * @returns The TapBranch hash of the concatenated buffers. + */ function tapBranchHash(a, b) { return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b])); } +/** + * Serializes a script by encoding its length as a varint and concatenating it with the script. + * + * @param s - The script to be serialized. + * @returns The serialized script as a Buffer. + */ function serializeScript(s) { const varintLen = bufferutils_1.varuint.encodingLength(s.length); const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better From 8207c6be4bf4cab42ab02100d7f90f58908d06ea Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 3 Aug 2024 00:55:31 +0900 Subject: [PATCH 203/249] Add logo --- README.md | 7 ++++++- logo/Bitcoin.js-square.png | Bin 0 -> 105566 bytes logo/Bitcoin.js-transparent.png | Bin 0 -> 173916 bytes logo/Bitcoin.js.png | Bin 0 -> 253745 bytes logo/Bitcoin.js.psd | Bin 0 -> 4609779 bytes logo/LICENSE.md | 3 +++ 6 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 logo/Bitcoin.js-square.png create mode 100644 logo/Bitcoin.js-transparent.png create mode 100644 logo/Bitcoin.js.png create mode 100644 logo/Bitcoin.js.psd create mode 100644 logo/LICENSE.md diff --git a/README.md b/README.md index f527badff..ae762f5a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -# BitcoinJS (bitcoinjs-lib) +BitcoinJS kawaii logo + +(Logo by [@sawaratsuki1004](https://x.com/sawaratsuki1004)) + +([LICENSE for the logo is on SAWARATSUKI Github repo](https://github.com/SAWARATSUKI/KawaiiLogos/blob/main/README_EN.md)) + [![Github CI](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml/badge.svg)](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml) [![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify. diff --git a/logo/Bitcoin.js-square.png b/logo/Bitcoin.js-square.png new file mode 100644 index 0000000000000000000000000000000000000000..e2cb28ab30602741bdad6dcc079d0e71e4611914 GIT binary patch literal 105566 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 z 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

CI<=Eaqr9^P@uq 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

3deGx;?Km8Z9d?0*i#7BTT7eke0u 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

h4skO!i>D$E2{8e@02w-KW#s9QPJesLhxfw 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 literal 0 HcmV?d00001 diff --git a/logo/Bitcoin.js.png b/logo/Bitcoin.js.png new file mode 100644 index 0000000000000000000000000000000000000000..412b633acbbf507287a31ee1a8d468167690b5c9 GIT binary patch literal 253745 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

Fmaj^-Lr&QW)-}P0NH0$* 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 literal 0 HcmV?d00001 diff --git a/logo/Bitcoin.js.psd b/logo/Bitcoin.js.psd new file mode 100644 index 0000000000000000000000000000000000000000..85d5ec4f941f12885984f94922b8c758ed295218 GIT binary patch literal 4609779 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%0Aw0mwU=VH@QJs0jj9yu{%< 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:pEXtBlWi9Z{EY3I31< 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

w!H(tA0Q$as8Z2e3t0n0B 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

G`LN>RP$NEM(H_xHSdar0`2 zz#B2N6$mzs=NBu(BQ}kZHfP1JSn^0vE(H4z32|E_1llgKPTHTZj z0d2UFfHpU-0<4MD$Ez}Ge>NO-61k|w|Cu9Ag@j6mz$orzzwVWz(MQ$m^pJ?k|1l85 z{YZjAhm*WtP9^%03_A%G_gi!^-ykn9CrvFrlI`GuGhZAJgcyd008cbhhd##ybckxa zBA|J2hDgbTRbla+eJiNxPTwz*Qiq%fP}|W6wRm1o)0@6J1g0LovHN82XS_ofKqDqn zLM1ioIF)!xP;(pcX9}q8xR1)PEui_vw0en;Z|ns@+u;pCf86ax@RY>|e zMirjnACrxx`5_u-oyba z$NOrZctM47r4ryNu=!EFi%MsUS}l>v-G|oPpY*CnU{mk2VZgQ`DoVwgB~=B(OEOxq zxYMvpSiWYm>L_RyB0SOCI>1wK^RxP$nuQnAf3I0r{jR?Dz0>T4eY)9_?RRyIouA{! z`9yoR-)>&R4cF;mXRp(*y8G4f8K0ZSb9T!-Z=D?vbUWbCc_19to57)d;{4pX$epXP z4nDUbEcV;`@Lbp6DVd+WY9;eU;qb1v2h4lB)RC}Q0Smir3V-A}zZmJnkJfFuvC{_L zUBg6YovCU3vYVHM!|QIq%k`?af^LZnT4#j8*-#gF_q8i6;FbtDdLaT%77N$~4z8Yl z%4eOb1rcygn4kO$0ms3@YF2p(J1Akdmhg0zESeXD#k)R8=<4mFfZ)C=5>9FfM@7Od z+_6u0z%DPLeKw6Q0`Dc^@TQ0OdVAg!NjQybbnJU@b33D$+Veb6Za4x9*&Iq4%U;Bmhe z@K9K+k$bzEL@#3qM@0!JpzCx`E5Xp2D8Y;<0R^&ALB>Y{?-%`!3Uc39X2=Z9XIfiXo&9_C(N8}|uD`@#u*bbG^ zRP8;_hE+Ku*IP?*^^BjW%Zi{ARz8+{8|UrkE%QZL z2sEAq8i!tx5iQ8KpdXDvkFFp{suF&SYbtQ{f_Cp+39j5yL6BF^ID5MF1)Oj$V9^-N zlI8_J#g!g#3YA*?J{<^V2A{PF<~2}Vxq;dChiMEZ1vG>0V;VGPL0e>1#&2;`SvHJn zeKaBO%Zb{@x`)Fgdd5k}j}TDXK@W|=s(_}ktQF7<**oT+x|s~27m|V#Ey!m-P}>1N zjlsO2=7g)tjYms4l{jLjRM1woi=;Grjz=(uXbPr5V?CD=f^h*|&M)e=fToFW0ciD% zGrkWMf|+FxM?_HCkDp<|?53?=BdbygJ*$`TVS<3uQW+oSLg<(A;k4vSKtTIARbJ#@ zl}HUif-a%4eogr*B=D?DP;*{#tx*R#QJx{u3(jtH2@DW)ipF|Z#;2fDo4Egps9_AF z08mh>Fhvbk&{S`bRcR|(X|1B!JJ(vp%7+7jnsf560my0vm5+0bC4o(?sutLsroIua zswS&$tyU&83_8eUjDNgqYHA3YblIkcAkG)7Qnczt{?n$0ps68fGRDL4kJG&agxgqg zi__f!$#2O;bg|=;$rx`k#&he-)DYz4T>Jh-x#roH<5`m~JDs$+0$rwqq)C@ut>Qee z=`*>4x$K^j6I$+D-ZJH#jHf+?C}@)_xPix8zhoLRxq?j@P5G8` zLlpdRFb{U=!5|DOE8sW&A9-#tQb^aXX12rl+-E_y0vyNwMvlgHRfEvoC}yr zvfOB>=Z@8wB<`c*mBZvJ%A5-@Pg|srUB#RWR3n{wjX4)EH3U_F9-{AcewB;_*Ki** zXqj^XelvQ|IXOMiVEoFvsBGt&bAgB@@nv%GUWkjPgY-pFSm=ywlrw(gu2#?B=vKYS z^Jz(rXEsIumv4#&Z2t?Mo$Niy2GmP>tv5f%)nAMb$d-7WfT{?fag4X0jNvL+;ee)u zj@+Anq47B*Ics0?HlqXHzwm>moX+0^PsxaW_CLLDG~oX6q-&64IR12Plk;N*WU!JUVKnZ;6C1T&T2DJ5n%F;56) zMrwsLX8JyEk<$xCr8T_B2CTm3Y5@km(mzEs!~n zIpJTHCehXnIl+iwT3_A3YbHmoWNB;5SKEVVU3)gOF&wUj18jr z?DOX`HjLcpld<6h^_xc^S1<@;k^l6JF7}m#{EV(WZzx@f#mJ2g!OX~jTVl4|UdCWd zlj!LHT60kh4)lrp2D>sg1$|;jp6~`h1MuW zLG%l1Mh5)wKvv7EDj3>U(5Nbb%@5;S(Wok-%B$2>?sCPRU!Bjc*>kJ2*%jBq>dK0< z=2~4w+loEAI+tBT+rqN5qLxPdFdpIh)%L*iDeBk z0xUT!tw!SugEa|D;e2MTG@eRiQS$@k=KOZWz1`WwTj2}3xj_8{e`!^|L%PUO~HcP zQ{ zf6>iLtR5@~7kMl4W#v4LM$dX`i3yQ!6s;aji293LNMC-lAlzt6l=H*Lshik5Z9!mw zV0OfWXv{PrzZ$)lG#Ds7(dfI{%j2;b0yBH8gR2Lf%T0QlI!QdW?rVr~N?0I79ymX75Md?9qPY%^rFEW?FMl9wvs%RQTR{t0CD9f)NCQ{(@i=UH8B+2z3r-!DVj;W`^@5AG24|bx3 z^Us1SsmY{@@6Knv&NuaRlu0D1LL<9Im%^`$)jhV#<+u+s%8AX$)m>&hN5`T z$6r|W|2&PDnF5)jvJz0nBaoQI)JUeLW^q8-;}_ISfut&doSGGmI?NOZuT8u7=5TYu z;EX6B4sh@F z>fM|JRNLtUJsa1#E;U7EO;K4!Z5v-8;|qkTwDAQ((3?33Lm{+=|A>RI!I#;Hdd_Gc z)&YswL0tyqIa;N-Otpts`7$68#1w-CY5GSHSZi}w;n`9-$Wg5$Ir7Pl~woF8M z84HGrIA~nT~i{Ff&fCxs33(@lPKF zvrlTvfW~@ZO``QX3gQ5?hg;jLq$*{cUL8QI7mU~UOCsjQH4-z(MSd;5H*CEI5=EuLlHyUySBbUS^no~}3N+4uI@N)zt1PT^Wz zyjQ~={I!GU?6aNLY@e%R_~HUT&ZqFhZdw)wZ@PM%zFd!bCk&Rzpk)Tvu8Z@*PO$E6 z+Y<(x&0x?z`W_6d3pF7e*2v-Pumc=i-F?TxVII%f&2zkSaoXSI^r_wsbto*>!Q9n0 zesSTv&t0goc7oUt7W?hJV9_~nM(*e86?rr*@YcP6ov($v7H|@exh~$1Kt7Zi+ZN`# zBH)k~a9VmOFJL#}^>n`l0~B`C5>DSsh(BuG6$V=ruw#S*+82}HW;Ll*L3gAD>@5~> zRv3tYuHN>NB74qCt^VemFDpjLrpFhHkJKy>nusDKu58=dkyaVMD>)_9N^V`)7k^N48N$4hQ$p9dC^;&xKel^liFItogDm9!H+eu#G~vPtDtCfxC%-gQC^kv^W>n8XcBcq8ND?}8aKaAH6d_HcrAX4 z_XV~T5*65tnEn_9Y&(+VH*H!_kD>K@rAEzf@fLac7`)cz%O>$vrU5zk1GXKmqNyAu zSvk}3enEB1q#Y0OW4tP`IpJy)*fbz-`L{tSBnnx0BuY~`FSu!{YlC>y zOQ-b%c1s{%Nfdv?khjZ_piVIAYz8z4E9NQe&nkiK=NfohVAB}11GXyP!u^YYpsu8? z91~$_tG^}%b&!q_Yh>mX&(kIuBifK-uZ$6G*r1FN1K(~?lM}GRjJICme|c!E>j>M! zS;-M43|I`@?v*iG2GNzq9jEvCD-yXmEzhBrC{>FlWJkuRpjxyb?+M!qs}v`0MXH6G zoapTUjr=;9c{|8V>lR)APPA@uk})i}Y5HzaqBe<2p1MY@0HJj9VOyq5{&aXdGY=;%h0yOm!#}kiN_pkmkgtsDN}mY7?ufki|oJDaBk= zL7KY%K3_qa^R3@1F-JKXn=BVB1*BPt8D;ILMW?zHa}7shjN(cK>04ciInL3LO3Xoa zD=SE=0F+3*VPWAI)qw@1!HP4!`%27w&vgZ9zOxN1#2lg$6$&x;N__AXzDnV+XMG7DLZIMtPy8GrvS zB%@T%n8!jP=4`$YGhg(4A!blDN&#u=&>=;PTAXwi3NaT~ke-z~%zS6R>k86gepk13 z9cEBz?V|Ea0ck6tq=IyjcBLt9Wt3kiT|ruAa9X|iVLjIBRY&#ur93&@H-4#4u1jC@ z%X%d0)dU8)yndOQf*22zDTZ(Rh$MES(K5M%P3~ZmI~d!`rlz3D9X!r7V*xH)OzvQG z!cS9Hv$))q$sLUOlQ>a_xDI%Yb89=%#QF3PD^3PvAz*3>;>9sF1+kzsxr6a$EQn)0 z>&^dYGP#5EQ*Ew>&*Dg5U~&gzz7aCHgH7(>;(3NCt69i}WXfuq^x1R)?19NJ>9f_m zOee$xsu9_cRK}#w#)6%v0;il$ARQ8qK$s3m1$io9%4+KR-M^<>xx)LD|IC_m0dp>3 zD$AP6vgT;a9F3XEvZk`EIURmTeSwKV54O2YSxse*#;~v`$}nn<#)`MB%+Z)R8jD*@ z3^O2!Vdx@SoET8F3g6L_Ue7cBzd0JKv{M0z5b$Ix zRfunPmmgPdMhLtQC!I^ppNl6>~rqrYGx$8?Ami&9{blpN_1jffVjX ztLbV#;w3YB+T14NV3v$ZSlTkleDptW3ggNve$qZ%Md)wb&nq`W1hTP&Q<+OxbhV!& zjhAs(by09i`E`hZ;HLX|x8SCm^oIy=+wn@ecTWp!Zmxe#YuNO)-z2X9hoe1^t+d?Y zCVdtn?pQ{g?MdbqhvO7*+sP>1yH^D^{f(OiHc#~4@NXlG%Nu~KU`01+Cl1_pB2HiX z1;NcE2=x&>>ZhCZ0lOtkZ{r@3m2T1>qk=lcoy{50SRQec{+-Xi^DO+2Le-QzN=0;Ad69nHvDJ} zON>C2_ZcH_97*GTqK-QZV?i>L7=fx3m{lcoi~z|(?AVqVu7Ywb9Kuzc{W`!aKXv~y zS?OeQm6pT^kS54}{oarOt8`eG(-hzg8R-$=5+e`?a66UZaLfp9M)iEI(YPf>fXpI% zXFDU=3^ao=0w+-dPu7YWk<8DR_9hMBc2z=R1Ozu@1X=~R_y*x`?RX;xV-V7Umjnr< zfZVQ3Nsxdb9|W(Tjd8FIQ;+u9GchVeuZSyA0tvyLzQO%hAcCKfytTCgJj!p>mH=m5 zY8QYjKh^&%Cb(<(u1|=#d}(hpf;%oz0%R7}Q35hfj1qVomT_tjXL}hZI!R|M1$a`T z1o&qmzP3%o9ikQ;P{x>)DeM>E)c=nfcon}ibVD7+3Hrqtlx-QNf__o|&xsov zTm6fEWhib{jNjIEG7IY{0RhgB>pQJr^;ED?0nQiKsf4^%l}dg>^RghPf;9_req0Yl z!J6@CF0WyzFL-S`lhxTaJH3$2Ub~$0=4PkO)zWn0WUuY)N*Z@+#&E5iYt`GpUwe4Y z&hFJ@+guI34j*&I@WW0o3xks;FvvBlb78PZ2Gx_eb~#sFjbPncdngRHGhk5He~nv~ zqlSgU8aZU&HniZm=C%*QVHVHXnHk~zvAxM@Q?2#tt*}@p^V%Wj@>&hl5y7U=AJw&j zzOnOCvz{V@^qerbXc6fei=`VCaM!){**2?M?Fi?6k*-HeH!j_?Bn(d3J&^8@M6}a-#miO|iWRahQV8n^%A`;^^N1Ad*YNZ2U6!Y+F zx?hS$FXPfW+$5sa`{TF)kAo+)b9mzkXaB$ zL1UF#wC`VI0@_d0wMr^C%}oP9t5O>3V|G=50gC;CnO5T_kC_Hw0W=VgGus*&9?mZg z`KXUm#BLcLW)#QIS<`S(VAE!Nj!0LFQZ16Iq8F{XKWSBuz^1Wt zf`E-Y01?{TSyGjAMxIwIPWE;Q%iBa&?FGH^QQBaf=5|7Gb5io1dWAWl?_RIK$>dVK zUoj7}hfFF-yfjhn%yZHt&ZKs#hI6J#asJ?k=HB^(b8?eNELG)H=S(6olSs@|)G2nO zGereWMVEP}Z!y~W9+$);WXACpbnEP~A<*errgO*KIPg3hQcoF*_08vXLbl(-xUQwAXnnyg zfpi70Sas;_3tUyu#k-4FPZ+w0EoKGq|&64Ih9n%wTFbbB?$$bBBG?ucyQR_<* z(aH9z!taEXH-yFs-u|1;^kEO$f@KyXqc+K@gu#mqr6gjnzAp{p0e2jYgCu%NXZjz0 zf$XL;|B67SUwU;}HiS}ujHfyl%nTdo5jhzFaOVYP+vlZY{G4EB$XUt_rZ1_&hh$&I zsDXAsww}t|$026h8>Ic46ws_G7y)QISW36}9l^|ChsF}ZW7NV&!R(2kaRfBh-%?ZX zXHY;_FxFy~R4*C0RRhrKZ=SfHvZLIr&etoL=}P}4kC}eS3!s5`4A7{O!Qn3E5g#pM z#m5dA97X`#xCOJHiM6NrM}IRwBQAqe%BY21aw?UAOw9tC5dfEE>}XFIkB*9}XN4Z+MM(yt4!L21cXpby`$hEUOGl?JL zTf$*&7#v3DclVtA+@6{n=VNQ&U~g@X;m+i%y&dx2UL=FzW!!OX?@UjD_w;0Ln;2Gw z!@>?YxOVq7hjnt;+yEZe#OyxMsl6SwAn;ZQZ(B`*MQ%#%2#Xc67+KsVi=Dk$z_Uhk z!s3yDJC6jMfPk*4nH|VyZK>sA0bl(p;Ft)=E`m_ju*ypaX(6HX(6;oD2$ zp4i*we00uDE66Qoe-9=iy1(9!eZ*~G*Il2M)!27X)x{El1uI;VK zf`InsGP=kbQAT$SAmD`70m^5M+Vgq{iv-MV zS{%a3*+)u1`K)bwXNsLPHMw0F!qIyzD2S+a)Ec+vs0OaRoq~we0ugaqYXL>fYk{?6 z?}?0gJzqo5QqQR%_j*nlb6XZfv?lGDKdLzgF|X%(3`GsBUAsuef2rsc(VDjNGCtCS z{bCu5G~L>>^D?rZu7bAzC4*G=o@RrpgrR56FL3olEJ&%(2`YDxFFS8P<;xy&JD@3* zcl`+(U(kTmmGHJ0ke7bYRItD+3-Mm#hFM{|k#&?@%y{)f3DNn|8{G*+a~ef|=o1VU3wV zwOi!$k`daCFS9}GiA4+Yp@g8_QM3+`=*bkW{J?DcDi{s6Dv%i~S1pif^G^7eWwamN zkQ0o=fOvq}_IkwV31-I1rONQ=3&!dlu}ey6KiWi2uJ^Ai1hF5DQy_sQWF%HpAeS(r zYDXZ`<~0Gbda?zgPo;vG=3`jIoWxa75SP*7t&!6U$?#GuV*`_CpS_T=VW?c6jEz_i zuRwM)5NwhE^o$YZm4y6^L2qv;U8%)TxlX}M>v2QOw#P@CH%+3y85y37VQ`>Nd87A8 zpTy=7G|ZP+4?#_Z3Q|&Esn|TSdeKZ)TB8`^eW5jqQ<8o`%~{lUFJ!fR%1u*U(5SS) zmT8n|RGO^vDrM59q6baRNX~amIW@KOOClRa4WGy1fT>c|7S|?Ivh%{}|Ef!-rRWv0O%%*4)&qRk%Odamr^n<#rL?S=ilu%#}gQ!G;{<7Mn zr-7+14pZ71$8mzGE{+_b5(behWP^Fd*vX*ek^fSsvtiJiqL2(gM^w_sz|@s3#2O=E z`M=G$W$Mb(8FiDB`Y3&M%(;L$7cl1n=3Ky>3z%~OT~NoI3m}%HN)|CGdeEA40dp>3 z&IJ&#F2%5#C3ankN^#BqUsO~xXa`F2aX|1{XJkMNUEXzU8Nz_>BLnD@{kMbx(UCiF z9|Z(QZdUWF6Nh<;3@8bJMn?wFIs2(Z1|YNy{`~pK0Q!VKm&gF|>x_qM6G3%o03h4m0K?%BS{6iT+e2(Xpj2W51hm8kh?_rS13s1s z=6pOH21jB81adix_^gprJ~lu`N5=-p=tyjUj1FT1z9O;=n00J`2+G)iDq?;vu>lmX zOveTYXdN3Mpd}t|nnceEu>pY+i4Bm^DQJ|$1`sr20~pbO$g;djNo;_>OkxAb>O~7s z<+M&QHsDljV@xkr=>}z{( zy+1qQ8k#vea$dWR4qpvsC+xnpz3gk({?72YGoeN&)WIPh++W0lcK;%XT!#k}qYhXM z?+A;vKCl>kbqE%@BQ-O`=hlVA_8?feMkbHQ;b4Ir`j&X-V1FDO)cAJ;hEMq=eut zA`VZ#)}uHpy_6SmMBt56M0;-u7_5Gk7jaVX4doSq@CIKMiKrF9+P6g$&`m`m_AjxQ z4(@wNk0K?s25k}XK@%vVYi~%lXdT%5V;IK=8L0{OK(UMic|~Bq4BPiI z7HPu%ZeB*M36#<9U*M1)+DDU6h5uV+N7&uO|3KNTBm3x|wY?xCw0#Ucjj+4Xy<%-Y z;AkCcZNC>%w0)Y|4o!GacD(U}FS8N#oNxTsfZvJOK_3H@m!6}wgx{%Z53guM-jv|V z!?3GvUOi`c*^MvYL~;RbL`d_J70OaxFbbD(PiX%I8p~PjtF4qo+^4?u1aaRV;Rktw z_g~QPMSV^LF=kQ`cayBjX!#n;Nkz+dQ69zvUVdALNc5bR?^{qXdl_H0BA99TYTVg~ z^->2_3uwk&^@^Z8Ot|#}v>os>>S%=^J}~S8i>b8@QjR(SUx`LrseCD(P2>awO2Yi$jzu}{?Q8> zzNCy!`2`wx$*J6;}Vh3_{Ky~237xzXw39V-w@2S*7Y2n7YwRCEdvoM zw9#Jo3u-Za0qJQ`sYOzibmQ6FpLD8AP>bpF0W~a7fHRXz3M)64TvRGFnOv&(aRtNZKvPo#sK#0VH7kK2s3p|L;Nc9Gm$4)kKnLUiN02L^9|Kl=lk#0;OYY4o4 z=yL-Wec$J^aVGlemyDlW{uVou()mdR$I$wvK1naSn0V2Vmq{mYiV7y79T9GFnKgh( zC(jgqEoKd1)&Sffp5&%LkbBKH)lz;n!Q))B1~6*?vj#A0fGUfJIP;ubhDPv>_|_nk z%P0@XcO^uF`Eq+Pr&R$-4yHF#s(HpXEngNM2;DI^n99T@AEU&0~{_>c>bH;DMc!mJd>rr%x+{5a!_<10XOz#-WUQE%k7l?_+6HfJb(pehZ~B=Ky96 zfF+SBEN<2SvU-1rB{PM^O+_6T6H`>N~;u(L#?MQ&=3GU=G6231$&&7Qto_Y!<<0 z5iAEv<{<1pu%wjn1~uNG|Eo`ehbpoORYqs+yJ*Bo*s(BoG?&ZJ>ZXfFeUMi?Vt?(% zRgkXyeY`58uXZ*ZbrQL##gmdb(p1n@I}-w1MM@~aMksfQ6+`(z)WS9}*hBQ}eE zaF-f&oO`fSf|}k0KP!RSj{E6Nzb&BY7vJV3KJMIJ5VRexh_My}f|?-!*Ll>eOtSzQ2*~~3IvF5tI3M!SKm*#kWq{}d zaTSujj?oKahJRGfQa))JAU}QScgV?aG1j78P;>X^eLzqr>4h;avT~dHYeIwOe)GD3 zX0SyQ2dJEJWG7xwVe6UE7J~wN4y|`l>1W!%n&0o#hGC>3j# zR22+e$SW2t?Ji;Yo5`x9pjiP*8%{9bVM1^-cHtW}%ZjVOy=Gm0w(llV*(N(Vf1Ca6 zy8F;j?=-m@>(1}8pY7Y_WVXpwIdXUBd{%d#RaXrjtlYuriZ+9cWSVT9M*-yaZMwht8e-w zyk8MrYFap4HP<;!sD=8wl-~B<;^TRmuNY%-_-fqykJ})v(fJH&qUkko%c2lO8 z_{e>rSlP9VO_zdiMr5237ME0ZtI@75DEn2dT@!zM`9tl5v$ZPG`RRx1jo~V^e)+>b zOIACzs;bty#skxQAUuuc>$)ljWXV&sZiLVp>jhb7g7>#^|6RNW_bVGe%wRPi5Lsbs0?l^~0z7ey<}Cr< z4YD2pSD{VZKS>Ji`WxJz6me(J>eLGE)Hzz$$Sh{{5x5N09v*lTlYwgff(J%rpyttf znGxVsAJMwRKZ_2b^?NIUM-S0R;8xOV2d_avzKr|tHS%gApCwgf9Ic-lF;LL3cWB*~ zaVluo2JW8{xK*Xk|wwSB(=v%HR7{kTfmNk6AkyY6gC1!$~0eYmQS=~O7;q*OE|L70<# zz%ACgGU|XV#F@!P1zLTy)-3_v=XC1SpTzx0hBK2951H{y`7RLyvqVH{+b)yL%UNJ! z2v;7?r`~vR73M6kpH~$!3yj8{R4$Hp5VJticxbF^BjA+uS)5+)3vLESb_s5#68IPf zZabFZglbw~b3*m4O2ej?%_e#I864T}&!#Zn;f(4mK-|$ZPqjx$Rw47iByih_2t7ns z1vck_O#+)Sm2dgCVNL@FAS+mLM&-nS+m6RLqna1ooCns0@u-gzssnaQh?CcDk(JZH zPZ2?#lnE7R5N$t-<{xQ+9pIE|TVQkY+5y1vh6@ zml}716RKAv^3Y%5X9j}=-QtXDN5-h2Tbu}95VjRnl`>Zs+@f14N}CADOpwfcop?B> zm1`H5CtA5`s9eJWoGXl5bal!~s3>Ra1?6fGH%8&hx4 z)EHr4fvGWqPB6K|R8PUH&g2r~+|AS&K_{3RBc{fPsWC!lU{0tH{>#$onUb4kEnwCH zCbhOnt!zB|NiXqW;tw@!)7^b zmcwQ_Y?i}jIc%20W;rZ5SLiWjPKV8M*er+d(+-*CuvrfO_b-Ptb~?iNt(`zNqoQ=c z?GEG0&mEzQSe27e$&7@mP4R&M1F`O^lMcvYbT{7cqcu#w;r1x+)7$tYlE(c+o%rnr z$&8My*HtMnt4dgO++8MFh%UG-F11-1I%61Vf|7Wv zAputDutkUCJps-LvK|3WH|h6r0Jl?_Til-!+>B}ZT%&Q*-*}VEB6J+?jASzi=AxI} zX_UZ|wRGGaCz+pbz)c#!?WzPFcUJ{By}MflH{;HbF)U-p8|k(?2x-BK@p4xwAh#=1 zblY7J(!KjOBe>&?;94WIu*Je}Z8A=bJ$oCLacYpf!7@%f7RXi# z@Fc_RmiT8OI(Rn`cSzhLC}T{5-}(hOJ*7Wt;8pZypCyr`4gOY#ae{u)3x8XNsi0r< zls+eJggRBz!MaGlG8DHec7fJ)G7ER(;ha{m9bCTC3RX`A8x`Pm@pdX9FMeh1e`XtD9mqd3m4k&OK3(1z3z|xQx6w8ebwIG)4lV%FCoG=V;XXa8<^BeOrzz zKZ_Cd@~VV+=dOccC!P!IP*Kv9aV#H44~vBtSJ@71UI{20_g}eaCeo)_56S6%JLqi%j{4%sU{23>ZFs8s!{fPjwj-J1rDRcf(V zR7ya5sS4WynlDTnK&ukIvG0ArYzLEk`9?)lexSa^1+=U<) z5Q8oUWQ3?ccVR&tqz*6gzsmWZ*AR6%KQu=a&}T7>qeoy%(4~eQ`GN;#NL0oTRIU<3 z1jXVPaz{p}pjdoiKM=MRREb{{D^6)6K7JwB$;{nOW?HitB5?DPabj%V=0ZeJmTcZYog&QNYZy#|r}c8Lr+ zW^wI0IUeZ+>%p#FVZDhzb#;%Efpwy$y2xQoIPCX=gR6h&EjhefAh?cs(72A@4)r-h zYOqJ`3yU={cXdskoctg5-m5E)CFmB+to~Q?*XChfj~uqWZyJyA-Xomw-Xn(qfh26V zyz9GnWI;E`omuN!v+mrJ(o0cQ5xpaGS4CE4L`L>FccdqKsbW)9Jn0>VivH0t_3>Ji6woxw(1f`B{ki3r&ZrWEsa#U>W%iG7-Q&A?W?{OINH5AN7op69-n&o>q zTg=f*t(Ql9JslL|$jet+FOTlge|!jpve8^f(G$#3rieqXv>%Ehr5px5yN{D)O!4X{ zp%A_Dg*Tjy>R=QnCwck)7>?oroQss3N`ur?%EYwhG79m{^tLR58~m(?I!vK^d=GyM z3uHgvsH*~*ui?7#OcYi9=<{9)X1>n)#7+*JJ_do=2?aTrSrE*84OjZ1w1gh-1=W`` zyQ>qBQJ9wABTLLqFv9ogV*yR)kpO5XQpVTmj$r0nxTTcvI8Hqi%mEhd8-$r zM+CH&Z__nOmC(C40JOeOk9Wj|^3zN93uZdJPcCMTV-_I;^XS}bq;r_dx5rz{=_b3R zbGXgGJHKEKbNDsIAG*(dAS$GD%INT(P*a)Yq_qlYx{mX5Hgq74TgSvuj!1G*gP9I* zQ!vxFKV&)6ahFYE@Z&WWuwXse9iZD4m8MkClAhS3Oihhe+J zFuq6|P#y&p`((+0gU1AC3v8u9ezoQ|#mH1wqm`;< z_9K`JZT2G=jbJ~5?MJZv2)1Q*Y?&Q93)IPyz_!eeEw;=VB|8mThk)xQI}OT^Ia_Vq zIhHEvl?{?QfJtc;QH)l1w?(aBu=pCEn>Zu5J5VNFN02(8_py(xlWvyG0c@A3!;IX! zaI2&)P z?eaf3d!MoN)nz=A@c;WbDyzo19v=O}@c)lqe>R-+OZwUzW$w|7r^=xyRK!s2=XY?j zm^o){4=BXtl}uS68`i!sPEPRh9Y)eeyf_yskqS3k)KtWjv&LcyG5r6eID{L@8A?Ay zp?|ov{Uacd%emmaDv%jTUsI9^BWjkECGP-a^^b)AzcZLaI6EnrS+Fk#%ucA7qmf;K%!IS1BBI;H51k8SZwx0N zK}OwClQ;|sb`V#zjZ7R(I(zbIiddn34Y zRP1DSXfA9pGaO-4Ff(4Eo_%wN$!>3oAcPuL<}5rVtl|c$YRyxsgp~48pY_ZwZ*D;O z=DIFWv9d7_6`qBq%{AP(BKW3PA>>)xTw2I1sX4tNDg@u`$~si!HuTCIK?uB+4IBgB zCg1^x{)LJ2Kbh#+*jk2|n$|Z=qz;-WtyEy5+e&I#3yhmKUbRx*M$hItO;l4_bo|;z zG#PAMhKbxCMMSh@e&-w;LA=nXp>O1hElZ0!P#w45E&W^_8D& z{D}x^%>fhsg^kY2I<%>IN2ma@8ym04PMZCK>}uWF`hP)owcz~T#+z)XiE7gkD!2~} z`tgr!H%u^na7ia3kNjOe^2{MCc;ZWg*H1hz-@SU`olxo31Fs)2h3p~UQ+LIO@IB@F z5P3~;Rr5y>FE<@5SCaN7?yJW8tn>*FGfg_ddCPkFy+kzgK~ms}4O-bbK!a zbKdbWZ8}><%;NYS3+B8NDWwzH=C2nqg^W%Fo`+uK%y4D_;ix0UAzvsMpAbmr}mv zwcBUg{le__*%CfY?^qB%ZRUIF>1-(-6W{IBUWa$NTRU= zAv+MV10g#Q!Vn?6f#Jj(yKyQPY;0v&G=i-x`%|u`{T7gqm}mH`c*!WDiWV*=jj-ry zSawdm3M0yAfl3rn3!{kQTsC|rQAD!%Em1_A3OzAVM75lAWfT$On7B6f`JqXJ<_zk# ze8eyf+5}N>Uh&xEk~MHuby9M28U1vXO2gUhbt;Q1?m=b(Njb525SNd(X8SE*zXj~K zfc+N0N{h^^aB1pR!oRMpleMc`8UNM`ST9hxsYGEE>>M3cbR2%>i;03wNEGZXhA~!0 zQlV9)uD`XXL>;|eO^Pd>CKIj?H|NZxBUddC&7@-!CmnC?xTtFMUw53lgfe&5(JqbI zNl^rX+kucB2-$&<9SGThkS(ajc|dN8ZPx|tw}4$2uxKM6a0D$+jW8e^9!ok z`r6U0kR65)0#GET|Z_zCx)|#;7&;aa6*UqJvT# z1sF8n$D<-9#k~(jvWZ+oF=~CDG9}Esst!U^yi76L?YY2awpgdYW{liL0I;2Kf@@V% zf|^xvQU)}G^tP$W%hdK(Zzh2PFguVs@ey^1Ve^kDRK%2qZ*ib@VnK$juL)>Q8$T4# z%rQIUj|Lbu-w#WniG%5nVW4&*VJ=iH2x_iK)dX;B8F!q0!A9{jXueA<<(gO~D465S z(w~M5(#e?8P$8gw47J}8&`iT<2WVZy{D4!xU}n(#m>9}bzV8Xa9N?PVI#rb^#t=41 z|1inyxJ>$o`G-T&Kg?|`W1vg#v=LoD-F&*M+VxCv^0-Q-F`@*Yws&a{idC*=@PwVrO%(-*2)s*S=v^_dI z!yjkkEty`YaUB;tr!U*Pv%R{rSHI?^(>dJdG|ipj(%I|2&MY*v@8K`!WTO!pTE@@L zvgf%o{iu!iZBfO^^VS~R*D-J+8fKcQd{LAib$3B|cZWU@6{}F;G*99-&)NBKJO9`f zbWb4TX&)jwb*dkWh859p*a>*KZv9FWEQx|?L3c9P0o^?<`kAQMF$SI!!_NkKx_Vzj zN}cEhF>qE?yty(^G;Gqq)-l(>(bi0_YSf!Vaq^;#7IyZ(6$@vnqH#g+9rwUOPj|<@ z(Y_`CCya$7V&RS`e~Kpev=2)ApVC0+UKS0nx?rKFyUSR(ED9b&8yb0#3R<4xHUQs| zHX1PoPDm52iGuwOD(LDthLCfz00pW^8w20=OAofa5Ddn^QP;pR80a+8K6T>kwTpp$ zZ}J8<83SMUih=DfMf*Cnx9D+WpdkSb^qf3v5fTg<5=;xeB`QC4UUiFworj`grO?75 zX`&|rZ3%id*!J;Ub!Ld4XP6oh&pII&(H8kH46=I<3bozcl z^IkSmcY%ctXfE2%eoynfg`QKPx$1U0Z_pe7uW4>9G&CR1lfY>t%kiK;3S=s^mtofL zQ^{w7^Q#pnqJdE{jd>}4XAt<0qqUuyG|2{7rZJ`P3c zFc)f``f)PC9NeA|h3NH8VimZato)4YQ@rk<#M!S^iEO1#M->ANHmNI2SI`lm7`@(c zBn`269lg#d#k_QTA1VZOoG;^5LCx1{b2L+_0*b-m2LhXgDu={Wy1h&iupQWo>k9&# z)y5j)P#U5Gc}De7X1DbMHBznULCy{AG=1f?z~-vdw;I59(h7m;A})%j_qo!brn}q_)Qli$V;A}9KwgC*g#Z!e5{?LNdcOB5X>B3aGNpopIGg(w zvjzk=J>O*lxK&c8NUb?a1-SqD50_hvVR=Gak#_3p&XX%jdlQxPn2!bcW8CnC57T6r7eyIg)rz*zL?Y5xi1KSlP zJ`SE<3+hxYyJ7-Ts25A>f2b1J365^pC>G)9tqrhskkmh5YbE)>jtJ^S$Yfm9d^asY z1_ttpZI&+LAmll3jdSoeAYDZ2pC_fUE6Jc4{!oym3BEQ+7sbe+9jc1S=&f5|llpJs zZ0uAGUr&=_EUEvo+Mp(ZHUu>v*mibNkkmhnLJE9+$k)>&0nTUkU0Rx3sI^S7@DR@C ze#Nap0Z#5;qySu1=rqYSPpJ?e+HWqo_+H&5uviCm_2k(Vr@^UezM&oqa&rF@*@Y4f zalWF+F5tek&h6Cvb(tHo`P`Z_yP+!D)m9a`3Ik-u`T@fz>~?yb@#1^bZl`Cr)034y zTUN&;S919CrXaa;S|W_;pgywdkFHHA4kTFh$7EM_Gc7dC;m<4CmoeQ)k34?8b|7h? zPEDEGXvzd7P`e?);NXZvLNkOlDv{8ParHA4=tN>wD4;9Ol5Gf4nc z7LiD32Iu-E5}IMQPNJUS^kWqB7eqoQBobO+Go-i$!MTVzmPly9&D@0{vGu1&Xu-`$ z=!X(gERoPuA7dnRA5bH1Um~FmY!eABuo(&cwbq1vb3m{ys3j7bfw_LJg1r&c$y&y5 zPe2M`)di8zafyVcSeUsBZ4&k^kJckgl=HGz#{=(5D6{RTBcZN5Xy4D;?{rwmq=)aa9>42&rvEU=?gBo_~G(| zz@jD+I?t{cBcYSkj2L??$Qfw%ne0L&bWE}Upbd@m6ZcIPfB+=-B~m-jeaQkS;C^8i zfWR}{7dqs*Z?XXL=nAs{1fAi&n+0%%&gDMoV7PC(QE;%#?(7V<4GMSi0mSDHpIE-v**GjG}w z`4>%HxT&r8jN%v552<-k#9wzpTz9tVLy=#leB%V=hgxv>a92)0ZOn9gS_dv6t}gT> z{&MP9ME+HKN47iHp^pXF62%`*;n;I=-rokzT}}GAsMrz9hH2SYfn^7+u*}om{f4T~ z^}JX%BdSk&3N71&YTYz?eyJZpT-EDM-gLg#Ov~DPPv!S#C|40{B_Opntpb?&@u5yN^;L|zPQn5X88 zH`3#stu)LzU#I3KJx0TZrOS7C*ZFhP<^AWBznGFPU!v*@=eWaI_DWQ*NW;)F&&8!! z*7#I3(=gSd#IgyoY*jQL8p}Fgi}<26%ruUn`K%Xxeof{r>GA?HZ-~5Dh9uAK-;ueq zWnTc$G?Mx9(z!Hj-WCNeoBIr#jcJC>#~o-{WO%l}5(QUm7SjgVX^l_h!V}^1Ao+}5 zzozqL(n9#?O+a%**eIZ7hXDR)uomYA zU@0_lu<;=R)J`!5ri;dY!7gicYaf`iDCN9C@XGxUa#G z^bgYzKSl*~nE7f;{HZe8A%LLE=#=+pUqI0(_6ll_kTM2!oPKJCLZuvmRKLv8wstLAg0zMz4>JPsJY}EvOf!b2CCfV7RkFteBY&B&rm|cZTb1udt z{M!m@bvnuUC{vw@D}AUujV$~3Y)v!nFQ zjZp2RC?nE8Ka{VpBqPG}37}?2T}B)qr%)ciscc{zIrqWS5lP&utHRlu3PxA2a|epJ zV)l~RQIrA42XTs}oB!jJq7f%l%r<&@Hxtn%i~w&f!qFYB-CdRjvk~o!;N&b(92fXtN z;t)tl%z`tqx|Z4TY;MiX zW*0njvs)XPo!pw5z?H|#o0%QGwWAkj_}2#RbJjK{HSMYac4WTqqg)?Uc>t43^aIV7q+Qk zeTCpAS0Llr++F}UJwL5C@uxxOnOfS~&hF&4_0kkUtP{lg=F~h?%q(mJp4!l&!!>Z* z7&vp=K+pEh;&f&~O=_yRZD78IjDcee=e? zRbwAp$k>M#qIpogj>dz1+rP8VZK4gD2i&Wsjpn5TXSS}J$a%bkW^nCuCTSj&-|WEO z*k?Krx=DdQ*jK;+wc)r1&WV981y+Ot+b#uYASeL))RZFzdbYPr2MUYUH>cTwKXX8B zIzj@^+)SPWc>_rSG0?NVK?i{m8N&m31<&W#?t*+hAU8VaacXYThTcP&J_ zN)N(Z;eh*;z`j3nT+KVvdHc*$9@hVur`(qf;~gvy`9GPkzR%#2>M|b5Lw+AeWy~4s z;n6>I8y~%aY#45X31V}U5x0>lhoVpsvzDIU#mN#Dl52ZFA$qzDyhd32!Z%XgWd z74hO+s8oroqoyLdjm8oRiK~Ox23~^09WXng z5_-B_fz0f=rXv0j9o@M=_PTB(3|~XP5faE{;^`>#!1Z*{$vn7YHd8tC(E0>19o-if zF}=njWMCXU9oz=G#`Sc7Os~-?UBk@+K9r-C9bb^6+$sLhU1t0xq-#pVX;4#%V)k5% zV7~GiVeO?y9~C<}D9nWoW+ohN3T7tH)w6HzG7atxTwPd|D|!w1I+#yi_YJ-d?JRs+ zqSXB!>iQL-x&$?S-ADMku=42u88u6(dn^azGRhr0fl&{ssx?n344wd+l!C7VEDoMX zDU{DOqbI1sDZ5SHRrDOw3%@m|F@m?dgdtId?LRf8|07xIy8tA*4#RkeIs z=OdnMwGg~XZMBfX&^cQz#Dx6WUBdEt(6(BLtrh~mVXK93cN1GJqy`h)G0XR8WE?E{+=w(kZ1K&FM{TtbTQwY%uhk+Q+I0ZC4!|HtTQ%IS17L9ZDk&LM zjZAhKzurGZvOS#|yAEL20n9Ft*Lw^7t95{(2brk8%i#a7!9X_Zg#3)8U%rd8Ma&ti z@$yK5|BG9?q#Owqj45fc8pqa4{2WTf!%2DmEU}n--a|z)w`u|KY`{Ot{3K?}@J;vD|5drOG z+SnSUikY!c577F)g#WXld`xuf7tE69=3-`I#v){39y1oIrE@rqzsFn4nCR9eox||| z_ddZKV$#?Yf9O7Itfi!LN;&RA&R7(wW=vUV70?X-KQCiL`6_ip9O{EZJvqd`wsD`cx80W+^9)TM9^jAtS^VMjtWv?t z=fhBt8__Qy!RW5&?DS8GPXwz zFP8|ecM&q4{g-3I*)csjsGo|8bt)g2KDap4Qv;OW5c%DKQOFOC?^E?ly&%>t@zVa@ zBe8DinaHnQTjx1Ad_0&LQ@wf%_c?nzh1Si9f)&yIoXrF4*gR@a8}sG>RLld0{vA=U zMe7E|I_H4KsUE#18lD;JMhmT5fVSMergdnaCr0;%Yu)9cb1+?C-V-rzcx+#qht>i1 z5@cZB=uhVL&!81t^PFB<2iQ9RHVM0b93eCM+IRNGjCBQ_e{CJx zr*~5tc!%wyh8$y_OK#~sm)s*iky{-&u6chYH>jyboDswBWigNJhIuZ#jd_OMQ}TEm zOc#=SkmSB;Ujez*s56+i?=s=Uz4RHVo|e!)zDhgFab&8e`D59H4kr{}z$kN-FxAt3 zpv)D-{CoqWdRju4GnJ~@$Lo9pzwyQ)7UIivq?Edts{SPw!O2)i@ePbBR|&Uznjc43 z)zcC>;8stA5Ir5MrzKE5jR{z&o`yT!>S+lbFIV&%sGbJce3^C%Y-UGa1OVHKBshvk z^)#SztEUk(bB$3wEupHdnEZwV)MRXq)$rFxoxrrS8= zj|P|k)(=Yys;4FL)zg5QzOFWaTgw?mgX(Ds?RTrE;fLtzE<=Jj&i5~>rzP^$(*WAX z{3}#X18Aw92GFK@+NobK(*dA*8Vsd>{)UQgz#L#kAF8J%kRV21*ChSJw6WuI=^wXx z8dTBQeM0p#fWE4p221Jd>InL(dRhWSeBJ745Ms@QtU(=ftEa)z5uD9chM+3$rQ1OD zv>UXdvpXYdR8LF(zzqeoN|07DKm7)(r;%1vW_a}!hp!E#L18#f#!2t3`uPMn}vi0%5m2Ed)_3YjVr z+#F=K3+`vQ6LB83PO6z*I09>-i^IIjN+5TtDmi3X6yzktkLDEAnt&m;Op=e@fS60} zpHv9$8V)FDA%uO#nEm*rL4YTiXt^!G`Rw%qxDJs<$4S9m&zHlbn9KLfhg!j%;v=(8 zWii}YWU5Gjlm4d$ zc=bEnFh?N?^!WM^{RDowfgU&bCFyR&jpd3ujw=iJRZDZLlYDA6s4UippU4@4?c(sz z5Uh~|8x!DsZnIUeS5@mY-^MF~oCIqXw&sdZ4Wy zXsZVzLa(|~d;sJ2Z0WShI>pgMK|Ke%OE8>=WX?oo7!=RcITbD6(MWcSRDRmoEmG|k zsr(YKb)(P-Urfw*LOun!cx3BF*)39ADwu$o&z|8&ysaB$w@8i3bdcR5)j3J$qrRCx zqardNWVc9_2_aLCKqiD3_v)mZIB|DtgDq8~D`i4RK4WCYk#C(F8w_1?Mo8+_E!!C( zJ0oOw2}X9FEuCgdr`bu->qLLN6>o(%X*(lSYqkK8g)wojTo|)6LTH415cR1U!qwOr zp&d*E*cl-^BV>!_@LREnjiI17vqgTrV==r zib)}w6-xOTHQyY;k&kh#`+gin8Gk;&qjCmHXXD9iI+v7+Yx9)xv8q^o44NteimUF= z1U9qUdIUCIz*!WqokW_sXw!n4`?GzkGN>6iyiHvpI*yJ|CXE1ke#O5D1GW>dVaW3s z#mX6P{HX%Com7lLV`~DNo})!zbNF_^pN%pXZxFUZ6{C&cC4k#WCKx!pAh=mCOYc_Y$Cj5U6r6xeaj>MZlO{G9rF zNZ5Xc+P|QM5yd2ue!T+CSNbSVEq(-?5VnfzN_wCI zYNcsyD#9#|4Jr$CQJLY^6C55HZZVDIk>F+!{)Z&2RVf{3MsuE9jRKqt@h^p2ja225 z3oBwxPGjZ%#TUJk%rxn$-t#YdFpZE%sxcXtXN!K+YME{GP<_*bdoX#}YWg%95 zgtMCooJ|ixthb8Sw{iWTvJuy7TR-6XWQxk>arWY2J(NAHRc~;5g<=&W5bLhSQR*4a zy{O1EIW?W1arg*_Ilz?;>YFZceO7=^;cV6g{t9QesH}1TQvH>gCY4ZIxZ$vh$m?3(Q8ZcNsy1??B|Xes7=4X$){|Z z>TakS7IW8d`E{+}ZY1tG2q75%F;2g4kruBy#AyS(A7TRlt`o%ls#%$>#A z%O=5HL)`0BmR3U$>yeInf*W3@rK8$E;)ZeQs70JTYY^adCpfz-=03vN&z%HbLEz75 zWVIsl5kbC!>u(J5hZneEUd)}u*$=JgC@|~*XSb!F@(kO=^&z*=SwJaId=I4 zX53QYJp8K$D9e-dtxM8#oGl<}o3Wi7-4)yO9F?c5fTJ_gY1bUpjn(S<=@OFmvC|Ur zbO}dmTglN$iU~*M=>kXjaIH$GU2#;Ul#oYA<_&(_;IycJZvZXm%H zh&(lcpZMSstl;&HvtMq#zn!|KTMrjIN;T^1_xI0U#7X_li9jYlK*_iR6b4b}2Pk#o z`+^*wJQd%UAD_su${n9DuH&(|Y^FnfI z!ZJLes<2d`tY%3HswFx8~2Ktk^`?JV8Q(TClqj^id(%fTn__;2tyiP?A!+N?*m^ZOBGDC0ZO6P+N_P0c>%)Eo7X+%=y7icr zl?{jz_?)$M>yb?M>$EbwQ|&Ae8X+HCWM_f!CbhFbg;8pD7Rb&5*;ybv3xtsbgLIDN zWAaHl?mUQB0h&E!%CULA5R0&2R!+t|PzomYoK@ zwW9<0k-L@8Zl^(`b{bTN83|OFvu?|F+i6fx;=HQ*R=%#C2L10%gDUO$+dt)e@KiY$ z9{s2P&Y7p#IsLOL=ac9EcD^~^{;21h^TpGGU;N`g{8BT1@z01Zfcle1{Yeq9csxzc z$G>R01fugl>G61gsNt_|^xhNmDEZCbDsz4SbCq-X7er@3^taZ&^u(Nx&W}H7LR)K3 z4Sw;D|6qIFzi91dp#I}O{7#EU%WwX^=ahq1`m3f>p#EF`XVJ4?VE-Tfp|yW_9^e;$ zj|W%qOHF@m?Q^@BRPbUp{Tk|DQc=wETbSX>0ly zPuu?^{riXJ#`P~i^gr{o)%*`VZI}Pz-Nv`=-=DUczddcR|08(&H%rpk?^+3{wv?s{MR;mkM}6wrhoY7K5f}QBI4V&=C5yC`nLCfdE3&rwfxn$@oD>e z|JVN2|9|q_`QPkm`-b-UH&0u`Ut9Ye?fAEMq*Wv8PS{>1n&XHDQBw<6HAJcK<;)G&pVdAJj3a%oW9YSxVBg?EZt;a6zKQnd4_R zik3-nvr)9&eC^g`j@^9CR;0jgQ8j5(osmzD1huuNaAY@MLvVuKe2pneCOp_~zLt;c zJa@Bu?dEG3u_mQLDc>FmrhE5FfUs03g1 z1V}PV{2(rM9h#Y=Z3{H&UC9#U7E<G#G)hG!yB&YTehzaxPww$!3pAn;Y=K7Xx`YWd9p*lX*&2*Y?dP!Fj{jtxoq}juEPLYT_(NvK!9S>ICbd^u=%#lAXkp)!AB|u2l@bn&J&% zZu9xEE}ex~Qnt_u;cScn5}gTNm(}5T4X#%_BzpZsr&HRiXfwj;ji^G*b?o?Gup=v?SSm__lDX5LfM#^lNWII$1h`ACd zS|PY=-b2?cgs{-ePk}ED0z4t(AOS8>asaMF5+#=u-1V~gm6*#Yxev91J0($aR2Gvc zxenDJpB0nw3B(#r(_d3dP zR7C!t`(xRJ4kr}*c7ivQusePv!&!*=`8!)hI9pEkHT!s-@ZNai5DQk~OX{_GGYM5B#TnoFh&r6c*-;7= zO+(e!I8ZyWAfLZA0nI4FMgh$xJ>-uDSckG7mO>L5^dSP&PBg-&Z$VJA4rOfsx0dr& z^MZ}yZ^XGSv6Rfu1O;=PT{;aJRiZ|5Ls~%lNZ}m;P1>~sv@YTUd+Ha=6*xI2hVu3F zJt3F_eDc<*s$7wFP0~Mj(KyHD(mypgIVAl<_I!#8=rA9>CH_L-G39m+ie(+3CH!`y+M8lZ{|s@U5d7=?=A@qMUJ zFZBX-^epkxEd$rC4fGrwJ|4`BsXo0W_;!qe2L%SM2)ySbg!gFVIWVX_Z7iI_4*|o( zFn*}t7`R0P2OpajqJ64IuZf0d#=y}+0~d^eV@G!$?RWD8vxI4cyOed?tn z9Po@hG8|Z98~4({+`e`V+{KN~9_>>D&Xn7O#y~@YCxg<1W5&QmApz}E`_A5&G4Sa% z37jhfg#*2txX-hvCw6?H1fd>k`EHX@D2M4NUlbm!92brc*zXk)@s1x$@ow|G%XN$O# zc8!-ud_5f$;mFHZS}%|8(tCXH2eZ*!P-+X$QRV>$E)_l~ij>gjJ-d&SrF^Be7gLBn z`O+K8Ms*;HlasuBpS~mJ$GJ$E;;U(kno3x2uc?$mPat$s8o>>|6iyCP=&rnnLju{y zH|nZDrthdL%S6=!#gW7-!CZ}#ePSnvO&;wah(w-O0^c|HxC@rSHdqMSOtXkIz z$m*`t&C3$A6NvDA`dC2Icf-g+q<|t+nGBA(6qgpzLW1l_VT1MC2C7r{) zvfldyb4Z*tf9O7kSt;q9Qf8(;p{7zr*WN0i={nBK*wFqMPL7G89M|Te1~XGhHU%?X zdjost{xe*ET>>GL@}rv^5Y!xxe+^1Y3!|1ORn$vezoJyPpyqh|gCD3>KnLlkXDRi7 zBNCTWba+n*tF)1-+VZ3-BdPo()wsYO#}P@TBD(B~R5*2OPRmxzLtJ5;PW&n31yUSl z4xnxkJ_|TzI;oJDvJZobd6tg6*`O21P5Z$$}DB%{%NgsLpQ=Y^OnU z+Lny8B_r)RfL#ZG^JL(XT?ZILD|~T3GVMA58bdY%b86D37)jW50E8QIsyD^hv_WkC zVM|69?gC@i0qi<}Y=B`hXY4wFT?er10RQ!MfWdp2u)f3a|8D_bHtYnw3^8B6gR}P; zOJ7~aBMJY%kE1e%()aM_4%bsZdIQ;TE}*_aW{xuVIFXSmhoVpsL$#mZ#mN%J(ziXJ z5X1j7Wx;G%`@%Rm!OM3UNgwgzT&PqrqiTzqikPL@SVAEQ|1SyQhVleX4pHb1!~cH- z1#&qv7FGo^Bk5~PGhua4!vBv1Gqag`#ZHc*Kly;!3HX>$H7}SMNncS8r4JW;)gzcZLaIQv*IKf+NAn4M4w!*X^7G6U(GiinQU|K|eP%Ru@^kU=8BV;qJA zav1}v*C_OW5!ZEqtnW(jKbxtXVf1~1m@712T*RLsvj`a&$GpBO=^7?%?(x=ArpR?l z*D(11LpfU6@dcT%FvTCb%P9JUbWMo_|5H*QEy}hd!r*Y%t25xlLHz4GhUfkKvLdB+0 zv9<;k8(TAT+4D{aw4HIXwaeruxJNSkP8=r*nN zM-yT11`Sjbdc}0&ymaDpK_@elltxnPdINyiN`;;HvxUwY4OEkk5CIncf(N^z zVrxmL#7z$D*hGH-;P%#)jo<;>Wz*SqZ7d){ z-o^qVY~WbUIKqUVxGuf;GuPF&^LrbA=DM173T>QR{*8?wJDI9>ob8{AZ=T5hqmaKJgJe@pz(Ji!u@8LoV^aN3@UcDLn8IeU}e>)R%?K)$<@g{L{_#PE8`}lj%kCQ>K;)(>^UPM1&)*F0CMBigt%;5tZmC=Rt@JQSqT}T9O zj~PmEd$^NsFYSeQ4z%N;E zf|)*~vJAHtxjrOv?e_3H%>H?Yw=odY>%oOY;K4?4m=MS%oN|EM17s$>wE(id!-VN` zxDX(A-5$)O*NX_^GN!z(Q&W+Y0&I}Jf!lLl!-b%4m>CWa0!@-W1`h(rKKea)5ZK8* z3E=i{#Mz&HT34#*_qwHT=t15Sv*Qmk>1~=q_n35$OQLhYCvkkzH+eo?`Mn78*Cc&R zI!8PREe)1R(i>G3wNjN~6tkt@8b;CYVH^e2W*h}uRZ#offt1Ur8i6eXDPdF%Rk@V1 zJ0+n7?M_L2o#eA;F5T=Iz8VU$XKWpv+jCmBj*jemFHzrEXm@NK9a~4omX*a@a|H9f zwvNsm15Bdh7;e})I`~?UH2{t-Z5+zNZi)Z!B?m(AM+#l zB%R*^!)_mMw~yx}gWW!!>jHNB_&&6P-9ElB1E?9vg}OFwwoR2RZ7#`~0B}x%zAXp< z&u=L7u>t#F;5(k{Z(AI%COA21XN2sGklj1m?wxHXMX}OicD%q_(R{2(GEE92Q(al7 z82?aE?u~(dc2d+%iXv1-zGAqm(5d-~vBkx0adA5-idB0%DQfEcG%^g%PKq){Y*9F2 zCq=ol*7Z2UPKsjNN=8oFNl`l~T4{@mYg=3#X=Qd@K=y864E~2Yer?v+bpg9BP^ThN zOUkYb*mVKLpxJ_*!R)#K#*n?TK|JG)k5jdHYgRTeMtD-*r87ADzD~Act6av_B}vQIP3v{X8mWP8oQAKcPL_1yAwGg@ zf|@A}je?pvW{3RMAj9SdU@A0SRa8q}Ra6t$%xS0%;#Mz%?NL!JsRPUw>lRZv1iB0f z=mhCF4VipJHGuY$#XAC8DyjjrE@pzjNdTCga0N5dP*M%1avbq3DWHS=5?`k-ui{r| zlk^a?%#OU$LvBem=wepGN0d|pYEx2;zg5oC9Q8z9&RF|B8W>Ov_U{$goD0nu*l|vM z%uuNG5zgkS&_y5?(-u)u?FO;<$et25N~)!95et?2(AWWnX>U-Ow}Z+IvzT`B+AxcW zlPIYM+zdYZgpz7W6?aRj32-v2L4Y&uWM7!oKvgcakR;IU>1uj$p1E|+w;M9ux_S8g zQja!fx}ExUob_Dnw{&K^bz8T7dx4uS=5UkKFo$E$#oO+-EW}%0^2+%}J;WPFAwJy9 zd$uTk{-UW1_q6q%iS+bCDqa-DuRGhJxU)?kiufwTore<_7ngdlg@;cB)Kf_4)`5#l zb)hFkdPSsPwRL2>a~=9vgqK8k3df#{^M0VxU5)y=DA+MpT>$%7fmsKwFw4{4O{>(o zo)@!bM8QdqYnI4w(yS($^{6S+t?KnAfB$^1+1Pc;-@lllc>RJPd(#EGJe_R^M)R6L z8>d}EV%M&yeMT&;{TG*zrd?3HEaFG)FwE1@eniFR%OX7~xAnI|r12T<0^nV?OUn?= z8kc5S6X}CCF{|s1NH0KIHE3hjX|I^oyeGeJ%zDJmhFMNMty1UC%N8-K=Ok}d!@OvI z+bw3b(k$nEothi<7|k->y~DfCpErtp|GCId39==szHp8^#IUwkqF}`}4DtiC>)}(} z<(xlZQ?#f-+BG54tMcoI#;(rSBD^TV(>R9SGh>%a;w{tN*CgH$aWPA^Inv#~B5`NS zzG$XhATfUa(wMbP`70Lp85SF}To!krSyZRvnpMbRG0U)+X6aV3Dovjwf6I+ z^DbSHt#&F?d{wRZaW>3XWlMrb^a^`n93|+MhIr)Xm%&9UovqHL)jM2YpiGGGoF5HI zXsU|x1^YCJvoXHkdn1&hGdoFC;(A3jeaRHB2U(2kb4@B+t*hdSuc}Qdi}D5E8Kc+^ zZa9inK`dEMXEH`Hzl<&_1$L5es#Sr_cV%llQ;mAEc*E*_!Od*0VX>9o?6Ly5opc3r ze-{NeQ@|c3p)|r5>@%v5^9A1r)Tmxbzi?(?S8-i(T3|C==xZ%tJ5@1y?rlNM7kpQg z_~;j23+hxYy~zZmR8Y~IeW()H2^LvfqgaIb+HHWXgUoq2U~47$h942si;&5W=x+arLilS2{^+a3ep9CtCuc{(Q)rkRZL2Kb_;CI zj=qVru~RinteX^L>CQe@8`SjN8-kke_jYzskR!RnD5O+UH_!_{65#a0@6yuTLak+r zg@02rPQq}XVlMXucH)}*A#%S3 z-2s*f_XRh{WJ6-BaG%etlTKZ8KPcQ65()RIK1LGt0reI44eTmDveN?FaNj)WY*XQW zM7S?qLGHg1)JemANMX!E?tiQj*zs%bhspg`vM3wN0QpgPXN17Fuz{xM!|ZVL(=dp z8pCcBj7IowHcxqphsACbjP`iL1OlhBj`MW4@&{C{PI01c6U&hHBiQbf`d{BC)y@Lh zSs+d(?l3GPsW|6sqB-m=kcp3OtH4kr#_$4uqc|09T-dM$1JMZiG-o>tgvZ82Gchv4 z&H_1KZN*HJ=Wl0$@;g7;f`PVRApC}%2IaDM!PciYhZuGmRM)0tvuS>#n;lbZ!9Y6= zY6}M1f`ORywd(+QDMsx&KqDiZ>^cDAKsj?0Rg&Lt*8vb%cWWadyABXljH!Gn(^sv` z^56#2I?Pbe^QvmBlvLYwfdBeBfMfv#e#!!11ZkL+s(uxzEm;78pRxcbBUu37ADZCc zUt|FU7zzFMUt|GDjO%q4K%gKCpn>u9k_8Z8M9urcEC7Mc0Pf#s0R(=^0ubDcgl_yr z7C=8@gAu&^`z(Mjg;@ZC`Z^0Bz(Am@EC7ZS|1JxFVi5*#-_8PHdYWVb1SAr=Fbe=O zf5-xmVEVvMSpfVY$pZLlV(Cp5096%a0R*@N@aHT5h7)fH>R)F8JmlL(vH$`Fk?(+}|6LXUw4r4m`7@Q;%PfHRsbsd&sYo!! ze8r2i0g|XG#v{oB2;eBjX!=1OdCC3DL@HaEOG%-ndCK_7q53!!rNcz`)Q6K1#y0hY zC?rV$u?k#IR&o$C#p}LFsQOx!$X4of)Fc5=SC}N~h)|5#oX3$g#Nu^q)=`RiN&OEM zf;!IJg;hb#X!_=8rV_FHOaeF%*d)=Am`dtrl7Q{NUR+-g*vuqph(l?J59~9lk20FR z7pRf7Na~*(*l9+yO$%)9zyGZou$^>-VZl3sntP*mhKY|4>>ELysAe$FIHXXIh|$EK z(gHhnj?-%t3z0-EfUSLWSchz_IBQJ}3u-bb>!N1r!V+X)Ad>(ZrH7ci@SL|snWxbw zJw)nX#HFz-7!EMQAM!~keVuesgrTduR25N7o$V6Xr2g9|8#__O_spai%Orr$l?JsW z0SIaqZ)#%~`JUqXt1zUH;LPON5y8!u>id+mwh(KXQh`C7&Haj51A?2(zf1r(a%ob` z+?k`4UlL4QZZUh|32{Z*sjE9rt|;wIRPt5xSb#GJ;0wux*wm=x06^PiucxQ=`AO#7 zIoWE;^m^JJoter{daUM51P4m2T`ns<(3l;5q zqGF>FDq6-}6_46@-z)QMgY6K6&Hn|@>B3sE5^wvM?bj(t)j+++ex^qf3v5he^8CQJ(xmZ<&IdDSg8b{ZR3Xk*J` z{1WJBYBlAShsPWEUb5-IzB zLH1t5guIO|*`faI_iWGG=s7*=Ml-1%r}I~AAC`_ZY~K|o43q87X`bwt2`668*rlh% znS}Q79oJEYqar$#bAL3O(4mCl^7I66C}w`vk7OtdF+XRVw~BDKjPJN+AFtD)yeW@E zEXbOABPG;DfAS?7#>r?<(KoD6s+4c$20xBU7-V)(ilYGIr}}tQM1S%=6v-xX5k=Q9 zPniVHc(I8#Jnt-P7d?=u0cL4rqfNA#quoRl;Pd#;MOvF!53^4KNHcq#8NuJOi(b#>CUDhgN+7Q@vuTb`{);T1T+)q+5uV@ zZA0qRFPK?VZ%hoOGx?qn%mI!!)~TvY649EZf4DioahdcFM=3+nKXfLaA_6)@x3I*Y zDy6@#Bj{54z?Zboujn~@1vQ;X)}W4l!3{GMD&dSlt`Z#tTG4gyO8?|(wT0_vM2%vz z$sf3(fK~|_8}-w5Z%|p8q@fvJ(F?vdyyCVK!-AW!nx7-ERz-E_9mMjysu$qQ;M*5o z)#KKjODtPK5G`o;i~kC8 z6(rTzZBp~W;I@1k=N#-dseSx{H3N-Z93GmI1Wo+(zLjsIYIG&%X7X>@o56t0H@Ji{ zmt*#G*nSS%ZBkJL%9c;F<)ipAtsRAQ35weAIkZ4hxWYA`Qqf}cs$4-jI6=SxZii2g`wAmwo z!EtXZxQ$s=9pk+6o2`5_TkfV9Qnz)TA#G!XY>bed6t$D0c2d+%iYmq%ADNA(ZI~R= zTPqo-T(IHF|GAT*VJB3?{GO+GGhtoKxb@ZtIQoa*>zUGEHmtp2oE+!nyW=>Dc(afx z_j zSTHlvK4vhpQ@5$9h*@e)MVYYrM{&LVteBv^ahx5Y&_7c+3IVedENAHXnm}eaPn|&K z?D!FXvMh~ry|5FC7-;{+2h5J&$A$ZO!OT2}bQx}az=8Z8o1~O^5FKJCvjg6h3*rz? zPC-Q7RZItr3*=%h_3sK~ra`m-vi^q;>v^dlj^X4ZG4nBw!h*PrNf_(Y^guC(uwMEG z2@TGh2humpf*6p#VFu$@zd-gf4P%MFbf4*IRfK$>6FK{|u2eA%qDwF{h4BM1JH8<2 zE~Y7TZwO~|33Lwl#E{x2(l>cNF*o3Xpph6Dfv!BE!n8D4$}Egcs(R2$RfbW_40vl8 z#XN{XLCws7Z+_USg4)YGj69>N1vZDr2g0aos&XlHnY-A`t*V*KvNOH9oZ0lOZY(Zj zmpw}h8!MSjXK8IZv+S8!S=-2N>h(>%JdYb^)^Ve=ybdAH{PN~H{#aWP4Qor#u(GkR zm|e~->Wx+2Mh(u)Dvmwto6GamJu3>9)~S1LWrMmm*7eFPLAWaBmvH04^5z;me=Q~!r)UY+SOr9ToM!K3se{rJsX?LP@!h@ zpG{m96FrLyG*K<-TQ&m34HK6)0nf9sP7BqvzG33hZ%p(o&20dztK2gy%|sLPt+c!$ zCc3S(LJOf?dU0*@XA?~;xh6W(>*y=BtkZKW$72)-L88XlloR?r{PjWDs026L{7 z1w2qI&W7p5B}0UQUc4g0oFM|6Pt7@EqT5W@MEJ=>!&9~92opR@g}r!-2sF{Nygmye zsCh@2dTpc2bug9Pc+GVOL_oW&L;J6&&TcHQkzA+p1!u9q#=ntWZ8$=OKiZflyIOMQ z@&Io<@Zb5sJAuDF?~uuRkdLkh;c`{pgQUz$iU~~5@8Eq?oOdAN^vVKP4rGFt?~Fnx zot_SrsB5oR#H^=AzFGefuO|+~cs&aJ!(6Q&0fAgjr?)C^R(`G5l;C}OPcbX*NZwKO zdUPTBA4XSy{>g(dRjZ;L*B|f${sq;ShzEfKg4+=nVldN%ObX__(<`Ra%X<(yJ^c@T z-nsF4;z1yz?x;zKi_;5~T|2!8;`9Jn-(gDJF`ZsH9Y~)bF7$a!uULecFpf^ocn~_h zm%O!v4y02Y2s5ldl%tg$Uw{sTUQgd)@}Kb^#o|AxsaP=uy;(4e11S-&NB=P@b~1V} zCvGoLmUkf6UhfVw;@;4IsB%RIl6QOGUAIU7;krHQ`pJPnDDU<@dVyN`w4aQcCG30L z>cVA|45J9ExPht)#!)4tlo>|}?5lAU^5jacCsEn#;S<^)5*MAyw^0=u)Y-C39k z(GkqU=>0gVS)`k}iG?k&#*dA88DiLtRqe*A*bkGR*>+=9?ttTF81NI=Zmhb>cSMYn z1nqc{V%ObgXd%0?s#BOoz_>ph83$oLH>M@#Ndic5*4CFobJ$s+IvwXCp+r2Hq{Xh3LfUCi)JI_rtrT}t8ARf@4l@kSPJ>1mDfxkeY*njMELOlGu4p71 z40pA?E=#z4YfghRb{Z7V4TE%Fr)u$TsjO%8=41I*W17kPI(g$%FtpcX(X%##od&hj zpmrKG!Z7s%?$v{ePEG&6mk0jZr7)Rb( zoa^P$J*JO+2!yiHT*#!4QKpEiuxUROMM~*sp54dEGG-`slu(H8l?!h;8`Z%mPEPXj zeHNFA2XHP@uDHa%MNOqld224C5L@+aSp+xu85KWFp?h>`-@*df&vmI)fy|7zy7EjE zg=CmN_DV1_D!xzbWbpsTATT?jAak)61T$Y?m3}BKVI=(vsxN1mmQFxMpgmW>vc&8J zBaBIZETEaOkN{{WQpSnB9l^|$x0X`E+kw!TU=A=%Zxk}>zT#xp_lSV@GJ|Z5QYFk- zXaHz^pW**UY$!i#|MUyy1;~7IG4oZt2pO2ijD;HM94_SU@z!#>^e*Wfj(gtu1#_6e zL{t2s`>Z8bA)QmkIQl2lRHm3^(JG)B{(oN1h7K@IZ%hoG#K~OLU}hZsreJ2u!bA4X zeJr*+uS+3>;x^3G8x+)%^%jzr7Dg>ms<@ZBenqJsK`mKt0iZ_GS%{C%ETxK=wcv7! zUgs%cl{HdTTb@+qBvpW<8W-3M|0k(bM3-NY>f+dWHP@9HaXOa|GRK}*FNX%RBc7pw z7l)Z+=U}xHSGs3jWsddHu^#W|Upu(ZIoRpWjClITkB{)js~P;^bgojt;fsNx>_~1% zzl4x;uuKJAb2#=Ky&CO@>fxRzqIwHI>gk=J0(GRPdZ=MtG(78v2G8Kg0W};h5?t2; zWIV49Mh3DYdbm$N6BX-F?&*1abd){L9qGwls@N12PkM);qJQ*|+7GocaEX^*?HdF8 zje%=o;I!a79`DPHs4l&Y`<#PaW8je(I4do*A`12f32$g{4?^lduZV@S_yZWm*g`$K zqCgDn69b*2Y1pSa^%^xCJ~svq?-v+2F9seyf`Ojl-lzBrx;JhZ_*mL#LsUE!0|y=* zii&x_1Mo%Bu|F(5*#BJGNDSO4y=U~Jbm?weLGvyjc4v2xC!zVq^p!%KBSJH(yNiJRZ6Uo()bNuf~t_Gc@P2j-(V{ z#FMEGDUS}TGYkv}AVP=6nDx5 zX2SG&2(X=Kitn^3LCuK9A8CV{saV_8<>yb8n6Dcp)DnX_{ zcZ;d?d6!`Uon)T%G-QyVOs7{VpaXOuI|7Qkh{E z$0f&xSsY@F2yQyQPcc}l;yOYfpJ!Hs0B2I-OJSB|u({O29`dzLOh zI0}*XZ2xnu=3hYeqI!7Q!@fuvOcu3xjN2rYO z-c?sZS#^b?RVx&Wa@f|E#8E^x&5PnF#ReMUQHXx|B3+fO%~i?6vOt-Ld@?sxKvhl5 z?3N_J$>BZ$PUfGa0Nkmp<*0H-aPxWnRBv#zH@2uO&aq~9JX4FR$z;)Sg22-aii2WC-_5992Q<`mS% z*c;E-GD*_7U(6->Pb&m>4Ih|U2q6+`0;j(;2=D~oPul{V4_^;}>kw)5Iw`p8Ik=n@ zbNQzEP%F4oe9+gaEGEOZ4(TU^eLDv+>8BBV(2Da^jOG4?Sj z=}+V=0G&)K&Yf4COv*VFl{>5jXF@jOpb@i5XVSr&!@c1%6;EBuEmpFj%^>%NALFUH z#k$VpSRzI~pj!5ZBMie%HtI!Dok>UU4(ok^r^CN6GX3rBFP&aoFN(sY=tWVzD4Iz! z*ldlbzJ(d;*~Y^qLJ}m$BSsTCy*LI{>5N_!Wtga56xEBO_&V@($p45%(Ug_+Gm>@B zolThmW+LhI;i`hW!=FM4J7q>w21AD@`GA|Ot8eM3jcOrIvoSN$;OMv$o$a>~a z;(nxxvBM)C3gaH@Lm~!YiHPK+Sz$FVCufZzTzMGC`p%21FcYZs^QwZ8(%ER-PUYeT zBXH+g$HQD>wGl{4`V6wQ4g@y|Ze4<#fzqGDz-`4+3|pTT*zAt)l?pbO8MY|P&#?J+ ze>Q~_Uwn1X1H>K8Fl>F4)halt{E`H2D-mJ*?V7-5TzHefW`Os5{%x4FyaDJ6Q4BY> zW58|2WBevA2yTWO*M;$@kJ0vrY?ctC!n;LRt_EF21a*?}&NHZkxPCGler5!Afbrcs z0-NE+9e{0Ca4LKe64aHP(~gOXD}?hSvm=>%R2F)ni(NCZ%F!x z!N#|7fgL4_W10WuWiWd^VS9O^-jeYM?B~x)3E@Yt_WxU;i|B!`WIh(RuaL?2X|nv~m5@I!{*HwPs~4 zOz)>T%?HBMD88+$v{5a2hSJRtN@KmK)|ue_9o+vAufhFv;}_f?k5JegO0(4&2&+yT zA5px*YSBSdYfs`Ta)7dr!Rd`{3nhUHH8Aa*W<}_s0 z#Ef?+-WTK@kku#1*YUtb3dpTYt&RIL0(=sscl8SRDN45}EZ&1!-SKQKI&>QkoFs_6 zvf%*_Ot4yL7Nyx5Ah)X1mndBm;Qgr9@dW%Be`}>$zT@^V)P<}?)Vj(5xmA_9Lh+&? zA4ci-mK4Osj!}5Prb!Zazo@&0+m~sOSjdsWy6yz(o|4|{YCh|Gfij1T5OCvf87WM(9JJL^i3){r~ zGXh6sf8B2uR!e=Wljo?mL1D38Jp5Cc*e)*LD-&y?iH!;Jd6eE%L1ClXto(%Hyot35 z@<8<-R(i4Vvw)bFMw%1}wz7k&cXDT&!mF%4N5uhAQ5nLAYKo|9zTtxp)6z<_ zxQ6trCh5hR6Okc?P&w#`=%FEqimr#_3`7{UR@<$}7#AHEMaNGK(v1bCZl$Rrq(A({ z(w%M`d1mSqUT-xhTVD_zCmu&|$4x6c}m-r}spwQTrj!oBsZ$@;S5fRrkcB9d% z&Ko;@bwxH|meJ)sL?d>>iiPQ`SISW8=5VS$$SdK+xp5WbaH@}2Wip%!N9{x|su-87 zQz4AY5Cp}&90l%|qtMTBNvDUEWE2>T;eI3`j+*z&$=>>r4BH7a9)L^3D3G$e9GA5C zS&gH>bAKEUgvjO_X00+AOvMCrh;DjSKyxtF5XdGlaN;2Fy`bi3s$VqaWWtUBwH1wU z5V#uDc$BJ zJ`Mpd3EB#$Il34_Eu`UNFxc0)zz&i z2)KwkP>_SEI_V&e0gw1-fIQJ|>7Z3qx(P{J$7Br1KPqQt#Ef*1U-*`kSI7qCrNo57hPqTo=~7=C?%jdOmAWbm2)%X)Qehh*uoPP*c<{rM5VFCtd?2J-G|cL z-|VVaU~>qV4Ffhvno*7c=UA(P@+G)kx1)TfJK8Cst%hDCV}2c9Q_vl1ngjtmW!t%UJP_F*+awo&)GPK!B6g|>^>0ATS zCk*R301To}XBxz4Umvl3gd_X)Bsfp6(UV|32}WR$o&@Vja3s$O(v#p2W3|3JX@IyZ zxkoltj9a-n)h3!lr`lu$;j+ZO%Tce-*V)YkqxuFUKM*&9b*4c?PH=rqXBtEs6h?wG ze0GVuw;^K)hVrwShXbf1gLR%M=O8XCRn4=Ve>;Ro=a~xdwD_xZCC_b3GX`go-=mNm znyvFp=~W=Qs1y8zAee++1=6cP2#!=~(&dm9y$XcaLWe@=RUo|z^dG$n zH25YPF^kBm{1))r5i96pZk6RCl$J0uz1qVoGWOn;;L5|u^ln}iaU^}`3)qodKv~=R;U9QlQb057QVgK2a5>}RcLX!p zqs?W6M-IRz!R(8na0GRbACS@XKf(gqL%PKpYn75cS_jZ(F=?QuY$-3ZV)Y4TQUD$t z%$(FMq7KyK+GCaU4nrA^`KX&&u{x!9NCCL@N<#-36hF;BDrP8sQhKMHyoEhVDi;!F zi-0Bz^vcbK_Ax)=m?+9f`d<-+nNy}s!OX#7J$t7Zv<2&|43!M8!K7IIf||sVZvkm( zF{vfiDv?Arf3vABK}}}Koe!uDzZoD)WR|twkXqsx6`2eBgjL>vXLGH2t8$~7>5Jge zae+OKD;kv%Hr-q@*n+5=3D@}G&O!FAwZGAyop24!93R_nUB^eWgV_nIZ+$=e)^)Hu zJZ?{zqZ8)g5gt5P!h=@-5-Pcl4kt!!h#1}#5$k;rF*th!5xHY?W{A&ih=`p*h;WTe z9#g{MA|>=K^UmSHI3$?kBj%onSfz-8d5UlyznvbYh%G#5?Qad>&hXS*NH7k~dHk~a zRz<}A=$Ji`8#50M`P>R5Sp5sQb{!p0jZpZYxh*0#pQ$(}Dy~t)!5&p~jZPe*nsHz* zi;9atlshv2rXpJjDzcS~KGV^0NMuYNNh{@b%pIE4(KS9o6^(s!O>~@lrsJjn+=e_y zN9%ANARv4tucK>hbiY8ydC~Fh6dP%f8bY?HI6VDU_2QhglB42?z#FHE*8Vau82zTB z;-ugkatwj+24@RYREA(2SYioirUDiFm)S~(kFBJ7ks2aho~Za_2~^RwzpX4`N?F2! zSOQfv4y}VR^y8C`v;=FQP{#qs5ZErm)}xLEmT<7==%_4#I$HgU?9wCacoL@YUp01P z)SCF;Fm~hEI{w$zp4So9K8BVy#;wu)LTf){ZyhOXe^gSeeVWz|OL#K&o1hgaA-PMS9Bv`w~abw{7!AXL}4+#NL?v!(~Ep837}2| zzsN{f3qkZEKmA@5`YQ}^eXLbPH&RuGtJidWABu2QUWu|UUcIF2`{MT5!JJRIKGu0n zFOsZ)q(HI3&(x7%CM~{IF!MZtY&kJ|iFr~m(~CqDX0APKQ&I_8bB!h0pz+cm|Ls#L zLA&`y9%7}JB$E9K0JP<=pzB)`%={wP2xijMPxznZ{2uo}QAlF6*H@o2Gl5w=Mgy-1rVO8VPv1t44A0NwQz>LB+n-AGhGm(um^3TU$CngH5-xs6(% z%LFq&$0MTXB(8!2xtxw~osx-D8f7^>KQ2{9ccCtlVoLV86==NQpBM)abn&M|`Hg>l+ojH#D&?s3Mjrx@8#z^T!TqQ7$< zbmC>5W2BH*ruPMO?s1)F2fjn6*)jFj7&cA=$g_aZmp?dhdTR{q^?dw+-Wqd`M{Sky z1Ot_i_BUkEp# z6hY$-H-NWak{b|`K<-lE2E1S(H`!Ws60$DbfH#cISKI&whrbYRfX{#A_4Su8MSm)IfH^A?30|fIEH{b=o|8^NMdxRSxn3=Fe z_zeD1F7s_u(i`Chyg|tO3&jm65fX%O16b+B6e{_F+43oBfIucSpjsd^UhnLRX{R$Vg~q2gc%@^ zg&6?I<_j+0UX==B#@G*wn!*ea#AVEIvrb74GeG)AF$1J;gc%@xLuSBtuRvDJ0RGc! z24AFw0wK%*YFDb50fJdE14t+E_=Fk2O0V+F0RJ0d21wuJ?Mavc1Px|D2!AjpsA*8b z4Dgi-Gk~Isn}I5)EQ-v4OJz}UJlZd)g&E+5u7=NaKS0@Kfi27cfi27cu_(n1P$qSg zyPmPewzjhe*4D;IcE&Y6zrSZ6xc2uJMzb^4@Y+`PAh&IfPunx*%)*Ry0EuzALRDU`EfqB4hh!w))?+g&hGD0_RbOo46ophYiD|CH5UcmD&g&zlMs=cGIvG9Dn*Pe z?NG$-{v6;Lqd5`rOu^k}3Qj;l*VN1|^fR{2l|lt)e^+o!6l4=YDP!1lG=#R$P+Dk5 zT1XVk?V2+{=bE0}20CNQe5T?4Uo=$hzq1Go5Uw=LiGr$$lzxk%-`@V*1e$Vm6Z%2+ za|Klsu?Jl{+mm?(t*sR_kuhQ}0}*;qDX45fdhnTo)-d%$qih!_=$OC?di_bk31tJ+ z53H{R)rAEL=C%xW;pE&iBcOi9j!CqZQ;;R5l9M*F4R@wZEHJkyaoo zPAe;*ijEZ+yVkzw=-Bx>T9$TB19`M_>X_R$prSEp&HUBO*@=#wt3DJnF!rnh9siex zP8E%5%hB`8^H`C}bAPyIz6&#qzEf z@Ct>c;ym84k~lmNo`R!@u&}L9pQ< z6#m0JV?(*=@_GgHBN>jE`5j(F9jHe(T%~l5u;KV<8C_n7bPhSycW%Kf#4@nqAfDvp zxO7gb<2^!fI!wmaET9#!j1BFf&l?p*$*InT6lQTBg4toiK`1%Z=Ow5FpF?6D_yP+VGk_+SDv-6%9bDV^a>n5;j#&Q7w6+0>cup z#pwxbIz1Ydf$@V3qacbp@!Yk1pr4ibw{d=ep)=~B5%TkVolpdZq!Wsm3vwWxK1s7`_?u5Frur;Z%(ul) z7jE})T|u9v%EbbQZ_EMEZ-irgmTJhrc+`BWw)?nnG9-q?r7nGzDmMG-lR^UGoI$6R z)hVU$TIiHg7|rXHQu;7qd=mFLG}DI(GiJSpYfbtvVSSjeK1?{s-POBl$v&Cj3Zy=4-@{6K1^8e13aE9G_2CD(guwLsIl4 zSWklWBskIXoIa-WDr2OeV}W!m5D&@Mu|Q~q{IQBU7Kq`S`Lp4(m6FF)f-Q9{P=;q3 z2zhc^#{$7l))@K}Vd!`p8QVb+Qp|VA=7}>EnZ!!R0`UtwFVU8x@VVcd%=P~VVu6Or zvvIS8!P;LUVLNU`gA7YwDM4ukBh2gkyn2hWw-vbZlWNqmNcK~OVGOrL1VfU?^# zP+O5OSpy4#nwj9L0ubtET+AWG`xq793D8D~5CCoz>KIAkQ-YeTg(N^*F)xGZcLX!T z;#)n0$D`~&3g#fGV56vm)Gs`n`CCjt`x#8X##(N!J~jZfS;n&pPS{idCW7l1%nPV< z=U~3Y{YBJ)dVF1Kq~-DJJtP2R1Jcq_^5mxZM`fhxRZ91G$D12NTG`bM?$nIo+O%)Y z-Uj~K$8%P8zb4z}YUs83m_3FcR%S&6oHju~uGzd00ZSB6J&9|Vebdzl(XF*dB4DQq z0_ys2am%pHVUe&-3EB4zEqJcE?UP8D!*f>EjL81n-ek9#t@Y-65wStxwL|vxtvOIf z1Y1)7xULoT8#}L+=qU=w%!`1_7SXP;P`gn9ciUT^Z8NIPU6Fhs+Vv>y#-(|dMZg)G z2il!ecGgttO^AeSS0nzY=1>IeQoH&-YG>IakcOiY)+ix+qO@!M$XnTYrJV@4qIRy< zx_5XEvbRLLLHh<025a-`y%Y7C+OI_R3uB=Fy5cQS_&OUe4JL8?~G6bMW#_=O(n?2I|2O@eGbzJqmO1)9(yH$#|tN9Q|JMyD3D1nXp6%1*827B`H!HF~vHOiyt3GesH&s?9 zB5o~^tumt|Ms>LjSdhZ#?rtkn5LTHm_(5A?wGh2POAJ>*IWZ=Lt2iml1HAH+n{=H_+f})= zArl_f36lKqqaguNnXo~Y+`a&(>*^8UWJ`aH1Gtr{BC~EraP!ObrAFcAhj@#^BBTU& zMzU3gUmYG9CGcb|UCubG`AO5e%K*4lnc(+qO>mRG)+)H2(AO#}-Y82I&=#`j^KMc= zZlzQ7Ig5gvRN`Naafl7my&te;Vsu}WzzyY zNeb68|13l{SQBxFB%uy@wdR!2< zkuIu2jxIm0+_e9X;iR25V`+o_e3B1%=$XG(#%xpz6W^vA zrGDB`Yl!y=?<|nQ{b)5UV#G^fG^6`u9Kw=ONn*RgYCaAqn!>p965d-Mt|A--_4CS2 z`_IM_b~=|Z*sk-e2CVx0Q%HuuhP2;}^3+jx#w4yA>sbA89I>MKBox=PEKT<8yNgO_%_)*P_a(F!?eMA$w zO$h85KR3(#FCRZq4TSCEJ9|V8!v?=meS({|f2D9I`LUU0B`?2GzpBwmuq}?BcBPN< zwnf{&By0?mDu1)BG__5J`O)PkMwMv!aGs)$+$;?7=-lfz!@Wa(p5Wuucb z_(viQ*DKcHc8!R^6)bh0iYx?0p~@zzcjOemn2NOjR3P=$ewR z3wHzbboeQZB7}uS^DVjGD?*9B(NDE|}8!Rt5pIN;-3`^3x!+J$XuL$WCA-y7`SA=wSag3h= z3=q{ZLV8g&RmZT!agM123~s%xlC#>93|ka#Sy(TM8gYXmp98X%#X!;j;OycVt1`@0 z+g*P)V@8A}5WO&-yq-RfY`G8}HXdU&AK!qxN&vUgabXDvZea-sZf*;_)! zd<`)?8e*9A0ox@i905^QI0A7&T`3#^)Insk#M;*g>c+#lb^*SN%M)c@bwX|s;KC6|L0=@%L2NMzsJNcC!qnUT$@?_<5i3 z&V30~3z57x;-)Ox(3e;Qg|Uz<>#VSrhep&8z?GZm-g$5pWXyUWugYlrb~tJ$b5Vo# zFwZ(}+E8@}k`i7;5fIo6TJIFtwEfRPz_ubuM(9loYKgT^E7WY&ZOZboGunLFB$$+Z zX)pYQ9VRDql(oueL|+qtZN);ghBZOWH>OcglOgb){~BaUs(z>nNi?C`2w+>$2(4j3 zV6!7?gLu^Y9%YAYl>p!KE>V@He;pFg3EJ=s>KGLUIiZyT+RqQoj)3NC-VV@a**0oj z27uWLr-jxas*)h^Eh(Ubq&2KlmRF9XYmy!kf`C_gh#8&+rH5!gcTqtd=DyZ4|I0%^ zS4Y$yzOctsFdzw@dIdHO|3<-%(Sm1L$xVVlE`u(DVevb&D?OAqEE@ht!Uicb#qUgk zVI`?;EWq!~28DS$C`?%v2?A%zvdCr~65RZ(-bbM=*aqP&s^u-KUV!tBeJhq#Pf?C( zA+O*^>td!Q+h;W`oo7F~E*}%lay{k;5wJ`FEi<@wU0w`!LUeE2z6jWAhJg0bj}Ty7 zniC>nof6KEIv~N--FG4q7VwAvpV`}C9*Kwz2zRxOUtZcDbC>2=J3(xU zh=cZCi0B+Rr}PVRmNJ?adFxTZ&bK06DL9G8T$dk5pr6rV?uhU`QE*5pI4v#YDA-MS zJ>Bmiz&JORhSQH4;*VPQM8Gx`>=>bf*5xFm8BOMzpgUFy_7*BQCjvx4S8w|vo`dX7 zQE=>0!M41DhoWFt{~4uU%;B$$W^;kkFHU-;2Rn`h#hUbBfr3`^wh}H1b`D;M1f}4z z2zc)(s7zo50?;T_5RE(}CZH7DL8ClV&}v$x=nLy@r}SWtvVlcHXr2`X&j-W?+7ARA z^)uS7M+L`4!3~0d5wHgx1<&Wj2&f;VpY$mOl@Y9qf)lC-M`;7iTOwh<4HAq_%h6C7 zxzg}Mk94798|p3mqe;hOP@Sa--XFG+d$t$I8zgD_0siRxVa_IsS){ zclDjJ`5pa~k)P7Yjb5uGZ{++uy~#$*B07+70lyuwfj2s; zrpr5JOL>|2w@)zB<2^W-xwE^7I#7?^y-IqAgX?2H>gLf2ozgozQQ+1q4IQKtnC2f9 zlK_~M-YKV>-lL>)gO0LAKy$En{$^8Mf|_HJJ0DOReltL$nq{pw9F#alMUS^nSmh0P zHrJZBDmSW`z6c&27ue&tqEQ*h&&UxQjS5BjC}CKTMAdMfqtl7NB=k|j7}wDxgi`b8 z)*fD-d;o<$M-Q)!K1UC)jn1iA7(U^g^2F)qo`fgn$poD0UnKM~VoyuG39fADW5m=^ zy=H;~-TX@L=M*dj%S@FVAO8PxjF=t*=^+s33_7KtPARD806LvWwUv>hUiBP6rxe8Z zK&KPIa+n-iBL{y;a$PfJk1app3z#-6+U={dn2Do{A7E=@g z?%k3?0P+pDyq$O_JqgBw34n=k1 z=Hy5uE0g5z)2b}ra`TNRA|VooVMA}lbu}~mW=IZF;+EZ2TF&-Qb0(yKr^rQrY7SfKuw*?{>E z_E-HekFo$V@sF%nYUP7CjmM|J4o5 zmdDNT@_E6`^XC)g5L!ga!V$%nkmt|}$i_bs{(q-1`%yX}n3 z%fE}L1Le3roR+R3W8p0yEg>JUUAl(B|M%r+Wy|d)%VLUu^nz;|QR$jDjKABXq&Eh+ z8I6LO!T;AKZ03p}3P(gwvRZQig_)Z@n}V4UYc=ef7a%NH7saRq9zh(XeS(_7|35v_ z&SFnXto6E!vi@dJ9fF#{{|^;FZMaPjEozpvUUE^}u_$sD_6h4vEk!lwO{#<@RY8*) z6WC;w(4@d4D0wugn>E+!;@U!X&6;1E%dWW=*H%|-l&whF+}ga9Ev?#X=JJ}kwu}cC z`JgqowuoEHYirAVe2Kpt5esW8cs#diN|_R|u(pUYd0ZqwEF|D>T}$$Z=IXL~T%nj> zTjt{{Yimm)miQI~%hKA~N_NdyGWiQ46uxC(&aIfv(}XwAYX_dWx?uiA#pOR#bpFu5 z<9}1Ju$8DHTM23#a{|WoyN*gkUPssJvOGR7(p`VoQ9Vu_ttBe=D132k#nJJpp&cEa zMp~dYM9vmrD-qxF^H!2JU6oc^poSD)*o)5%p+H5|(8hwbMoY+dW1))b9}5hDkE?E6 zeP#&@1tLU8NBWW_I=WV*nUpa-*KuBYObo$T%B>my6&)dgeYA|n#ZsT^xaxExyY|tT zsG@ovh5t2UH&(3YI{s^GXEz$FR)LPN1ZmZOY3<4q6uuiek%Ctquk?T6jko+yFT7bq zI##WGr^JWwJLUKg`Al(1Pke~!Etivt^L~#`#C*x^fRiU5lJ|RG9lys(Uf%B|@NqT$ zMFt(8c##6fN8YrZcYMD)krFzQZGP{>@6n0C^U#ZYR$heEr6GRW{+Ys+aw6qVPUQ8I z6L~3aj~~WTQswCOAgIub;4DIfCa_skb`dyWes_j#R?iX;i3@*(hh)04R{kTSgHzZ*$Wlw(r*#5}a1J~8k4nvp&$NuQOZ&q~5k@RccgN1akI z4q}kbp`eco!@%K}&Z?=?i0BjZbYfWqg9f=2sx#?u<+)8KmMx?b{|6r$i6qJT^lW{4 zc0mY*o(}8ju$~U<=`iUp5=Nw_!`!yl(_uXw)>$?2S9BLKmd>G2{r^%9 z1w9?syTf{SSg#1_6(PMMgs3YLQe*l2=XyH)bSQ?N4(sW#o(}8junv$w$gZ4Qpm&Gi zEcJBw|EcM480lpgWxrpP4I>i>cK(qFBy7GWz3jRoXopQ-7=`1!UF0MJL7fW55pKOk zVJQRV8_IcGlFJ}{N!au<`h4gO1sn z*<}ct=X-5alw`zdD$9nA*OE}}Bht%2Q_`-6S?M+T3_p=wCTs=0jI>`9(B#C`3TUQ* zJ7Gh)N#jFC97aFWsF_`+(v3$;8EB8}GGViVS=ib|Q*r}rWS0Tv z5Sd!jsDqGxlH%e5x}0(LI|7=y8H& zjofA(d}veZAGG)3p{~JmRzII@rQ{{vIseex1IfKz=G${hUX}Vt-Ui8+Bb|7zbw>p6 z0*$M4n9z`9Y+A6bh=g}Y{nd};ZSO_EG6l5G;M#R@KGX%-eQl=FZd20zE6{Hm*SQOGm;&1N@EAmIqmrxBQEyTj zXiWqhccJrKJ$)ZUz#sVDZ`rtEu1`d?{xo;Pp2-a{4HgjvRq z(gTtuCal$Y}$7-aL zo%`Z=Kyrr;vz8>`WbROa4)OcD%1Um2Ns&4>xkD$Q_FRb#jNIF%D4nfsGX+qisM?)6+3^C{fcrE}{+; zB*7TDL!mWk?nityz#&DqbkHg)F$pKyT9R-gcPPZu(;){ZL7$`2E+zSd($+4hIS~06 z5Y$OJ$Z^q?zU~_mo}#TKcj$(IX3DxIc2GH;#;F&zaDXRs)(r}5lJ_2vI~1_RtdKht zSo=_#`_7RMrcgyj=bTSwlq0@OB~;Cl0f;O1%1 z-)UJ!+r;>O%UQ4A$SxpizOU7s< zUM7`sRWkZAinIlZmyw8-CiG6F^hF^vFPlCN@vi?!yv!lQulhJM3DblmU$n}+4A`&> zTK*@6TT(C22yVWw-&Dfr2#qM8dAU(`OM&&Q&$U|j}Cbz#&5--#AKNlok zp5;IJM^XA+C0>s3`5N$a&y>~K3^F3FIm-%sQlf26u zhJRq*W!P99UzvQyXxqscEi52n2oB)k+~2G#pE3F>fmGK<(oE63=2n8XbLtFwID((a|Re^UKnh#6pukK^Wng z^Z+`G2nG^5i%4Gt$I%C51oTm*K-5`85UZHr>ZLwGSf3!QPY~882M@#I9TR8flcPZfT+q)=xhwI zE$EB;3j&)F#kCO#^$Q1p;zJDk?g47UEpb4f73>rT#M1(skDwyjQxKs|M*e*Z)U&#KL-D-6lB9b%c9JnfEdj|cEJ3!{R