From 5f8b9c1b078b20353bc8bd145b9bc5dd35885e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Landabaso=20D=C3=ADaz?= Date: Wed, 18 Oct 2023 18:45:25 +0200 Subject: [PATCH] Ledger: udpate ledger methods so that they won't require passing outputs. New approach for finalizers: now uses a returned function when setting an input. Better docs --- README.md | 31 +-- src/descriptors.ts | 149 +++++++++-- src/index.ts | 6 +- src/keyExpressions.ts | 43 +++ src/ledger.ts | 304 ++++++++++++++++++++-- src/scriptExpressions.ts | 84 +++++- src/signers.ts | 223 ++++++++++------ test/descriptors.test.ts | 16 +- test/fixtures/bitcoinCore.ts | 260 +++++++++--------- test/fixtures/custom.ts | 50 ++-- test/integration/ledger.ts | 78 +++--- test/integration/miniscript.ts | 22 +- test/integration/standardOutputs.ts | 99 ++++--- test/tools/generateBitcoinCoreFixtures.js | 4 +- 14 files changed, 964 insertions(+), 405 deletions(-) diff --git a/README.md b/README.md index ded450e..a37d011 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ The library can be split into four main parts: The `Output` class is dynamically created by providing a cryptographic secp256k1 engine as shown below: ```javascript -import * as secp256k1 from '@bitcoinerlab/secp256k1'; +import * as ecc from '@bitcoinerlab/secp256k1'; import * as descriptors from '@bitcoinerlab/descriptors'; -const { Output } = descriptors.DescriptorsFactory(secp256k1); +const { Output } = descriptors.DescriptorsFactory(ecc); ``` Once set up, you can obtain an instance for an output, described by a descriptor such as a `wpkh`, as follows: @@ -129,7 +129,7 @@ const recipientOutput = recipientOutput.updatePsbtAsOutput({ psbt, value: 10000 }); ``` -The `finalizePsbtInput()` method completes a PSBT input by adding the unlocking script (either `scriptWitness` or `scriptSig`) that satisfies the input's spending conditions to the PSBT. Bear in mind that both `scriptSig` and `scriptWitness` incorporate signatures. As such, you should complete all necessary signing operations before calling this method. Detailed [explanations on the `finalizePsbtInput` method](#signers-and-finalizers-finalize-psbt-input) can be found in the Signers and Finalizers section. +The `finalizePsbtInput()` method completes a PSBT input by adding the unlocking script (`scriptWitness` or `scriptSig`) that satisfies the output's spending conditions. Bear in mind that both `scriptSig` and `scriptWitness` incorporate signatures. As such, you should complete all necessary signing operations before calling this method. Detailed [explanations on the `finalizePsbtInput` method](#signers-and-finalizers-finalize-psbt-input) can be found in the Signers and Finalizers section. For further information on using the `Output` class, refer to the [comprehensive guides](https://bitcoinerlab.com/guides) that offer explanations and playgrounds to help learn the module. Additionally, a [Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665) provides a focused explanation on the constructor, specifically the `signersPubKeys` parameter, and the usage of `updatePsbtAsInput`, `finalizePsbtInput`, `getAddress`, and `getScriptPubKey`. @@ -138,7 +138,7 @@ For further information on using the `Output` class, refer to the [comprehensive `DescriptorsFactory` provides a convenient `expand()` function that allows you to parse a descriptor without the need to instantiate the `Output` class. This function can be used as follows: ```javascript -const { expand } = descriptors.DescriptorsFactory(secp256k1); +const { expand } = descriptors.DescriptorsFactory(ecc); const result = expand({ descriptor: 'sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))', network: networks.testnet, // One of bitcoinjs-lib `networks` @@ -215,7 +215,7 @@ pkhBIP32(params: { }) ``` -For functions suffixed with *Ledger* (designed to generate descriptors for Ledger Hardware devices), replace `masterNode` with both `ledgerClient` and `ledgerState`. Detailed information on Ledger integration will be provided in subsequent sections. +For functions suffixed with *Ledger* (designed to generate descriptors for Ledger Hardware devices), replace `masterNode` with `ledgerManager`. Detailed information on Ledger integration will be provided in subsequent sections. The `keyExpressions` category includes functions that generate string representations of key expressions for public keys. @@ -241,7 +241,7 @@ function keyExpressionBIP32({ }); ``` -For the `keyExpressionLedger` function, you'd use `ledgerClient` and `ledgerState` instead of `masterNode`. Detailed information on Ledger in subsequent sections. +For the `keyExpressionLedger` function, you'd use `ledgerManager` instead of `masterNode`. Detailed information on Ledger in subsequent sections. Both functions will generate strings that fully define BIP32 keys. For example: `[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*`. Read [Bitcoin Core descriptors documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) to learn more about Key Expressions. @@ -259,16 +259,7 @@ For signing operations, utilize the methods provided by the `signers`: ```javascript // For Ledger -await signers.signLedger({ - ledgerClient, - ledgerState, - psbt, - outputs -}); -// Note: The `outputs` array consists of `Output` instances. It should encompass -// all unspent outputs associated with Ledger-controlled keys, as these are -// essential for signing their linked inputs within the `psbt`. Nevertheless, -// the array may also contain other unrelated unspent outputs. +await signers.signLedger({ psbt, ledgerManager }); // For BIP32 - https://github.com/bitcoinjs/bip32 signers.signBIP32({ psbt, masterNode }); @@ -318,6 +309,7 @@ await ledger.assertLedgerApp({ }); const ledgerClient = new AppClient(transport); +const ledgerManager = { ledgerClient, ledgerState: {}, ecc, network }; ``` Here, `transport` is an instance of a Transport object that allows communication with Ledger devices. You can use any of the transports [provided by Ledger](https://github.com/LedgerHQ/ledger-live#libs---libraries). @@ -326,8 +318,7 @@ To register the policies of non-standard descriptors on the Ledger device, use t ```javascript await ledger.registerLedgerWallet({ - ledgerClient, - ledgerState, + ledgerManager, descriptor: wshDescriptor, policyName: 'BitcoinerLab' }); @@ -335,7 +326,7 @@ await ledger.registerLedgerWallet({ This code will auto-skip the policy registration process if it already exists. Please refer to [Ledger documentation](https://github.com/LedgerHQ/app-bitcoin-new/blob/develop/doc/wallet.md) to learn more about their Wallet Policies registration procedures. -Finally, `ledgerState` is an object used to store information related to Ledger devices. Although Ledger devices themselves are stateless, this object can be used to store information such as xpubs, master fingerprints, and wallet policies. You can pass an initially empty object that will be updated with more information as it is used. The object can be serialized and stored for future use. +Finally, `ledgerManager.ledgerState` is an object used to store information related to Ledger devices. Although Ledger devices themselves are stateless, this object can be used to store information such as xpubs, master fingerprints, and wallet policies. You can pass an initially empty object that will be updated with more information as it is used. The object can be serialized and stored for future use. @@ -357,8 +348,6 @@ For more information, refer to the following resources: The generated documentation will be available in the `docs/` directory. Open the `index.html` file to view the documentation. - Please note that not all the functions have been fully documented yet. However, you can easily understand their usage by reading the source code or by checking the integration tests or playgrounds. - ## Authors and Contributors The project was initially developed and is currently maintained by [Jose-Luis Landabaso](https://github.com/landabaso). Contributions and help from other developers are welcome. diff --git a/src/descriptors.ts b/src/descriptors.ts index 56ef797..01f0485 100644 --- a/src/descriptors.ts +++ b/src/descriptors.ts @@ -157,9 +157,11 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { }; /** - * Parses and analyzies a descriptor expression and destructures it into {@link Expansion |its elemental parts}. + * Parses and analyzies a descriptor expression and destructures it into + * {@link Expansion |its elemental parts}. * - * @throws {Error} Throws an error if the descriptor cannot be parsed or does not conform to the expected format. + * @throws {Error} Throws an error if the descriptor cannot be parsed or does + * not conform to the expected format. */ function expand(params: { /** @@ -526,6 +528,12 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { }); } + /** + * The `Output` class is the central component for managing descriptors. + * It facilitates the creation of outputs to receive funds and enables the + * signing and finalization of PSBTs (Partially Signed Bitcoin Transactions) + * for spending UTXOs (Unspent Transaction Outputs). + */ class Output { readonly #payment: Payment; readonly #preimages: Preimage[] = []; @@ -588,7 +596,17 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { preimages?: Preimage[]; /** - * An array of the public keys used for signing the transaction when spending the output associated with this descriptor. This parameter is only used if the descriptor object is being used to finalize a transaction. It is necessary to specify the spending path when working with miniscript-based expressions that have multiple spending paths. Set this parameter to an array containing the public keys involved in the desired spending path. Leave it `undefined` if you only need to generate the `scriptPubKey` or `address` for a descriptor, or if all the public keys involved in the descriptor will sign the transaction. In the latter case, the satisfier will automatically choose the most optimal spending path (if more than one is available). + * An array of the public keys used for signing the transaction when + * spending the output associated with this descriptor. This parameter is + * only used if the descriptor object is being used to finalize a + * transaction. It is necessary to specify the spending path when working + * with miniscript-based expressions that have multiple spending paths. + * Set this parameter to an array containing the public keys involved in + * the desired spending path. Leave it `undefined` if you only need to + * generate the `scriptPubKey` or `address` for a descriptor, or if all + * the public keys involved in the descriptor will sign the transaction. + * In the latter case, the satisfier will automatically choose the most + * optimal spending path (if more than one is available). */ signersPubKeys?: Buffer[]; }) { @@ -695,28 +713,50 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { return { nLockTime, nSequence }; } else return undefined; } + /** + * Creates and returns an instance of bitcoinjs-lib + * [`Payment`](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts)'s interface with the `scriptPubKey` of this `Output`. + */ getPayment(): Payment { return this.#payment; } /** - * Returns the Bitcoin Address + * Returns the Bitcoin Address of this `Output`. */ getAddress(): string { if (!this.#payment.address) throw new Error(`Error: could extract an address from the payment`); return this.#payment.address; } + /** + * Returns this `Output`'s scriptPubKey. + */ getScriptPubKey(): Buffer { if (!this.#payment.output) throw new Error(`Error: could extract output.script from the payment`); return this.#payment.output; } /** - * Returns the compiled script satisfaction - * @param {PartialSig[]} signatures An array of signatures using this format: `interface PartialSig { pubkey: Buffer; signature: Buffer; }` - * @returns {Buffer} + * Returns the compiled Script Satisfaction if this `Output` was created + * using a miniscript-based descriptor. + * + * The Satisfaction is the unlocking script that fulfills + * (satisfies) this `Output` and it is derived using the Safisfier algorithm + * [described here](https://bitcoin.sipa.be/miniscript/). + * + * Important: As mentioned above, note that this function only applies to + * miniscript descriptors. */ - getScriptSatisfaction(signatures: PartialSig[]): Buffer { + getScriptSatisfaction( + /** + * An array with all the signatures needed to + * build the Satisfaction of this miniscript-based `Output`. + * + * `signatures` must be passed using this format (pairs of `pubKey/signature`): + * `interface PartialSig { pubkey: Buffer; signature: Buffer; }` + */ + signatures: PartialSig[] + ): Buffer { const miniscript = this.#miniscript; const expandedMiniscript = this.#expandedMiniscript; const expansionMap = this.#expansionMap; @@ -751,21 +791,41 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { throw new Error(`Error: could not produce a valid satisfaction`); return scriptSatisfaction; } + /** + * Gets the nSequence required to fulfill this `Output`. + */ getSequence(): number | undefined { return this.#getTimeConstraints()?.nSequence; } + /** + * Gets the nLockTime required to fulfill this `Output`. + */ getLockTime(): number | undefined { return this.#getTimeConstraints()?.nLockTime; } + /** + * Gets the witnessScript required to fulfill this `Output`. Only applies to + * Segwit outputs. + */ getWitnessScript(): Buffer | undefined { return this.#witnessScript; } + /** + * Gets the redeemScript required to fullfill this `Output`. Only applies to + * SH outputs: sh(wpkh), sh(wsh), sh(lockingScript). + */ getRedeemScript(): Buffer | undefined { return this.#redeemScript; } + /** + * Gets the bitcoinjs-lib [`network`](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/networks.ts) used to create this `Output`. + */ getNetwork(): Network { return this.#network; } + /** + * Whether this `Output` is Segwit. + */ isSegwit(): boolean | undefined { return this.#isSegwit; } @@ -780,7 +840,8 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { value?: number; vout: number; }) { - return this.updatePsbtAsInput(params); + this.updatePsbtAsInput(params); + return params.psbt.data.inputs.length - 1; } /** @@ -800,7 +861,12 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { * * When unsure, always use `txHex`, and skip `txId` and `value` for safety. * - * @returns The index of the added input. + * @returns A finalizer function to be used after signing the `psbt`. + * This function ensures that this input is properly finalized. + * The finalizer has this signature: + * + * `( { psbt, validate = true } : { psbt: Psbt; validate: boolean | undefined } ) => void` + * */ updatePsbtAsInput({ psbt, @@ -814,7 +880,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { txId?: string; value?: number; vout: number; - }): number { + }) { if (txHex === undefined) { console.warn(`Warning: missing txHex may allow fee attacks`); } @@ -825,7 +891,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { `Error: could not determine whether this is a segwit descriptor` ); } - return updatePsbt({ + const index = updatePsbt({ psbt, vout, ...(txHex !== undefined ? { txHex } : {}), @@ -839,6 +905,15 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { witnessScript: this.getWitnessScript(), redeemScript: this.getRedeemScript() }); + const finalizer = ({ + psbt, + validate = true + }: { + psbt: Psbt; + /** @default true */ + validate?: boolean | undefined; + }) => this.finalizePsbtInput({ index, psbt, validate }); + return finalizer; } /** @@ -890,6 +965,37 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { ); } } + + /** + * Finalizes a PSBT input by adding the necessary unlocking script that satisfies this `Output`'s + * spending conditions. + * + * 🔴 IMPORTANT 🔴 + * It is STRONGLY RECOMMENDED to use the finalizer function returned by + * {@link _Internal_.Output.updatePsbtAsInput | `updatePsbtAsInput`} instead + * of calling this method directly. + * This approach eliminates the need to manage the `Output` instance and the + * input's index, simplifying the process. + * + * The `finalizePsbtInput` method completes a PSBT input by adding the + * unlocking script (`scriptWitness` or `scriptSig`) that satisfies + * this `Output`'s spending conditions. Bear in mind that both + * `scriptSig` and `scriptWitness` incorporate signatures. As such, you + * should complete all necessary signing operations before calling this + * method. + * + * For each unspent output from a previous transaction that you're + * referencing in a `psbt` as an input to be spent, apply this method as + * follows: `output.finalizePsbtInput({ index, psbt })`. + * + * It's essential to specify the exact position (or `index`) of the input in + * the `psbt` that references this unspent `Output`. This `index` should + * align with the value returned by the `updatePsbtAsInput` method. + * Note: + * The `index` corresponds to the position of the input in the `psbt`. + * To get this index, right after calling `updatePsbtAsInput()`, use: + * `index = psbt.data.inputs.length - 1`. + */ finalizePsbtInput({ index, psbt, @@ -897,6 +1003,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { }: { index: number; psbt: Psbt; + /** @default true */ validate?: boolean | undefined; }): void { if ( @@ -928,6 +1035,10 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { ); } } + /** + * Decomposes the descriptor used to form this `Output` into its elemental + * parts. See {@link ExpansionMap ExpansionMap} for a detailed explanation. + */ expand() { return { ...(this.#expandedExpression !== undefined @@ -967,14 +1078,22 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) { } } - return { Descriptor, Output, parseKeyExpression, expand, ECPair, BIP32 }; + return { + // deprecated TAG must also be below so it is exported to descriptors.d.ts + /** @deprecated */ Descriptor, + Output, + parseKeyExpression, + expand, + ECPair, + BIP32 + }; } -/** @hidden */ +/** @hidden @deprecated */ type DescriptorConstructor = ReturnType< typeof DescriptorsFactory >['Descriptor']; -/** @hidden */ +/** @hidden @deprecated */ type DescriptorInstance = InstanceType; export { DescriptorInstance, DescriptorConstructor }; diff --git a/src/index.ts b/src/index.ts index 36d0759..1616604 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import * as signers from './signers'; export { signers }; /** + * @hidden @deprecated * To finalize the `psbt`, you can either call the method * `output.finalizePsbtInput({ index, psbt })` on each descriptor, passing as * arguments the `psbt` and its input `index`, or call this helper function: @@ -76,7 +77,8 @@ import { getLedgerMasterFingerPrint, getLedgerXpub, registerLedgerWallet, - assertLedgerApp + assertLedgerApp, + LedgerManager } from './ledger'; export const ledger = { getLedgerMasterFingerPrint, @@ -85,4 +87,4 @@ export const ledger = { assertLedgerApp }; -export type { LedgerState }; +export type { LedgerState, LedgerManager }; diff --git a/src/keyExpressions.ts b/src/keyExpressions.ts index 7089903..aa0bcdc 100644 --- a/src/keyExpressions.ts +++ b/src/keyExpressions.ts @@ -7,6 +7,7 @@ import type { BIP32API, BIP32Interface } from 'bip32'; import type { KeyInfo } from './types'; import { LedgerState, + LedgerManager, getLedgerMasterFingerPrint, getLedgerXpub } from './ledger'; @@ -54,6 +55,7 @@ export function parseKeyExpression({ network = networks.bitcoin }: { keyExpression: string; + /** @default networks.bitcoin */ network?: Network; /** * Indicates if this key expression belongs to a a SegWit output. When set, @@ -218,7 +220,21 @@ function assertChangeIndexKeyPath({ * * @returns {string} - The formed key expression for the Ledger device. */ +export async function keyExpressionLedger({ + ledgerManager, + originPath, + keyPath, + change, + index +}: { + ledgerManager: LedgerManager; + originPath: string; + change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) + index?: number | undefined | '*'; + keyPath?: string | undefined; //In the case of the Ledger, keyPath must be /<1;0>/number +}): Promise; +/** @deprecated @hidden */ export async function keyExpressionLedger({ ledgerClient, ledgerState, @@ -233,7 +249,30 @@ export async function keyExpressionLedger({ change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) index?: number | undefined | '*'; keyPath?: string | undefined; //In the case of the Ledger, keyPath must be /<1;0>/number +}): Promise; +/** @hidden */ +export async function keyExpressionLedger({ + ledgerClient, + ledgerState, + ledgerManager, + originPath, + keyPath, + change, + index +}: { + ledgerClient?: unknown; + ledgerState?: LedgerState; + ledgerManager?: LedgerManager; + originPath: string; + change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) + index?: number | undefined | '*'; + keyPath?: string | undefined; //In the case of the Ledger, keyPath must be /<1;0>/number }) { + if (ledgerManager && (ledgerClient || ledgerState)) + throw new Error(`ledgerClient and ledgerState have been deprecated`); + if (ledgerManager) ({ ledgerClient, ledgerState } = ledgerManager); + if (!ledgerClient || !ledgerState) + throw new Error(`Could not retrieve ledgerClient or ledgerState`); assertChangeIndexKeyPath({ change, index, keyPath }); const masterFingerprint = await getLedgerMasterFingerPrint({ @@ -269,6 +308,10 @@ export function keyExpressionBIP32({ change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) index?: number | undefined | '*'; keyPath?: string | undefined; //In the case of the Ledger, keyPath must be /<1;0>/number + /** + * Compute an xpub or xprv + * @default true + */ isPublic?: boolean; }) { assertChangeIndexKeyPath({ change, index, keyPath }); diff --git a/src/ledger.ts b/src/ledger.ts index 355bbca..3f0a5cd 100644 --- a/src/ledger.ts +++ b/src/ledger.ts @@ -24,9 +24,14 @@ * All the conditions above are checked in function ledgerPolicyFromOutput. */ -import type { DescriptorInstance, OutputInstance } from './descriptors'; -import { Network, networks } from 'bitcoinjs-lib'; +import { + DescriptorInstance, + OutputInstance, + DescriptorsFactory +} from './descriptors'; +import { Network, networks, Psbt, Transaction } from 'bitcoinjs-lib'; import { reOriginPath } from './re'; +import type { TinySecp256k1Interface } from './types'; /** * Dynamically imports the 'ledger-bitcoin' module and, if provided, checks if `ledgerClient` is an instance of `AppClient`. @@ -164,19 +169,55 @@ export type LedgerPolicy = { policyId?: Buffer; policyHmac?: Buffer; }; +/** + * Ledger devices operate in a state-less manner. Therefore, policy information + * needs to be maintained in a separate data structure, `ledgerState`. For optimization, + * `ledgerState` also stores cached xpubs and the masterFingerprint. + */ export type LedgerState = { masterFingerprint?: Buffer; policies?: LedgerPolicy[]; xpubs?: { [key: string]: string }; }; +export type LedgerManager = { + ledgerClient: unknown; + ledgerState: LedgerState; + ecc: TinySecp256k1Interface; + network: Network; +}; + +/** Retrieves the masterFingerPrint of a Ledger device */ +export async function getLedgerMasterFingerPrint({ + ledgerManager +}: { + ledgerManager: LedgerManager; +}): Promise; + +/** @deprecated @hidden */ export async function getLedgerMasterFingerPrint({ ledgerClient, ledgerState }: { ledgerClient: unknown; ledgerState: LedgerState; +}): Promise; + +/** @hidden */ +export async function getLedgerMasterFingerPrint({ + ledgerClient, + ledgerState, + ledgerManager +}: { + ledgerClient?: unknown; + ledgerState?: LedgerState; + ledgerManager?: LedgerManager; }): Promise { + if (ledgerManager && (ledgerClient || ledgerState)) + throw new Error(`ledgerClient and ledgerState have been deprecated`); + if (ledgerManager) ({ ledgerClient, ledgerState } = ledgerManager); + if (!ledgerClient || !ledgerState) + throw new Error(`Could not retrieve ledgerClient or ledgerState`); const { AppClient } = (await importAndValidateLedgerBitcoin( ledgerClient )) as typeof import('ledger-bitcoin'); @@ -192,6 +233,17 @@ export async function getLedgerMasterFingerPrint({ } return masterFingerprint; } + +/** Retrieves the xpub of a certain originPath of a Ledger device */ +export async function getLedgerXpub({ + originPath, + ledgerManager +}: { + originPath: string; + ledgerManager: LedgerManager; +}): Promise; + +/** @deprecated @hidden */ export async function getLedgerXpub({ originPath, ledgerClient, @@ -200,7 +252,25 @@ export async function getLedgerXpub({ originPath: string; ledgerClient: unknown; ledgerState: LedgerState; +}): Promise; + +/** @hidden */ +export async function getLedgerXpub({ + originPath, + ledgerClient, + ledgerState, + ledgerManager +}: { + originPath: string; + ledgerClient?: unknown; + ledgerState?: LedgerState; + ledgerManager?: LedgerManager; }): Promise { + if (ledgerManager && (ledgerClient || ledgerState)) + throw new Error(`ledgerClient and ledgerState have been deprecated`); + if (ledgerManager) ({ ledgerClient, ledgerState } = ledgerManager); + if (!ledgerClient || !ledgerState) + throw new Error(`Could not retrieve ledgerClient or ledgerState`); const { AppClient } = (await importAndValidateLedgerBitcoin( ledgerClient )) as typeof import('ledger-bitcoin'); @@ -222,6 +292,173 @@ export async function getLedgerXpub({ return xpub; } +/** + * Checks whether there is a policy in ledgerState that the ledger + * could use to sign this psbt input. + * + * It found return the policy, otherwise, return undefined + * + * All considerations in the header of this file are applied + */ +export async function ledgerPolicyFromPsbtInput({ + ledgerManager, + psbt, + index +}: { + ledgerManager: LedgerManager; + psbt: Psbt; + index: number; +}) { + const { ledgerState, ledgerClient, ecc, network } = ledgerManager; + + const { Output } = DescriptorsFactory(ecc); + const input = psbt.data.inputs[index]; + if (!input) throw new Error(`Input numer ${index} not set.`); + let scriptPubKey: Buffer | undefined; + if (input.nonWitnessUtxo) { + const vout = psbt.txInputs[index]?.index; + if (vout === undefined) + throw new Error( + `Could not extract vout from nonWitnessUtxo for input ${index}.` + ); + scriptPubKey = Transaction.fromBuffer(input.nonWitnessUtxo).outs[vout] + ?.script; + } else if (input.witnessUtxo) { + scriptPubKey = input.witnessUtxo.script; + } + if (!scriptPubKey) + throw new Error(`Could not retrieve scriptPubKey for input ${index}.`); + + const bip32Derivations = input.bip32Derivation; + if (!bip32Derivations || !bip32Derivations.length) + throw new Error(`Input ${index} does not contain bip32 derivations.`); + + const ledgerMasterFingerprint = await getLedgerMasterFingerPrint({ + ledgerManager + }); + for (const bip32Derivation of bip32Derivations) { + //get the keyRoot and keyPath. If it matches one of our policies then + //we are still not sure this is the policy that must be used yet + //So we must use the template and the keyRoot of each policy and compute the + //scriptPubKey: + if ( + Buffer.compare( + bip32Derivation.masterFingerprint, + ledgerMasterFingerprint + ) === 0 + ) { + // Match /m followed by n consecutive hardened levels and then 2 consecutive unhardened levels: + const match = bip32Derivation.path.match(/m((\/\d+['hH])*)(\/\d+\/\d+)?/); + const originPath = match ? match[1] : undefined; //n consecutive hardened levels + const keyPath = match ? match[3] : undefined; //2 unhardened levels or undefined + + if (originPath && keyPath) { + const [, strChange, strIndex] = keyPath.split('/'); + if (!strChange || !strIndex) + throw new Error(`keyPath ${keyPath} incorrectly extracted`); + const change = parseInt(strChange, 10); + const index = parseInt(strIndex, 10); + + const coinType = network === networks.bitcoin ? 0 : 1; + + //standard policy candidate. This policy will be added to the pool + //of policies below and check if it produces the correct scriptPubKey + let standardPolicy; + if (change === 0 || change === 1) { + const standardTemplate = originPath.match( + new RegExp(`^/44'/${coinType}'/(\\d+)'$`) + ) + ? 'pkh(@0/**)' + : originPath.match(new RegExp(`^/84'/${coinType}'/(\\d+)'$`)) + ? 'wpkh(@0/**)' + : originPath.match(new RegExp(`^/49'/${coinType}'/(\\d+)'$`)) + ? 'sh(wpkh(@0/**))' + : originPath.match(new RegExp(`^/86'/${coinType}'/(\\d+)'$`)) + ? 'tr(@0/**)' + : undefined; + if (standardTemplate) { + const xpub = await getLedgerXpub({ + originPath, + ledgerClient, + ledgerState + }); + standardPolicy = { + ledgerTemplate: standardTemplate, + keyRoots: [ + `[${ledgerMasterFingerprint.toString( + 'hex' + )}${originPath}]${xpub}` + ] + }; + } + } + + const policies = [...(ledgerState.policies || [])]; + if (standardPolicy) policies.push(standardPolicy); + + for (const policy of policies) { + //Build the descriptor from the ledgerTemplate + keyRoots + //then get the scriptPubKey + let descriptor: string | undefined = policy.ledgerTemplate; + // Replace change (making sure the value in the change level for the + // template of the policy meets the change in bip32Derivation): + descriptor = descriptor.replace(/\/\*\*/g, `/<0;1>/*`); + const regExpMN = new RegExp(`/<(\\d+);(\\d+)>`, 'g'); + let matchMN; + while (descriptor && (matchMN = regExpMN.exec(descriptor)) !== null) { + const [M, N] = [ + parseInt(matchMN[1]!, 10), + parseInt(matchMN[2]!, 10) + ]; + if (M === change || N === change) + descriptor = descriptor.replace(`/<${M};${N}>`, `/${change}`); + else descriptor = undefined; + } + if (descriptor) { + // Replace index: + descriptor = descriptor.replace(/\/\*/g, `/${index}`); + // Replace origin in reverse order to prevent + // misreplacements, e.g., @10 being mistaken for @1 and leaving a 0. + for (let i = policy.keyRoots.length - 1; i >= 0; i--) { + const keyRoot = policy.keyRoots[i]; + if (!keyRoot) + throw new Error(`keyRoot ${keyRoot} invalidly extracted.`); + const match = keyRoot.match(/\[([^]+)\]/); + const keyRootOrigin = match && match[1]; + if (keyRootOrigin) { + const [, ...arrKeyRootOriginPath] = keyRootOrigin.split('/'); + const keyRootOriginPath = '/' + arrKeyRootOriginPath.join('/'); + //We check all origins to be the same even if they do not + //belong to the ledger (read the header in this file) + if (descriptor && keyRootOriginPath === originPath) + descriptor = descriptor.replace( + new RegExp(`@${i}`, 'g'), + keyRoot + ); + else descriptor = undefined; + } else descriptor = undefined; + } + + //verify the scriptPubKey from the input vs. the one obtained from + //the policy after having filled in the keyPath in the template + if (descriptor) { + const policyScriptPubKey = new Output({ + descriptor, + network + }).getScriptPubKey(); + + if (Buffer.compare(policyScriptPubKey, scriptPubKey) === 0) { + return policy; + } + } + } + } + } + } + } + return; +} + /** * Given an output, it extracts its descriptor and converts it to a Ledger * Wallet Policy, that is, its keyRoots and template. @@ -339,22 +576,29 @@ export async function ledgerPolicyFromOutput({ } /** - * It registers a policy based on the descriptor retrieved from of an `output`. - * It stores the policy in `ledgerState`. + * Registers a policy based on a provided descriptor. + * + * This function will: + * 1. Store the policy in `ledgerState` inside the `ledgerManager`. + * 2. Avoid re-registering if the policy was previously registered. + * 3. Skip registration if the policy is considered "standard". * - * If the policy was already registered, it does not register it. - * If the policy is standard, it does not register it. + * It's important to understand the nature of the Ledger Policy being registered: + * - While a descriptor might point to a specific output index of a particular change address, + * the corresponding Ledger Policy abstracts this and represents potential outputs for + * all addresses (both external and internal). + * - This means that the registered Ledger Policy is a generalized version of the descriptor, + * not assuming specific values for the keyPath. * */ export async function registerLedgerWallet({ - output, - ledgerClient, - ledgerState, + descriptor, + ledgerManager, policyName }: { - output: OutputInstance; - ledgerClient: unknown; - ledgerState: LedgerState; + descriptor: string; + ledgerManager: LedgerManager; + /** The Name we want to assign to this specific policy */ policyName: string; }): Promise; @@ -380,27 +624,45 @@ export async function registerLedgerWallet({ * @hidden **/ export async function registerLedgerWallet({ - output, descriptor, ledgerClient, ledgerState, + ledgerManager, policyName }: { - output?: OutputInstance; - descriptor?: DescriptorInstance; - ledgerClient: unknown; - ledgerState: LedgerState; + descriptor: DescriptorInstance | string; + ledgerClient?: unknown; + ledgerState?: LedgerState; + ledgerManager?: LedgerManager; policyName: string; }) { - if (descriptor && output) - throw new Error(`descriptor param has been deprecated`); - output = descriptor || output; - if (!output) throw new Error(`output not provided`); + if (typeof descriptor !== 'string' && ledgerManager) + throw new Error(`Invalid usage: descriptor must be a string`); + if (ledgerManager && (ledgerClient || ledgerState)) + throw new Error( + `Invalid usage: either ledgerManager or ledgerClient + ledgerState` + ); + if (ledgerManager) ({ ledgerClient, ledgerState } = ledgerManager); + if (!ledgerClient) throw new Error(`ledgerManager not provided`); + if (!ledgerState) throw new Error(`ledgerManager not provided`); const { WalletPolicy, AppClient } = (await importAndValidateLedgerBitcoin( ledgerClient )) as typeof import('ledger-bitcoin'); if (!(ledgerClient instanceof AppClient)) throw new Error(`Error: pass a valid ledgerClient`); + + let output: OutputInstance; + if (typeof descriptor === 'string') { + if (!ledgerManager) throw new Error(`ledgerManager not provided`); + const { Output } = DescriptorsFactory(ledgerManager.ecc); + output = new Output({ + descriptor, + ...(descriptor.includes('*') ? { index: 0 } : {}), //if ranged set any index + network: ledgerManager.network + }); + } else output = descriptor; + if (await ledgerPolicyFromStandard({ output, ledgerClient, ledgerState })) + return; const result = await ledgerPolicyFromOutput({ output, ledgerClient, diff --git a/src/scriptExpressions.ts b/src/scriptExpressions.ts index 7f427aa..3d85d82 100644 --- a/src/scriptExpressions.ts +++ b/src/scriptExpressions.ts @@ -1,5 +1,5 @@ import { networks, Network } from 'bitcoinjs-lib'; -import type { LedgerState } from './ledger'; +import type { LedgerState, LedgerManager } from './ledger'; import { keyExpressionBIP32, keyExpressionLedger } from './keyExpressions'; import type { BIP32Interface } from 'bip32'; @@ -17,7 +17,20 @@ function standardExpressionsBIP32Maker( purpose: number, scriptTemplate: string ) { - function standardKeyExpressionBIP32({ + /** + * Computes the standard descriptor based on given parameters. + * + * You can define the output location either by: + * - Providing the full `keyPath` (e.g., "/0/2"). + * OR + * - Specifying the `change` and `index` values separately (e.g., `{change:0, index:2}`). + * + * For ranged indexing, the `index` can be set as a wildcard '*'. For example: + * - `keyPath="/0/*"` + * OR + * - `{change:0, index:'*'}`. + */ + function standardScriptExpressionBIP32({ masterNode, network = networks.bitcoin, keyPath, @@ -27,11 +40,16 @@ function standardExpressionsBIP32Maker( isPublic = true }: { masterNode: BIP32Interface; + /** @default networks.bitcoin */ network?: Network; account: number; change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) index?: number | undefined | '*'; keyPath?: string; + /** + * Compute an xpub or xprv + * @default true + */ isPublic?: boolean; }) { const originPath = `/${purpose}'/${ @@ -49,7 +67,7 @@ function standardExpressionsBIP32Maker( return scriptTemplate.replace('KEYEXPRESSION', keyExpression); } - return standardKeyExpressionBIP32; + return standardScriptExpressionBIP32; } export const pkhBIP32 = standardExpressionsBIP32Maker(44, 'pkh(KEYEXPRESSION)'); @@ -66,7 +84,34 @@ function standardExpressionsLedgerMaker( purpose: number, scriptTemplate: string ) { - async function standardKeyExpressionLedger({ + /** + * Computes the standard descriptor based on given parameters. + * + * You can define the output location either by: + * - Providing the full `keyPath` (e.g., "/0/2"). + * OR + * - Specifying the `change` and `index` values separately (e.g., `{change:0, index:2}`). + * + * For ranged indexing, the `index` can be set as a wildcard '*'. For example: + * - `keyPath="/0/*"` + * OR + * - `{change:0, index:'*'}`. + */ + async function standardScriptExpressionLedger({ + ledgerManager, + account, + keyPath, + change, + index + }: { + ledgerManager: LedgerManager; + account: number; + keyPath?: string; + change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) + index?: number | undefined | '*'; + }): Promise; + /** @deprecated @hidden */ + async function standardScriptExpressionLedger({ ledgerClient, ledgerState, network = networks.bitcoin, @@ -77,12 +122,41 @@ function standardExpressionsLedgerMaker( }: { ledgerClient: unknown; ledgerState: LedgerState; + /** @default networks.bitcoin */ + network?: Network; + account: number; + keyPath?: string; + change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) + index?: number | undefined | '*'; + }): Promise; + /** @hidden */ + async function standardScriptExpressionLedger({ + ledgerClient, + ledgerState, + ledgerManager, + network = networks.bitcoin, + account, + keyPath, + change, + index + }: { + ledgerClient?: unknown; + ledgerState?: LedgerState; + ledgerManager?: LedgerManager; + /** @default networks.bitcoin */ network?: Network; account: number; keyPath?: string; change?: number | undefined; //0 -> external (reveive), 1 -> internal (change) index?: number | undefined | '*'; }) { + if (ledgerManager && (ledgerClient || ledgerState)) + throw new Error(`ledgerClient and ledgerState have been deprecated`); + if (ledgerManager && network) + throw new Error(`ledgerManager already includes the network object`); + if (ledgerManager) ({ ledgerClient, ledgerState, network } = ledgerManager); + if (!ledgerClient || !ledgerState) + throw new Error(`Could not retrieve ledgerClient or ledgerState`); const originPath = `/${purpose}'/${ network === networks.bitcoin ? 0 : 1 }'/${account}'`; @@ -98,7 +172,7 @@ function standardExpressionsLedgerMaker( return scriptTemplate.replace('KEYEXPRESSION', keyExpression); } - return standardKeyExpressionLedger; + return standardScriptExpressionLedger; } export const pkhLedger = standardExpressionsLedgerMaker( diff --git a/src/signers.ts b/src/signers.ts index f403c4c..b995483 100644 --- a/src/signers.ts +++ b/src/signers.ts @@ -4,7 +4,7 @@ import type { Psbt } from 'bitcoinjs-lib'; import type { ECPairInterface } from 'ecpair'; import type { BIP32Interface } from 'bip32'; -import type { DescriptorInstance, OutputInstance } from './descriptors'; +import type { DescriptorInstance } from './descriptors'; import { importAndValidateLedgerBitcoin, comparePolicies, @@ -12,7 +12,9 @@ import { ledgerPolicyFromState, ledgerPolicyFromStandard, ledgerPolicyFromOutput, - LedgerState + LedgerState, + LedgerManager, + ledgerPolicyFromPsbtInput } from './ledger'; type DefaultDescriptorTemplate = | 'pkh(@0/**)' @@ -77,18 +79,20 @@ const ledgerSignaturesForInputIndex = ( signature: partialSignature.signature })); +/** + * Signs an input of the `psbt` where the keys are controlled by a Ledger + * device. + * + * The function will throw an error if it's unable to sign the input. + */ export async function signInputLedger({ psbt, index, - output, - ledgerClient, - ledgerState + ledgerManager }: { psbt: Psbt; index: number; - output: OutputInstance; - ledgerClient: unknown; - ledgerState: LedgerState; + ledgerManager: LedgerManager; }): Promise; /** @@ -117,22 +121,30 @@ export async function signInputLedger({ export async function signInputLedger({ psbt, index, - output, descriptor, ledgerClient, - ledgerState + ledgerState, + ledgerManager }: { psbt: Psbt; index: number; - output?: OutputInstance; descriptor?: DescriptorInstance; - ledgerClient: unknown; - ledgerState: LedgerState; + ledgerClient?: unknown; + ledgerState?: LedgerState; + ledgerManager?: LedgerManager; }): Promise { - if (descriptor && output) - throw new Error(`descriptor param has been deprecated`); - output = descriptor || output; - if (!output) throw new Error(`output not provided`); + if (!descriptor && !ledgerManager) + throw new Error(`ledgerManager not provided`); + if (descriptor && ledgerManager) + throw new Error(`Invalid usage: don't pass descriptor`); + if (ledgerManager && (ledgerClient || ledgerState)) + throw new Error( + `Invalid usage: either ledgerManager or ledgerClient + ledgerState` + ); + const output = descriptor; + if (ledgerManager) ({ ledgerClient, ledgerState } = ledgerManager); + if (!ledgerClient) throw new Error(`ledgerManager not provided`); + if (!ledgerState) throw new Error(`ledgerManager not provided`); const { PsbtV2, DefaultWalletPolicy, WalletPolicy, AppClient } = (await importAndValidateLedgerBitcoin( ledgerClient @@ -140,48 +152,83 @@ export async function signInputLedger({ if (!(ledgerClient instanceof AppClient)) throw new Error(`Error: pass a valid ledgerClient`); - const result = await ledgerPolicyFromOutput({ - output, - ledgerClient, - ledgerState - }); - if (!result) throw new Error(`Error: output does not have a ledger input`); - const { ledgerTemplate, keyRoots } = result; - let ledgerSignatures; - const standardPolicy = await ledgerPolicyFromStandard({ - output, - ledgerClient, - ledgerState - }); - if (standardPolicy) { - ledgerSignatures = await ledgerClient.signPsbt( - new PsbtV2().fromBitcoinJS(psbt), - new DefaultWalletPolicy( - ledgerTemplate as DefaultDescriptorTemplate, - keyRoots[0]! - ), - null - ); + if (ledgerManager) { + const policy = await ledgerPolicyFromPsbtInput({ + psbt, + index, + ledgerManager + }); + if (!policy) + throw new Error(`Error: the ledger cannot sign this pstb input`); + if (policy.policyName && policy.policyHmac && policy.policyId) { + //non-standard policy + const walletPolicy = new WalletPolicy( + policy.policyName, + policy.ledgerTemplate, + policy.keyRoots + ); + + ledgerSignatures = await ledgerClient.signPsbt( + new PsbtV2().fromBitcoinJS(psbt), + walletPolicy, + policy.policyHmac + ); + } else { + //standard policy + ledgerSignatures = await ledgerClient.signPsbt( + new PsbtV2().fromBitcoinJS(psbt), + new DefaultWalletPolicy( + policy.ledgerTemplate as DefaultDescriptorTemplate, + policy.keyRoots[0]! + ), + null + ); + } } else { - const policy = await ledgerPolicyFromState({ + if (!output) throw new Error(`outputs not provided`); + const result = await ledgerPolicyFromOutput({ output, ledgerClient, ledgerState }); - if (!policy || !policy.policyName || !policy.policyHmac) - throw new Error(`Error: the descriptor's policy is not registered`); - const walletPolicy = new WalletPolicy( - policy.policyName, - ledgerTemplate, - keyRoots - ); + if (!result) throw new Error(`Error: output does not have a ledger input`); + const { ledgerTemplate, keyRoots } = result; - ledgerSignatures = await ledgerClient.signPsbt( - new PsbtV2().fromBitcoinJS(psbt), - walletPolicy, - policy.policyHmac - ); + const standardPolicy = await ledgerPolicyFromStandard({ + output, + ledgerClient, + ledgerState + }); + if (standardPolicy) { + ledgerSignatures = await ledgerClient.signPsbt( + new PsbtV2().fromBitcoinJS(psbt), + new DefaultWalletPolicy( + ledgerTemplate as DefaultDescriptorTemplate, + keyRoots[0]! + ), + null + ); + } else { + const policy = await ledgerPolicyFromState({ + output, + ledgerClient, + ledgerState + }); + if (!policy || !policy.policyName || !policy.policyHmac) + throw new Error(`Error: the descriptor's policy is not registered`); + const walletPolicy = new WalletPolicy( + policy.policyName, + ledgerTemplate, + keyRoots + ); + + ledgerSignatures = await ledgerClient.signPsbt( + new PsbtV2().fromBitcoinJS(psbt), + walletPolicy, + policy.policyHmac + ); + } } //Add the signatures to the Psbt object using PartialSig format: @@ -191,21 +238,21 @@ export async function signInputLedger({ } /** - * signLedger is able to sign several inputs of the same wallet policy since it - * it clusters together wallet policy types before signing. + * Signs the inputs of the `psbt` where the keys are controlled by a Ledger + * device. * - * It throws if it cannot sign any input. + * `signLedger` can sign multiple inputs of the same wallet policy in a single + * pass by grouping inputs by their wallet policy type before the signing + * process. + * + * The function will throw an error if it's unable to sign any input. */ export async function signLedger({ psbt, - outputs, - ledgerClient, - ledgerState + ledgerManager }: { psbt: Psbt; - outputs: OutputInstance[]; - ledgerClient: unknown; - ledgerState: LedgerState; + ledgerManager: LedgerManager; }): Promise; /** @@ -231,37 +278,57 @@ export async function signLedger({ */ export async function signLedger({ psbt, - outputs, descriptors, ledgerClient, - ledgerState + ledgerState, + ledgerManager }: { psbt: Psbt; - outputs?: OutputInstance[]; descriptors?: DescriptorInstance[]; - ledgerClient: unknown; - ledgerState: LedgerState; + ledgerClient?: unknown; + ledgerState?: LedgerState; + ledgerManager?: LedgerManager; }): Promise { - if (descriptors && outputs) - throw new Error(`descriptors param has been deprecated`); - outputs = descriptors || outputs; - if (!outputs) throw new Error(`outputs not provided`); + if (!descriptors && !ledgerManager) + throw new Error(`ledgerManager not provided`); + if (descriptors && ledgerManager) + throw new Error(`Invalid usage: don't pass descriptors`); + if (ledgerManager && (ledgerClient || ledgerState)) + throw new Error( + `Invalid usage: either ledgerManager or ledgerClient + ledgerState` + ); + const outputs = descriptors; + if (ledgerManager) ({ ledgerClient, ledgerState } = ledgerManager); + if (!ledgerClient) throw new Error(`ledgerManager not provided`); + if (!ledgerState) throw new Error(`ledgerManager not provided`); const { PsbtV2, DefaultWalletPolicy, WalletPolicy, AppClient } = (await importAndValidateLedgerBitcoin( ledgerClient )) as typeof import('ledger-bitcoin'); if (!(ledgerClient instanceof AppClient)) throw new Error(`Error: pass a valid ledgerClient`); + const ledgerPolicies = []; - for (const output of outputs) { - const policy = - (await ledgerPolicyFromState({ output, ledgerClient, ledgerState })) || - (await ledgerPolicyFromStandard({ output, ledgerClient, ledgerState })); - if (policy) ledgerPolicies.push(policy); + if (ledgerManager) + for (let index = 0; index < psbt.data.inputs.length; index++) { + const policy = await ledgerPolicyFromPsbtInput({ + psbt, + index, + ledgerManager + }); + if (policy) ledgerPolicies.push(policy); + } + else { + if (!outputs) throw new Error(`outputs not provided`); + for (const output of outputs) { + const policy = + (await ledgerPolicyFromState({ output, ledgerClient, ledgerState })) || + (await ledgerPolicyFromStandard({ output, ledgerClient, ledgerState })); + if (policy) ledgerPolicies.push(policy); + } + if (ledgerPolicies.length === 0) + throw new Error(`Error: there are no inputs which could be signed`); } - if (ledgerPolicies.length === 0) - throw new Error(`Error: there are no inputs which could be signed`); - //cluster unique LedgerPolicies const uniquePolicies: LedgerPolicy[] = []; for (const policy of ledgerPolicies) { diff --git a/test/descriptors.test.ts b/test/descriptors.test.ts index 6861074..0b4e6ca 100644 --- a/test/descriptors.test.ts +++ b/test/descriptors.test.ts @@ -10,7 +10,7 @@ import { DescriptorsFactory } from '../dist'; import { fixtures as customFixtures } from './fixtures/custom'; import { fixtures as bitcoinCoreFixtures } from './fixtures/bitcoinCore'; import * as ecc from '@bitcoinerlab/secp256k1'; -const { Descriptor, expand } = DescriptorsFactory(ecc); +const { Output, expand } = DescriptorsFactory(ecc); function partialDeepEqual(obj) { if (typeof obj === 'object' && obj !== null && obj.constructor === Object) { @@ -29,12 +29,12 @@ for (const fixtures of [customFixtures, bitcoinCoreFixtures]) { fixtures === customFixtures ? 'custom fixtures' : 'Bitcoin Core fixtures' }`, () => { for (const fixture of fixtures.valid) { - test(`Parse valid ${fixture.expression}`, () => { - const descriptor = new Descriptor(fixture); + test(`Parse valid ${fixture.descriptor}`, () => { + const descriptor = new Output(fixture); let expansion; expect(() => { expansion = expand({ - expression: fixture.expression, + descriptor: fixture.descriptor, network: fixture.network, allowMiniscriptInP2SH: fixture.allowMiniscriptInP2SH }); @@ -45,7 +45,7 @@ for (const fixtures of [customFixtures, bitcoinCoreFixtures]) { } if (!fixture.script && !fixture.address) - throw new Error(`Error: pass a valid test for ${fixture.expression}`); + throw new Error(`Error: pass a valid test for ${fixture.descriptor}`); if (fixture.script) { expect(descriptor.getScriptPubKey().toString('hex')).toEqual( fixture.script @@ -61,14 +61,14 @@ for (const fixtures of [customFixtures, bitcoinCoreFixtures]) { fixtures === customFixtures ? 'custom fixtures' : 'Bitcoin Core fixtures' }`, () => { for (const fixture of fixtures.invalid) { - test(`Parse invalid ${fixture.expression}`, () => { + test(`Parse invalid ${fixture.descriptor}`, () => { if (typeof fixture.throw !== 'string') { expect(() => { - new Descriptor(fixture); + new Output(fixture); }).toThrow(); } else { expect(() => { - new Descriptor(fixture); + new Output(fixture); }).toThrow(fixture.throw); } }); diff --git a/test/fixtures/bitcoinCore.ts b/test/fixtures/bitcoinCore.ts index c5cdb7d..136c84a 100644 --- a/test/fixtures/bitcoinCore.ts +++ b/test/fixtures/bitcoinCore.ts @@ -6,270 +6,270 @@ export const fixtures = { valid: [ { - expression: 'pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', + descriptor: 'pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', script: '2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac', checksumRequired: false }, { - expression: + descriptor: 'pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)', script: '2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac', checksumRequired: false }, { - expression: + descriptor: "pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", script: '76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac', checksumRequired: false }, { - expression: + descriptor: "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", script: '76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac', checksumRequired: false }, { - expression: 'wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', + descriptor: 'wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', script: '00149a1c78a507689f6f54b847ad1cef1e614ee23f1e', checksumRequired: false }, { - expression: + descriptor: 'wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)', script: '00149a1c78a507689f6f54b847ad1cef1e614ee23f1e', checksumRequired: false }, { - expression: + descriptor: 'sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', script: 'a91484ab21b1b2fd065d4504ff693d832434b6108d7b87', checksumRequired: false }, { - expression: + descriptor: 'sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', script: 'a91484ab21b1b2fd065d4504ff693d832434b6108d7b87', checksumRequired: false }, { - expression: 'pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)', + descriptor: 'pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)', script: '4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac', checksumRequired: false }, { - expression: + descriptor: 'pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)', script: '4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac', checksumRequired: false }, { - expression: 'pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)', + descriptor: 'pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)', script: '76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac', checksumRequired: false }, { - expression: + descriptor: 'pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)', script: '76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac', checksumRequired: false }, { - expression: + descriptor: 'sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', script: 'a9141857af51a5e516552b3086430fd8ce55f7c1a52487', checksumRequired: false }, { - expression: + descriptor: 'sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', script: 'a9141857af51a5e516552b3086430fd8ce55f7c1a52487', checksumRequired: false }, { - expression: + descriptor: 'sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', script: 'a9141a31ad23bf49c247dd531a623c2ef57da3c400c587', checksumRequired: false }, { - expression: + descriptor: 'sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', script: 'a9141a31ad23bf49c247dd531a623c2ef57da3c400c587', checksumRequired: false }, { - expression: + descriptor: 'wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', script: '00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa', checksumRequired: false }, { - expression: + descriptor: 'wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', script: '00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa', checksumRequired: false }, { - expression: + descriptor: 'wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', script: '0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b', checksumRequired: false }, { - expression: + descriptor: 'wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', script: '0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))', script: 'a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))', script: 'a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))', script: 'a914b61b92e2ca21bac1e72a3ab859a742982bea960a87', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))', script: 'a914b61b92e2ca21bac1e72a3ab859a742982bea960a87', checksumRequired: false }, { - expression: + descriptor: 'pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)', script: '210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac', checksumRequired: false }, { - expression: + descriptor: 'pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)', script: '210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac', checksumRequired: false }, { - expression: + descriptor: "pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", script: '76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac', checksumRequired: false }, { - expression: + descriptor: "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", script: '0014326b2249e3a25d5dc60935f044ee835d090ba859', index: 0, checksumRequired: false }, { - expression: + descriptor: "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", script: '0014326b2249e3a25d5dc60935f044ee835d090ba859', index: 0, checksumRequired: false }, { - expression: + descriptor: "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", script: '0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7', index: 1, checksumRequired: false }, { - expression: + descriptor: "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", script: '0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7', index: 1, checksumRequired: false }, { - expression: + descriptor: "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", script: '00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27', index: 2, checksumRequired: false }, { - expression: + descriptor: "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", script: '00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27', index: 2, checksumRequired: false }, { - expression: + descriptor: "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", script: 'a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87', index: 0, checksumRequired: false }, { - expression: + descriptor: "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", script: 'a914bed59fc0024fae941d6e20a3b44a109ae740129287', index: 1, checksumRequired: false }, { - expression: + descriptor: "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", script: 'a9148483aa1116eb9c05c482a72bada4b1db24af654387', index: 2, checksumRequired: false }, { - expression: + descriptor: 'wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', script: '0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00', checksumRequired: false }, { - expression: + descriptor: 'wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', script: '0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00', checksumRequired: false }, { - expression: + descriptor: "pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", script: '76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac', checksumRequired: false }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", script: 'a91445a9a622a8b0a1269944be477640eedc447bbd8487', checksumRequired: false }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", script: 'a91445a9a622a8b0a1269944be477640eedc447bbd8487', checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", script: '0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f', @@ -277,7 +277,7 @@ export const fixtures = { checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", script: '002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203', @@ -285,7 +285,7 @@ export const fixtures = { checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", script: '0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c', @@ -293,87 +293,87 @@ export const fixtures = { checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))', script: 'a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))', script: 'a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87', checksumRequired: false }, { - expression: + descriptor: 'wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))', script: '0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac', checksumRequired: false }, { - expression: + descriptor: 'wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))', script: '0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))', script: 'a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))', script: 'a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87', checksumRequired: false }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", script: 'a91445a9a622a8b0a1269944be477640eedc447bbd8487' }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", script: 'a91445a9a622a8b0a1269944be477640eedc447bbd8487' }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", script: 'a91445a9a622a8b0a1269944be477640eedc447bbd8487', checksumRequired: false }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", script: 'a91445a9a622a8b0a1269944be477640eedc447bbd8487', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))', script: '0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))', script: '0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))', script: 'a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487', checksumRequired: false }, { - expression: + descriptor: 'sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))', script: 'a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487', checksumRequired: false @@ -381,342 +381,342 @@ export const fixtures = { ], invalid: [ { - expression: + descriptor: 'sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))', checksumRequired: false }, { - expression: + descriptor: 'sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))', checksumRequired: false }, { - expression: + descriptor: "pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", checksumRequired: false }, { - expression: + descriptor: "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", checksumRequired: false }, { - expression: + descriptor: "pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", checksumRequired: false }, { - expression: + descriptor: "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", checksumRequired: false }, { - expression: 'wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)', + descriptor: 'wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)', checksumRequired: false }, { - expression: + descriptor: 'wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)', checksumRequired: false }, { - expression: + descriptor: 'wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))', checksumRequired: false }, { - expression: + descriptor: 'wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))', checksumRequired: false }, { - expression: + descriptor: 'sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))', checksumRequired: false }, { - expression: + descriptor: 'sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))', checksumRequired: false }, { - expression: + descriptor: 'pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)', checksumRequired: false }, { - expression: + descriptor: 'pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)', checksumRequired: false }, { - expression: + descriptor: 'pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)', checksumRequired: false }, { - expression: + descriptor: 'pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)', checksumRequired: false }, { - expression: + descriptor: 'sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))', checksumRequired: false }, { - expression: + descriptor: 'sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))', checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", checksumRequired: false }, { - expression: + descriptor: 'sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', checksumRequired: false }, { - expression: + descriptor: 'sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', checksumRequired: false }, { - expression: 'sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', + descriptor: 'sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', checksumRequired: false }, { - expression: + descriptor: 'sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)', checksumRequired: false }, { - expression: 'wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', + descriptor: 'wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', checksumRequired: false }, { - expression: + descriptor: 'wsh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)', checksumRequired: false }, { - expression: + descriptor: 'wsh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', checksumRequired: false }, { - expression: + descriptor: 'wsh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', checksumRequired: false }, { - expression: + descriptor: 'wsh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))', checksumRequired: false }, { - expression: + descriptor: 'sh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))', checksumRequired: false }, { - expression: + descriptor: 'sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))', checksumRequired: false }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5" }, { - expression: + descriptor: "sh(multi(3,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy" }, { - expression: + descriptor: "sh(multi(3,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggssrxfy" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjq09x4t" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))##ggssrxfy" }, { - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))##tjq09x4t" }, { - expression: '', + descriptor: '', checksumRequired: false }, { - expression: 'addr(asdf)', + descriptor: 'addr(asdf)', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))#abcdef12' }, { - expression: + descriptor: 'wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))#abcdef12' }, { - expression: + descriptor: 'sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))', checksumRequired: false }, { - expression: + descriptor: 'sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))', checksumRequired: false }, { - expression: '', + descriptor: '', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_b(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_b(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_v(vc:andor(v:pk_k(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_v(vc:andor(v:pk_k(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(or_i(older(1),pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(or_i(older(1),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_b(or_b(pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),s:pk(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))', checksumRequired: false }, { - expression: + descriptor: 'wsh(and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))', checksumRequired: false } diff --git a/test/fixtures/custom.ts b/test/fixtures/custom.ts index c6358bf..ab5bbbc 100644 --- a/test/fixtures/custom.ts +++ b/test/fixtures/custom.ts @@ -27,7 +27,7 @@ import { networks } from 'bitcoinjs-lib'; export const fixtures = { valid: [ { - expression: + descriptor: 'sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', address: '345X16vrwhSrbV4hp1AM5wqLh8s2kj6di4', note: 'bdk-cli -n bitcoin wallet -d "sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))" get_new_address', @@ -44,7 +44,7 @@ export const fixtures = { }, { - expression: + descriptor: 'sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))', address: '345X16vrwhSrbV4hp1AM5wqLh8s2kj6di4', note: 'using the pubkey instead of thw WIF of the test above; bdk-cli -n bitcoin wallet -d "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))" get_new_address', @@ -60,7 +60,7 @@ export const fixtures = { } }, { - expression: 'pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', + descriptor: 'pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', script: '2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac', checksumRequired: false, @@ -75,7 +75,7 @@ export const fixtures = { } }, { - expression: + descriptor: "pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", script: '76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac', checksumRequired: false, @@ -93,13 +93,13 @@ export const fixtures = { }, { network: networks.testnet, - expression: 'addr(tb1q7a6n3dadstfjpp6p56nxklxac6efz0lyy0rgss)', + descriptor: 'addr(tb1q7a6n3dadstfjpp6p56nxklxac6efz0lyy0rgss)', checksumRequired: false, address: 'tb1q7a6n3dadstfjpp6p56nxklxac6efz0lyy0rgss' }, { network: networks.testnet, - expression: + descriptor: "wpkh([de41e56d/84'/1'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/0/*)", checksumRequired: false, index: 23, @@ -120,7 +120,7 @@ export const fixtures = { }, { network: networks.testnet, - expression: + descriptor: "wpkh([de41e56d/84'/1'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/0/*)#lj5cryhp", index: 23, //bdk-cli -n testnet wallet -d "wpkh([de41e56d/84'/1'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/0/23)" get_new_address @@ -141,7 +141,7 @@ export const fixtures = { { network: networks.bitcoin, note: 'This is a wif address', - expression: + descriptor: "wpkh([de41e56d/84'/1'/0']KynD8ZKdViVo5W82oyxvE18BbG6nZPVQ8Td8hYbwU94RmyUALUik)", checksumRequired: false, //bdk-cli -n bitcoin wallet -d "wpkh([de41e56d/84'/1'/0']KynD8ZKdViVo5W82oyxvE18BbG6nZPVQ8Td8hYbwU94RmyUALUik)" get_new_address @@ -160,7 +160,7 @@ export const fixtures = { }, { network: networks.bitcoin, - expression: + descriptor: "pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/3/4/*)", checksumRequired: false, index: 11, @@ -181,7 +181,7 @@ export const fixtures = { }, { network: networks.regtest, - expression: + descriptor: "sh(wpkh([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))", checksumRequired: false, index: 11, @@ -203,7 +203,7 @@ export const fixtures = { }, { network: networks.testnet, - expression: + descriptor: "sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))", checksumRequired: false, index: 10, @@ -229,7 +229,7 @@ export const fixtures = { }, { network: networks.testnet, - expression: + descriptor: "sh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*)))", checksumRequired: false, allowMiniscriptInP2SH: true, @@ -256,7 +256,7 @@ export const fixtures = { }, { network: networks.testnet, - expression: + descriptor: "wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*)))", checksumRequired: false, index: 10, @@ -282,7 +282,7 @@ export const fixtures = { }, { network: networks.testnet, - expression: + descriptor: "wpkh([de41e56d/84'/1'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/0/*)#lj5cryhp", index: 23, note: 'it still works even if passing a checksum when not required', @@ -305,7 +305,7 @@ export const fixtures = { { network: networks.bitcoin, note: 'Bitcoin Core test - https://github.com/bitcoin/bitcoin/blob/b5c88a547996776dbdc2e101bae9b67ac639fd02/src/test/descriptor_tests.cpp#L413', - expression: + descriptor: 'wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', checksumRequired: false, script: @@ -328,7 +328,7 @@ export const fixtures = { }, { note: 'Bitcoin Core test - https://github.com/bitcoin/bitcoin/blob/b5c88a547996776dbdc2e101bae9b67ac639fd02/src/test/descriptor_tests.cpp#L442', - expression: + descriptor: 'wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))', checksumRequired: false, script: @@ -423,7 +423,7 @@ export const fixtures = { }, { note: 'Bitcoin Core test - https://github.com/bitcoin/bitcoin/blob/b5c88a547996776dbdc2e101bae9b67ac639fd02/src/test/descriptor_tests.cpp#L442', - expression: + descriptor: 'wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))', checksumRequired: false, script: @@ -518,7 +518,7 @@ export const fixtures = { }, { note: 'https://github.com/bitcoin/bitcoin/blob/392dc68e37be9fc7adb32496b13d9b50262e317d/src/test/descriptor_tests.cpp#L454', - expression: + descriptor: "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", script: 'a91445a9a622a8b0a1269944be477640eedc447bbd8487', expansion: { @@ -544,7 +544,7 @@ export const fixtures = { { note: 'This is a wif address, note however that KynD8ZKdViVo5W82oyxvE18BbG6nZPVQ8Td8hYbwU94RmyUALUik corresponds to mainnet', network: networks.testnet, - expression: + descriptor: "wpkh([de41e56d/84'/1'/0']KynD8ZKdViVo5W82oyxvE18BbG6nZPVQ8Td8hYbwU94RmyUALUik)", checksumRequired: false, //bdk-cli should throw here, but it does not :-/ @@ -555,7 +555,7 @@ export const fixtures = { { note: 'it fails when not passing a checksum if required', network: networks.testnet, - expression: + descriptor: "wpkh([de41e56d/84'/1'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/0/*)", index: 23, checksumRequired: true, @@ -565,7 +565,7 @@ export const fixtures = { { note: 'using a upub (which does not make sense using descriptors anymore; only xpub and tpub are supported)', network: networks.testnet, - expression: + descriptor: "wpkh([de41e56d/84'/1'/0']upubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/0/*)", checksumRequired: false, index: 23, @@ -574,25 +574,25 @@ export const fixtures = { }, { note: 'https://github.com/bitcoin/bitcoin/blob/392dc68e37be9fc7adb32496b13d9b50262e317d/src/test/descriptor_tests.cpp#L445', - expression: 'sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', + descriptor: 'sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', checksumRequired: false }, { note: 'https://github.com/bitcoin/bitcoin/blob/392dc68e37be9fc7adb32496b13d9b50262e317d/src/test/descriptor_tests.cpp#L447', - expression: 'wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', + descriptor: 'wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)', checksumRequired: false, throw: 'Error: Miniscript @0 is not sane' }, { note: 'https://github.com/bitcoin/bitcoin/blob/392dc68e37be9fc7adb32496b13d9b50262e317d/src/test/descriptor_tests.cpp#L448', - expression: + descriptor: 'wsh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))', checksumRequired: false, throw: 'Error: Miniscript wpkh(@0) is not sane' }, { note: 'https://github.com/bitcoin/bitcoin/blob/392dc68e37be9fc7adb32496b13d9b50262e317d/src/test/descriptor_tests.cpp#L449', - expression: + descriptor: 'wsh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))', checksumRequired: false, throw: 'Error: Miniscript sh(pk(@0)) is not sane' diff --git a/test/integration/ledger.ts b/test/integration/ledger.ts index 5efd3f3..398598b 100644 --- a/test/integration/ledger.ts +++ b/test/integration/ledger.ts @@ -72,21 +72,18 @@ const SOFT_MNEMONIC = import * as ecc from '@bitcoinerlab/secp256k1'; import { - finalizePsbt, signers, keyExpressionBIP32, keyExpressionLedger, scriptExpressions, DescriptorsFactory, - DescriptorInstance, - ledger, - LedgerState + ledger } from '../../dist/'; const { signLedger, signBIP32 } = signers; const { pkhLedger } = scriptExpressions; const { registerLedgerWallet, assertLedgerApp } = ledger; import { AppClient } from 'ledger-bitcoin'; -const { Descriptor, BIP32 } = DescriptorsFactory(ecc); +const { Output, BIP32 } = DescriptorsFactory(ecc); import { compilePolicy } from '@bitcoinerlab/miniscript'; @@ -103,9 +100,8 @@ if (!issane) throw new Error(`Error: miniscript not sane`); let txHex: string; let txId: string; let vout: number; -let inputIndex: number; -//In this array, we will keep track of the descriptors of each input: -const psbtInputDescriptors: DescriptorInstance[] = []; +//In this array, we will keep track the previous output of each input: +const finalizers = []; (async () => { let transport; @@ -122,13 +118,18 @@ const psbtInputDescriptors: DescriptorInstance[] = []; }); const ledgerClient = new AppClient(transport); - //The Ledger is stateless. We keep state externally (keeps track of masterFingerprint, xpubs, wallet policies, ...) - const ledgerState: LedgerState = {}; + const ledgerManager = { + ledgerClient, + ledgerState: {}, + ecc, + network: NETWORK + }; + const ledgerState = ledgerManager.ledgerState; //Let's create the utxos. First create a descriptor expression using a Ledger. - //pkhExternalExpression will be something like this: + //pkhExternalDescriptor will be something like this: //pkh([1597be92/44'/1'/0']tpubDCxfn3TkomFUmqNzKq5AEDS6VHA7RupajLi38JkahFrNeX3oBGp2C7SVWi5a1kr69M8GpeqnGkgGLdja5m5Xbe7E87PEwR5kM2PWKcSZMoE/0/0) - const pkhExternalExpression: string = await pkhLedger({ + const pkhExternalDescriptor: string = await pkhLedger({ ledgerClient, ledgerState, network: NETWORK, @@ -136,24 +137,22 @@ const psbtInputDescriptors: DescriptorInstance[] = []; change: 0, index: 0 }); - const pkhExternalDescriptor = new Descriptor({ + const pkhExternalOutput = new Output({ network: NETWORK, - expression: pkhExternalExpression + descriptor: pkhExternalDescriptor }); //Fund this utxo. regtestUtils communicates with the regtest node manager on port 8080. ({ txId, vout } = await regtestUtils.faucet( - pkhExternalDescriptor.getAddress(), + pkhExternalOutput.getAddress(), UTXO_VALUE )); //Retrieve the tx from the mempool: txHex = (await regtestUtils.fetch(txId)).txHex; //Now add an input to the psbt. updatePsbt would also update timelock if needed (not in this case). - inputIndex = pkhExternalDescriptor.updatePsbt({ psbt, txHex, vout }); - //Save the descriptor for later, indexed by its psbt input number. - psbtInputDescriptors[inputIndex] = pkhExternalDescriptor; + finalizers.push(pkhExternalOutput.updatePsbtAsInput({ psbt, txHex, vout })); //Repeat the same for another pkh change address: - const pkhChangeExpression = await pkhLedger({ + const pkhChangeDescriptor = await pkhLedger({ ledgerClient, ledgerState, network: NETWORK, @@ -161,17 +160,16 @@ const psbtInputDescriptors: DescriptorInstance[] = []; change: 1, index: 0 }); - const pkhChangeDescriptor = new Descriptor({ + const pkhChangeOutput = new Output({ network: NETWORK, - expression: pkhChangeExpression + descriptor: pkhChangeDescriptor }); ({ txId, vout } = await regtestUtils.faucet( - pkhChangeDescriptor.getAddress(), + pkhChangeOutput.getAddress(), UTXO_VALUE )); txHex = (await regtestUtils.fetch(txId)).txHex; - inputIndex = pkhChangeDescriptor.updatePsbt({ psbt, txHex, vout }); - psbtInputDescriptors[inputIndex] = pkhChangeDescriptor; + finalizers.push(pkhChangeOutput.updatePsbtAsInput({ psbt, txHex, vout })); //Here we create the BIP32 software wallet that will be used to co-sign the 3rd utxo of this test: const masterNode = BIP32.fromSeed(mnemonicToSeedSync(SOFT_MNEMONIC), NETWORK); @@ -195,8 +193,7 @@ const psbtInputDescriptors: DescriptorInstance[] = []; //[1597be92/69420'/1'/0']tpubDCNNkdMMfhdsCFf1uufBVvHeHSEAEMiXydCvxuZKgM2NS3NcRCUP7dxihYVTbyu1H87pWakBynbYugEQcCbpR66xyNRVQRzr1TcTqqsWJsK/0/* //Since WSH_ORIGIN_PATH is a non-standard path, the Ledger will warn the user about this. const ledgerKeyExpression: string = await keyExpressionLedger({ - ledgerClient, - ledgerState, + ledgerManager, originPath: WSH_ORIGIN_PATH, change: 0, index: '*' @@ -205,21 +202,21 @@ const psbtInputDescriptors: DescriptorInstance[] = []; //Now, we prepare the ranged miniscript descriptor expression for external addresses (change = 0). //expression will be something like this: //wsh(and_v(v:sha256(6c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd5333),and_v(and_v(v:pk([1597be92/69420'/1'/0']tpubDCNNkdMMfhdsCFf1uufBVvHeHSEAEMiXydCvxuZKgM2NS3NcRCUP7dxihYVTbyu1H87pWakBynbYugEQcCbpR66xyNRVQRzr1TcTqqsWJsK/0/*),v:pk([73c5da0a/69420'/1'/0']tpubDDB5ZuMuWmdzs7r4h58fwZQ1eYJvziXaLMiAfHYrAev3jFrfLtsYsu7Cp1hji8KcG9z9CcvHe1FfkvpsjbvMd2JTLwFkwXQCYjTZKGy8jWg/0/*)),older(5)))) - const expression = `wsh(${miniscript + const miniscriptDescriptor = `wsh(${miniscript .replace('@ledger', ledgerKeyExpression) .replace('@soft', softKeyExpression)})`; //Get the descriptor for index WSH_RECEIVE_INDEX. Here we need to pass the index because //we used range key expressions above. `index` is only necessary when using range expressions. - //We also pass the PREIMAGE so that miniscriptDescriptor will be able to finalize the tx later (creating the scriptWitness) - const miniscriptDescriptor = new Descriptor({ - expression, + //We also pass the PREIMAGE so that miniscriptOutput will be able to finalize the tx later (creating the scriptWitness) + const miniscriptOutput = new Output({ + descriptor: miniscriptDescriptor, index: WSH_RECEIVE_INDEX, preimages: [{ digest: `sha256(${SHA256_DIGEST})`, preimage: PREIMAGE }], network: NETWORK }); //We can now fund the wsh utxo: ({ txId, vout } = await regtestUtils.faucet( - miniscriptDescriptor.getAddress(), + miniscriptOutput.getAddress(), UTXO_VALUE )); txHex = (await regtestUtils.fetch(txId)).txHex; @@ -228,9 +225,7 @@ const psbtInputDescriptors: DescriptorInstance[] = []; //set the tx timelock, if needed. //In this case the timelock won't be set since this is a relative-timelock //script (it will set the sequence in the input) - inputIndex = miniscriptDescriptor.updatePsbt({ psbt, txHex, vout }); - //Save the descriptor, indexed by input index, for later: - psbtInputDescriptors[inputIndex] = miniscriptDescriptor; + finalizers.push(miniscriptOutput.updatePsbtAsInput({ psbt, txHex, vout })); //Now add an ouput. This is where we'll send the funds. We'll send them to //some random address that we don't care about in this test. @@ -246,8 +241,7 @@ const psbtInputDescriptors: DescriptorInstance[] = []; //an external address, the policy will be used interchangeably with internal //and external addresses. await registerLedgerWallet({ - ledgerClient, - ledgerState, + ledgerManager, descriptor: miniscriptDescriptor, policyName: 'BitcoinerLab' }); @@ -257,21 +251,17 @@ const psbtInputDescriptors: DescriptorInstance[] = []; //retrieved from state by parsing the descriptors of each input and retrieving //the wallet policy that can sign it. Also a Default Policy is automatically //constructed when the input is of BIP 44, 49, 84 or 86 type. - await signLedger({ - ledgerClient, - ledgerState, - psbt, - descriptors: psbtInputDescriptors - }); + await signLedger({ psbt, ledgerManager }); //Now sign the PSBT with the BIP32 node (the software wallet) signBIP32({ psbt, masterNode }); //============= //Finalize the psbt: - //descriptors must be indexed wrt its psbt input number. + //outputs correspond to the corresponding previous tx output for each input + //and must be indexed wrt its psbt input index. //finalizePsbt uses the miniscript satisfier from @bitcoinerlab/miniscript to //create the scriptWitness among other things. - finalizePsbt({ psbt, descriptors: psbtInputDescriptors }); + finalizers.forEach(finalizer => finalizer({ psbt })); //Since the miniscript uses a relative-timelock, we need to mine BLOCKS before //broadcasting the tx so that it can be accepted by the network diff --git a/test/integration/miniscript.ts b/test/integration/miniscript.ts index 24671be..a515fa3 100644 --- a/test/integration/miniscript.ts +++ b/test/integration/miniscript.ts @@ -29,7 +29,7 @@ import { DescriptorsFactory, keyExpressionBIP32, signers } from '../../dist/'; import { compilePolicy } from '@bitcoinerlab/miniscript'; const { signBIP32, signECPair } = signers; -const { Descriptor, BIP32, ECPair } = DescriptorsFactory(ecc); +const { Output, BIP32, ECPair } = DescriptorsFactory(ecc); const masterNode = BIP32.fromSeed(mnemonicToSeedSync(SOFT_MNEMONIC), NETWORK); const ecpair = ECPair.makeRandom(); @@ -108,9 +108,9 @@ const keys: { ); } } - const expression = template.replace('SCRIPT', miniscript); - const descriptor = new Descriptor({ - expression, + const descriptor = template.replace('SCRIPT', miniscript); + const output = new Output({ + descriptor, //Use signersPubKeys to mark which spending path will be used //(which pubkey must be used) signersPubKeys, @@ -119,24 +119,24 @@ const keys: { }); const { txId, vout } = await regtestUtils.faucetComplex( - descriptor.getScriptPubKey(), + output.getScriptPubKey(), INITIAL_VALUE ); const { txHex } = await regtestUtils.fetch(txId); const psbt = new Psbt(); - const index = descriptor.updatePsbt({ psbt, vout, txHex }); + const inputFinalizer = output.updatePsbtAsInput({ psbt, vout, txHex }); //There are different ways to add an output: //import { address } from 'bitcoinjs-lib'; //const FINAL_SCRIPTPUBKEY = address.toOutputScript(FINAL_ADDRESS, NETWORK); //psbt.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE }); //But can also be done like this: - new Descriptor({ - expression: `addr(${FINAL_ADDRESS})`, + new Output({ + descriptor: `addr(${FINAL_ADDRESS})`, network: NETWORK }).updatePsbtAsOutput({ psbt, value: FINAL_VALUE }); if (keyExpressionType === 'BIP32') signBIP32({ masterNode, psbt }); else signECPair({ ecpair, psbt }); - descriptor.finalizePsbtInput({ index, psbt }); + inputFinalizer({ psbt }); const spendTx = psbt.extractTransaction(); //Now let's mine BLOCKS - 1 and see how the node complains about //trying to broadcast it now. @@ -171,13 +171,13 @@ const keys: { spendTx.ins[0]?.sequence !== older ) throw new Error(`Error: final sequence was not correct`); - console.log(`\nDescriptor: ${expression}`); + console.log(`\nDescriptor: ${descriptor}`); console.log( `Branch: ${spendingBranch}, ${keyExpressionType} signing, tx locktime: ${ psbt.locktime }, input sequence: ${psbt.txInputs?.[0]?.sequence?.toString( 16 - )}, ${descriptor + )}, ${output .expand() .expandedExpression?.replace('@0', '@olderKey') .replace('@1', '@afterKey')}: OK` diff --git a/test/integration/standardOutputs.ts b/test/integration/standardOutputs.ts index 2dd2bda..c7d23ca 100644 --- a/test/integration/standardOutputs.ts +++ b/test/integration/standardOutputs.ts @@ -4,7 +4,7 @@ //npm run test:integration console.log('Standard output integration tests'); -import { networks, Psbt, address } from 'bitcoinjs-lib'; +import { networks, Psbt } from 'bitcoinjs-lib'; import { mnemonicToSeedSync } from 'bip39'; import { RegtestUtils } from 'regtest-client'; const regtestUtils = new RegtestUtils(); @@ -13,14 +13,13 @@ const NETWORK = networks.regtest; const INITIAL_VALUE = 2e4; const FINAL_VALUE = INITIAL_VALUE - 1000; const FINAL_ADDRESS = regtestUtils.RANDOM_ADDRESS; -const FINAL_SCRIPTPUBKEY = address.toOutputScript(FINAL_ADDRESS, NETWORK); +//const FINAL_SCRIPTPUBKEY = address.toOutputScript(FINAL_ADDRESS, NETWORK); const SOFT_MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; import * as ecc from '@bitcoinerlab/secp256k1'; import { DescriptorsFactory, - DescriptorInstance, scriptExpressions, keyExpressionBIP32, signers @@ -28,7 +27,7 @@ import { const { wpkhBIP32, shWpkhBIP32, pkhBIP32 } = scriptExpressions; const { signBIP32, signECPair } = signers; -const { Descriptor, BIP32, ECPair } = DescriptorsFactory(ecc); +const { Output, BIP32, ECPair } = DescriptorsFactory(ecc); const masterNode = BIP32.fromSeed(mnemonicToSeedSync(SOFT_MNEMONIC), NETWORK); //masterNode will be able to sign all the expressions below: @@ -60,19 +59,20 @@ const expressionsECPair = [ (async () => { const psbtMultiInputs = new Psbt(); - const multiInputsDescriptors: DescriptorInstance[] = []; - for (const expression of expressionsBIP32) { - const descriptorBIP32 = new Descriptor({ expression, network: NETWORK }); + const finalizers = []; + for (const descriptor of expressionsBIP32) { + const outputBIP32 = new Output({ descriptor, network: NETWORK }); let { txId, vout } = await regtestUtils.faucetComplex( - descriptorBIP32.getScriptPubKey(), + outputBIP32.getScriptPubKey(), INITIAL_VALUE ); let { txHex } = await regtestUtils.fetch(txId); const psbt = new Psbt(); //Add an input and update timelock (if necessary): - const index = descriptorBIP32.updatePsbt({ psbt, vout, txHex }); - if (descriptorBIP32.isSegwit()) { + const inputFinalizer = outputBIP32.updatePsbtAsInput({ psbt, vout, txHex }); + const index = psbt.data.inputs.length - 1; + if (outputBIP32.isSegwit()) { //Do some additional tests. Create a tmp psbt using txId and value instead //of txHex using Segwit. Passing the value instead of the txHex is not //recommended anyway. It's the user's responsibility to make sure that @@ -85,12 +85,13 @@ const expressionsECPair = [ capturedOutput += message; }; //Add an input and update timelock (if necessary): - const indexSegwit = descriptorBIP32.updatePsbt({ + outputBIP32.updatePsbtAsInput({ psbt: tmpPsbtSegwit, vout, txId, value: INITIAL_VALUE }); + const indexSegwit = tmpPsbtSegwit.data.inputs.length - 1; if (capturedOutput !== 'Warning: missing txHex may allow fee attacks') throw new Error(`Error: did not warn about fee attacks`); console.warn = originalWarn; @@ -103,9 +104,14 @@ const expressionsECPair = [ `Error: could not create same psbt ${nonFinalTxHex} for Segwit not using txHex: ${nonFinalSegwitTxHex}` ); } - psbt.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE }); + //2 ways to achieve the same: + //psbt.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE }); + new Output({ + descriptor: `addr(${FINAL_ADDRESS})`, + network: NETWORK + }).updatePsbtAsOutput({ psbt, value: FINAL_VALUE }); signBIP32({ psbt, masterNode }); - descriptorBIP32.finalizePsbtInput({ index, psbt }); + inputFinalizer({ psbt }); const spendTx = psbt.extractTransaction(); await regtestUtils.broadcast(spendTx.toHex()); await regtestUtils.verify({ @@ -114,46 +120,49 @@ const expressionsECPair = [ vout: 0, value: FINAL_VALUE }); - console.log(`${expression}: OK`); + console.log(`${descriptor}: OK`); ///Update multiInputs PSBT with a similar BIP32 input ({ txId, vout } = await regtestUtils.faucetComplex( - descriptorBIP32.getScriptPubKey(), + outputBIP32.getScriptPubKey(), INITIAL_VALUE )); ({ txHex } = await regtestUtils.fetch(txId)); //Adds an input and updates timelock (if necessary): - const bip32Index = descriptorBIP32.updatePsbt({ - psbt: psbtMultiInputs, - vout, - txHex - }); - multiInputsDescriptors[bip32Index] = descriptorBIP32; + finalizers.push( + outputBIP32.updatePsbtAsInput({ + psbt: psbtMultiInputs, + vout, + txHex + }) + ); } - for (const expression of expressionsECPair) { - const descriptorECPair = new Descriptor({ - expression, + for (const descriptor of expressionsECPair) { + const outputECPair = new Output({ + descriptor, network: NETWORK }); let { txId, vout } = await regtestUtils.faucetComplex( - descriptorECPair.getScriptPubKey(), + outputECPair.getScriptPubKey(), INITIAL_VALUE ); let { txHex } = await regtestUtils.fetch(txId); const psbtECPair = new Psbt(); //Adds an input and updates timelock (if necessary): - const indexECPair = descriptorECPair.updatePsbt({ + const inputFinalizer = outputECPair.updatePsbtAsInput({ psbt: psbtECPair, vout, txHex }); - psbtECPair.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE }); + //2 ways to achieve the same: + //psbtECPair.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE }); + new Output({ + descriptor: `addr(${FINAL_ADDRESS})`, + network: NETWORK + }).updatePsbtAsOutput({ psbt: psbtECPair, value: FINAL_VALUE }); signECPair({ psbt: psbtECPair, ecpair }); - descriptorECPair.finalizePsbtInput({ - index: indexECPair, - psbt: psbtECPair - }); + inputFinalizer({ psbt: psbtECPair }); const spendTxECPair = psbtECPair.extractTransaction(); await regtestUtils.broadcast(spendTxECPair.toHex()); await regtestUtils.verify({ @@ -162,30 +171,34 @@ const expressionsECPair = [ vout: 0, value: FINAL_VALUE }); - console.log(`${expression}: OK`); + console.log(`${descriptor}: OK`); ///Update multiInputs PSBT with a similar ECPair input ({ txId, vout } = await regtestUtils.faucetComplex( - descriptorECPair.getScriptPubKey(), + outputECPair.getScriptPubKey(), INITIAL_VALUE )); ({ txHex } = await regtestUtils.fetch(txId)); //Add an input and update timelock (if necessary): - const ecpairIndex = descriptorECPair.updatePsbt({ - psbt: psbtMultiInputs, - vout, - txHex - }); - multiInputsDescriptors[ecpairIndex] = descriptorECPair; + finalizers.push( + outputECPair.updatePsbtAsInput({ + psbt: psbtMultiInputs, + vout, + txHex + }) + ); } - psbtMultiInputs.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE }); + //2 ways to achieve the same: + //psbtMultiInputs.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE }); + new Output({ + descriptor: `addr(${FINAL_ADDRESS})`, + network: NETWORK + }).updatePsbtAsOutput({ psbt: psbtMultiInputs, value: FINAL_VALUE }); //Sign and finish psbtMultiInputs signECPair({ psbt: psbtMultiInputs, ecpair }); signBIP32({ psbt: psbtMultiInputs, masterNode }); - multiInputsDescriptors.forEach((descriptor, index) => - descriptor.finalizePsbtInput({ index, psbt: psbtMultiInputs }) - ); + finalizers.forEach(finalizer => finalizer({ psbt: psbtMultiInputs })); const spendTxMultiInputs = psbtMultiInputs.extractTransaction(); await regtestUtils.broadcast(spendTxMultiInputs.toHex()); diff --git a/test/tools/generateBitcoinCoreFixtures.js b/test/tools/generateBitcoinCoreFixtures.js index 62535c1..726aa37 100644 --- a/test/tools/generateBitcoinCoreFixtures.js +++ b/test/tools/generateBitcoinCoreFixtures.js @@ -128,7 +128,7 @@ fs.readFile(input, 'utf8', (err, data) => { ); } const fixture = {}; - fixture.expression = expression; + fixture.descriptor = expression; fixture.script = script; if (expression.indexOf('*') !== -1) { fixture.index = index; @@ -143,7 +143,7 @@ fs.readFile(input, 'utf8', (err, data) => { //invalid parsed.expressions.forEach(expression => { const fixture = {}; - fixture.expression = expression; + fixture.descriptor = expression; if (expression.indexOf('#') === -1) { fixture.checksumRequired = false; }