@bitcoinerlab/descriptors 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -145
- package/dist/checksum.d.ts +4 -0
- package/dist/checksum.js +4 -0
- package/dist/descriptors.d.ts +415 -37
- package/dist/descriptors.js +283 -130
- package/dist/index.d.ts +25 -5
- package/dist/index.js +12 -2
- package/dist/keyExpressions.d.ts +54 -5
- package/dist/keyExpressions.js +30 -2
- package/dist/ledger.d.ts +89 -12
- package/dist/ledger.js +241 -28
- package/dist/scriptExpressions.d.ts +73 -28
- package/dist/scriptExpressions.js +26 -4
- package/dist/signers.d.ts +34 -1
- package/dist/signers.js +102 -41
- package/dist/types.d.ts +71 -39
- package/package.json +3 -2
package/dist/signers.js
CHANGED
|
@@ -26,37 +26,77 @@ const ledgerSignaturesForInputIndex = (index, ledgerSignatures) => ledgerSignatu
|
|
|
26
26
|
pubkey: partialSignature.pubkey,
|
|
27
27
|
signature: partialSignature.signature
|
|
28
28
|
}));
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* To be removed in v3.0 and replaced by a version that does not accept
|
|
31
|
+
* descriptor
|
|
32
|
+
* @hidden
|
|
33
|
+
*/
|
|
34
|
+
async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerState, ledgerManager }) {
|
|
35
|
+
if (!descriptor && !ledgerManager)
|
|
36
|
+
throw new Error(`ledgerManager not provided`);
|
|
37
|
+
if (descriptor && ledgerManager)
|
|
38
|
+
throw new Error(`Invalid usage: don't pass descriptor`);
|
|
39
|
+
if (ledgerManager && (ledgerClient || ledgerState))
|
|
40
|
+
throw new Error(`Invalid usage: either ledgerManager or ledgerClient + ledgerState`);
|
|
41
|
+
const output = descriptor;
|
|
42
|
+
if (ledgerManager)
|
|
43
|
+
({ ledgerClient, ledgerState } = ledgerManager);
|
|
44
|
+
if (!ledgerClient)
|
|
45
|
+
throw new Error(`ledgerManager not provided`);
|
|
46
|
+
if (!ledgerState)
|
|
47
|
+
throw new Error(`ledgerManager not provided`);
|
|
30
48
|
const { PsbtV2, DefaultWalletPolicy, WalletPolicy, AppClient } = (await (0, ledger_1.importAndValidateLedgerBitcoin)(ledgerClient));
|
|
31
49
|
if (!(ledgerClient instanceof AppClient))
|
|
32
50
|
throw new Error(`Error: pass a valid ledgerClient`);
|
|
33
|
-
const result = await (0, ledger_1.descriptorToLedgerFormat)({
|
|
34
|
-
descriptor,
|
|
35
|
-
ledgerClient,
|
|
36
|
-
ledgerState
|
|
37
|
-
});
|
|
38
|
-
if (!result)
|
|
39
|
-
throw new Error(`Error: descriptor does not have a ledger input`);
|
|
40
|
-
const { ledgerTemplate, keyRoots } = result;
|
|
41
51
|
let ledgerSignatures;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
if (ledgerManager) {
|
|
53
|
+
const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
|
|
54
|
+
psbt,
|
|
55
|
+
index,
|
|
56
|
+
ledgerManager
|
|
57
|
+
});
|
|
58
|
+
if (!policy)
|
|
59
|
+
throw new Error(`Error: the ledger cannot sign this pstb input`);
|
|
60
|
+
if (policy.policyName && policy.policyHmac && policy.policyId) {
|
|
61
|
+
//non-standard policy
|
|
62
|
+
const walletPolicy = new WalletPolicy(policy.policyName, policy.ledgerTemplate, policy.keyRoots);
|
|
63
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), walletPolicy, policy.policyHmac);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
//standard policy
|
|
67
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), new DefaultWalletPolicy(policy.ledgerTemplate, policy.keyRoots[0]), null);
|
|
68
|
+
}
|
|
49
69
|
}
|
|
50
70
|
else {
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
if (!output)
|
|
72
|
+
throw new Error(`outputs not provided`);
|
|
73
|
+
const result = await (0, ledger_1.ledgerPolicyFromOutput)({
|
|
74
|
+
output,
|
|
75
|
+
ledgerClient,
|
|
76
|
+
ledgerState
|
|
77
|
+
});
|
|
78
|
+
if (!result)
|
|
79
|
+
throw new Error(`Error: output does not have a ledger input`);
|
|
80
|
+
const { ledgerTemplate, keyRoots } = result;
|
|
81
|
+
const standardPolicy = await (0, ledger_1.ledgerPolicyFromStandard)({
|
|
82
|
+
output,
|
|
53
83
|
ledgerClient,
|
|
54
84
|
ledgerState
|
|
55
85
|
});
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
if (standardPolicy) {
|
|
87
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), new DefaultWalletPolicy(ledgerTemplate, keyRoots[0]), null);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const policy = await (0, ledger_1.ledgerPolicyFromState)({
|
|
91
|
+
output,
|
|
92
|
+
ledgerClient,
|
|
93
|
+
ledgerState
|
|
94
|
+
});
|
|
95
|
+
if (!policy || !policy.policyName || !policy.policyHmac)
|
|
96
|
+
throw new Error(`Error: the descriptor's policy is not registered`);
|
|
97
|
+
const walletPolicy = new WalletPolicy(policy.policyName, ledgerTemplate, keyRoots);
|
|
98
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), walletPolicy, policy.policyHmac);
|
|
99
|
+
}
|
|
60
100
|
}
|
|
61
101
|
//Add the signatures to the Psbt object using PartialSig format:
|
|
62
102
|
psbt.updateInput(index, {
|
|
@@ -64,30 +104,51 @@ async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerSt
|
|
|
64
104
|
});
|
|
65
105
|
}
|
|
66
106
|
exports.signInputLedger = signInputLedger;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
107
|
+
/**
|
|
108
|
+
* To be removed in v3.0 and replaced by a version that does not accept
|
|
109
|
+
* descriptors
|
|
110
|
+
* @hidden
|
|
111
|
+
*/
|
|
112
|
+
async function signLedger({ psbt, descriptors, ledgerClient, ledgerState, ledgerManager }) {
|
|
113
|
+
if (!descriptors && !ledgerManager)
|
|
114
|
+
throw new Error(`ledgerManager not provided`);
|
|
115
|
+
if (descriptors && ledgerManager)
|
|
116
|
+
throw new Error(`Invalid usage: don't pass descriptors`);
|
|
117
|
+
if (ledgerManager && (ledgerClient || ledgerState))
|
|
118
|
+
throw new Error(`Invalid usage: either ledgerManager or ledgerClient + ledgerState`);
|
|
119
|
+
const outputs = descriptors;
|
|
120
|
+
if (ledgerManager)
|
|
121
|
+
({ ledgerClient, ledgerState } = ledgerManager);
|
|
122
|
+
if (!ledgerClient)
|
|
123
|
+
throw new Error(`ledgerManager not provided`);
|
|
124
|
+
if (!ledgerState)
|
|
125
|
+
throw new Error(`ledgerManager not provided`);
|
|
71
126
|
const { PsbtV2, DefaultWalletPolicy, WalletPolicy, AppClient } = (await (0, ledger_1.importAndValidateLedgerBitcoin)(ledgerClient));
|
|
72
127
|
if (!(ledgerClient instanceof AppClient))
|
|
73
128
|
throw new Error(`Error: pass a valid ledgerClient`);
|
|
74
129
|
const ledgerPolicies = [];
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
130
|
+
if (ledgerManager)
|
|
131
|
+
for (let index = 0; index < psbt.data.inputs.length; index++) {
|
|
132
|
+
const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
|
|
133
|
+
psbt,
|
|
134
|
+
index,
|
|
135
|
+
ledgerManager
|
|
136
|
+
});
|
|
137
|
+
if (policy)
|
|
138
|
+
ledgerPolicies.push(policy);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
if (!outputs)
|
|
142
|
+
throw new Error(`outputs not provided`);
|
|
143
|
+
for (const output of outputs) {
|
|
144
|
+
const policy = (await (0, ledger_1.ledgerPolicyFromState)({ output, ledgerClient, ledgerState })) ||
|
|
145
|
+
(await (0, ledger_1.ledgerPolicyFromStandard)({ output, ledgerClient, ledgerState }));
|
|
146
|
+
if (policy)
|
|
147
|
+
ledgerPolicies.push(policy);
|
|
148
|
+
}
|
|
149
|
+
if (ledgerPolicies.length === 0)
|
|
150
|
+
throw new Error(`Error: there are no inputs which could be signed`);
|
|
88
151
|
}
|
|
89
|
-
if (ledgerPolicies.length === 0)
|
|
90
|
-
throw new Error(`Error: there are no inputs which could be signed`);
|
|
91
152
|
//cluster unique LedgerPolicies
|
|
92
153
|
const uniquePolicies = [];
|
|
93
154
|
for (const policy of ledgerPolicies) {
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export type TimeConstraints = {
|
|
|
23
23
|
nLockTime: number | undefined;
|
|
24
24
|
nSequence: number | undefined;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* See {@link _Internal_.ParseKeyExpression | ParseKeyExpression}.
|
|
28
|
+
*/
|
|
26
29
|
export type KeyInfo = {
|
|
27
30
|
keyExpression: string;
|
|
28
31
|
pubkey?: Buffer;
|
|
@@ -33,6 +36,35 @@ export type KeyInfo = {
|
|
|
33
36
|
keyPath?: string;
|
|
34
37
|
path?: string;
|
|
35
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* An `ExpansionMap` contains destructured information of a descritptor expression.
|
|
41
|
+
*
|
|
42
|
+
* For example, this descriptor `sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))` has the following
|
|
43
|
+
* `expandedExpression`: `sh(wsh(andor(pk(@0),older(8640),pk(@1))))`
|
|
44
|
+
*
|
|
45
|
+
* `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.
|
|
46
|
+
*
|
|
47
|
+
* For the given example, the `ExpansionMap` is:
|
|
48
|
+
*
|
|
49
|
+
* ```javascript
|
|
50
|
+
* {
|
|
51
|
+
* '@0': {
|
|
52
|
+
* keyExpression:
|
|
53
|
+
* '0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
|
|
54
|
+
* },
|
|
55
|
+
* '@1': {
|
|
56
|
+
* keyExpression:
|
|
57
|
+
* "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
|
|
58
|
+
* keyPath: '/1/2/3/4/*',
|
|
59
|
+
* originPath: "/49'/0'/0'",
|
|
60
|
+
* path: "m/49'/0'/0'/1/2/3/4/*",
|
|
61
|
+
* // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
*```
|
|
65
|
+
*
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
36
68
|
export type ExpansionMap = {
|
|
37
69
|
[key: string]: KeyInfo;
|
|
38
70
|
};
|
|
@@ -56,6 +88,11 @@ export interface TinySecp256k1Interface {
|
|
|
56
88
|
xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null;
|
|
57
89
|
privateNegate(d: Uint8Array): Uint8Array;
|
|
58
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* `DescriptorsFactory` creates and returns the {@link DescriptorsFactory | `expand()`}
|
|
93
|
+
* function that parses a descriptor expression and destructures it
|
|
94
|
+
* into its elemental parts. `Expansion` is the type that `expand()` returns.
|
|
95
|
+
*/
|
|
59
96
|
export type Expansion = {
|
|
60
97
|
/**
|
|
61
98
|
* The corresponding [bitcoinjs-lib Payment](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts) for the provided expression, if applicable.
|
|
@@ -63,6 +100,7 @@ export type Expansion = {
|
|
|
63
100
|
payment?: Payment;
|
|
64
101
|
/**
|
|
65
102
|
* The expanded descriptor expression.
|
|
103
|
+
* See {@link ExpansionMap ExpansionMap} for a detailed explanation.
|
|
66
104
|
*/
|
|
67
105
|
expandedExpression?: string;
|
|
68
106
|
/**
|
|
@@ -71,14 +109,17 @@ export type Expansion = {
|
|
|
71
109
|
miniscript?: string;
|
|
72
110
|
/**
|
|
73
111
|
* A map of key expressions in the descriptor to their corresponding expanded keys.
|
|
112
|
+
* See {@link ExpansionMap ExpansionMap} for a detailed explanation.
|
|
74
113
|
*/
|
|
75
114
|
expansionMap?: ExpansionMap;
|
|
76
115
|
/**
|
|
77
|
-
* A boolean indicating whether the descriptor
|
|
116
|
+
* A boolean indicating whether the descriptor uses SegWit.
|
|
78
117
|
*/
|
|
79
118
|
isSegwit?: boolean;
|
|
80
119
|
/**
|
|
81
120
|
* The expanded miniscript, if any.
|
|
121
|
+
* It corresponds to the `expandedExpression` without the top-level script
|
|
122
|
+
* expression.
|
|
82
123
|
*/
|
|
83
124
|
expandedMiniscript?: string;
|
|
84
125
|
/**
|
|
@@ -90,56 +131,47 @@ export type Expansion = {
|
|
|
90
131
|
*/
|
|
91
132
|
witnessScript?: Buffer;
|
|
92
133
|
/**
|
|
93
|
-
* Whether
|
|
134
|
+
* Whether the descriptor is a ranged-descriptor.
|
|
94
135
|
*/
|
|
95
136
|
isRanged: boolean;
|
|
96
137
|
/**
|
|
97
|
-
* This is the preferred or authoritative representation of
|
|
138
|
+
* This is the preferred or authoritative representation of an output
|
|
139
|
+
* descriptor expression.
|
|
140
|
+
* It removes the checksum and, if it is a ranged-descriptor, it
|
|
141
|
+
* particularizes it to its index.
|
|
98
142
|
*/
|
|
99
143
|
canonicalExpression: string;
|
|
100
144
|
};
|
|
101
145
|
/**
|
|
102
|
-
* The {@link DescriptorsFactory | `DescriptorsFactory`} function creates and
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*/
|
|
124
|
-
network?: Network;
|
|
125
|
-
/**
|
|
126
|
-
* Flag to allow miniscript in P2SH.
|
|
127
|
-
* @defaultValue false
|
|
128
|
-
*/
|
|
129
|
-
allowMiniscriptInP2SH?: boolean;
|
|
130
|
-
}): Expansion;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* The {@link DescriptorsFactory | `DescriptorsFactory`} function creates and returns an implementation of the `ParseKeyExpression` interface.
|
|
134
|
-
* This returned implementation is tailored for the provided `TinySecp256k1Interface`.
|
|
146
|
+
* The {@link DescriptorsFactory | `DescriptorsFactory`} function creates and
|
|
147
|
+
* returns the `parseKeyExpression` function, which is an implementation of this
|
|
148
|
+
* interface.
|
|
149
|
+
*
|
|
150
|
+
* It parses and destructures a key expression string (xpub, xprv, pubkey or
|
|
151
|
+
* wif) into {@link KeyInfo | `KeyInfo`}.
|
|
152
|
+
*
|
|
153
|
+
* For example, given this `keyExpression`: `[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*`, this is the parsed result:
|
|
154
|
+
*
|
|
155
|
+
* ```javascript
|
|
156
|
+
* {
|
|
157
|
+
* keyExpression:
|
|
158
|
+
* "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
|
|
159
|
+
* keyPath: '/1/2/3/4/*',
|
|
160
|
+
* originPath: "/49'/0'/0'",
|
|
161
|
+
* path: "m/49'/0'/0'/1/2/3/4/*",
|
|
162
|
+
* // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* See {@link KeyInfo} for the complete list of elements retrieved by this function.
|
|
135
167
|
*/
|
|
136
168
|
export interface ParseKeyExpression {
|
|
137
169
|
(params: {
|
|
138
170
|
keyExpression: string;
|
|
139
171
|
/**
|
|
140
|
-
* Indicates if this
|
|
141
|
-
* ensure the public key (if present in the
|
|
142
|
-
* (33 bytes).
|
|
172
|
+
* Indicates if this key expression belongs to a a SegWit output. When set,
|
|
173
|
+
* further checks are done to ensure the public key (if present in the
|
|
174
|
+
* expression) is compressed (33 bytes).
|
|
143
175
|
*/
|
|
144
176
|
isSegwit?: boolean;
|
|
145
177
|
network?: Network;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@bitcoinerlab/descriptors",
|
|
3
3
|
"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.",
|
|
4
4
|
"homepage": "https://github.com/bitcoinerlab/descriptors",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "2.0.0",
|
|
6
6
|
"author": "Jose-Luis Landabaso",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -38,10 +38,11 @@
|
|
|
38
38
|
"ensureTester": "./node_modules/@bitcoinerlab/configs/scripts/ensureTester.sh",
|
|
39
39
|
"test:integration:soft": "npm run ensureTester && node test/integration/standardOutputs.js && echo \"\\n\\n\" && node test/integration/miniscript.js",
|
|
40
40
|
"test:integration:ledger": "npm run ensureTester && node test/integration/ledger.js",
|
|
41
|
+
"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",
|
|
41
42
|
"test:unit": "jest",
|
|
42
43
|
"test": "npm run lint && npm run build && npm run test:unit && npm run test:integration:soft",
|
|
43
44
|
"testledger": "npm run lint && npm run build && npm run test:integration:ledger",
|
|
44
|
-
"prepublishOnly": "npm run test && echo \"\\n\\n\" && npm run test:integration:ledger"
|
|
45
|
+
"prepublishOnly": "npm run test && echo \"\\n\\n\" && npm run test:integration:deprecated && npm run test:integration:ledger"
|
|
45
46
|
},
|
|
46
47
|
"files": [
|
|
47
48
|
"dist"
|