diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa37d2e..c4a4462 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,18 +5,78 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## [Unreleased]
+## [2.0.0] - 2023-10-19
+
+### Changed
+
+- **Main Class Renaming**:
+ - Deprecated the old naming convention.
+ - Renamed the main class from "Descriptor" to "Output".
+ - This change emphasizes that a descriptor describes an "Output".
+
+- **Parameter Refactoring**:
+ - Refactored the main input parameter in the constructor of the "Output" class.
+ - Previously: "expression" (string).
+ - Now: "descriptor" (string).
+ - This modification better aligns with the principle above that a descriptor describes an Output.
+
+- **Function Updates**:
+ - All functions that utilized "expression" have been updated to use "descriptor".
+
+- **Ledger Hardware Wallet & PSBT Finalizers Improvements**:
+ - Refined functions related to the Ledger Hardware Wallet and PSBT finalizers.
+ - These refinements greatly simplify and enhance the library's usability. See details below.
+
+- **Finalizers Update**:
+ - Deprecated `updatePsbt` in favor of `updatePsbtAsInput`.
+ - The new function returns the finalizer directly instead of the input number.
+ - This change eliminates the need to explicitly call the `finalizePsbtInput` method of the Output class.
+ - Previous implementations were error-prone due to the need to keep track of the input number of the PSBT input being finalized and the Output instance of the previous output.
+
+- **Ledger Enhancements**:
+ - Simplified the signer's requirements before v2.0.0, which previously required tracking the Output instances of each input and passing them to the signer.
+ - The essential information is now directly extracted from the PSBT, facilitating usability.
+ - Unified `ledgerClient` and `ledgerState` parameters into a new type `LedgerManager`, which also includes an instance to the Elliptic Curve Library (`ecc`).
+ - To initialize: `const ledgerManager = {ledgerClient, ledgerState: {}, ecc};`, where `import * as ecc from '@bitcoinerlab/secp256k1'`.
+
+- **Deprecation Notices**:
+ - While the old functions and classes with former signatures remain available in 2.0.0, they are now deprecated.
+ - Transitioning to v2.0.0 requires no immediate action, but you may encounter "deprecated" warnings if your code editor supports typedoc/jsdoc linting.
+ - It's highly recommended to start updating to the new functions and classes.
+
+- **Key Updates to Consider**:
+ - Substitute `new Descriptor({expression})` with `new Output({descriptor})`.
+ - Transition from `expand({expression})` to `expand({descriptor})`.
+ - Use `updatePsbtAsInput` as `updatePsbt` is now deprecated.
+ - Introduced `updatePsbtAsOutput` for completeness.
+ - Opt for finalizers returned by `updatePsbtAsInput` as `finalizePsbtInput` and `finalizePsbt` have been deprecated.
+
+- **Additional Ledger Updates**:
+ - Functions previously expecting `ledgerClient` and `ledgerState` should now receive `ledgerManager` instead.
+ - This change affects multiple functions, including `signLedger`, all Ledger script expression functions and also: `keyExpressionLedger`, `registerLedgerWallet`, `getLedgerMasterFingerPrint`, and `assertLedgerApp`.
+ - `signLedger` and `signInputLedger` no longer necessitate passing an instance to the former `Descriptor` class. All relevant information is automatically retrieved from the psbt now.
+
+- **Testing Enhancements**:
+ - **Deprecated Function Testing**:
+ - Retained old tests, now suffixed with `-deprecated`, to continue testing the deprecated functions and classes.
+ - **New API Testing**:
+ - Introduced additional tests specifically designed to evaluate the new API's functionality.
+
+- **Documentation Enhancements**:
+ - Extensively documented all methods using typedoc.
+ - This facilitates the generation of a comprehensive API reference for developers.
+ - Updated the README.md to mirror the latest changes, optimizing clarity by referencing the API for intricate details.
### Fixed
- **Descriptor Buffer Comparison**:
- - Addressed a bug related to buffer comparisons in `src/descriptors.ts`.
- - Modified the comparison logic for `witnessScript` and `redeemScript` to handle cases where one of the buffers may be `undefined`.
- - Introduced the `eqBuffers` function to compare two buffers, ensuring that it correctly handles `undefined` values.
- - This fix ensures accurate and error-free descriptor comparisons, particularly crucial for finalizing psbt indexes.
- - Refer to [issue-20](https://github.com/bitcoinerlab/descriptors/issues/20) for more details.
+ - Resolved a bug associated with buffer comparisons in `src/descriptors.ts`.
+ - Adjusted the comparison logic for `witnessScript` and `redeemScript` to manage scenarios where one buffer may be `undefined`.
+ - Introduced the `eqBuffers` function for accurate buffer comparisons, particularly when handling `undefined` values.
+ - This correction is vital for precise descriptor comparisons, especially when determining psbt indexes.
+ - For an in-depth analysis, consult [issue-20](https://github.com/bitcoinerlab/descriptors/issues/20).
-## [1.1.1] - 2023-10-12
+## [1.1.1] - 2023-9-12
### Changed
@@ -26,7 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The new approach bypasses this by using a direct `require` statement. For more details on the underlying issue, refer to [this React Native discussion](https://github.com/react-native-community/discussions-and-proposals/issues/120).
- This update ensures smoother integration for developers using this library in React Native projects.
-## [1.1.0] - 2023-10-7
+## [1.1.0] - 2023-9-7
### Changed
diff --git a/README.md b/README.md
index 7387d14..83fa4ee 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Bitcoin Descriptors Library
-This library is designed to parse and create Bitcoin Descriptors, including Miniscript, and generate Partially Signed Bitcoin Transactions (PSBTs). It also provides PSBT finalizers and signers for single-signature, BIP32, and Hardware Wallets.
+This library is designed to parse and create Bitcoin Descriptors, including Miniscript, and generate Partially Signed Bitcoin Transactions (PSBTs). It also provides PSBT signers and finalizers for single-signature, BIP32, and Hardware Wallets.
## Features
@@ -10,7 +10,7 @@ This library is designed to parse and create Bitcoin Descriptors, including Mini
## Concepts
-This library has two main capabilities related to Bitcoin descriptors. Firstly, it can generate addresses and scriptPubKeys from descriptors. These addresses and scriptPubKeys can be used to receive funds from other parties. Secondly, the library is able to sign and spend unspent outputs described by those same descriptors. In order to do this, the descriptors must first be set into a PSBT.
+This library has two main capabilities related to Bitcoin descriptors. Firstly, it can generate `addresses` and `scriptPubKeys` from descriptors. These `addresses` and `scriptPubKeys` can be used to receive funds from other parties. Secondly, the library is able to sign transactions and spend unspent outputs described by those same descriptors. In order to do this, the descriptors must first be set into a PSBT.
If you are not familiar with _Bitcoin descriptors_ and _partially signed Bitcoin transactions (PSBTs)_, click on the section below to expand and read more about these concepts.
@@ -37,6 +37,8 @@ PSBTs come in handy when working with descriptors, especially when using scripts
Before we dive in, it's worth mentioning that we have several comprehensive guides available covering different aspects of the library. These guides provide explanations and code examples in interactive playgrounds, allowing you to see the changes in the output as you modify the code. This hands-on learning experience, combined with clear explanations, helps you better understand how to use the library effectively. [Check out the available guides here](https://bitcoinerlab.com/guides).
+Furthermore, we've meticulously documented our API. For an in-depth look into Classes, functions, and types, head over [here](https://bitcoinerlab.com/modules/descriptors/api).
+
To use this library (and accompanying libraries), you can install them using:
```bash
@@ -47,163 +49,175 @@ npm install @bitcoinerlab/secp256k1
The library can be split into four main parts:
-- The `Descriptor` class, which is the core component that parses descriptors and can be used to finalize partially signed Bitcoin transactions (PSBTs).
-- `keyExpressions` and `scriptExpressions`, which provide functions to create descriptor and key expressions (strings) from structured data, making it easier to work with complex descriptors.
+- 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).
- PSBT signers and finalizers, which are used to manage the signing and finalization of PSBTs.
+- `keyExpressions` and `scriptExpressions`, which provide functions to create key and standard descriptor expressions (strings) from structured data.
- Hardware wallet integration, which provides support for interacting with hardware wallets such as Ledger devices.
-### Descriptor class
+### Output class
-The Descriptor class is created dynamically by providing a cryptographic secp256k1 engine as shown below:
+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 { Descriptor } = descriptors.DescriptorsFactory(secp256k1);
+const { Output } = descriptors.DescriptorsFactory(ecc);
```
-After that, you can obtain an instance for a descriptor expression, such as a wpkh expression, like this:
+Once set up, you can obtain an instance for an output, described by a descriptor such as a `wpkh`, as follows:
```javascript
-const wpkhDescriptor = new Descriptor({
- expression:
+const wpkhOutput = new Output({
+ descriptor:
'wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)'
});
```
-Here are the parameters that can be used to create a `new Descriptor`:
+Refer to [the API](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html#constructor) for the complete list of parameters in the constructor.
+
+The `Output` class [offers various helpful methods](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html), including `getAddress()`, which returns the address associated with the descriptor, `getScriptPubKey()`, which returns the `scriptPubKey` for the descriptor, `expand()`, which decomposes a descriptor into its elemental parts, `updatePsbtAsInput()` and `updatePsbtAsOutput()`.
+
+The `updatePsbtAsInput()` method is an essential part of the library, responsible for adding an input to the PSBT corresponding to the UTXO described by the descriptor. Additionally, when the descriptor expresses an absolute time-spending condition, such as "This UTXO can only be spent after block N," `updatePsbtAsInput()` adds timelock information to the PSBT.
+
+To call `updatePsbtAsInput()`, use the following syntax:
```javascript
-constructor({
- expression, // The descriptor string in ASCII format. It may include a "*"
- // to denote an arbitrary index.
- index, // The descriptor's index in the case of a range descriptor
- // (must be an integer >= 0).
- checksumRequired = false // Optional flag indicating if the descriptor is
- // required to include a checksum. Defaults to false.
- allowMiniscriptInP2SH = false, // Flag indicating if this instance can parse
- // and generate script satisfactions for
- // sh(miniscript) top-level expressions of
- // miniscripts. This is not recommended.
- network = networks.bitcoin, // One of bitcoinjs-lib `networks`
- // (https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/networks.js)
- // or another one with the same interface.
- preimages = [], // An array of preimages of type `Preimage`: `Preimage[]`.
- // This info is necessary to finalize Psbts.
- signersPubKeys // (Optional): 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 in terms
- // of tx size (if more than one path is available).
- // For more details on using this parameter, refer to this
- // Stack Exchange answer: https://bitcoin.stackexchange.com/a/118036/89665
-});
+import { Psbt } from 'bitcoinjs-lib';
+const psbt = new Psbt();
+const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
```
-The `Descriptor` class offers various helpful methods, including `getAddress()`, which returns the address associated with the descriptor, `getScriptPubKey()`, which returns the scriptPubKey for the descriptor, `expand()`, which decomposes a descriptor into its elemental parts, `updatePsbt()` and `finalizePsbt()`.
+Here, `psbt` refers to an instance of the [bitcoinjs-lib Psbt class](https://github.com/bitcoinjs/bitcoinjs-lib). The parameter `txHex` denotes a hex string that serializes the previous transaction containing this output. Meanwhile, `vout` is an integer that marks the position of the output within that transaction.
-The `updatePsbt()` method is an essential part of the library, responsible for adding an input to the PSBT corresponding to the UTXO (unspent transaction output) described by the descriptor. Additionally, when the descriptor expresses an absolute time-spending condition, such as "This UTXO can only be spent after block N," `updatePsbt()` adds timelock information to the PSBT.
+The method returns the `inputFinalizer()` function. This finalizer function completes a PSBT input by adding the unlocking script (`scriptWitness` or `scriptSig`) that satisfies the previous 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 `inputFinalizer()`. Detailed [explanations on the `inputFinalizer` method](#signers-and-finalizers-finalize-psbt-input) can be found in the Signers and Finalizers section.
-To call `updatePsbt()`, use the following syntax:
+Conversely, `updatePsbtAsOutput` allows you to add an output to a PSBT. For instance, to configure a `psbt` that sends `10,000` sats to the SegWit address `bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x`:
```javascript
-const inputIndex = descriptor.updatePsbt({ psbt, txHex, vout });
+const recipientOutput =
+ new Output({ descriptor: `addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)` });
+recipientOutput.updatePsbtAsOutput({ psbt, value: 10000 });
```
-Here, `psbt` is an instance of a [bitconjs-lib Psbt class](https://github.com/bitcoinjs/bitcoinjs-lib), `txHex` is the hex string that serializes the previous transaction, and `vout` is an integer corresponding to the output index of the descriptor in the previous transaction. The method returns a number that corresponds to the input number that this descriptor will take in the `psbt`.
+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. For specific details on the methods, refer directly to [the API](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html). For insights into the constructor, especially regarding the `signersPubKeys` parameter, as well as the usage of `updatePsbtAsInput`, `getAddress`, and `getScriptPubKey`, see this detailed [Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665).
-The `finalizePsbt()` method is the final step in adding the unlocking script (scriptWitness or scriptSig) that satisfies the spending condition to the transaction, effectively finalizing the Psbt. It should be called after all necessary signing operations have been completed. The syntax for calling this method is as follows:
+#### Parsing Descriptors with `expand()`
-```javascript
-descriptor.finalizePsbt({ index, psbt });
-```
+The `expand()` function serves as a mechanism to parse Bitcoin descriptors, unveiling a detailed breakdown of the descriptor's content. There are two main pathways to utilize this function:
-Here, `index` is the `inputIndex` obtained from the `updatePsbt()` method and `psbt` is an instance of a bitcoinjs-lib `Psbt` object.
+##### 1. Directly from an `Output` Instance
-For further information on using the Descriptor 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 `updatePsbt`, `finalizePsbt`, `getAddress`, and `getScriptPubKey`.
+If you have already instantiated the `Output` class and created an output, you can directly use the [`expand()` method](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html#expand) on that `Output` instance. This method provides a straightforward way to parse descriptors without the need for additional utilities.
-#### Tip: Parsing descriptors without instantiating a class
+```javascript
+const output = new Output({ descriptor: "your-descriptor-here" });
+const result = output.expand();
+```
-`DescriptorsFactory` provides a convenient `expand()` function that allows you to parse a descriptor expression without the need to instantiate the `Descriptor` class. This function can be used as follows:
+##### 2. Through the `DescriptorsFactory`
+
+If you haven't instantiated the `Output` class or simply prefer a standalone utility, the `DescriptorsFactory` provides an `expand()` function that allows you to directly parse the descriptor. For a comprehensive understanding of all the function arguments, refer to [this reference](https://bitcoinerlab.com/modules/descriptors/api/functions/DescriptorsFactory.html#DescriptorsFactory). Here's how you can use it:
```javascript
-const { expand } = descriptors.DescriptorsFactory(secp256k1);
+const { expand } = descriptors.DescriptorsFactory(ecc);
const result = expand({
- expression: '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`
- // (https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/networks.js)
- // or another one with the same interface.
- // Optional (defaults to bitcoin mainnet).
- allowMiniscriptInP2SH: true, // Optional flag to allow miniscript in P2SH.
- // Defaults to false.
- index, // Optional. The descriptor's index in the case of a range descriptor
- // (must be an integer >= 0). If not set for ranged descriptors, then
- // the function will return an expansionMap with ranged keyPaths and
- // won't compute Payment or scripts.
- checksumRequired = false // Optional flag indicating if the descriptor is
- // required to include a checksum. Defaults to false.
+ descriptor: "sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))"
});
```
-The `expand()` function returns an object with the following properties:
-
-- `payment: Payment | undefined`: The corresponding [bitcoinjs-lib Payment](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts) for the provided expression, if applicable.
-- `expandedExpression: string | undefined`: The expanded descriptor expression.
-- `miniscript: string | undefined`: The extracted miniscript from the expression, if any.
-- `expansionMap: ExpansionMap | undefined`: A map of key expressions in the descriptor to their corresponding expanded keys.
-- `isSegwit: boolean | undefined`: A boolean indicating whether the descriptor represents a SegWit script.
-- `expandedMiniscript: string | undefined`: The expanded miniscript, if any.
-- `redeemScript: Buffer | undefined`: The redeem script for the descriptor, if applicable.
-- `witnessScript: Buffer | undefined`: The witness script for the descriptor, if applicable.
-- `isRanged: boolean` : Whether the expression represents a ranged descriptor.
-- `canonicalExpression` : This is the preferred or authoritative representation of the descriptor expression. It standardizes the descriptor by replacing indexes on wildcards and eliminating checksums.
+Regardless of your chosen pathway, the outcome from `expand()` grants an insightful exploration into the descriptor's structure. For an exhaustive list of return properties, you can refer to [the API](https://bitcoinerlab.com/modules/descriptors/api/types/Expansion.html).
-For the example expression provided, the `expandedExpression` and a portion of the `expansionMap` would be as follows:
+For illustration, given the descriptor above, the corresponding `expandedExpression` and a section of the `expansionMap` would appear as:
```javascript
-// expression: 'sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))'
-
-expandedExpression: 'sh(wsh(andor(pk(@0),older(8640),pk(@1))))',
-expansionMap: {
- '@0': {
- keyExpression:
- '0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
- },
- '@1': {
- keyExpression:
- "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
- keyPath: '/1/2/3/4/*',
- originPath: "/49'/0'/0'",
- path: "m/49'/0'/0'/1/2/3/4/*",
- // Other relevant properties returned: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
- }
+{
+ expandedExpression: 'sh(wsh(andor(pk(@0),older(8640),pk(@1))))',
+ expansionMap: {
+ '@0': {
+ keyExpression:
+ '0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
+ },
+ '@1': {
+ keyExpression:
+ "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
+ keyPath: '/1/2/3/4/*',
+ originPath: "/49'/0'/0'",
+ path: "m/49'/0'/0'/1/2/3/4/*",
+ // Other relevant properties returned: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
+ }
+ }
+ //...
}
```
-### keyExpressions and scriptExpressions
+### Signers and Finalizers
+
+This library encompasses a PSBT finalizer as well as three distinct signers: ECPair for single-signatures, BIP32, and Ledger (specifically crafted for Ledger Wallet devices, with upcoming support for other devices planned).
-This library also includes a set of function helpers that facilitate the generation of the `expression` parameter in the constructor of the `Descriptor` class. These helpers are located under the `scriptExpressions` module, which can be imported using the following statement:
+To incorporate these functionalities, use the following import statement:
+
+```javascript
+import { signers } from '@bitcoinerlab/descriptors';
+```
+
+For signing operations, utilize the methods provided by the [`signers`](https://bitcoinerlab.com/modules/descriptors/api/modules/signers.html):
+
+```javascript
+// For Ledger
+await signers.signLedger({ psbt, ledgerManager });
+
+// For BIP32 - https://github.com/bitcoinjs/bip32
+signers.signBIP32({ psbt, masterNode });
+
+// For ECPair - https://github.com/bitcoinjs/ecpair
+signers.signECPair({ psbt, ecpair }); // Here, `ecpair` is an instance of the bitcoinjs-lib ECPairInterface
+```
+
+Detailed information on Ledger integration will be provided in subsequent sections.
+
+
+
+#### Finalizing the `psbt`
+
+When finalizing the `psbt`, the [`updatePsbtAsInput` method](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html#updatePsbtAsInput) plays a key role. When invoked, the `output.updatePsbtAsInput()` sets up the `psbt` by designating the output as an input and, if required, adjusts the transaction locktime. In addition, it returns a `inputFinalizer` function tailored for this specific `psbt` input.
+
+##### Procedure:
+
+1. For each unspent output from a previous transaction that you're referencing in a `psbt` as an input to be spent, call the `updatePsbtAsInput` method:
+
+ ```javascript
+ const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
+ ```
+
+2. Once you've completed the necessary signing operations on the `psbt`, use the returned finalizer function on each input:
+
+ ```javascript
+ inputFinalizer({ psbt });
+ ```
+
+##### Important Notes:
+
+- The finalizer function returned from `updatePsbtAsInput` adds the necessary unlocking script (`scriptWitness` or `scriptSig`) that satisfies the `Output`'s spending conditions. Remember, both `scriptSig` and `scriptWitness` contain signatures. Ensure that all necessary signing operations are completed before finalizing.
+
+- When using `updatePsbtAsInput`, the `txHex` parameter is crucial. For Segwit inputs, you can choose to pass `txId` and `value` instead of `txHex`. However, ensure the accuracy of the `value` to avoid potential fee attacks. When unsure, use `txHex` and skip `txId` and `value`.
+
+- Hardware wallets require the [full `txHex` for Segwit](https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd).
+
+### Key Expressions and Script Expressions
+
+This library also provides a series of function helpers designed to streamline the generation of `descriptor` strings. These strings can serve as input parameters in the `Output` class constructor. These helpers are nested within the `scriptExpressions` module. You can import them as illustrated below:
```javascript
import { scriptExpressions } from '@bitcoinerlab/descriptors';
```
-`scriptExpressions` includes functions that generate script expressions for commonly used script expressions. Some of the available functions are `pkhBIP32()`, `shWpkhBIP32`, `wpkhBIP32`, `pkhLedger()`, `shWpkhLedger` and `wpkhLedger`.
+Within the `scriptExpressions` module, there are functions designed to generate descriptors for commonly used scripts. Some examples include `pkhBIP32()`, `shWpkhBIP32()`, `wpkhBIP32()`, `pkhLedger()`, `shWpkhLedger()`, and `wpkhLedger()`. Refer to [the API](https://bitcoinerlab.com/modules/descriptors/api/modules/scriptExpressions.html#expand) for a detailed list and further information.
When using BIP32-based descriptors, the following parameters are required for the `scriptExpressions` functions:
```javascript
pkhBIP32(params: {
- masterNode: BIP32Interface; //A bitcoinjs-lib instance of a BIP32 object.
+ masterNode: BIP32Interface; //bitcoinjs-lib BIP32 - https://github.com/bitcoinjs/bip32
network?: Network; //A bitcoinjs-lib network
account: number;
change?: number | undefined; //0 -> external (receive), 1 -> internal (change)
@@ -213,11 +227,11 @@ pkhBIP32(params: {
})
```
-For Ledger, `ledgerClient` and `ledgerState` are used instead of `masterNode`. These will be explained later when we discuss Ledger integration.
+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 the following section.
-The `keyExpressions` category includes functions that generate string representations of key expressions for public keys. This is useful when working with miniscript-based descriptors.
+The `keyExpressions` category includes functions that generate string representations of key expressions for public keys.
-This library includes the following `keyExpressions`: `keyExpressionBIP32` and `keyExpressionLedger`. They can be imported as follows:
+This library includes the following `keyExpressions`: [`keyExpressionBIP32`](https://bitcoinerlab.com/modules/descriptors/api/functions/keyExpressionBIP32.html) and [`keyExpressionLedger`](https://bitcoinerlab.com/modules/descriptors/api/functions/keyExpressionLedger.html). They can be imported as follows:
```javascript
import {
@@ -230,7 +244,7 @@ The parameters required for these functions are:
```javascript
function keyExpressionBIP32({
- masterNode: BIP32Interface;
+ masterNode: BIP32Interface; //bitcoinjs-lib BIP32 - https://github.com/bitcoinjs/bip32
originPath: string;
change?: number | undefined; //0 -> external (receive), 1 -> internal (change)
index?: number | undefined | '*';
@@ -239,35 +253,13 @@ function keyExpressionBIP32({
});
```
-For Ledger, `ledgerClient` and `ledgerState` are used instead of `masterNode`.
-
-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.
-
-### Signers and Finalizers
-
-This library provides a Psbt finalizer and three types of signers: ECPair for single-signature, BIP32, and Ledger (for Ledger Wallet devices, with plans for other devices).
-
-To use them, import them as follows:
-
-```javascript
-import { signers, finalizePsbt } from '@bitcoinerlab/descriptors';
-```
-
-To sign with the signers:
+For the `keyExpressionLedger` function, you'd use `ledgerManager` instead of `masterNode`.
-```javascript
-await signers.signLedger({
- ledgerClient,
- ledgerState,
- psbt,
- descriptors: psbtInputDescriptors
-});
-//Here psbtInputDescriptors is an array of descriptors odered by their respective inputIndex in the psbt
-signers.signBIP32({ psbt, masterNode });
-signers.signECPair({ psbt, ecpair }); //Where ecpair is an instance of bitcoinjs-lib ECPairInterface
+Both functions will generate strings that fully define BIP32 keys. For example:
+```text
+[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*
```
-
-To finalize the `psbt`, you can either call the method `finalizePsbtInput({ index, psbt })` on each descriptor, passing as arguments the `psbt` and its input `index`, or call the helper function: `finalizePsbt({psbt, descriptors })`. In the latter case, `descriptors` is an array of descriptors ordered by their respective input index in the `psbt`.
+Read [Bitcoin Core descriptors documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) to learn more about Key Expressions.
### Hardware Wallet Integration
@@ -301,6 +293,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).
@@ -309,8 +302,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'
});
@@ -318,7 +310,9 @@ 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.
+
+The [API reference for the ledger module](https://bitcoinerlab.com/modules/descriptors/api/variables/ledger.html) provides a comprehensive list of functions related to the Ledger Hardware Wallet, along with detailed explanations of their parameters and behavior.
@@ -326,10 +320,11 @@ Finally, `ledgerState` is an object used to store information related to Ledger
For more information, refer to the following resources:
-- [Guides](https://bitcoinerlab.com/guides): Comprehensive explanations and playgrounds to help you learn how to use the module.
-- [Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665): Focused explanation on the constructor, specifically the `signersPubKeys` parameter, and the usage of `updatePsbt`, `finalizePsbt`, `getAddress`, and `getScriptPubKey`.
-- [Integration tests](https://github.com/bitcoinerlab/descriptors/tree/main/test/integration): Well-commented code examples showcasing the usage of all functions in the module.
-- API Documentation: Auto-generated documentation from the source code, providing detailed information about the library and its methods. To generate the API documentation locally, follow these commands:
+- **[Guides](https://bitcoinerlab.com/guides)**: Comprehensive explanations and playgrounds to help you learn how to use the module.
+- **[API](https://bitcoinerlab.com/modules/descriptors/api)**: Dive into the details of the Classes, functions, and types.
+- **[Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665)**: Focused explanation on the constructor, specifically the `signersPubKeys` parameter, and the usage of `updatePsbtAsInput`, `getAddress`, and `getScriptPubKey`.
+- **[Integration tests](https://github.com/bitcoinerlab/descriptors/tree/main/test/integration)**: Well-commented code examples showcasing the usage of all functions in the module.
+- **Local Documentation**: Generate comprehensive API documentation from the source code:
```bash
git clone https://github.com/bitcoinerlab/descriptors
@@ -340,8 +335,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/package-lock.json b/package-lock.json
index 00f638e..79ece81 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@bitcoinerlab/descriptors",
- "version": "1.1.1",
+ "version": "2.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@bitcoinerlab/descriptors",
- "version": "1.1.1",
+ "version": "2.0.0",
"license": "MIT",
"dependencies": {
"@bitcoinerlab/miniscript": "^1.2.1",
diff --git a/package.json b/package.json
index fe58da0..740a37f 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "@bitcoinerlab/descriptors",
"description": "This library parses and creates Bitcoin Miniscript Descriptors and generates Partially Signed Bitcoin Transactions (PSBTs). It provides PSBT finalizers and signers for single-signature, BIP32 and Hardware Wallets.",
"homepage": "https://github.com/bitcoinerlab/descriptors",
- "version": "1.1.1",
+ "version": "2.0.0",
"author": "Jose-Luis Landabaso",
"license": "MIT",
"repository": {
@@ -38,10 +38,11 @@
"ensureTester": "./node_modules/@bitcoinerlab/configs/scripts/ensureTester.sh",
"test:integration:soft": "npm run ensureTester && node test/integration/standardOutputs.js && echo \"\\n\\n\" && node test/integration/miniscript.js",
"test:integration:ledger": "npm run ensureTester && node test/integration/ledger.js",
+ "test:integration:deprecated": "npm run ensureTester && node test/integration/standardOutputs-deprecated.js && echo \"\\n\\n\" && node test/integration/miniscript-deprecated.js && echo \"\\n\\n\" && node test/integration/ledger-deprecated.js",
"test:unit": "jest",
"test": "npm run lint && npm run build && npm run test:unit && npm run test:integration:soft",
"testledger": "npm run lint && npm run build && npm run test:integration:ledger",
- "prepublishOnly": "npm run test && echo \"\\n\\n\" && npm run test:integration:ledger"
+ "prepublishOnly": "npm run test && echo \"\\n\\n\" && npm run test:integration:deprecated && npm run test:integration:ledger"
},
"files": [
"dist"
diff --git a/src/checksum.ts b/src/checksum.ts
index 82ba2f4..6a438d1 100644
--- a/src/checksum.ts
+++ b/src/checksum.ts
@@ -13,6 +13,11 @@ const PolyMod = (c: bigint, val: bigint): bigint => {
};
export const CHECKSUM_CHARSET: string = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
+
+/**
+ * Implements the Bitcoin descriptor's checksum algorithm described in
+ * {@link https://github.com/bitcoin/bitcoin/blob/master/src/script/descriptor.cpp}
+ */
export const DescriptorChecksum = (span: string): string => {
const INPUT_CHARSET =
'0123456789()[],\'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#"\\ ';
diff --git a/src/descriptors.ts b/src/descriptors.ts
index 279c6cd..c362c18 100644
--- a/src/descriptors.ts
+++ b/src/descriptors.ts
@@ -20,8 +20,8 @@ import type {
TinySecp256k1Interface,
Preimage,
TimeConstraints,
+ Expansion,
ExpansionMap,
- Expand,
ParseKeyExpression
} from './types';
@@ -53,58 +53,82 @@ function countNonPushOnlyOPs(script: Buffer): number {
/*
* Returns a bare descriptor without checksum and particularized for a certain
* index (if desc was a range descriptor)
+ * @hidden
*/
function evaluate({
- expression,
+ descriptor,
checksumRequired,
index
}: {
- expression: string;
+ descriptor: string;
checksumRequired: boolean;
index?: number;
}): string {
- const mChecksum = expression.match(String.raw`(${RE.reChecksum})$`);
+ if (!descriptor) throw new Error('You must provide a descriptor.');
+
+ const mChecksum = descriptor.match(String.raw`(${RE.reChecksum})$`);
if (mChecksum === null && checksumRequired === true)
- throw new Error(`Error: descriptor ${expression} has not checksum`);
- //evaluatedExpression: a bare desc without checksum and particularized for a certain
+ throw new Error(`Error: descriptor ${descriptor} has not checksum`);
+ //evaluatedDescriptor: a bare desc without checksum and particularized for a certain
//index (if desc was a range descriptor)
- let evaluatedExpression = expression;
+ let evaluatedDescriptor = descriptor;
if (mChecksum !== null) {
const checksum = mChecksum[0].substring(1); //remove the leading #
- evaluatedExpression = expression.substring(
+ evaluatedDescriptor = descriptor.substring(
0,
- expression.length - mChecksum[0].length
+ descriptor.length - mChecksum[0].length
);
- if (checksum !== DescriptorChecksum(evaluatedExpression)) {
- throw new Error(`Error: invalid descriptor checksum for ${expression}`);
+ if (checksum !== DescriptorChecksum(evaluatedDescriptor)) {
+ throw new Error(`Error: invalid descriptor checksum for ${descriptor}`);
}
}
if (index !== undefined) {
- const mWildcard = evaluatedExpression.match(/\*/g);
+ const mWildcard = evaluatedDescriptor.match(/\*/g);
if (mWildcard && mWildcard.length > 0) {
//From https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
//To prevent a combinatorial explosion of the search space, if more than
//one of the multi() key arguments is a BIP32 wildcard path ending in /* or
- //*', the multi() expression only matches multisig scripts with the ith
+ //*', the multi() descriptor only matches multisig scripts with the ith
//child key from each wildcard path in lockstep, rather than scripts with
//any combination of child keys from each wildcard path.
//We extend this reasoning for musig for all cases
- evaluatedExpression = evaluatedExpression.replaceAll(
+ evaluatedDescriptor = evaluatedDescriptor.replaceAll(
'*',
index.toString()
);
} else
throw new Error(
- `Error: index passed for non-ranged descriptor: ${expression}`
+ `Error: index passed for non-ranged descriptor: ${descriptor}`
);
}
- return evaluatedExpression;
+ return evaluatedDescriptor;
}
/**
- * Builds the functions needed to operate with descriptors using an external elliptic curve (ecc) library.
- * @param {Object} ecc - an object containing elliptic curve operations, such as [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1) or [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
+ * Constructs the necessary functions and classes for working with descriptors
+ * using an external elliptic curve (ecc) library.
+ *
+ * Notably, it returns the {@link _Internal_.Output | `Output`} class, which
+ * provides methods to create, sign, and finalize PSBTs based on descriptor
+ * expressions.
+ *
+ * While this Factory function includes the `Descriptor` class, note that
+ * this class was deprecated in v2.0 in favor of `Output`. For backward
+ * compatibility, the `Descriptor` class remains, but using `Output` is advised.
+ *
+ * The Factory also returns utility methods like `expand` (detailed below)
+ * and `parseKeyExpression` (see {@link ParseKeyExpression}).
+ *
+ * Additionally, for convenience, the function returns `BIP32` and `ECPair`.
+ * These are {@link https://github.com/bitcoinjs bitcoinjs-lib} classes designed
+ * for managing {@link https://github.com/bitcoinjs/bip32 | `BIP32`} keys and
+ * public/private key pairs:
+ * {@link https://github.com/bitcoinjs/ecpair | `ECPair`}, respectively.
+ *
+ * @param {Object} ecc - An object with elliptic curve operations, such as
+ * [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1) or
+ * [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
*/
export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
const BIP32: BIP32API = BIP32Factory(ecc);
@@ -133,17 +157,79 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
};
/**
- * Takes a descriptor (expression) and expands it to its corresponding Bitcoin script and other relevant details.
+ * 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: {
+ /**
+ * The descriptor expression to be expanded.
+ */
+ descriptor: string;
+
+ /**
+ * The descriptor index, if ranged.
+ */
+ index?: number;
+
+ /**
+ * A flag indicating whether the descriptor is required to include a checksum.
+ * @defaultValue false
+ */
+ checksumRequired?: boolean;
+
+ /**
+ * The Bitcoin network to use.
+ * @defaultValue `networks.bitcoin`
+ */
+ network?: Network;
+
+ /**
+ * Flag to allow miniscript in P2SH.
+ * @defaultValue false
+ */
+ allowMiniscriptInP2SH?: boolean;
+ }): Expansion;
+
+ /**
+ * @deprecated
+ * @hidden
+ * To be removed in version 3.0
*/
- const expand: Expand = ({
+ function expand(params: {
+ expression: string;
+ index?: number;
+ checksumRequired?: boolean;
+ network?: Network;
+ allowMiniscriptInP2SH?: boolean;
+ }): Expansion;
+
+ /**
+ * @hidden
+ * To be removed in v3.0 and replaced by the version with the signature that
+ * does not accept descriptors
+ */
+ function expand({
+ descriptor,
expression,
index,
checksumRequired = false,
network = networks.bitcoin,
allowMiniscriptInP2SH = false
- }) => {
+ }: {
+ descriptor?: string;
+ expression?: string;
+ index?: number;
+ checksumRequired?: boolean;
+ network?: Network;
+ allowMiniscriptInP2SH?: boolean;
+ }): Expansion {
+ if (descriptor && expression)
+ throw new Error(`expression param has been deprecated`);
+ descriptor = descriptor || expression;
+ if (!descriptor) throw new Error(`descriptor not provided`);
let expandedExpression: string | undefined;
let miniscript: string | undefined;
let expansionMap: ExpansionMap | undefined;
@@ -152,7 +238,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
let payment: Payment | undefined;
let witnessScript: Buffer | undefined;
let redeemScript: Buffer | undefined;
- const isRanged = expression.indexOf('*') !== -1;
+ const isRanged = descriptor.indexOf('*') !== -1;
if (index !== undefined)
if (!Number.isInteger(index) || index < 0)
@@ -161,7 +247,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
//Verify and remove checksum (if exists) and
//particularize range descriptor for index (if desc is range descriptor)
const canonicalExpression = evaluate({
- expression,
+ descriptor,
...(index !== undefined ? { index } : {}),
checksumRequired
});
@@ -172,7 +258,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
if (isRanged) throw new Error(`Error: addr() cannot be ranged`);
const matchedAddress = canonicalExpression.match(RE.reAddrAnchored)?.[1]; //[1]-> whatever is found addr(->HERE<-)
if (!matchedAddress)
- throw new Error(`Error: could not get an address in ${expression}`);
+ throw new Error(`Error: could not get an address in ${descriptor}`);
let output;
try {
output = address.toOutputScript(matchedAddress, network);
@@ -205,7 +291,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
if (!keyExpression)
throw new Error(`Error: keyExpression could not me extracted`);
if (canonicalExpression !== `pk(${keyExpression})`)
- throw new Error(`Error: invalid expression ${expression}`);
+ throw new Error(`Error: invalid expression ${descriptor}`);
expandedExpression = 'pk(@0)';
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
expansionMap = { '@0': pKE };
@@ -214,7 +300,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
//Note there exists no address for p2pk, but we can still use the script
if (!pubkey)
throw new Error(
- `Error: could not extract a pubkey from ${expression}`
+ `Error: could not extract a pubkey from ${descriptor}`
);
payment = p2pk({ pubkey, network });
}
@@ -226,7 +312,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
if (!keyExpression)
throw new Error(`Error: keyExpression could not me extracted`);
if (canonicalExpression !== `pkh(${keyExpression})`)
- throw new Error(`Error: invalid expression ${expression}`);
+ throw new Error(`Error: invalid expression ${descriptor}`);
expandedExpression = 'pkh(@0)';
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
expansionMap = { '@0': pKE };
@@ -234,7 +320,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
const pubkey = pKE.pubkey;
if (!pubkey)
throw new Error(
- `Error: could not extract a pubkey from ${expression}`
+ `Error: could not extract a pubkey from ${descriptor}`
);
payment = p2pkh({ pubkey, network });
}
@@ -246,7 +332,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
if (!keyExpression)
throw new Error(`Error: keyExpression could not me extracted`);
if (canonicalExpression !== `sh(wpkh(${keyExpression}))`)
- throw new Error(`Error: invalid expression ${expression}`);
+ throw new Error(`Error: invalid expression ${descriptor}`);
expandedExpression = 'sh(wpkh(@0))';
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
expansionMap = { '@0': pKE };
@@ -254,13 +340,13 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
const pubkey = pKE.pubkey;
if (!pubkey)
throw new Error(
- `Error: could not extract a pubkey from ${expression}`
+ `Error: could not extract a pubkey from ${descriptor}`
);
payment = p2sh({ redeem: p2wpkh({ pubkey, network }), network });
redeemScript = payment.redeem?.output;
if (!redeemScript)
throw new Error(
- `Error: could not calculate redeemScript for ${expression}`
+ `Error: could not calculate redeemScript for ${descriptor}`
);
}
}
@@ -271,7 +357,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
if (!keyExpression)
throw new Error(`Error: keyExpression could not me extracted`);
if (canonicalExpression !== `wpkh(${keyExpression})`)
- throw new Error(`Error: invalid expression ${expression}`);
+ throw new Error(`Error: invalid expression ${descriptor}`);
expandedExpression = 'wpkh(@0)';
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
expansionMap = { '@0': pKE };
@@ -279,7 +365,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
const pubkey = pKE.pubkey;
if (!pubkey)
throw new Error(
- `Error: could not extract a pubkey from ${expression}`
+ `Error: could not extract a pubkey from ${descriptor}`
);
payment = p2wpkh({ pubkey, network });
}
@@ -289,7 +375,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
isSegwit = true;
miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
if (!miniscript)
- throw new Error(`Error: could not get miniscript in ${expression}`);
+ throw new Error(`Error: could not get miniscript in ${descriptor}`);
({ expandedMiniscript, expansionMap } = expandMiniscript({
miniscript,
isSegwit,
@@ -318,7 +404,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
redeemScript = payment.redeem?.output;
if (!redeemScript)
throw new Error(
- `Error: could not calculate redeemScript for ${expression}`
+ `Error: could not calculate redeemScript for ${descriptor}`
);
}
}
@@ -329,7 +415,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
isSegwit = false;
miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
if (!miniscript)
- throw new Error(`Error: could not get miniscript in ${expression}`);
+ throw new Error(`Error: could not get miniscript in ${descriptor}`);
if (
allowMiniscriptInP2SH === false &&
//These top-level expressions within sh are allowed within sh.
@@ -372,7 +458,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
isSegwit = true;
miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
if (!miniscript)
- throw new Error(`Error: could not get miniscript in ${expression}`);
+ throw new Error(`Error: could not get miniscript in ${descriptor}`);
({ expandedMiniscript, expansionMap } = expandMiniscript({
miniscript,
isSegwit,
@@ -397,7 +483,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
payment = p2wsh({ redeem: { output: script, network }, network });
}
} else {
- throw new Error(`Error: Could not parse descriptor ${expression}`);
+ throw new Error(`Error: Could not parse descriptor ${descriptor}`);
}
return {
@@ -412,7 +498,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
isRanged,
canonicalExpression
};
- };
+ }
/**
* Expand a miniscript to a generalized form using variables instead of key
@@ -442,7 +528,13 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
});
}
- class Descriptor {
+ /**
+ * 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[] = [];
readonly #signersPubKeys: Buffer[];
@@ -461,7 +553,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
* @throws {Error} - when descriptor is invalid
*/
constructor({
- expression,
+ descriptor,
index,
checksumRequired = false,
allowMiniscriptInP2SH = false,
@@ -470,9 +562,9 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
signersPubKeys
}: {
/**
- * The descriptor string in ASCII format. It may include a "*" to denote an arbitrary index.
+ * The descriptor string in ASCII format. It may include a "*" to denote an arbitrary index (aka ranged descriptors).
*/
- expression: string;
+ descriptor: string;
/**
* The descriptor's index in the case of a range descriptor (must be an integer >=0).
@@ -480,7 +572,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
index?: number;
/**
- * A flag indicating whether the descriptor is required to include a checksum.
+ * An optional flag indicating whether the descriptor is required to include a checksum.
* @defaultValue false
*/
checksumRequired?: boolean;
@@ -504,17 +596,29 @@ 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).
+ * For more details on using this parameter, refer to [this Stack Exchange
+ * answer](https://bitcoin.stackexchange.com/a/118036/89665).
*/
signersPubKeys?: Buffer[];
}) {
this.#network = network;
this.#preimages = preimages;
- if (typeof expression !== 'string')
+ if (typeof descriptor !== 'string')
throw new Error(`Error: invalid descriptor type`);
const expandedResult = expand({
- expression,
+ descriptor,
...(index !== undefined ? { index } : {}),
checksumRequired,
network,
@@ -524,7 +628,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
throw new Error(`Error: index was not provided for ranged descriptor`);
if (!expandedResult.payment)
throw new Error(
- `Error: could not extract a payment from ${expression}`
+ `Error: could not extract a payment from ${descriptor}`
);
this.#payment = expandedResult.payment;
@@ -552,7 +656,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
const pubkey = keyInfo.pubkey;
if (!pubkey)
throw new Error(
- `Error: could not extract a pubkey from ${expression}`
+ `Error: could not extract a pubkey from ${descriptor}`
);
return pubkey;
}
@@ -561,7 +665,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
//We should only miss expansionMap in addr() expressions:
if (!expandedResult.canonicalExpression.match(RE.reAddrAnchored)) {
throw new Error(
- `Error: expansionMap not available for expression ${expression} that is not an address`
+ `Error: expansionMap not available for expression ${descriptor} that is not an address`
);
}
this.#signersPubKeys = [this.getScriptPubKey()];
@@ -611,28 +715,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;
@@ -667,45 +793,84 @@ 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;
}
+
+ /** @deprecated - Use updatePsbtAsInput instead
+ * @hidden
+ */
+ updatePsbt(params: {
+ psbt: Psbt;
+ txHex?: string;
+ txId?: string;
+ value?: number;
+ vout: number;
+ }) {
+ this.updatePsbtAsInput(params);
+ return params.psbt.data.inputs.length - 1;
+ }
+
/**
- * Updates a Psbt where the descriptor describes an utxo.
- * The txHex (nonWitnessUtxo) and vout of the utxo must be passed.
+ * Sets this output as an input of the provided `psbt` and updates the
+ * `psbt` locktime if required by the descriptor.
+ *
+ * `psbt` and `vout` are mandatory. Include `txHex` as well. The pair
+ * `vout` and `txHex` define the transaction and output number this instance
+ * pertains to.
+ *
+ * Though not advised, for Segwit inputs you can pass `txId` and `value`
+ * in lieu of `txHex`. If doing so, ensure `value` accuracy to avoid
+ * potential fee attacks -
+ * [See this issue](https://github.com/bitcoinjs/bitcoinjs-lib/issues/1625).
+ *
+ * Note: Hardware wallets need the [full `txHex` for Segwit](https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd).
*
- * updatePsbt adds an input to the psbt and updates the tx locktime if needed.
- * It also adds a new input to the Psbt based on txHex
- * It returns the number of the input that is added.
- * psbt and vout are mandatory. Also pass txHex.
+ * When unsure, always use `txHex`, and skip `txId` and `value` for safety.
*
- * The following is not recommended but, alternatively, ONLY for Segwit inputs,
- * you can pass txId and value, instead of txHex.
- * If you do so, it is your responsibility to make sure that `value` is
- * correct to avoid possible fee vulnerability attacks:
- * https://github.com/bitcoinjs/bitcoinjs-lib/issues/1625
- * Note that HW wallets require the full txHex also for Segwit anyways:
- * https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd
+ * @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`
*
- * In doubt, simply pass txHex (and you can skip passing txId and value) and
- * you shall be fine.
*/
- updatePsbt({
+ updatePsbtAsInput({
psbt,
txHex,
txId,
@@ -717,7 +882,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`);
}
@@ -728,7 +893,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 } : {}),
@@ -742,7 +907,31 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
witnessScript: this.getWitnessScript(),
redeemScript: this.getRedeemScript()
});
+ const finalizer = ({
+ psbt,
+ validate = true
+ }: {
+ psbt: Psbt;
+ /** Runs further test on the validity of the signatures.
+ * It speeds down the finalization process but makes sure the psbt will
+ * be valid.
+ * @default true */
+ validate?: boolean | undefined;
+ }) => this.finalizePsbtInput({ index, psbt, validate });
+ return finalizer;
}
+
+ /**
+ * Adds this output as an output of the provided `psbt` with the given
+ * value.
+ *
+ * @param psbt - The Partially Signed Bitcoin Transaction.
+ * @param value - The value for the output in satoshis.
+ */
+ updatePsbtAsOutput({ psbt, value }: { psbt: Psbt; value: number }) {
+ psbt.addOutput({ script: this.getScriptPubKey(), value });
+ }
+
#assertPsbtInput({ psbt, index }: { psbt: Psbt; index: number }): void {
const input = psbt.data.inputs[index];
const txInput = psbt.txInputs[index];
@@ -781,6 +970,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,
@@ -788,6 +1008,10 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
}: {
index: number;
psbt: Psbt;
+ /** Runs further test on the validity of the signatures.
+ * It speeds down the finalization process but makes sure the psbt will
+ * be valid.
+ * @default true */
validate?: boolean | undefined;
}): void {
if (
@@ -819,6 +1043,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
@@ -837,18 +1065,56 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
}
}
- return { Descriptor, parseKeyExpression, expand, ECPair, BIP32 };
+ /**
+ * @hidden
+ * @deprecated Use `Output` instead
+ */
+ class Descriptor extends Output {
+ constructor({
+ expression,
+ ...rest
+ }: {
+ expression: string;
+ index?: number;
+ checksumRequired?: boolean;
+ allowMiniscriptInP2SH?: boolean;
+ network?: Network;
+ preimages?: Preimage[];
+ signersPubKeys?: Buffer[];
+ }) {
+ super({ descriptor: expression, ...rest });
+ }
+ }
+
+ return {
+ // deprecated TAG must also be below so it is exported to descriptors.d.ts
+ /** @deprecated */ Descriptor,
+ Output,
+ parseKeyExpression,
+ expand,
+ ECPair,
+ BIP32
+ };
}
-/**
- * The {@link DescriptorsFactory | `DescriptorsFactory`} function internally creates and returns the {@link _Internal_.Descriptor | `Descriptor`} class.
- * This class is specialized for the provided `TinySecp256k1Interface`.
- * Use `DescriptorInstance` to declare instances for this class: `const: DescriptorInstance = new Descriptor();`
- *
- * See the {@link _Internal_.Descriptor | documentation for the internal Descriptor class} for a complete list of available methods.
- */
+
+/** @hidden @deprecated */
type DescriptorConstructor = ReturnType<
typeof DescriptorsFactory
>['Descriptor'];
+/** @hidden @deprecated */
type DescriptorInstance = InstanceType;
-
export { DescriptorInstance, DescriptorConstructor };
+
+type OutputConstructor = ReturnType['Output'];
+/**
+ * The {@link DescriptorsFactory | `DescriptorsFactory`} function internally
+ * creates and returns the {@link _Internal_.Output | `Descriptor`} class.
+ * This class is specialized for the provided `TinySecp256k1Interface`.
+ * Use `OutputInstance` to declare instances for this class:
+ * `const: OutputInstance = new Output();`
+ *
+ * See the {@link _Internal_.Output | documentation for the internal `Output`
+ * class} for a complete list of available methods.
+ */
+type OutputInstance = InstanceType;
+export { OutputInstance, OutputConstructor };
diff --git a/src/index.ts b/src/index.ts
index 562f8f3..1616604 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,31 +3,71 @@
export type { KeyInfo, Expansion } from './types';
import type { Psbt } from 'bitcoinjs-lib';
-import type { DescriptorInstance } from './descriptors';
+import type { DescriptorInstance, OutputInstance } from './descriptors';
export {
DescriptorsFactory,
DescriptorInstance,
- DescriptorConstructor
+ DescriptorConstructor,
+ OutputInstance,
+ OutputConstructor
} from './descriptors';
export { DescriptorChecksum as checksum } from './checksum';
import * as signers from './signers';
export { signers };
-export function finalizePsbt({
+/**
+ * @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:
+ * `finalizePsbt({psbt, outputs })`. In the latter case, `outputs` is an
+ * array of {@link _Internal_.Output | Output elements} ordered in the array by
+ * their respective input index in the `psbt`.
+ */
+function finalizePsbt(params: {
+ psbt: Psbt;
+ outputs: OutputInstance[];
+ validate?: boolean | undefined;
+}): void;
+
+/**
+ * @deprecated
+ * @hidden
+ * To be removed in version 3.0
+ */
+function finalizePsbt(params: {
+ psbt: Psbt;
+ descriptors: DescriptorInstance[];
+ validate?: boolean | undefined;
+}): void;
+/**
+ * @hidden
+ * To be removed in v3.0 and replaced by the version with the signature that
+ * does not accept descriptors
+ */
+function finalizePsbt({
psbt,
+ outputs,
descriptors,
validate = true
}: {
psbt: Psbt;
- descriptors: DescriptorInstance[];
+ outputs?: OutputInstance[];
+ descriptors?: DescriptorInstance[];
validate?: boolean | undefined;
}) {
- descriptors.forEach((descriptor, inputIndex) =>
- descriptor.finalizePsbtInput({ index: inputIndex, psbt, validate })
+ if (descriptors && outputs)
+ throw new Error(`descriptors param has been deprecated`);
+ outputs = descriptors || outputs;
+ if (!outputs) throw new Error(`outputs not provided`);
+ outputs.forEach((output, inputIndex) =>
+ output.finalizePsbtInput({ index: inputIndex, psbt, validate })
);
}
+export { finalizePsbt };
+
export { keyExpressionBIP32, keyExpressionLedger } from './keyExpressions';
import * as scriptExpressions from './scriptExpressions';
export { scriptExpressions };
@@ -37,7 +77,8 @@ import {
getLedgerMasterFingerPrint,
getLedgerXpub,
registerLedgerWallet,
- assertLedgerApp
+ assertLedgerApp,
+ LedgerManager
} from './ledger';
export const ledger = {
getLedgerMasterFingerPrint,
@@ -46,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 35b4804..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';
@@ -31,7 +32,20 @@ const derivePath = (node: BIP32Interface, path: string) => {
};
/**
- * Parses a key expression (xpub, xprv, pubkey or wif) into KeyInfo
+ * Parses a key expression (xpub, xprv, pubkey or wif) into {@link KeyInfo | `KeyInfo`}.
+ *
+ * For example, given this `keyExpression`: `"[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*"`, this is its parsed result:
+ *
+ * ```javascript
+ * {
+ * keyExpression:
+ * "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
+ * keyPath: '/1/2/3/4/*',
+ * originPath: "/49'/0'/0'",
+ * path: "m/49'/0'/0'/1/2/3/4/*",
+ * // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
+ * }
+ * ```
*/
export function parseKeyExpression({
keyExpression,
@@ -41,11 +55,12 @@ export function parseKeyExpression({
network = networks.bitcoin
}: {
keyExpression: string;
+ /** @default networks.bitcoin */
network?: Network;
/**
- * Indicates if this is a SegWit key expression. When set, further checks
- * ensure the public key (if present in the expression) is compressed
- * (33 bytes).
+ * Indicates if this key expression belongs to a a SegWit output. When set,
+ * further checks are done to ensure the public key (if present in the
+ * expression) is compressed (33 bytes).
*/
isSegwit?: boolean;
ECPair: ECPairAPI;
@@ -190,6 +205,36 @@ function assertChangeIndexKeyPath({
throw new Error(`Error: Pass either change and index or a keyPath`);
}
+/**
+ * Constructs a key expression string for a Ledger device from the provided
+ * components.
+ *
+ * This function assists in crafting key expressions tailored for Ledger
+ * hardware wallets. It fetches the master fingerprint and xpub for a
+ * specified origin path and then combines them with the input parameters.
+ *
+ * For detailed understanding and examples of terms like `originPath`,
+ * `change`, and `keyPath`, refer to the documentation of
+ * {@link _Internal_.ParseKeyExpression | ParseKeyExpression}, which consists
+ * of the reverse procedure.
+ *
+ * @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,
@@ -204,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({
@@ -219,6 +287,14 @@ export async function keyExpressionLedger({
else return `${keyRoot}/${change}/${index}`;
}
+/**
+ * Constructs a key expression string from its constituent components.
+ *
+ * This function essentially performs the reverse operation of
+ * {@link _Internal_.ParseKeyExpression | ParseKeyExpression}. For detailed
+ * explanations and examples of the terms used here, refer to
+ * {@link _Internal_.ParseKeyExpression | ParseKeyExpression}.
+ */
export function keyExpressionBIP32({
masterNode,
originPath,
@@ -232,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 1031523..aeb652f 100644
--- a/src/ledger.ts
+++ b/src/ledger.ts
@@ -21,12 +21,17 @@
* 4) Since all originPaths must be the same and originPaths for the Ledger are
* necessary, a Ledger device can only sign at most 1 key per policy and input.
*
- * All the conditions above are checked in function descriptorToLedgerFormat.
+ * All the conditions above are checked in function ledgerPolicyFromOutput.
*/
-import type { DescriptorInstance } 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`.
@@ -93,14 +98,33 @@ async function ledgerAppInfo(transport: any) {
return { name, version, flags, format };
}
+/**
+ * Verifies if the Ledger device is connected, if the required Bitcoin App is opened,
+ * and if the version of the app meets the minimum requirements.
+ *
+ * @throws Will throw an error if the Ledger device is not connected, the required
+ * Bitcoin App is not opened, or if the version is below the required number.
+ *
+ * @returns Promise - A promise that resolves if all assertions pass, or throws otherwise.
+ */
export async function assertLedgerApp({
transport,
name,
minVersion
}: {
+ /**
+ * Connection transport with the Ledger device.
+ * One of these: https://github.com/LedgerHQ/ledger-live#libs---libraries
+ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transport: any;
+ /**
+ * The name of the Bitcoin App. "Bitcoin" for mainnet or "Bitcoin Test" for testnet.
+ */
name: string;
+ /**
+ * The minimum acceptable version of the Bitcoin App in semver format (major.minor.patch).
+ */
minVersion: string;
}): Promise {
const { name: openName, version } = await ledgerAppInfo(transport);
@@ -164,19 +188,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 +252,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 +271,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');
@@ -223,7 +312,176 @@ export async function getLedgerXpub({
}
/**
- * Takes a descriptor and gets its Ledger Wallet Policy, that is, its keyRoots and template.
+ * 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.
+ *
* keyRoots and template follow Ledger's specifications:
* https://github.com/LedgerHQ/app-bitcoin-new/blob/develop/doc/wallet.md
*
@@ -247,19 +505,19 @@ export async function getLedgerXpub({
* This function takes into account all the considerations regarding Ledger
* policy implementation details expressed in the header of this file.
*/
-export async function descriptorToLedgerFormat({
- descriptor,
+export async function ledgerPolicyFromOutput({
+ output,
ledgerClient,
ledgerState
}: {
- descriptor: DescriptorInstance;
+ output: OutputInstance;
ledgerClient: unknown;
ledgerState: LedgerState;
}): Promise<{ ledgerTemplate: string; keyRoots: string[] } | null> {
- const expandedExpression = descriptor.expand().expandedExpression;
- const expansionMap = descriptor.expand().expansionMap;
+ const expandedExpression = output.expand().expandedExpression;
+ const expansionMap = output.expand().expansionMap;
if (!expandedExpression || !expansionMap)
- throw new Error(`Error: invalid descriptor`);
+ throw new Error(`Error: invalid output`);
const ledgerMasterFingerprint = await getLedgerMasterFingerPrint({
ledgerClient,
@@ -337,12 +595,36 @@ export async function descriptorToLedgerFormat({
}
/**
- * It registers a policy based on a descriptor. It stores it in ledgerState.
+ * Registers a policy based on a provided descriptor.
*
- * If the policy was already registered, it does not register it.
- * If the policy is standard, it does not register it.
+ * 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".
*
- **/
+ * 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({
+ descriptor,
+ ledgerManager,
+ policyName
+}: {
+ descriptor: string;
+ ledgerManager: LedgerManager;
+ /** The Name we want to assign to this specific policy */
+ policyName: string;
+}): Promise;
+
+/**
+ * @deprecated
+ * @hidden
+ */
export async function registerLedgerWallet({
descriptor,
ledgerClient,
@@ -353,27 +635,67 @@ export async function registerLedgerWallet({
ledgerClient: unknown;
ledgerState: LedgerState;
policyName: string;
+}): Promise;
+
+/**
+ * To be removed in v3.0 and replaced by a version that does not accept
+ * descriptors
+ * @hidden
+ **/
+export async function registerLedgerWallet({
+ descriptor,
+ ledgerClient,
+ ledgerState,
+ ledgerManager,
+ policyName
+}: {
+ descriptor: DescriptorInstance | string;
+ ledgerClient?: unknown;
+ ledgerState?: LedgerState;
+ ledgerManager?: LedgerManager;
+ policyName: string;
}) {
+ 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`);
- const result = await descriptorToLedgerFormat({
- descriptor,
+
+ 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,
ledgerState
});
- if (await ledgerPolicyFromStandard({ descriptor, ledgerClient, ledgerState }))
+ if (await ledgerPolicyFromStandard({ output, ledgerClient, ledgerState }))
return;
- if (!result)
- throw new Error(`Error: descriptor does not have a ledger input`);
+ if (!result) throw new Error(`Error: output does not have a ledger input`);
const { ledgerTemplate, keyRoots } = result;
if (!ledgerState.policies) ledgerState.policies = [];
let walletPolicy, policyHmac;
//Search in ledgerState first
const policy = await ledgerPolicyFromState({
- descriptor,
+ output,
ledgerClient,
ledgerState
});
@@ -401,16 +723,16 @@ export async function registerLedgerWallet({
* Retrieve a standard ledger policy or null if it does correspond.
**/
export async function ledgerPolicyFromStandard({
- descriptor,
+ output,
ledgerClient,
ledgerState
}: {
- descriptor: DescriptorInstance;
+ output: OutputInstance;
ledgerClient: unknown;
ledgerState: LedgerState;
}): Promise {
- const result = await descriptorToLedgerFormat({
- descriptor,
+ const result = await ledgerPolicyFromOutput({
+ output,
ledgerClient,
ledgerState
});
@@ -421,7 +743,7 @@ export async function ledgerPolicyFromStandard({
isLedgerStandard({
ledgerTemplate,
keyRoots,
- network: descriptor.getNetwork()
+ network: output.getNetwork()
})
)
return { ledgerTemplate, keyRoots };
@@ -450,21 +772,20 @@ export function comparePolicies(policyA: LedgerPolicy, policyB: LedgerPolicy) {
* Retrieve a ledger policy from ledgerState or null if it does not exist yet.
**/
export async function ledgerPolicyFromState({
- descriptor,
+ output,
ledgerClient,
ledgerState
}: {
- descriptor: DescriptorInstance;
+ output: OutputInstance;
ledgerClient: unknown;
ledgerState: LedgerState;
}): Promise {
- const result = await descriptorToLedgerFormat({
- descriptor,
+ const result = await ledgerPolicyFromOutput({
+ output,
ledgerClient,
ledgerState
});
- if (!result)
- throw new Error(`Error: descriptor does not have a ledger input`);
+ if (!result) throw new Error(`Error: output does not have a ledger input`);
const { ledgerTemplate, keyRoots } = result;
if (!ledgerState.policies) ledgerState.policies = [];
//Search in ledgerState:
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 8dcb85a..b995483 100644
--- a/src/signers.ts
+++ b/src/signers.ts
@@ -11,8 +11,10 @@ import {
LedgerPolicy,
ledgerPolicyFromState,
ledgerPolicyFromStandard,
- descriptorToLedgerFormat,
- LedgerState
+ ledgerPolicyFromOutput,
+ LedgerState,
+ LedgerManager,
+ ledgerPolicyFromPsbtInput
} from './ledger';
type DefaultDescriptorTemplate =
| 'pkh(@0/**)'
@@ -77,6 +79,26 @@ 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,
+ ledgerManager
+}: {
+ psbt: Psbt;
+ index: number;
+ ledgerManager: LedgerManager;
+}): Promise;
+
+/**
+ * @deprecated
+ * @hidden
+ */
export async function signInputLedger({
psbt,
index,
@@ -89,7 +111,40 @@ export async function signInputLedger({
descriptor: DescriptorInstance;
ledgerClient: unknown;
ledgerState: LedgerState;
+}): Promise;
+
+/**
+ * To be removed in v3.0 and replaced by a version that does not accept
+ * descriptor
+ * @hidden
+ */
+export async function signInputLedger({
+ psbt,
+ index,
+ descriptor,
+ ledgerClient,
+ ledgerState,
+ ledgerManager
+}: {
+ psbt: Psbt;
+ index: number;
+ descriptor?: DescriptorInstance;
+ ledgerClient?: unknown;
+ ledgerState?: LedgerState;
+ ledgerManager?: LedgerManager;
}): Promise {
+ 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
@@ -97,49 +152,83 @@ export async function signInputLedger({
if (!(ledgerClient instanceof AppClient))
throw new Error(`Error: pass a valid ledgerClient`);
- const result = await descriptorToLedgerFormat({
- descriptor,
- ledgerClient,
- ledgerState
- });
- if (!result)
- throw new Error(`Error: descriptor does not have a ledger input`);
- const { ledgerTemplate, keyRoots } = result;
-
let ledgerSignatures;
- const standardPolicy = await ledgerPolicyFromStandard({
- descriptor,
- 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({
- descriptor,
+ 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:
@@ -148,9 +237,28 @@ 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
-//it throws if it cannot sign any input.
+/**
+ * Signs the inputs of the `psbt` where the keys are controlled by a Ledger
+ * device.
+ *
+ * `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,
+ ledgerManager
+}: {
+ psbt: Psbt;
+ ledgerManager: LedgerManager;
+}): Promise;
+
+/**
+ * @deprecated
+ * @hidden
+ */
export async function signLedger({
psbt,
descriptors,
@@ -161,31 +269,66 @@ export async function signLedger({
descriptors: DescriptorInstance[];
ledgerClient: unknown;
ledgerState: LedgerState;
+}): Promise;
+
+/**
+ * To be removed in v3.0 and replaced by a version that does not accept
+ * descriptors
+ * @hidden
+ */
+export async function signLedger({
+ psbt,
+ descriptors,
+ ledgerClient,
+ ledgerState,
+ ledgerManager
+}: {
+ psbt: Psbt;
+ descriptors?: DescriptorInstance[];
+ ledgerClient?: unknown;
+ ledgerState?: LedgerState;
+ ledgerManager?: LedgerManager;
}): Promise {
+ 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 descriptor of descriptors) {
- const policy =
- (await ledgerPolicyFromState({
- descriptor,
- ledgerClient,
- ledgerState
- })) ||
- (await ledgerPolicyFromStandard({
- descriptor,
- 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/src/types.ts b/src/types.ts
index ce12541..3b18e07 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -27,6 +27,9 @@ export type TimeConstraints = {
nSequence: number | undefined;
};
+/**
+ * See {@link _Internal_.ParseKeyExpression | ParseKeyExpression}.
+ */
export type KeyInfo = {
keyExpression: string;
pubkey?: Buffer; //Must be set unless this corresponds to a ranged-descriptor
@@ -38,6 +41,35 @@ export type KeyInfo = {
path?: string; //The complete path from the master. Format is: "m/val/val/...", starting with an m/, and where val are integers or integers followed by a tilde ', for the hardened case
};
+/**
+ * An `ExpansionMap` contains destructured information of a descritptor expression.
+ *
+ * For example, this descriptor `sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))` has the following
+ * `expandedExpression`: `sh(wsh(andor(pk(@0),older(8640),pk(@1))))`
+ *
+ * `key`'s are set using this format: `@i`, where `i` is an integer starting from `0` assigned by parsing and retrieving keys from the descriptor from left to right.
+ *
+ * For the given example, the `ExpansionMap` is:
+ *
+ * ```javascript
+ * {
+ * '@0': {
+ * keyExpression:
+ * '0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
+ * },
+ * '@1': {
+ * keyExpression:
+ * "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
+ * keyPath: '/1/2/3/4/*',
+ * originPath: "/49'/0'/0'",
+ * path: "m/49'/0'/0'/1/2/3/4/*",
+ * // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
+ * }
+ * }
+ *```
+ *
+ *
+ */
export type ExpansionMap = {
//key will have this format: @i, where i is an integer
[key: string]: KeyInfo;
@@ -77,6 +109,11 @@ export interface TinySecp256k1Interface {
privateNegate(d: Uint8Array): Uint8Array;
}
+/**
+ * `DescriptorsFactory` creates and returns the {@link DescriptorsFactory | `expand()`}
+ * function that parses a descriptor expression and destructures it
+ * into its elemental parts. `Expansion` is the type that `expand()` returns.
+ */
export type Expansion = {
/**
* The corresponding [bitcoinjs-lib Payment](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts) for the provided expression, if applicable.
@@ -85,6 +122,7 @@ export type Expansion = {
/**
* The expanded descriptor expression.
+ * See {@link ExpansionMap ExpansionMap} for a detailed explanation.
*/
expandedExpression?: string;
@@ -95,16 +133,19 @@ export type Expansion = {
/**
* A map of key expressions in the descriptor to their corresponding expanded keys.
+ * See {@link ExpansionMap ExpansionMap} for a detailed explanation.
*/
expansionMap?: ExpansionMap;
/**
- * A boolean indicating whether the descriptor represents a SegWit script.
+ * A boolean indicating whether the descriptor uses SegWit.
*/
isSegwit?: boolean;
/**
* The expanded miniscript, if any.
+ * It corresponds to the `expandedExpression` without the top-level script
+ * expression.
*/
expandedMiniscript?: string;
@@ -119,63 +160,49 @@ export type Expansion = {
witnessScript?: Buffer;
/**
- * Whether this expression represents a ranged-descriptor.
+ * Whether the descriptor is a ranged-descriptor.
*/
isRanged: boolean;
/**
- * This is the preferred or authoritative representation of the descriptor expression.
+ * This is the preferred or authoritative representation of an output
+ * descriptor expression.
+ * It removes the checksum and, if it is a ranged-descriptor, it
+ * particularizes it to its index.
*/
canonicalExpression: string;
};
/**
- * The {@link DescriptorsFactory | `DescriptorsFactory`} function creates and returns an implementation of the `Expand` interface.
- * This returned implementation is tailored for the provided `TinySecp256k1Interface`.
- */
-export interface Expand {
- (params: {
- /**
- * The descriptor expression to be expanded.
- */
- expression: string;
-
- /**
- * The descriptor index, if ranged.
- */
- index?: number;
-
- /**
- * A flag indicating whether the descriptor is required to include a checksum.
- * @defaultValue false
- */
- checksumRequired?: boolean;
-
- /**
- * The Bitcoin network to use.
- * @defaultValue `networks.bitcoin`
- */
- network?: Network;
-
- /**
- * Flag to allow miniscript in P2SH.
- * @defaultValue false
- */
- allowMiniscriptInP2SH?: boolean;
- }): Expansion;
-}
-
-/**
- * The {@link DescriptorsFactory | `DescriptorsFactory`} function creates and returns an implementation of the `ParseKeyExpression` interface.
- * This returned implementation is tailored for the provided `TinySecp256k1Interface`.
+ * The {@link DescriptorsFactory | `DescriptorsFactory`} function creates and
+ * returns the `parseKeyExpression` function, which is an implementation of this
+ * interface.
+ *
+ * It parses and destructures a key expression string (xpub, xprv, pubkey or
+ * wif) into {@link KeyInfo | `KeyInfo`}.
+ *
+ * For example, given this `keyExpression`: `[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*`, this is the parsed result:
+ *
+ * ```javascript
+ * {
+ * keyExpression:
+ * "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
+ * keyPath: '/1/2/3/4/*',
+ * originPath: "/49'/0'/0'",
+ * path: "m/49'/0'/0'/1/2/3/4/*",
+ * // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
+ * }
+ * ```
+ *
+ * See {@link KeyInfo} for the complete list of elements retrieved by this function.
*/
export interface ParseKeyExpression {
(params: {
keyExpression: string;
/**
- * Indicates if this is a SegWit key expression. When set, further checks
- * ensure the public key (if present in the expression) is compressed
- * (33 bytes).
+ * Indicates if this key expression belongs to a a SegWit output. When set,
+ * further checks are done to ensure the public key (if present in the
+ * expression) is compressed (33 bytes).
*/
isSegwit?: boolean;
network?: Network;
diff --git a/test/descriptors-deprecated.test.ts b/test/descriptors-deprecated.test.ts
new file mode 100644
index 0000000..ccc585f
--- /dev/null
+++ b/test/descriptors-deprecated.test.ts
@@ -0,0 +1,81 @@
+// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
+// Distributed under the MIT software license
+
+// This file still needs to be properly converted to Typescript:
+/* eslint-disable @typescript-eslint/ban-ts-comment */
+// @ts-nocheck
+/* eslint-enable @typescript-eslint/ban-ts-comment */
+
+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);
+
+function partialDeepEqual(obj) {
+ if (typeof obj === 'object' && obj !== null && obj.constructor === Object) {
+ const newObj = {};
+ for (const key in obj) {
+ newObj[key] = partialDeepEqual(obj[key]);
+ }
+ return expect.objectContaining(newObj);
+ } else {
+ return obj;
+ }
+}
+
+for (const fixtures of [customFixtures, bitcoinCoreFixtures]) {
+ describe(`Deprecated API - Parse valid ${
+ fixtures === customFixtures ? 'custom fixtures' : 'Bitcoin Core fixtures'
+ }`, () => {
+ for (const fixture of fixtures.valid) {
+ fixture.expression = fixture.descriptor;
+ delete fixture.descriptor;
+ test(`Parse valid ${fixture.expression}`, () => {
+ const descriptor = new Descriptor(fixture);
+ let expansion;
+ expect(() => {
+ expansion = expand({
+ expression: fixture.expression,
+ network: fixture.network,
+ allowMiniscriptInP2SH: fixture.allowMiniscriptInP2SH
+ });
+ }).not.toThrow();
+
+ if (fixture.expansion) {
+ expect(expansion).toEqual(partialDeepEqual(fixture.expansion));
+ }
+
+ if (!fixture.script && !fixture.address)
+ throw new Error(`Error: pass a valid test for ${fixture.expression}`);
+ if (fixture.script) {
+ expect(descriptor.getScriptPubKey().toString('hex')).toEqual(
+ fixture.script
+ );
+ }
+ if (fixture.address) {
+ expect(descriptor.getAddress()).toEqual(fixture.address);
+ }
+ });
+ }
+ });
+ describe(`Deprecated API - Parse invalid ${
+ fixtures === customFixtures ? 'custom fixtures' : 'Bitcoin Core fixtures'
+ }`, () => {
+ for (const fixture of fixtures.invalid) {
+ fixture.expression = fixture.descriptor;
+ delete fixture.descriptor;
+ test(`Parse invalid ${fixture.expression}`, () => {
+ if (typeof fixture.throw !== 'string') {
+ expect(() => {
+ new Descriptor(fixture);
+ }).toThrow();
+ } else {
+ expect(() => {
+ new Descriptor(fixture);
+ }).toThrow(fixture.throw);
+ }
+ });
+ }
+ });
+}
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-deprecated.ts b/test/integration/ledger-deprecated.ts
new file mode 100644
index 0000000..5efd3f3
--- /dev/null
+++ b/test/integration/ledger-deprecated.ts
@@ -0,0 +1,297 @@
+// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
+// Distributed under the MIT software license
+
+/*
+This test will create a set of UTXOs for a Ledger wallet:
+ * 1 P2PKH output on an internal address, change 1, in account 0, index 0
+ * 1 P2PKH output on an external address, change 0, in account 0, index 0
+ * 1 P2WSH output corresponding to a script based on this policy:
+ and(and(and(pk(@ledger),pk(@soft)),older(${OLDER})),sha256(${SHA256_DIGEST})),
+ which means it can be spent by co-signing with a Ledger and a Software
+ wallet after BLOCKS blocks since it was mined and providing a preimage for
+ a certain SHA256_DIGEST.
+
+In the test, the UTXOs are created, funded (each one with UTXO_VALUE),
+and finally spent by co-signing (Ledger + Soft) a partially-signed Bitcoin
+Transaction (PSBT), finalizing it and broadcasting it to the network.
+
+================================================================================
+
+To run this test, follow these steps:
+
+1. Clone the `descriptors` repository by running
+ `git clone https://github.com/bitcoinerlab/descriptors.git`.
+
+2. Install the necessary dependencies by running `npm install`.
+
+3. Ensure that you are running a Bitcoin regtest node and have set up this
+ Express-based bitcoind manager: https://github.com/bitcoinjs/regtest-server
+ running on 127.0.0.1:8080.
+ You can use the following steps to install and run a Docker image already
+ configured with the mentioned services:
+
+ docker pull junderw/bitcoinjs-regtest-server
+ docker run -d -p 127.0.0.1:8080:8080 junderw/bitcoinjs-regtest-server
+
+4. Connect your Ledger device, unlock it, and open the Bitcoin Testnet 2.1 App.
+
+5. You are now ready to run the test:
+ npx ts-node test/integration/ledger.ts
+
+*/
+
+console.log(
+ 'Ledger integration tests: 2 pkh inputs (one internal & external addresses) + 1 miniscript input (co-signed with a software wallet) -> 1 output'
+);
+import Transport from '@ledgerhq/hw-transport-node-hid';
+import { networks, Psbt } from 'bitcoinjs-lib';
+import { mnemonicToSeedSync } from 'bip39';
+const { encode: olderEncode } = require('bip68');
+import { RegtestUtils } from 'regtest-client';
+const regtestUtils = new RegtestUtils();
+
+const NETWORK = networks.regtest;
+
+const UTXO_VALUE = 2e4;
+const FINAL_ADDRESS = regtestUtils.RANDOM_ADDRESS;
+const FEE = 1000;
+const BLOCKS = 5;
+const OLDER = olderEncode({ blocks: BLOCKS });
+const PREIMAGE =
+ '107661134f21fc7c02223d50ab9eb3600bc3ffc3712423a1e47bb1f9a9dbf55f';
+const SHA256_DIGEST =
+ '6c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd5333';
+
+const POLICY = `and(and(and(pk(@ledger),pk(@soft)),older(${OLDER})),sha256(${SHA256_DIGEST}))`;
+
+const WSH_ORIGIN_PATH = `/69420'/1'/0'`; //Actually, this could be any random path. Note that the Ledger will show a warning for non-standardness, though.
+const WSH_RECEIVE_INDEX = 0;
+
+const SOFT_MNEMONIC =
+ 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
+
+import * as ecc from '@bitcoinerlab/secp256k1';
+import {
+ finalizePsbt,
+ signers,
+ keyExpressionBIP32,
+ keyExpressionLedger,
+ scriptExpressions,
+ DescriptorsFactory,
+ DescriptorInstance,
+ ledger,
+ LedgerState
+} from '../../dist/';
+const { signLedger, signBIP32 } = signers;
+const { pkhLedger } = scriptExpressions;
+const { registerLedgerWallet, assertLedgerApp } = ledger;
+import { AppClient } from 'ledger-bitcoin';
+const { Descriptor, BIP32 } = DescriptorsFactory(ecc);
+
+import { compilePolicy } from '@bitcoinerlab/miniscript';
+
+//Create the psbt that will spend the pkh and wsh outputs and send funds to FINAL_ADDRESS:
+const psbt = new Psbt({ network: NETWORK });
+
+//Build the miniscript-based descriptor.
+//POLICY will be: 'and(and(and(pk(@ledger),pk(@soft)),older(5)),sha256(6c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd5333))'
+//and miniscript: 'and_v(v:sha256(6c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd5333),and_v(and_v(v:pk(@ledger),v:pk(@soft)),older(5)))'
+const { miniscript, issane }: { miniscript: string; issane: boolean } =
+ compilePolicy(POLICY);
+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[] = [];
+
+(async () => {
+ let transport;
+ try {
+ transport = await Transport.create(3000, 3000);
+ } catch (err) {
+ throw new Error(`Error: Ledger device not detected`);
+ }
+ //Throw if not running Bitcoin Test >= 2.1.0
+ await assertLedgerApp({
+ transport,
+ name: 'Bitcoin Test',
+ minVersion: '2.1.0'
+ });
+
+ const ledgerClient = new AppClient(transport);
+ //The Ledger is stateless. We keep state externally (keeps track of masterFingerprint, xpubs, wallet policies, ...)
+ const ledgerState: LedgerState = {};
+
+ //Let's create the utxos. First create a descriptor expression using a Ledger.
+ //pkhExternalExpression will be something like this:
+ //pkh([1597be92/44'/1'/0']tpubDCxfn3TkomFUmqNzKq5AEDS6VHA7RupajLi38JkahFrNeX3oBGp2C7SVWi5a1kr69M8GpeqnGkgGLdja5m5Xbe7E87PEwR5kM2PWKcSZMoE/0/0)
+ const pkhExternalExpression: string = await pkhLedger({
+ ledgerClient,
+ ledgerState,
+ network: NETWORK,
+ account: 0,
+ change: 0,
+ index: 0
+ });
+ const pkhExternalDescriptor = new Descriptor({
+ network: NETWORK,
+ expression: pkhExternalExpression
+ });
+ //Fund this utxo. regtestUtils communicates with the regtest node manager on port 8080.
+ ({ txId, vout } = await regtestUtils.faucet(
+ pkhExternalDescriptor.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;
+
+ //Repeat the same for another pkh change address:
+ const pkhChangeExpression = await pkhLedger({
+ ledgerClient,
+ ledgerState,
+ network: NETWORK,
+ account: 0,
+ change: 1,
+ index: 0
+ });
+ const pkhChangeDescriptor = new Descriptor({
+ network: NETWORK,
+ expression: pkhChangeExpression
+ });
+ ({ txId, vout } = await regtestUtils.faucet(
+ pkhChangeDescriptor.getAddress(),
+ UTXO_VALUE
+ ));
+ txHex = (await regtestUtils.fetch(txId)).txHex;
+ inputIndex = pkhChangeDescriptor.updatePsbt({ psbt, txHex, vout });
+ psbtInputDescriptors[inputIndex] = pkhChangeDescriptor;
+
+ //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);
+
+ //Let's prepare the wsh utxo. First create the Ledger and Soft key expressions
+ //that will be used to co-sign the wsh output.
+ //First, create a ranged key expression (index: '*') using the software wallet
+ //on the WSH_ORIGIN_PATH origin path.
+ //We could have also created a non-ranged key expression by providing a number
+ //to index.
+ //softKeyExpression will be something like this:
+ //[73c5da0a/69420'/1'/0']tpubDDB5ZuMuWmdzs7r4h58fwZQ1eYJvziXaLMiAfHYrAev3jFrfLtsYsu7Cp1hji8KcG9z9CcvHe1FfkvpsjbvMd2JTLwFkwXQCYjTZKGy8jWg/0/*
+ const softKeyExpression: string = keyExpressionBIP32({
+ masterNode,
+ originPath: WSH_ORIGIN_PATH,
+ change: 0,
+ index: '*'
+ });
+ //Create the equivalent ranged key expression using the Ledger wallet.
+ //ledgerKeyExpression will be something like this:
+ //[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,
+ originPath: WSH_ORIGIN_PATH,
+ change: 0,
+ index: '*'
+ });
+
+ //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
+ .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,
+ 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(),
+ UTXO_VALUE
+ ));
+ txHex = (await regtestUtils.fetch(txId)).txHex;
+
+ //Now add a the input to the psbt (including bip32 derivation info & sequence) and
+ //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;
+
+ //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.
+ psbt.addOutput({ address: FINAL_ADDRESS, value: UTXO_VALUE * 3 - FEE });
+
+ //=============
+ //Register Ledger policies of non-standard descriptors.
+ //Registration is stored in ledgerState and is a necessary step before
+ //signing with non-standard policies when using a Ledger wallet.
+ //registerLedgerWallet internally takes all the necessary steps to register
+ //the generalized Ledger format: a policy template finished with /** and its keyRoots.
+ //So, even though this wallet policy is created using a descriptor representing
+ //an external address, the policy will be used interchangeably with internal
+ //and external addresses.
+ await registerLedgerWallet({
+ ledgerClient,
+ ledgerState,
+ descriptor: miniscriptDescriptor,
+ policyName: 'BitcoinerLab'
+ });
+
+ //=============
+ //Sign the psbt with the Ledger. The relevant wallet policy is automatically
+ //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
+ });
+ //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.
+ //finalizePsbt uses the miniscript satisfier from @bitcoinerlab/miniscript to
+ //create the scriptWitness among other things.
+ finalizePsbt({ psbt, descriptors: psbtInputDescriptors });
+
+ //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
+ await regtestUtils.mine(BLOCKS);
+ //Broadcast the tx:
+ const spendTx = psbt.extractTransaction();
+ const resultSpend = await regtestUtils.broadcast(spendTx.toHex());
+ //Mine it
+ await regtestUtils.mine(1);
+ //Verify that the tx was accepted. This will throw if not ok:
+ await regtestUtils.verify({
+ txId: spendTx.getId(),
+ address: FINAL_ADDRESS,
+ vout: 0,
+ value: UTXO_VALUE * 3 - FEE
+ });
+
+ console.log({
+ result: resultSpend === null ? 'success' : resultSpend,
+ psbt: psbt.toBase64(),
+ tx: spendTx.toHex()
+ });
+})();
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-deprecated.ts b/test/integration/miniscript-deprecated.ts
new file mode 100644
index 0000000..0803e2e
--- /dev/null
+++ b/test/integration/miniscript-deprecated.ts
@@ -0,0 +1,181 @@
+// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
+// Distributed under the MIT software license
+
+//npm run test:integration
+
+import { networks, Psbt, address } from 'bitcoinjs-lib';
+import { mnemonicToSeedSync } from 'bip39';
+const { encode: afterEncode } = require('bip65');
+const { encode: olderEncode } = require('bip68');
+import { RegtestUtils } from 'regtest-client';
+const regtestUtils = new RegtestUtils();
+
+const BLOCKS = 5;
+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 SOFT_MNEMONIC =
+ 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
+const POLICY = (older: number, after: number) =>
+ `or(and(pk(@olderKey),older(${older})),and(pk(@afterKey),after(${after})))`;
+
+console.log(
+ `Miniscript integration tests: ${POLICY.toString().match(/`([^`]*)`/)![1]}`
+);
+
+import * as ecc from '@bitcoinerlab/secp256k1';
+import { DescriptorsFactory, keyExpressionBIP32, signers } from '../../dist/';
+import { compilePolicy } from '@bitcoinerlab/miniscript';
+const { signBIP32, signECPair } = signers;
+
+const { Descriptor, BIP32, ECPair } = DescriptorsFactory(ecc);
+
+const masterNode = BIP32.fromSeed(mnemonicToSeedSync(SOFT_MNEMONIC), NETWORK);
+const ecpair = ECPair.makeRandom();
+
+const keys: {
+ [key: string]: {
+ originPath: string;
+ keyPath: string;
+ };
+} = {
+ '@olderKey': { originPath: "/0'/1'/0'", keyPath: '/0' },
+ '@afterKey': { originPath: "/0'/1'/1'", keyPath: '/1' }
+};
+
+(async () => {
+ //The 3 for loops below test all possible combinations of
+ //signer type (BIP32 or ECPair), top-level scripts (sh, wsh, sh-wsh) and
+ //who is spending the tx: the "older" or the "after" branch
+ for (const keyExpressionType of ['BIP32', 'ECPair']) {
+ for (const template of [`sh(SCRIPT)`, `wsh(SCRIPT)`, `sh(wsh(SCRIPT))`]) {
+ for (const spendingBranch of Object.keys(keys)) {
+ const currentBlockHeight = await regtestUtils.height();
+ const after = afterEncode({ blocks: currentBlockHeight + BLOCKS });
+ const older = olderEncode({ blocks: BLOCKS }); //relative locktime (sequence)
+ //The policy below has been selected for the tests because it has 2 spending
+ //branches: the "after" and the "older" branch.
+ //Note that the hash to be signed depends on the nSequence and nLockTime
+ //values, which is different on each branch.
+ //This makes it an interesting test scenario.
+ const policy = POLICY(older, after);
+ const { miniscript: expandedMiniscript, issane } =
+ compilePolicy(policy);
+ if (!issane)
+ throw new Error(
+ `Error: miniscript ${expandedMiniscript} from policy ${policy} is not sane`
+ );
+
+ //Note that the hash to be signed is different depending on how we decide
+ //to spend the script.
+ //Here we decide how are we going to spend the script.
+ //spendingBranch is either @olderKey or @afterKey.
+ //Use signersPubKeys in Descriptor's constructor to account for this
+ let miniscript = expandedMiniscript;
+ const signersPubKeys: Buffer[] = [];
+ for (const key in keys) {
+ const keyValue = keys[key];
+ if (!keyValue) throw new Error();
+ const { originPath, keyPath } = keyValue;
+ const keyExpression = keyExpressionBIP32({
+ masterNode,
+ originPath,
+ keyPath
+ });
+ const node = masterNode.derivePath(`m${originPath}${keyPath}`);
+ const pubkey = node.publicKey;
+ if (key === spendingBranch) {
+ if (keyExpressionType === 'BIP32') {
+ miniscript = miniscript.replace(
+ new RegExp(key, 'g'),
+ keyExpression
+ );
+ signersPubKeys.push(pubkey);
+ } else {
+ miniscript = miniscript.replace(
+ new RegExp(key, 'g'),
+ ecpair.publicKey.toString('hex')
+ );
+
+ signersPubKeys.push(ecpair.publicKey);
+ }
+ } else {
+ //For the non spending branch we can simply use the pubKey as key expressions
+ miniscript = miniscript.replace(
+ new RegExp(key, 'g'),
+ pubkey.toString('hex')
+ );
+ }
+ }
+ const expression = template.replace('SCRIPT', miniscript);
+ const descriptor = new Descriptor({
+ expression,
+ //Use signersPubKeys to mark which spending path will be used
+ //(which pubkey must be used)
+ signersPubKeys,
+ allowMiniscriptInP2SH: true, //Default is false. Activated to test sh(SCRIPT).
+ network: NETWORK
+ });
+
+ const { txId, vout } = await regtestUtils.faucetComplex(
+ descriptor.getScriptPubKey(),
+ INITIAL_VALUE
+ );
+ const { txHex } = await regtestUtils.fetch(txId);
+ const psbt = new Psbt();
+ const index = descriptor.updatePsbt({ psbt, vout, txHex });
+ psbt.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE });
+ if (keyExpressionType === 'BIP32') signBIP32({ masterNode, psbt });
+ else signECPair({ ecpair, psbt });
+ descriptor.finalizePsbtInput({ index, psbt });
+ const spendTx = psbt.extractTransaction();
+ //Now let's mine BLOCKS - 1 and see how the node complains about
+ //trying to broadcast it now.
+ await regtestUtils.mine(BLOCKS - 1);
+ try {
+ await regtestUtils.broadcast(spendTx.toHex());
+ throw new Error(`Error: mining BLOCKS - 1 did not fail`);
+ } catch (error) {
+ const expectedErrorMessage =
+ spendingBranch === '@olderKey' ? 'non-BIP68-final' : 'non-final';
+ if (
+ error instanceof Error &&
+ error.message !== expectedErrorMessage
+ ) {
+ throw new Error(error.message);
+ }
+ }
+ //Mine the last block needed
+ await regtestUtils.mine(1);
+ await regtestUtils.broadcast(spendTx.toHex());
+ await regtestUtils.verify({
+ txId: spendTx.getId(),
+ address: FINAL_ADDRESS,
+ vout: 0,
+ value: FINAL_VALUE
+ });
+ //Verify the final locking and sequence depending on the branch
+ if (spendingBranch === '@afterKey' && spendTx.locktime !== after)
+ throw new Error(`Error: final locktime was not correct`);
+ if (
+ spendingBranch === '@olderKey' &&
+ spendTx.ins[0]?.sequence !== older
+ )
+ throw new Error(`Error: final sequence was not correct`);
+ console.log(`\nDescriptor: ${expression}`);
+ console.log(
+ `Branch: ${spendingBranch}, ${keyExpressionType} signing, tx locktime: ${
+ psbt.locktime
+ }, input sequence: ${psbt.txInputs?.[0]?.sequence?.toString(
+ 16
+ )}, ${descriptor
+ .expand()
+ .expandedExpression?.replace('@0', '@olderKey')
+ .replace('@1', '@afterKey')}: OK`
+ );
+ }
+ }
+ }
+})();
diff --git a/test/integration/miniscript.ts b/test/integration/miniscript.ts
index 0803e2e..a515fa3 100644
--- a/test/integration/miniscript.ts
+++ b/test/integration/miniscript.ts
@@ -3,7 +3,7 @@
//npm run test:integration
-import { networks, Psbt, address } from 'bitcoinjs-lib';
+import { networks, Psbt } from 'bitcoinjs-lib';
import { mnemonicToSeedSync } from 'bip39';
const { encode: afterEncode } = require('bip65');
const { encode: olderEncode } = require('bip68');
@@ -15,7 +15,6 @@ 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 SOFT_MNEMONIC =
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const POLICY = (older: number, after: number) =>
@@ -30,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();
@@ -109,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,
@@ -120,16 +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 });
- psbt.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE });
+ 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 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.
@@ -164,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-deprecated.ts b/test/integration/standardOutputs-deprecated.ts
new file mode 100644
index 0000000..2dd2bda
--- /dev/null
+++ b/test/integration/standardOutputs-deprecated.ts
@@ -0,0 +1,201 @@
+// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
+// Distributed under the MIT software license
+
+//npm run test:integration
+
+console.log('Standard output integration tests');
+import { networks, Psbt, address } from 'bitcoinjs-lib';
+import { mnemonicToSeedSync } from 'bip39';
+import { RegtestUtils } from 'regtest-client';
+const regtestUtils = new RegtestUtils();
+
+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 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
+} from '../../dist/';
+const { wpkhBIP32, shWpkhBIP32, pkhBIP32 } = scriptExpressions;
+const { signBIP32, signECPair } = signers;
+
+const { Descriptor, BIP32, ECPair } = DescriptorsFactory(ecc);
+
+const masterNode = BIP32.fromSeed(mnemonicToSeedSync(SOFT_MNEMONIC), NETWORK);
+//masterNode will be able to sign all the expressions below:
+const expressionsBIP32 = [
+ `pk(${keyExpressionBIP32({
+ masterNode,
+ originPath: "/0'/1'/0'",
+ change: 0,
+ index: 0
+ })})`,
+ pkhBIP32({ masterNode, network: NETWORK, account: 0, change: 0, index: 0 }),
+ wpkhBIP32({ masterNode, network: NETWORK, account: 0, change: 0, index: 0 }),
+ shWpkhBIP32({ masterNode, network: NETWORK, account: 0, change: 0, index: 0 })
+];
+if (
+ pkhBIP32({ masterNode, network: NETWORK, account: 0, keyPath: '/0/0' }) !==
+ pkhBIP32({ masterNode, network: NETWORK, account: 0, change: 0, index: 0 })
+)
+ throw new Error(`Error: cannot use keyPath <-> change, index, indistinctly`);
+
+const ecpair = ECPair.makeRandom();
+//The same ecpair will be able to sign all the expressions below:
+const expressionsECPair = [
+ `pk(${ecpair.publicKey.toString('hex')})`,
+ `pkh(${ecpair.publicKey.toString('hex')})`,
+ `wpkh(${ecpair.publicKey.toString('hex')})`,
+ `sh(wpkh(${ecpair.publicKey.toString('hex')}))`
+];
+
+(async () => {
+ const psbtMultiInputs = new Psbt();
+ const multiInputsDescriptors: DescriptorInstance[] = [];
+ for (const expression of expressionsBIP32) {
+ const descriptorBIP32 = new Descriptor({ expression, network: NETWORK });
+
+ let { txId, vout } = await regtestUtils.faucetComplex(
+ descriptorBIP32.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()) {
+ //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
+ //the value is correct to avoid possible fee attacks.
+ //updatePsbt should output a Warning message.
+ const tmpPsbtSegwit = new Psbt();
+ const originalWarn = console.warn;
+ let capturedOutput = '';
+ console.warn = (message: string) => {
+ capturedOutput += message;
+ };
+ //Add an input and update timelock (if necessary):
+ const indexSegwit = descriptorBIP32.updatePsbt({
+ psbt: tmpPsbtSegwit,
+ vout,
+ txId,
+ value: INITIAL_VALUE
+ });
+ if (capturedOutput !== 'Warning: missing txHex may allow fee attacks')
+ throw new Error(`Error: did not warn about fee attacks`);
+ console.warn = originalWarn;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const nonFinalTxHex = (psbt as any).__CACHE.__TX.toHex();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const nonFinalSegwitTxHex = (tmpPsbtSegwit as any).__CACHE.__TX.toHex();
+ if (indexSegwit !== index || nonFinalTxHex !== nonFinalSegwitTxHex)
+ throw new Error(
+ `Error: could not create same psbt ${nonFinalTxHex} for Segwit not using txHex: ${nonFinalSegwitTxHex}`
+ );
+ }
+ psbt.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE });
+ signBIP32({ psbt, masterNode });
+ descriptorBIP32.finalizePsbtInput({ index, psbt });
+ const spendTx = psbt.extractTransaction();
+ await regtestUtils.broadcast(spendTx.toHex());
+ await regtestUtils.verify({
+ txId: spendTx.getId(),
+ address: FINAL_ADDRESS,
+ vout: 0,
+ value: FINAL_VALUE
+ });
+ console.log(`${expression}: OK`);
+
+ ///Update multiInputs PSBT with a similar BIP32 input
+ ({ txId, vout } = await regtestUtils.faucetComplex(
+ descriptorBIP32.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;
+ }
+
+ for (const expression of expressionsECPair) {
+ const descriptorECPair = new Descriptor({
+ expression,
+ network: NETWORK
+ });
+ let { txId, vout } = await regtestUtils.faucetComplex(
+ descriptorECPair.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({
+ psbt: psbtECPair,
+ vout,
+ txHex
+ });
+ psbtECPair.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE });
+ signECPair({ psbt: psbtECPair, ecpair });
+ descriptorECPair.finalizePsbtInput({
+ index: indexECPair,
+ psbt: psbtECPair
+ });
+ const spendTxECPair = psbtECPair.extractTransaction();
+ await regtestUtils.broadcast(spendTxECPair.toHex());
+ await regtestUtils.verify({
+ txId: spendTxECPair.getId(),
+ address: FINAL_ADDRESS,
+ vout: 0,
+ value: FINAL_VALUE
+ });
+ console.log(`${expression}: OK`);
+
+ ///Update multiInputs PSBT with a similar ECPair input
+ ({ txId, vout } = await regtestUtils.faucetComplex(
+ descriptorECPair.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;
+ }
+
+ psbtMultiInputs.addOutput({ script: FINAL_SCRIPTPUBKEY, value: FINAL_VALUE });
+ //Sign and finish psbtMultiInputs
+ signECPair({ psbt: psbtMultiInputs, ecpair });
+ signBIP32({ psbt: psbtMultiInputs, masterNode });
+ multiInputsDescriptors.forEach((descriptor, index) =>
+ descriptor.finalizePsbtInput({ index, psbt: psbtMultiInputs })
+ );
+
+ const spendTxMultiInputs = psbtMultiInputs.extractTransaction();
+ await regtestUtils.broadcast(spendTxMultiInputs.toHex());
+ await regtestUtils.verify({
+ txId: spendTxMultiInputs.getId(),
+ address: FINAL_ADDRESS,
+ vout: 0,
+ value: FINAL_VALUE
+ });
+ console.log(
+ `Spend Psbt with BIP32 & ECPair signers from multiple standard inputs: 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;
}