@bitcoinerlab/descriptors 0.2.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.
@@ -0,0 +1,58 @@
1
+ import type { AppClient } from '@bitcoinerlab/ledger';
2
+ import { Network } from 'bitcoinjs-lib';
3
+ import type { LedgerState } from './ledger';
4
+ import type { BIP32Interface } from 'bip32';
5
+ export declare const pkhBIP32: ({ masterNode, network, keyPath, account, change, index, isPublic }: {
6
+ masterNode: BIP32Interface;
7
+ network?: Network;
8
+ account: number;
9
+ change?: number | undefined;
10
+ index?: number | undefined | '*';
11
+ keyPath?: string;
12
+ isPublic?: boolean;
13
+ }) => string;
14
+ export declare const shWpkhBIP32: ({ masterNode, network, keyPath, account, change, index, isPublic }: {
15
+ masterNode: BIP32Interface;
16
+ network?: Network;
17
+ account: number;
18
+ change?: number | undefined;
19
+ index?: number | undefined | '*';
20
+ keyPath?: string;
21
+ isPublic?: boolean;
22
+ }) => string;
23
+ export declare const wpkhBIP32: ({ masterNode, network, keyPath, account, change, index, isPublic }: {
24
+ masterNode: BIP32Interface;
25
+ network?: Network;
26
+ account: number;
27
+ change?: number | undefined;
28
+ index?: number | undefined | '*';
29
+ keyPath?: string;
30
+ isPublic?: boolean;
31
+ }) => string;
32
+ export declare const pkhLedger: ({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
33
+ ledgerClient: AppClient;
34
+ ledgerState: LedgerState;
35
+ network?: Network;
36
+ account: number;
37
+ keyPath?: string;
38
+ change?: number | undefined;
39
+ index?: number | undefined | '*';
40
+ }) => Promise<string>;
41
+ export declare const shWpkhLedger: ({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
42
+ ledgerClient: AppClient;
43
+ ledgerState: LedgerState;
44
+ network?: Network;
45
+ account: number;
46
+ keyPath?: string;
47
+ change?: number | undefined;
48
+ index?: number | undefined | '*';
49
+ }) => Promise<string>;
50
+ export declare const wpkhLedger: ({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
51
+ ledgerClient: AppClient;
52
+ ledgerState: LedgerState;
53
+ network?: Network;
54
+ account: number;
55
+ keyPath?: string;
56
+ change?: number | undefined;
57
+ index?: number | undefined | '*';
58
+ }) => Promise<string>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wpkhLedger = exports.shWpkhLedger = exports.pkhLedger = exports.wpkhBIP32 = exports.shWpkhBIP32 = exports.pkhBIP32 = void 0;
4
+ const bitcoinjs_lib_1 = require("bitcoinjs-lib");
5
+ const keyExpressions_1 = require("./keyExpressions");
6
+ function assertStandardKeyPath(keyPath) {
7
+ // Regular expression to match "/change/index" or "/change/*" format
8
+ const regex = /^\/[01]\/(\d+|\*)$/;
9
+ if (!regex.test(keyPath)) {
10
+ throw new Error("Error: Key path must be in the format '/change/index', where change is either 0 or 1 and index is a non-negative integer.");
11
+ }
12
+ }
13
+ function standardExpressionsBIP32Maker(purpose, scriptTemplate) {
14
+ function standardKeyExpressionBIP32({ masterNode, network = bitcoinjs_lib_1.networks.bitcoin, keyPath, account, change, index, isPublic = true }) {
15
+ const originPath = `/${purpose}'/${network === bitcoinjs_lib_1.networks.bitcoin ? 0 : 1}'/${account}'`;
16
+ if (keyPath !== undefined)
17
+ assertStandardKeyPath(keyPath);
18
+ const keyExpression = (0, keyExpressions_1.keyExpressionBIP32)({
19
+ masterNode,
20
+ originPath,
21
+ keyPath,
22
+ change,
23
+ index,
24
+ isPublic
25
+ });
26
+ return scriptTemplate.replace('KEYEXPRESSION', keyExpression);
27
+ }
28
+ return standardKeyExpressionBIP32;
29
+ }
30
+ exports.pkhBIP32 = standardExpressionsBIP32Maker(44, 'pkh(KEYEXPRESSION)');
31
+ exports.shWpkhBIP32 = standardExpressionsBIP32Maker(49, 'sh(wpkh(KEYEXPRESSION))');
32
+ exports.wpkhBIP32 = standardExpressionsBIP32Maker(84, 'wpkh(KEYEXPRESSION)');
33
+ function standardExpressionsLedgerMaker(purpose, scriptTemplate) {
34
+ async function standardKeyExpressionLedger({ ledgerClient, ledgerState, network = bitcoinjs_lib_1.networks.bitcoin, account, keyPath, change, index }) {
35
+ const originPath = `/${purpose}'/${network === bitcoinjs_lib_1.networks.bitcoin ? 0 : 1}'/${account}'`;
36
+ if (keyPath !== undefined)
37
+ assertStandardKeyPath(keyPath);
38
+ const keyExpression = await (0, keyExpressions_1.keyExpressionLedger)({
39
+ ledgerClient,
40
+ ledgerState,
41
+ originPath,
42
+ keyPath,
43
+ change,
44
+ index
45
+ });
46
+ return scriptTemplate.replace('KEYEXPRESSION', keyExpression);
47
+ }
48
+ return standardKeyExpressionLedger;
49
+ }
50
+ exports.pkhLedger = standardExpressionsLedgerMaker(44, 'pkh(KEYEXPRESSION)');
51
+ exports.shWpkhLedger = standardExpressionsLedgerMaker(49, 'sh(wpkh(KEYEXPRESSION))');
52
+ exports.wpkhLedger = standardExpressionsLedgerMaker(84, 'wpkh(KEYEXPRESSION)');
@@ -0,0 +1,37 @@
1
+ import type { Psbt } from 'bitcoinjs-lib';
2
+ import type { ECPairInterface } from 'ecpair';
3
+ import type { BIP32Interface } from 'bip32';
4
+ import { AppClient } from '@bitcoinerlab/ledger';
5
+ import type { DescriptorInterface } from './types';
6
+ import { LedgerState } from './ledger';
7
+ export declare function signInputECPair({ psbt, index, ecpair }: {
8
+ psbt: Psbt;
9
+ index: number;
10
+ ecpair: ECPairInterface;
11
+ }): void;
12
+ export declare function signECPair({ psbt, ecpair }: {
13
+ psbt: Psbt;
14
+ ecpair: ECPairInterface;
15
+ }): void;
16
+ export declare function signInputBIP32({ psbt, index, node }: {
17
+ psbt: Psbt;
18
+ index: number;
19
+ node: BIP32Interface;
20
+ }): void;
21
+ export declare function signBIP32({ psbt, masterNode }: {
22
+ psbt: Psbt;
23
+ masterNode: BIP32Interface;
24
+ }): void;
25
+ export declare function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerState }: {
26
+ psbt: Psbt;
27
+ index: number;
28
+ descriptor: DescriptorInterface;
29
+ ledgerClient: AppClient;
30
+ ledgerState: LedgerState;
31
+ }): Promise<void>;
32
+ export declare function signLedger({ psbt, descriptors, ledgerClient, ledgerState }: {
33
+ psbt: Psbt;
34
+ descriptors: DescriptorInterface[];
35
+ ledgerClient: AppClient;
36
+ ledgerState: LedgerState;
37
+ }): Promise<void>;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
3
+ // Distributed under the MIT software license
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.signLedger = exports.signInputLedger = exports.signBIP32 = exports.signInputBIP32 = exports.signECPair = exports.signInputECPair = void 0;
6
+ const ledger_1 = require("@bitcoinerlab/ledger");
7
+ const ledger_2 = require("./ledger");
8
+ function signInputECPair({ psbt, index, ecpair }) {
9
+ psbt.signInput(index, ecpair);
10
+ }
11
+ exports.signInputECPair = signInputECPair;
12
+ function signECPair({ psbt, ecpair }) {
13
+ psbt.signAllInputs(ecpair);
14
+ }
15
+ exports.signECPair = signECPair;
16
+ function signInputBIP32({ psbt, index, node }) {
17
+ psbt.signInputHD(index, node);
18
+ }
19
+ exports.signInputBIP32 = signInputBIP32;
20
+ function signBIP32({ psbt, masterNode }) {
21
+ psbt.signAllInputsHD(masterNode);
22
+ }
23
+ exports.signBIP32 = signBIP32;
24
+ const ledgerSignaturesForInputIndex = (index, ledgerSignatures) => ledgerSignatures
25
+ .filter(([i]) => i === index)
26
+ .map(([_i, pubkey, signature]) => ({
27
+ pubkey,
28
+ signature
29
+ }));
30
+ async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerState }) {
31
+ const result = await (0, ledger_2.descriptorToLedgerFormat)({
32
+ descriptor,
33
+ ledgerClient,
34
+ ledgerState
35
+ });
36
+ if (!result)
37
+ throw new Error(`Error: descriptor does not have a ledger input`);
38
+ const { ledgerTemplate, keyRoots } = result;
39
+ let ledgerSignatures;
40
+ const standardPolicy = await (0, ledger_2.ledgerPolicyFromStandard)({
41
+ descriptor,
42
+ ledgerClient,
43
+ ledgerState
44
+ });
45
+ if (standardPolicy) {
46
+ ledgerSignatures = await ledgerClient.signPsbt(new ledger_1.PsbtV2().fromBitcoinJS(psbt), new ledger_1.DefaultWalletPolicy(ledgerTemplate, keyRoots[0]), null);
47
+ }
48
+ else {
49
+ const policy = await (0, ledger_2.ledgerPolicyFromState)({
50
+ descriptor,
51
+ ledgerClient,
52
+ ledgerState
53
+ });
54
+ if (!policy || !policy.policyName || !policy.policyHmac)
55
+ throw new Error(`Error: the descriptor's policy is not registered`);
56
+ const walletPolicy = new ledger_1.WalletPolicy(policy.policyName, ledgerTemplate, keyRoots);
57
+ ledgerSignatures = await ledgerClient.signPsbt(new ledger_1.PsbtV2().fromBitcoinJS(psbt), walletPolicy, policy.policyHmac);
58
+ }
59
+ //Add the signatures to the Psbt object using PartialSig format:
60
+ psbt.updateInput(index, {
61
+ partialSig: ledgerSignaturesForInputIndex(index, ledgerSignatures)
62
+ });
63
+ }
64
+ exports.signInputLedger = signInputLedger;
65
+ //signLedger is able to sign several inputs of the same wallet policy since it
66
+ //it clusters together wallet policy types before signing
67
+ //it throws if it cannot sign any input.
68
+ async function signLedger({ psbt, descriptors, ledgerClient, ledgerState }) {
69
+ const ledgerPolicies = [];
70
+ for (const descriptor of descriptors) {
71
+ const policy = (await (0, ledger_2.ledgerPolicyFromState)({
72
+ descriptor,
73
+ ledgerClient,
74
+ ledgerState
75
+ })) ||
76
+ (await (0, ledger_2.ledgerPolicyFromStandard)({
77
+ descriptor,
78
+ ledgerClient,
79
+ ledgerState
80
+ }));
81
+ if (policy)
82
+ ledgerPolicies.push(policy);
83
+ }
84
+ if (ledgerPolicies.length === 0)
85
+ throw new Error(`Error: there are no inputs which could be signed`);
86
+ //cluster unique LedgerPolicies
87
+ const uniquePolicies = [];
88
+ for (const policy of ledgerPolicies) {
89
+ if (!uniquePolicies.find((uniquePolicy) => (0, ledger_2.comparePolicies)(uniquePolicy, policy)))
90
+ uniquePolicies.push(policy);
91
+ }
92
+ for (const uniquePolicy of uniquePolicies) {
93
+ let ledgerSignatures;
94
+ if (uniquePolicy.policyName &&
95
+ uniquePolicy.policyHmac &&
96
+ uniquePolicy.policyId) {
97
+ //non-standard policy
98
+ const walletPolicy = new ledger_1.WalletPolicy(uniquePolicy.policyName, uniquePolicy.ledgerTemplate, uniquePolicy.keyRoots);
99
+ ledgerSignatures = await ledgerClient.signPsbt(new ledger_1.PsbtV2().fromBitcoinJS(psbt), walletPolicy, uniquePolicy.policyHmac);
100
+ }
101
+ else {
102
+ //standard policy
103
+ ledgerSignatures = await ledgerClient.signPsbt(new ledger_1.PsbtV2().fromBitcoinJS(psbt), new ledger_1.DefaultWalletPolicy(uniquePolicy.ledgerTemplate, uniquePolicy.keyRoots[0]), null);
104
+ }
105
+ for (const [index, ,] of ledgerSignatures) {
106
+ psbt.updateInput(index, {
107
+ partialSig: ledgerSignaturesForInputIndex(index, ledgerSignatures)
108
+ });
109
+ }
110
+ }
111
+ }
112
+ exports.signLedger = signLedger;
@@ -0,0 +1,94 @@
1
+ /// <reference types="node" />
2
+ import type { ECPairInterface } from 'ecpair';
3
+ import type { BIP32Interface } from 'bip32';
4
+ import type { Network, Payment, Psbt } from 'bitcoinjs-lib';
5
+ import type { PartialSig } from 'bip174/src/lib/interfaces';
6
+ export interface Preimage {
7
+ digest: string;
8
+ preimage: string;
9
+ }
10
+ export type TimeConstraints = {
11
+ nLockTime: number | undefined;
12
+ nSequence: number | undefined;
13
+ };
14
+ export type KeyInfo = {
15
+ keyExpression: string;
16
+ pubkey: Buffer;
17
+ ecpair?: ECPairInterface;
18
+ bip32?: BIP32Interface;
19
+ masterFingerprint?: Buffer;
20
+ originPath?: string;
21
+ keyPath?: string;
22
+ path?: string;
23
+ };
24
+ export type ExpansionMap = {
25
+ [key: string]: KeyInfo;
26
+ };
27
+ export interface ParseKeyExpression {
28
+ (params: {
29
+ keyExpression: string;
30
+ isSegwit?: boolean;
31
+ network?: Network;
32
+ }): KeyInfo;
33
+ }
34
+ interface XOnlyPointAddTweakResult {
35
+ parity: 1 | 0;
36
+ xOnlyPubkey: Uint8Array;
37
+ }
38
+ export interface TinySecp256k1Interface {
39
+ isPoint(p: Uint8Array): boolean;
40
+ pointCompress(p: Uint8Array, compressed?: boolean): Uint8Array;
41
+ isPrivate(d: Uint8Array): boolean;
42
+ pointFromScalar(d: Uint8Array, compressed?: boolean): Uint8Array | null;
43
+ pointAddScalar(p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null;
44
+ privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null;
45
+ sign(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
46
+ signSchnorr?(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
47
+ verify(h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean;
48
+ verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean;
49
+ xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null;
50
+ privateNegate(d: Uint8Array): Uint8Array;
51
+ }
52
+ export type DescriptorInfo = {
53
+ expression: string;
54
+ index?: number;
55
+ checksumRequired?: boolean;
56
+ allowMiniscriptInP2SH?: boolean;
57
+ network?: Network;
58
+ preimages?: Preimage[];
59
+ signersPubKeys?: Buffer[];
60
+ };
61
+ export interface DescriptorInterface {
62
+ getPayment(): Payment;
63
+ getAddress(): string;
64
+ getScriptPubKey(): Buffer;
65
+ getScriptSatisfaction(signatures: PartialSig[]): Buffer;
66
+ getSequence(): number | undefined;
67
+ getLockTime(): number | undefined;
68
+ getWitnessScript(): Buffer | undefined;
69
+ getRedeemScript(): Buffer | undefined;
70
+ getNetwork(): Network;
71
+ isSegwit(): boolean | undefined;
72
+ updatePsbt({ psbt, vout, txHex, txId, value }: {
73
+ psbt: Psbt;
74
+ vout: number;
75
+ txHex?: string;
76
+ txId?: string;
77
+ value?: number;
78
+ }): number;
79
+ finalizePsbtInput({ index, psbt, validate }: {
80
+ index: number;
81
+ psbt: Psbt;
82
+ validate?: boolean | undefined;
83
+ }): void;
84
+ expand(): {
85
+ expandedExpression?: string;
86
+ miniscript?: string;
87
+ expandedMiniscript?: string;
88
+ expansionMap?: ExpansionMap;
89
+ };
90
+ }
91
+ export interface DescriptorInterfaceConstructor {
92
+ new (args: DescriptorInfo): DescriptorInterface;
93
+ }
94
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
3
+ // Distributed under the MIT software license
4
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@bitcoinerlab/descriptors",
3
+ "homepage": "https://github.com/bitcoinerlab/descriptors",
4
+ "version": "0.2.0",
5
+ "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.",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "npx tsc",
10
+ "prepublishOnly": "npm run build && npm test && echo \"\\n\\n\" && npm run test:integration:ledger",
11
+ "docs": "jsdoc -c jsdoc.json",
12
+ "regtest-docker": "docker ps | grep bitcoinjs-regtest-server > /dev/null || (docker pull junderw/bitcoinjs-regtest-server && docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server && sleep 5)",
13
+ "test:integration:soft": "npm run regtest-docker && npx ts-node test/integration/standardOutputs.ts && echo \"\\n\\n\" && npx ts-node test/integration/miniscript.ts",
14
+ "test:integration:ledger": "npm run regtest-docker && npx ts-node test/integration/ledger.ts",
15
+ "test:unit": "npm run build && node test/tools/generateBitcoinCoreFixtures.js && jest",
16
+ "test": "npm run lint && npm run lint:test && npm run test:unit && npm run test:integration:soft",
17
+ "lint": "eslint --ignore-path .gitignore --ext .js,.ts src/",
18
+ "lint:test": "eslint --ignore-path .gitignore --ext .js,.ts test/"
19
+ },
20
+ "COMMENT_babel": "Babel plugins are are only needed for the jest testing environment. Jest needs to use commonjs. Also, jest cannot handle ESM converted code, since it uses 'import.meta.url'. See src/bindings.js. babel-plugin-transform-import-meta fixes it.",
21
+ "babel": {
22
+ "env": {
23
+ "test": {
24
+ "plugins": [
25
+ "@babel/plugin-transform-modules-commonjs",
26
+ "babel-plugin-transform-import-meta"
27
+ ]
28
+ }
29
+ }
30
+ },
31
+ "jest": {
32
+ "testPathIgnorePatterns": [
33
+ "example/",
34
+ "dist/"
35
+ ]
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/bitcoinerlab/descriptors.git"
40
+ },
41
+ "keywords": [
42
+ "bitcoin",
43
+ "descriptors",
44
+ "bitcoinjs",
45
+ "miniscript"
46
+ ],
47
+ "author": "Jose-Luis Landabaso",
48
+ "license": "MIT",
49
+ "bugs": {
50
+ "url": "https://github.com/bitcoinerlab/descriptors/issues"
51
+ },
52
+ "files": [
53
+ "dist"
54
+ ],
55
+ "dependencies": {
56
+ "@bitcoinerlab/ledger": "^0.1.6",
57
+ "@bitcoinerlab/miniscript": "^1.2.1",
58
+ "@bitcoinerlab/secp256k1": "^1.0.2",
59
+ "bip32": "^3.1.0",
60
+ "bitcoinjs-lib": "^6.1.0",
61
+ "ecpair": "^2.1.0"
62
+ },
63
+ "devDependencies": {
64
+ "@babel/plugin-transform-modules-commonjs": "^7.20.11",
65
+ "@ledgerhq/hw-transport-node-hid": "^6.27.12",
66
+ "@typescript-eslint/eslint-plugin": "^5.53.0",
67
+ "@typescript-eslint/parser": "^5.53.0",
68
+ "babel-plugin-transform-import-meta": "^2.2.0",
69
+ "bip39": "^3.0.4",
70
+ "bip65": "^1.0.3",
71
+ "bip68": "^1.0.4",
72
+ "eslint-config-prettier": "^8.6.0",
73
+ "eslint-plugin-jest": "^27.2.1",
74
+ "eslint-plugin-prettier": "^4.2.1",
75
+ "fs": "^0.0.1-security",
76
+ "jest": "^29.4.3",
77
+ "jsdoc": "^4.0.0",
78
+ "path": "^0.12.7",
79
+ "prettier": "^2.8.4",
80
+ "regtest-client": "^0.2.0",
81
+ "ts-node-dev": "^2.0.0"
82
+ }
83
+ }