@scure/btc-signer 0.5.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/LICENSE +21 -0
- package/README.md +836 -0
- package/index.d.ts +1277 -0
- package/index.d.ts.map +1 -0
- package/index.js +2552 -0
- package/index.js.map +1 -0
- package/index.ts +2571 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
# scure-btc-signer
|
|
2
|
+
|
|
3
|
+
Audited minimal library for creating, signing & decoding Bitcoin transactions.
|
|
4
|
+
|
|
5
|
+
- โ๏ธ Create transactions, inputs, outputs, sign them
|
|
6
|
+
- ๐ก No network code: simplified audits and offline usage
|
|
7
|
+
- ๐ป Classic & SegWit: P2PK, P2PKH, P2WPKH, P2SH, P2WSH, P2MS
|
|
8
|
+
- ๐งช Schnorr & Taproot BIP340/BIP341: P2TR, P2TR-NS, P2TR-MS
|
|
9
|
+
- ๐จ BIP174 PSBT
|
|
10
|
+
- ๐ฅ Multisig support
|
|
11
|
+
- ๐ [**Audited**](#security) by an independent security firm
|
|
12
|
+
- ๐ชถ ~2600 lines
|
|
13
|
+
|
|
14
|
+
Initial development has been funded by [Ryan Shea](https://shea.io). Check out [the demo](https://signerdemo.micro-btc.dev/) & [its github](https://github.com/shea256/micro-btc-web-demo).
|
|
15
|
+
|
|
16
|
+
### This library belongs to _scure_
|
|
17
|
+
|
|
18
|
+
> **scure** โ secure, independently audited packages for every use case.
|
|
19
|
+
|
|
20
|
+
- All releases are signed with PGP keys
|
|
21
|
+
- Check out all libraries:
|
|
22
|
+
[base](https://github.com/paulmillr/scure-base),
|
|
23
|
+
[bip32](https://github.com/paulmillr/scure-bip32),
|
|
24
|
+
[bip39](https://github.com/paulmillr/scure-bip39),
|
|
25
|
+
[btc-signer](https://github.com/paulmillr/scure-btc-signer)
|
|
26
|
+
|
|
27
|
+
Also, check out all _web3 utility libraries:_ [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer), [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer), [micro-sol-signer](https://github.com/paulmillr/micro-sol-signer), [micro-web3](https://github.com/paulmillr/micro-web3), [tx-tor-broadcaster](https://github.com/paulmillr/tx-tor-broadcaster)
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Use NPM for browser / node.js:
|
|
32
|
+
|
|
33
|
+
> npm install @scure/btc-signer
|
|
34
|
+
|
|
35
|
+
For [Deno](https://deno.land), use it with [npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import * as btc from '@scure/btc-signer';
|
|
39
|
+
// import * as btc from "npm:@scure/btc-signer@0.5.0"; // Deno
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Table of Contents
|
|
43
|
+
|
|
44
|
+
- [Payments](#payments)
|
|
45
|
+
- [P2PK Pay To Public Key](#p2pk-pay-to-public-key)
|
|
46
|
+
- [P2PKH Public Key Hash](#p2pkh-public-key-hash)
|
|
47
|
+
- [P2WPKH Witness Public Key Hash](#p2wpkh-witness-public-key-hash)
|
|
48
|
+
- [P2SH Script Hash](#p2sh-script-hash)
|
|
49
|
+
- [P2WSH Witness Script Hash](#p2wsh-witness-script-hash)
|
|
50
|
+
- [P2SH-P2WSH](#p2sh-p2wsh)
|
|
51
|
+
- [P2MS classic multisig](#p2ms-classic-multisig)
|
|
52
|
+
- [P2TR Taproot](#p2tr-taproot)
|
|
53
|
+
- [P2TR-NS Taproot multisig](#p2tr-ns-taproot-multisig)
|
|
54
|
+
- [P2TR-MS Taproot M-of-N multisig](#p2tr-ms-taproot-m-of-n-multisig)
|
|
55
|
+
- [Transaction](#transaction)
|
|
56
|
+
- [Encode/decode](#encodedecode)
|
|
57
|
+
- [Inputs](#inputs)
|
|
58
|
+
- [Outputs](#outputs)
|
|
59
|
+
- [Basic transaction sign](#basic-transaction-sign)
|
|
60
|
+
- [BIP174 PSBT multi-sig example](#bip174-psbt-multi-sig-example)
|
|
61
|
+
- [Utils](#utils)
|
|
62
|
+
- [getAddress](#getaddress)
|
|
63
|
+
- [WIF](#wif)
|
|
64
|
+
- [Script](#script)
|
|
65
|
+
- [OutScript](#outscript)
|
|
66
|
+
|
|
67
|
+
## Payments
|
|
68
|
+
|
|
69
|
+
BTC has several UTXO types:
|
|
70
|
+
|
|
71
|
+
- P2PK: Legacy, from 2010
|
|
72
|
+
- P2PKH, P2SH, P2MS: Classic
|
|
73
|
+
- P2WPKH, P2WSH: classic, SegWit
|
|
74
|
+
- P2TR: Taproot, recommended
|
|
75
|
+
|
|
76
|
+
For test examples, the usage is as following:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
npm install @scure/btc-signer @scure/base assert
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import * as btc from '@scure/btc-signer';
|
|
84
|
+
import { hex } from '@scure/base';
|
|
85
|
+
import { deepStrictEqual, throws } from 'assert';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### P2PK (Pay To Public Key)
|
|
89
|
+
|
|
90
|
+
Old script, doesn't have address at all. Should be wrapped in P2SH/P2WSH/P2SH-P2WSH.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const uncompressed = hex.decode(
|
|
94
|
+
'04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28'
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
deepStrictEqual(btc.p2pk(uncompressed), {
|
|
98
|
+
type: 'pk',
|
|
99
|
+
script: hex.decode(
|
|
100
|
+
'4104ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28ac'
|
|
101
|
+
),
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### P2PKH (Public Key Hash)
|
|
106
|
+
|
|
107
|
+
Classic address (pre-SegWit)
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
111
|
+
deepStrictEqual(btc.p2pkh(PubKey), {
|
|
112
|
+
type: 'pkh',
|
|
113
|
+
address: '134D6gYy8DsR5m4416BnmgASuMBqKvogQh',
|
|
114
|
+
script: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
115
|
+
});
|
|
116
|
+
// P2SH-P2PKH
|
|
117
|
+
deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
|
|
118
|
+
type: 'sh',
|
|
119
|
+
address: '3EPhLJ1FuR2noj6qrTs4YvepCvB6sbShoV',
|
|
120
|
+
script: hex.decode('a9148b530b962725af3bb7c818f197c619db3f71495087'),
|
|
121
|
+
redeemScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
122
|
+
});
|
|
123
|
+
// P2WSH-P2PKH
|
|
124
|
+
deepStrictEqual(btc.p2wsh(btc.p2pkh(PubKey)), {
|
|
125
|
+
type: 'wsh',
|
|
126
|
+
address: 'bc1qhxtthndg70cthfasy8y4qlk9h7r3006azn9md0fad5dg9hh76nkqaufnuz',
|
|
127
|
+
script: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
128
|
+
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
129
|
+
});
|
|
130
|
+
// P2SH-P2WSH-P2PKH
|
|
131
|
+
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
|
|
132
|
+
type: 'sh',
|
|
133
|
+
address: '3EHxWHyLv5Seu5Cd6D1cH56jLKxSi3ps8C',
|
|
134
|
+
script: hex.decode('a9148a3d36fb710a9c7cae06cfcdf39792ff5773e8f187'),
|
|
135
|
+
redeemScript: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
136
|
+
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### P2WPKH (Witness Public Key Hash)
|
|
141
|
+
|
|
142
|
+
Same as P2PKH, but for SegWit V0. Basic bech32 address.
|
|
143
|
+
Cannot be wrapped in P2WSH.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
147
|
+
deepStrictEqual(btc.p2wpkh(PubKey), {
|
|
148
|
+
type: 'wpkh',
|
|
149
|
+
address: 'bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm',
|
|
150
|
+
script: hex.decode('0014168b992bcfc44050310b3a94bd0771136d0b28d1'),
|
|
151
|
+
});
|
|
152
|
+
// P2SH-P2WPKH
|
|
153
|
+
deepStrictEqual(btc.p2sh(btc.p2wpkh(PubKey)), {
|
|
154
|
+
type: 'sh',
|
|
155
|
+
address: '3BCuRViGCTXmQjyJ9zjeRUYrdZTUa38zjC',
|
|
156
|
+
script: hex.decode('a91468602f2db7b7d7cdcd2639ab6bf7f5bfe828e53f87'),
|
|
157
|
+
redeemScript: hex.decode('0014168b992bcfc44050310b3a94bd0771136d0b28d1'),
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### P2SH (Script Hash)
|
|
162
|
+
|
|
163
|
+
Classic (pre-SegWit) script address. Useful for multisig and other smart-contracts.
|
|
164
|
+
Takes full output of other payments, not just script.
|
|
165
|
+
|
|
166
|
+
**_NOTE_**: redeemScript should be added to transaction input in order to spend.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
170
|
+
// Wrap P2PKH in P2SH
|
|
171
|
+
deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
|
|
172
|
+
type: 'sh',
|
|
173
|
+
address: '3EPhLJ1FuR2noj6qrTs4YvepCvB6sbShoV',
|
|
174
|
+
script: hex.decode('a9148b530b962725af3bb7c818f197c619db3f71495087'),
|
|
175
|
+
redeemScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### P2WSH (Witness Script Hash)
|
|
180
|
+
|
|
181
|
+
Same as P2SH but for SegWit V0.
|
|
182
|
+
|
|
183
|
+
**_NOTE_**: witnessScript should be added to transaction input in order to spend.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
187
|
+
deepStrictEqual(btc.p2wsh(btc.p2pkh(PubKey)), {
|
|
188
|
+
type: 'wsh',
|
|
189
|
+
address: 'bc1qhxtthndg70cthfasy8y4qlk9h7r3006azn9md0fad5dg9hh76nkqaufnuz',
|
|
190
|
+
script: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
191
|
+
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### P2SH-P2WSH
|
|
196
|
+
|
|
197
|
+
Not really script type, but construction of P2WSH inside P2SH.
|
|
198
|
+
|
|
199
|
+
**_NOTE_**: both reedemScript and witnessScript should be added to transaction in order to spend.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
203
|
+
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
|
|
204
|
+
type: 'sh',
|
|
205
|
+
address: '3EHxWHyLv5Seu5Cd6D1cH56jLKxSi3ps8C',
|
|
206
|
+
script: hex.decode('a9148a3d36fb710a9c7cae06cfcdf39792ff5773e8f187'),
|
|
207
|
+
redeemScript: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
208
|
+
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### P2MS (classic multisig)
|
|
213
|
+
|
|
214
|
+
Classic (pre-taproot) M-of-N Multisig, doesn't have an address, should be wrapped in P2SH/P2WSH/P2SH-P2WSH.
|
|
215
|
+
|
|
216
|
+
**_NOTE_**: By default we don't accept duplicate public keys, to avoid creating wrong multisig by mistake. However there is a flag: allowSamePubkeys, in case you really need that.
|
|
217
|
+
Valid use-case: `2-of-[A,A,B,C]`, can be signed by `A or (B and C)`.
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const PubKeys = [
|
|
221
|
+
hex.decode('030000000000000000000000000000000000000000000000000000000000000001'),
|
|
222
|
+
hex.decode('030000000000000000000000000000000000000000000000000000000000000002'),
|
|
223
|
+
hex.decode('030000000000000000000000000000000000000000000000000000000000000003'),
|
|
224
|
+
];
|
|
225
|
+
// Multisig 2-of-3 wrapped in P2SH
|
|
226
|
+
deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), {
|
|
227
|
+
type: 'sh',
|
|
228
|
+
address: '3G4AeQtzCLoDAyv2eb3UVTG5atfkyHtuRn',
|
|
229
|
+
script: hex.decode('a9149d91c6de4eacde72a7cc86bff98d1915b3c7818f87'),
|
|
230
|
+
redeemScript: hex.decode(
|
|
231
|
+
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
232
|
+
),
|
|
233
|
+
});
|
|
234
|
+
// Multisig 2-of-3 wrapped in P2WSH
|
|
235
|
+
deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), {
|
|
236
|
+
type: 'wsh',
|
|
237
|
+
address: 'bc1qwnhzkn8wcyyrnfyfcp7555urssu5dq0rmnvg70hg02z3nxgg4f0qljmr2h',
|
|
238
|
+
script: hex.decode('002074ee2b4ceec10839a489c07d4a538384394681e3dcd88f3ee87a85199908aa5e'),
|
|
239
|
+
witnessScript: hex.decode(
|
|
240
|
+
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
241
|
+
),
|
|
242
|
+
});
|
|
243
|
+
// Multisig 2-of-3 wrapped in P2SH-P2WSH
|
|
244
|
+
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2ms(2, PubKeys))), {
|
|
245
|
+
type: 'sh',
|
|
246
|
+
address: '3HKWSo57kmcJZ3h43pXS3m5UESR4wXcWTd',
|
|
247
|
+
script: hex.decode('a914ab70ab84b12b891364b4b2a14ca813cac308b24287'),
|
|
248
|
+
redeemScript: hex.decode('002074ee2b4ceec10839a489c07d4a538384394681e3dcd88f3ee87a85199908aa5e'),
|
|
249
|
+
witnessScript: hex.decode(
|
|
250
|
+
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
251
|
+
),
|
|
252
|
+
});
|
|
253
|
+
// Useful util: wraps P2MS in P2SH or P2WSH
|
|
254
|
+
deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), btc.multisig(2, PubKeys));
|
|
255
|
+
deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), btc.multisig(2, PubKeys, undefined, true));
|
|
256
|
+
// Sorted multisig (BIP67)
|
|
257
|
+
deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), btc.sortedMultisig(2, PubKeys));
|
|
258
|
+
deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), btc.sortedMultisig(2, PubKeys, true));
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### P2TR (Taproot)
|
|
262
|
+
|
|
263
|
+
TapRoot (SegWit V1) script which replaces both public key and script types from previous versions.
|
|
264
|
+
|
|
265
|
+
**_NOTE_**: it takes `p2tr(PubKey?, ScriptTree?)` and works as PubKey OR ScriptTree, which means
|
|
266
|
+
if you use any spendable PubKey and ScriptTree of multi-sig, owner of private key for PubKey will
|
|
267
|
+
be able to spend output. If PubKey is undefined we use static unspendable PubKey by default, which leaks information about script type. However, any dynamic unspendable keys will require complex interaction
|
|
268
|
+
to sign multi-sig wallets, and there is no BIP/PSBT fields for that yet.
|
|
269
|
+
|
|
270
|
+
**_NOTE_**: tapInternalKey, tapMerkleRoot, tapLeafScript should be added to transaction input in order to spend.
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
274
|
+
// Key Path Spend (owned of private key for PubKey can spend)
|
|
275
|
+
deepStrictEqual(btc.p2tr(PubKey), {
|
|
276
|
+
type: 'tr',
|
|
277
|
+
address: 'bc1p7yu5dsly83jg5tkxcljsa30vnpdpl22wr6rty98t6x6p6ekz2gkqzf2t2s',
|
|
278
|
+
script: hex.decode('5120f13946c3e43c648a2ec6c7e50ec5ec985a1fa94e1e86b214ebd1b41d66c2522c'),
|
|
279
|
+
tweakedPubkey: hex.decode('f13946c3e43c648a2ec6c7e50ec5ec985a1fa94e1e86b214ebd1b41d66c2522c'),
|
|
280
|
+
tapInternalKey: hex.decode('0101010101010101010101010101010101010101010101010101010101010101'),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
|
|
284
|
+
|
|
285
|
+
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
|
|
286
|
+
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');
|
|
287
|
+
// Nested P2TR, owner of private key for any of PubKeys can spend whole
|
|
288
|
+
// NOTE: by default P2TR expects binary tree, but btc.p2tr can build it if list of scripts passed.
|
|
289
|
+
// Also, you can include {weight: N} to scripts to create differently balanced tree.
|
|
290
|
+
deepStrictEqual(
|
|
291
|
+
clean(btc.p2tr(undefined, [btc.p2tr_pk(PubKey), btc.p2tr_pk(PubKey2), btc.p2tr_pk(PubKey3)])),
|
|
292
|
+
{
|
|
293
|
+
type: 'tr',
|
|
294
|
+
// weights for bitcoinjs-lib: [3,2,1]
|
|
295
|
+
address: 'bc1pj2uvajyygyu2zw0rg0d6yxdsc920kzc5pamfgtlqepe30za922cqjjmkta',
|
|
296
|
+
script: '512092b8cec8844138a139e343dba219b0c154fb0b140f76942fe0c873178ba552b0',
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
// If scriptsTree is already binary tree, it will be used as-is
|
|
300
|
+
deepStrictEqual(
|
|
301
|
+
clean(btc.p2tr(undefined, [btc.p2tr_pk(PubKey2), [btc.p2tr_pk(PubKey), btc.p2tr_pk(PubKey3)]])),
|
|
302
|
+
{
|
|
303
|
+
type: 'tr',
|
|
304
|
+
// default weights for bitcoinjs-lib
|
|
305
|
+
address: 'bc1pvue6sk9efyvcvpzzqkg8at4qy2u67zj7rj5sfsy573m7alxavqjqucc26a',
|
|
306
|
+
script: '51206733a858b9491986044205907eaea022b9af0a5e1ca904c094f477eefcdd6024',
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### P2TR-NS (Taproot multisig)
|
|
312
|
+
|
|
313
|
+
Taproot N-of-N multisig (`[<PubKeys[0:n-1]> CHECKSIGVERIFY] <PubKeys[n-1]> CHECKSIG`).
|
|
314
|
+
|
|
315
|
+
**_NOTE_**: First arg is M, if M!=PubKeys.length, it will create a multi-leaf M-of-N taproot script tree.
|
|
316
|
+
This allows one to reveal only `M` PubKeys on spend, without any information about the others.
|
|
317
|
+
This is fast for cases like 15-of-20, but extremely slow for cases like 5-of-20.
|
|
318
|
+
|
|
319
|
+
**_NOTE_**: By default we don't accept duplicate public keys, to avoid creating the wrong multisig by mistake. However there is a flag called allowSamePubkeys, in case you really need that.
|
|
320
|
+
Valid use-case: `2-of-[A,A,B,C]`, can be signed by `A or (B and C)`.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
324
|
+
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
|
|
325
|
+
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');
|
|
326
|
+
|
|
327
|
+
// Simple 3-of-3 multisig
|
|
328
|
+
// Creates a single script that requires all three pubkeys: [PubKey, PubKey2, PubKey3]
|
|
329
|
+
deepStrictEqual(btc.p2tr_ns(3, [PubKey, PubKey2, PubKey3]), [
|
|
330
|
+
{
|
|
331
|
+
type: 'tr_ns',
|
|
332
|
+
script: hex.decode(
|
|
333
|
+
'200101010101010101010101010101010101010101010101010101010101010101ad200202020202020202020202020202020202020202020202020202020202020202ad201212121212121212121212121212121212121212121212121212121212121212ac'
|
|
334
|
+
),
|
|
335
|
+
},
|
|
336
|
+
]);
|
|
337
|
+
// Simple 2-of-3 multisig
|
|
338
|
+
// If M (pubkeys required) is less than N (# of pubkeys), then multiple scripts are created: [[PubKey, PubKey2], [PubKey, PubKey3], [PubKey2, PubKey3]]
|
|
339
|
+
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
|
|
340
|
+
deepStrictEqual(clean(btc.p2tr(undefined, btc.p2tr_ns(2, [PubKey, PubKey2, PubKey3]))), {
|
|
341
|
+
type: 'tr',
|
|
342
|
+
address: 'bc1pevfcmnkqqq09a4n0fs8c7mwlc6r4efqpvgyqpjvegllavgw235fq3kz7a0',
|
|
343
|
+
script: '5120cb138dcec0001e5ed66f4c0f8f6ddfc6875ca401620800c99947ffd621ca8d12',
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### P2TR-MS (Taproot M-of-N multisig)
|
|
348
|
+
|
|
349
|
+
M-of-N single leaf TapRoot multisig (`<PubKeys[0]> CHECKSIG [<PubKeys[1:n]> CHECKSIGADD] <M> NUMEQUAL`)
|
|
350
|
+
|
|
351
|
+
**_NOTE_**: By default, we don't accept duplicate public keys in order to avoid creating the wrong multisig by mistake. However, there is a flag called allowSamePubkeys, in case you really need that.
|
|
352
|
+
Valid use-case: `2-of-[A,A,B,C]`, can be signed by `A or (B and C)`.
|
|
353
|
+
|
|
354
|
+
**_NOTE_**: experimental, use at your own risk.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
358
|
+
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
|
|
359
|
+
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');
|
|
360
|
+
// 2-of-3 TapRoot multisig
|
|
361
|
+
deepStrictEqual(btc.p2tr_ms(2, [PubKey, PubKey2, PubKey3]), {
|
|
362
|
+
type: 'tr_ms',
|
|
363
|
+
script: hex.decode(
|
|
364
|
+
'200101010101010101010101010101010101010101010101010101010101010101ac200202020202020202020202020202020202020202020202020202020202020202ba201212121212121212121212121212121212121212121212121212121212121212ba529c'
|
|
365
|
+
),
|
|
366
|
+
});
|
|
367
|
+
// Creates a single script for [PubKey, PubKey2, PubKey3]
|
|
368
|
+
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
|
|
369
|
+
deepStrictEqual(clean(btc.p2tr(undefined, btc.p2tr_ms(2, [PubKey, PubKey2, PubKey3]))), {
|
|
370
|
+
type: 'tr',
|
|
371
|
+
address: 'bc1p6m2xevckax9zucumnnyvu4xhxem66ugc5r2zlw2a20s0hxnutl8qfef23s',
|
|
372
|
+
script: '5120d6d46cb316e98a2e639b9cc8ce54d73677ad7118a0d42fb95d53e0fb9a7c5fce',
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### P2TR-PK (Taproot single P2PK script)
|
|
377
|
+
|
|
378
|
+
This is a specific case of `p2tr_ns(1, [pubkey])`, which is the same as the BTC descriptor: `tr($H,pk(PUBKEY))`
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
382
|
+
// P2PK for taproot
|
|
383
|
+
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
|
|
384
|
+
deepStrictEqual(clean(btc.p2tr(undefined, [btc.p2tr_pk(PubKey)])), {
|
|
385
|
+
type: 'tr',
|
|
386
|
+
address: 'bc1pfj6w68w3v2f4pkzesc9tsqfvy5znw5qgydwa832v3v83vjn76kdsmr4360',
|
|
387
|
+
script: '51204cb4ed1dd1629350d859860ab8012c2505375008235dd3c54c8b0f164a7ed59b',
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Transaction
|
|
392
|
+
|
|
393
|
+
### Encode/decode
|
|
394
|
+
|
|
395
|
+
**_NOTE_**: we support both PSBTv0 and draft PSBTv2 (there is no PSBTv1). If PSBTv2 transaction encoded into PSBTv1, all PSBTv2 fields will be stripped.
|
|
396
|
+
**_NOTE_**: we strip 'unknown' keys inside PSBT, they needed for new version/features support, however any unsupported feature/new version can significantly break assumptions about code.
|
|
397
|
+
If you have use-case where they needed, please open issue.
|
|
398
|
+
For PSBTv2: tx_modifiable, taproot+bip32 is not supported yet.
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
// Decode
|
|
402
|
+
Transaction.fromRaw(raw: Bytes, opts: TxOpts = {}); // Raw tx
|
|
403
|
+
Transaction.fromPSBT(psbt: Bytes, opts: TxOpts = {}); // PSBT tx
|
|
404
|
+
// Encode
|
|
405
|
+
tx.unsignedTx; // Bytes of raw unsigned tx
|
|
406
|
+
tx.hex; // hex encoded signed raw tx
|
|
407
|
+
tx.toPSBT(ver = this.PSBTVersion); // PSBT
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Inputs
|
|
411
|
+
|
|
412
|
+
We have txid (BE) instead of hash (LE) in transactions. We can support both,
|
|
413
|
+
but txid is consistent across block explorers, while some explorers treat hash
|
|
414
|
+
as txid - so hash is not consistent.
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
type TransactionInput = {
|
|
418
|
+
txid?: Bytes,
|
|
419
|
+
index?: number,
|
|
420
|
+
nonWitnessUtxo?: <RawTransactionBytesOrHex>,
|
|
421
|
+
witnessUtxo?: {script?: Bytes; amount: bigint},
|
|
422
|
+
partialSig?: [Bytes, Bytes][]; // [PubKey, Signature]
|
|
423
|
+
sighashType?: P.U32LE,
|
|
424
|
+
redeemScript?: Bytes,
|
|
425
|
+
witnessScript?: Bytes,
|
|
426
|
+
bip32Derivation?: [Bytes, {fingerprint: number; path: number[]}]; // [PubKey, DeriviationPath]
|
|
427
|
+
finalScriptSig?: Bytes,
|
|
428
|
+
finalScriptWitness?: Bytes[],
|
|
429
|
+
porCommitment?: Bytes,
|
|
430
|
+
sequence?: number,
|
|
431
|
+
requiredTimeLocktime?: number,
|
|
432
|
+
requiredHeightLocktime?: number,
|
|
433
|
+
tapKeySig?: Bytes,
|
|
434
|
+
tapScriptSig?: [Bytes, Bytes][]; // [PubKeySchnorr, LeafHash]
|
|
435
|
+
// [ControlBlock, ScriptWithVersion]
|
|
436
|
+
tapLeafScript?: [{version: number; internalKey: Bytes; merklePath: Bytes[]}, Bytes];
|
|
437
|
+
tapInternalKey?: Bytes,
|
|
438
|
+
tapMerkleRoot?: Bytes,
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
tx.addInput(input: TransactionInput): number;
|
|
442
|
+
tx.updateInput(idx: number, input: TransactionInput);
|
|
443
|
+
|
|
444
|
+
// Input
|
|
445
|
+
tx.addInput({ txid: new Uint8Array(32), index: 0 });
|
|
446
|
+
deepStrictEqual(tx.inputs[0], {
|
|
447
|
+
txid: new Uint8Array(32),
|
|
448
|
+
index: 0,
|
|
449
|
+
sequence: btc.DEFAULT_SEQUENCE,
|
|
450
|
+
});
|
|
451
|
+
// Update basic value
|
|
452
|
+
tx.updateInput(0, { index: 10 });
|
|
453
|
+
deepStrictEqual(tx.inputs[0], {
|
|
454
|
+
txid: new Uint8Array(32),
|
|
455
|
+
index: 10,
|
|
456
|
+
sequence: btc.DEFAULT_SEQUENCE,
|
|
457
|
+
});
|
|
458
|
+
// Add value as hex
|
|
459
|
+
tx.addInput({
|
|
460
|
+
txid: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
461
|
+
index: 0,
|
|
462
|
+
});
|
|
463
|
+
deepStrictEqual(tx.inputs[2], {
|
|
464
|
+
txid: new Uint8Array(32),
|
|
465
|
+
index: 0,
|
|
466
|
+
sequence: btc.DEFAULT_SEQUENCE,
|
|
467
|
+
});
|
|
468
|
+
// Update key map
|
|
469
|
+
const pubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
470
|
+
const bip1 = [pubKey, { fingerprint: 5, path: [1, 2, 3] }];
|
|
471
|
+
const pubKey2 = hex.decode('030000000000000000000000000000000000000000000000000000000000000002');
|
|
472
|
+
const bip2 = [pubKey2, { fingerprint: 6, path: [4, 5, 6] }];
|
|
473
|
+
const pubKey3 = hex.decode('030000000000000000000000000000000000000000000000000000000000000003');
|
|
474
|
+
const bip3 = [pubKey3, { fingerprint: 7, path: [7, 8, 9] }];
|
|
475
|
+
// Add K-V
|
|
476
|
+
tx.updateInput(0, { bip32Derivation: [bip1] });
|
|
477
|
+
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip1]);
|
|
478
|
+
// Add another K-V
|
|
479
|
+
tx.updateInput(0, { bip32Derivation: [bip2] });
|
|
480
|
+
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip1, bip2]);
|
|
481
|
+
// Delete K-V
|
|
482
|
+
tx.updateInput(0, { bip32Derivation: [[pubKey, undefined]] });
|
|
483
|
+
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip2]);
|
|
484
|
+
// Second add of same k-v does nothing
|
|
485
|
+
tx.updateInput(0, { bip32Derivation: [bip2] });
|
|
486
|
+
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip2]);
|
|
487
|
+
// Second add of k-v with different value breaks
|
|
488
|
+
throws(() => tx.updateInput(0, { bip32Derivation: [[pubKey2, bip1[1]]] }));
|
|
489
|
+
tx.updateInput(0, { bip32Derivation: [bip1, bip2, bip3] });
|
|
490
|
+
// Preserves order (re-ordered on PSBT encoding)
|
|
491
|
+
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip2, bip1, bip3]);
|
|
492
|
+
// PSBT encoding re-order k-v
|
|
493
|
+
const tx2 = btc.Transaction.fromPSBT(tx.toPSBT());
|
|
494
|
+
deepStrictEqual(tx2.inputs[0].bip32Derivation, [bip1, bip2, bip3]);
|
|
495
|
+
// Remove field
|
|
496
|
+
tx.updateInput(0, { bip32Derivation: undefined });
|
|
497
|
+
deepStrictEqual(tx.inputs[0], {
|
|
498
|
+
txid: new Uint8Array(32),
|
|
499
|
+
index: 10,
|
|
500
|
+
sequence: btc.DEFAULT_SEQUENCE,
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Outputs
|
|
505
|
+
|
|
506
|
+
**_NOTE_**: amount in addOutputAddress handled as 'bitcoins' if string, and as satoshi if bigint.
|
|
507
|
+
Why? BigInt usually comes from calculations/API, which is usually in satoshi. String is probably user input, so it is worth
|
|
508
|
+
to handle conversion to satoshi's in that case.
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
type TransactionOutput = {
|
|
512
|
+
script?: Bytes,
|
|
513
|
+
amount?: bigint,
|
|
514
|
+
redeemScript?: Bytes,
|
|
515
|
+
witnessScript?: Bytes,
|
|
516
|
+
bip32Derivation?: [Bytes, {fingerprint: number; path: number[]}]; // [PubKey, DeriviationPath]
|
|
517
|
+
tapInternalKey?: Bytes,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
tx.addOutput(o: TransactionOutput): number;
|
|
521
|
+
tx.updateOutput(idx: number, output: TransactionOutput);
|
|
522
|
+
tx.addOutputAddress(address: string, amount: string | bigint, network = NETWORK): number;
|
|
523
|
+
|
|
524
|
+
const compressed = hex.decode(
|
|
525
|
+
'030000000000000000000000000000000000000000000000000000000000000001'
|
|
526
|
+
);
|
|
527
|
+
const script = btc.p2pkh(compressed).script;
|
|
528
|
+
tx.addOutput({ script, amount: 100n });
|
|
529
|
+
deepStrictEqual(tx.outputs[0], {
|
|
530
|
+
script,
|
|
531
|
+
amount: 100n,
|
|
532
|
+
});
|
|
533
|
+
// Update basic value
|
|
534
|
+
tx.updateOutput(0, { amount: 200n });
|
|
535
|
+
deepStrictEqual(tx.outputs[0], {
|
|
536
|
+
script,
|
|
537
|
+
amount: 200n,
|
|
538
|
+
});
|
|
539
|
+
// Add K-V
|
|
540
|
+
tx.updateOutput(0, { bip32Derivation: [bip1] });
|
|
541
|
+
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip1]);
|
|
542
|
+
// Add another K-V
|
|
543
|
+
tx.updateOutput(0, { bip32Derivation: [bip2] });
|
|
544
|
+
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip1, bip2]);
|
|
545
|
+
// Delete K-V
|
|
546
|
+
tx.updateOutput(0, { bip32Derivation: [[pubKey, undefined]] });
|
|
547
|
+
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip2]);
|
|
548
|
+
// Second add of same k-v does nothing
|
|
549
|
+
tx.updateOutput(0, { bip32Derivation: [bip2] });
|
|
550
|
+
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip2]);
|
|
551
|
+
// Second add of k-v with different value breaks
|
|
552
|
+
throws(() => tx.updateOutput(0, { bip32Derivation: [[pubKey2, bip1[1]]] }));
|
|
553
|
+
tx.updateOutput(0, { bip32Derivation: [bip1, bip2, bip3] });
|
|
554
|
+
// Preserves order (re-ordered on PSBT encoding)
|
|
555
|
+
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip2, bip1, bip3]);
|
|
556
|
+
// PSBT encoding re-order k-v
|
|
557
|
+
const tx3 = btc.Transaction.fromPSBT(tx.toPSBT());
|
|
558
|
+
deepStrictEqual(tx3.outputs[0].bip32Derivation, [bip1, bip2, bip3]);
|
|
559
|
+
// Remove field
|
|
560
|
+
tx.updateOutput(0, { bip32Derivation: undefined });
|
|
561
|
+
deepStrictEqual(tx.outputs[0], {
|
|
562
|
+
script,
|
|
563
|
+
amount: 200n,
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Basic transaction sign
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
571
|
+
const txP2WPKH = new btc.Transaction();
|
|
572
|
+
for (const inp of TX_TEST_INPUTS) {
|
|
573
|
+
txP2WPKH.addInput({
|
|
574
|
+
txid: inp.txid,
|
|
575
|
+
index: inp.index,
|
|
576
|
+
witnessUtxo: {
|
|
577
|
+
amount: inp.amount,
|
|
578
|
+
script: btc.p2wpkh(secp256k1.getPublicKey(privKey, true)).script,
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
for (const [address, amount] of TX_TEST_OUTPUTS) tx32.addOutputAddress(address, amount);
|
|
583
|
+
deepStrictEqual(hex.encode(tx32.unsignedTx), RAW_TX_HEX);
|
|
584
|
+
txP2WPKH.sign(privKey);
|
|
585
|
+
txP2WPKH.finalize();
|
|
586
|
+
deepStrictEqual(txP2WPKH.id, 'cbb94443b19861df0824914fa654212facc071854e0df6f7388b482a6394526d');
|
|
587
|
+
deepStrictEqual(
|
|
588
|
+
txP2WPKH.hex,
|
|
589
|
+
'010000000001033edaa6c4e0740ae334dbb5857dd8c6faf6ea5196760652ad7033ed9031c261c00000000000ffffffff0d9ae8a4191b3ba5a2b856c21af0f7a4feb97957ae80725ef38a933c906519a20000000000ffffffffc7a4a37d38c2b0de3d3b3e8d8e8a331977c12532fc2a4632df27a89c311ee2fa0000000000ffffffff03e8030000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac881300000000000017a914a860f76561c85551594c18eecceffaee8c4822d7876b24000000000000160014e8df018c7e326cc253faac7e46cdc51e68542c4202473044022024e7b1a6ae19a95c69c192745db09cc54385a80cc7684570cfbf2da84cbbfa0802205ad55efb2019a1aa6edc03cf243989ea428c4d216699cbae2cfaf3c26ddef5650121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0247304402204415ef16f341e888ca2483b767b47fcf22977b6d673c3f7c6cae2f6b4bc2ac08022055be98747345b02a6f40edcc2f80390dcef4efe57b38c1bb7d16bdbca710abfd0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f02473044022069769fb5c97a7dd9401dbd3f6d32a38fe82bc8934c49c7c4cd3b39c6d120080c02202c181604203dc45c10e5290ded103195fae117d7fb0db19cdc411e73a76da6cb0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f00000000'
|
|
590
|
+
);
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### BIP174 PSBT multi-sig example
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
const testnet = {
|
|
597
|
+
wif: 0xef,
|
|
598
|
+
bip32: {
|
|
599
|
+
public: 0x043587cf,
|
|
600
|
+
private: 0x04358394,
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
// The private keys in the tests below are derived from the following master private key:
|
|
604
|
+
const epriv =
|
|
605
|
+
'tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF';
|
|
606
|
+
const hdkey = bip32.HDKey.fromExtendedKey(epriv, testnet.bip32);
|
|
607
|
+
// const seed = 'cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T';
|
|
608
|
+
const tx = new btc.Transaction(2);
|
|
609
|
+
// A creator creating a PSBT for a transaction which creates the following outputs:
|
|
610
|
+
tx.addOutput({ script: '0014d85c2b71d0060b09c9886aeb815e50991dda124d', amount: '1.49990000' });
|
|
611
|
+
tx.addOutput({ script: '001400aea9a2e5f0f876a588df5546e8742d1d87008f', amount: '1.00000000' });
|
|
612
|
+
// and spends the following inputs:
|
|
613
|
+
tx.addInput({
|
|
614
|
+
txid: '75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858',
|
|
615
|
+
index: 0,
|
|
616
|
+
});
|
|
617
|
+
tx.addInput({
|
|
618
|
+
txid: '1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83',
|
|
619
|
+
index: 1,
|
|
620
|
+
});
|
|
621
|
+
// must create this PSBT:
|
|
622
|
+
const psbt1 = tx.toPSBT();
|
|
623
|
+
// Given the above PSBT, an updater with only the following:
|
|
624
|
+
const tx2 = btc.Transaction.fromPSBT(psbt1);
|
|
625
|
+
tx2.updateInput(0, {
|
|
626
|
+
nonWitnessUtxo:
|
|
627
|
+
'0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000',
|
|
628
|
+
redeemScript:
|
|
629
|
+
'5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae',
|
|
630
|
+
bip32Derivation: [
|
|
631
|
+
[
|
|
632
|
+
'029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f',
|
|
633
|
+
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/0'") },
|
|
634
|
+
],
|
|
635
|
+
[
|
|
636
|
+
'02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7',
|
|
637
|
+
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/1'") },
|
|
638
|
+
],
|
|
639
|
+
],
|
|
640
|
+
});
|
|
641
|
+
tx2.updateInput(1, {
|
|
642
|
+
// use witness utxo ({script, amount})
|
|
643
|
+
witnessUtxo: btc.RawTx.decode(
|
|
644
|
+
hex.decode(
|
|
645
|
+
'0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000'
|
|
646
|
+
)
|
|
647
|
+
).outputs[1],
|
|
648
|
+
redeemScript: '00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903',
|
|
649
|
+
witnessScript:
|
|
650
|
+
'522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae',
|
|
651
|
+
bip32Derivation: [
|
|
652
|
+
[
|
|
653
|
+
'03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc',
|
|
654
|
+
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/2'") },
|
|
655
|
+
],
|
|
656
|
+
[
|
|
657
|
+
'023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73',
|
|
658
|
+
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/3'") },
|
|
659
|
+
],
|
|
660
|
+
],
|
|
661
|
+
});
|
|
662
|
+
tx2.updateOutput(0, {
|
|
663
|
+
bip32Derivation: [
|
|
664
|
+
[
|
|
665
|
+
'03a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58771',
|
|
666
|
+
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/4'") },
|
|
667
|
+
],
|
|
668
|
+
],
|
|
669
|
+
});
|
|
670
|
+
tx2.updateOutput(1, {
|
|
671
|
+
bip32Derivation: [
|
|
672
|
+
[
|
|
673
|
+
'027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b50051096',
|
|
674
|
+
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/5'") },
|
|
675
|
+
],
|
|
676
|
+
],
|
|
677
|
+
});
|
|
678
|
+
// Must create this PSBT:
|
|
679
|
+
const psbt2 = tx2.toPSBT();
|
|
680
|
+
// An updater which adds SIGHASH_ALL to the above PSBT must create this PSBT:
|
|
681
|
+
const tx3 = btc.Transaction.fromPSBT(psbt2);
|
|
682
|
+
for (let i = 0; i < tx3.inputs.length; i++)
|
|
683
|
+
tx3.updateInput(i, { sighashType: btc.SignatureHash.ALL });
|
|
684
|
+
const psbt3 = tx3.toPSBT();
|
|
685
|
+
/*
|
|
686
|
+
Given the above updated PSBT, a signer that supports SIGHASH_ALL for P2PKH and P2WPKH spends and uses RFC6979 for nonce generation and has the following keys:
|
|
687
|
+
- cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr (m/0'/0'/0')
|
|
688
|
+
- cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d (m/0'/0'/2')
|
|
689
|
+
*/
|
|
690
|
+
// NOTE: we don't use HDKey, because it will everything because of bip32 derivation
|
|
691
|
+
const tx4 = btc.Transaction.fromPSBT(psbt3);
|
|
692
|
+
tx4.sign(btc.WIF(testnet).decode('cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr'));
|
|
693
|
+
tx4.sign(btc.WIF(testnet).decode('cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d'));
|
|
694
|
+
// must create this PSBT:
|
|
695
|
+
const psbt4 = tx4.toPSBT();
|
|
696
|
+
// Given the above updated PSBT, a signer with the following keys:
|
|
697
|
+
// cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au (m/0'/0'/1')
|
|
698
|
+
// cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE (m/0'/0'/3')
|
|
699
|
+
const tx5 = btc.Transaction.fromPSBT(psbt3);
|
|
700
|
+
tx5.sign(btc.WIF(testnet).decode('cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au'));
|
|
701
|
+
tx5.sign(btc.WIF(testnet).decode('cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE'));
|
|
702
|
+
// must create this PSBT:
|
|
703
|
+
const psbt5 = tx5.toPSBT();
|
|
704
|
+
// Given both of the above PSBTs, a combiner must create this PSBT:
|
|
705
|
+
const psbt6 = btc.PSBTCombine([psbt4, psbt5]);
|
|
706
|
+
// Given the above PSBT, an input finalizer must create this PSBT:
|
|
707
|
+
const tx7 = btc.Transaction.fromPSBT(psbt6);
|
|
708
|
+
tx7.finalize();
|
|
709
|
+
const psbt7 = tx7.toPSBT();
|
|
710
|
+
// Given the above PSBT, a transaction extractor must create this Bitcoin transaction:
|
|
711
|
+
const tx8 = btc.Transaction.fromPSBT(psbt7);
|
|
712
|
+
deepStrictEqual(
|
|
713
|
+
tx8.extract(),
|
|
714
|
+
hex.decode(
|
|
715
|
+
'0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000'
|
|
716
|
+
)
|
|
717
|
+
);
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
## Utils
|
|
721
|
+
|
|
722
|
+
### getAddress
|
|
723
|
+
|
|
724
|
+
Returns common addresses from privateKey
|
|
725
|
+
|
|
726
|
+
```ts
|
|
727
|
+
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
728
|
+
deepStrictEqual(btc.getAddress('pkh', privKey), '1C6Rc3w25VHud3dLDamutaqfKWqhrLRTaD'); // P2PKH (legacy address)
|
|
729
|
+
deepStrictEqual(btc.getAddress('wpkh', privKey), 'bc1q0xcqpzrky6eff2g52qdye53xkk9jxkvrh6yhyw'); // SegWit V0 address
|
|
730
|
+
deepStrictEqual(
|
|
731
|
+
btc.getAddress('tr', priv),
|
|
732
|
+
'bc1p33wm0auhr9kkahzd6l0kqj85af4cswn276hsxg6zpz85xe2r0y8syx4e5t'
|
|
733
|
+
); // TapRoot KeyPathSpend
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
#### WIF
|
|
737
|
+
|
|
738
|
+
Encoding/decoding of WIF privateKeys. Only compessed keys are supported for now.
|
|
739
|
+
|
|
740
|
+
```ts
|
|
741
|
+
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
742
|
+
deepStrictEqual(btc.WIF().encode(privKey), 'KwFfNUhSDaASSAwtG7ssQM1uVX8RgX5GHWnnLfhfiQDigjioWXHH');
|
|
743
|
+
deepStrictEqual(
|
|
744
|
+
hex.encode(btc.WIF().decode('KwFfNUhSDaASSAwtG7ssQM1uVX8RgX5GHWnnLfhfiQDigjioWXHH')),
|
|
745
|
+
'0101010101010101010101010101010101010101010101010101010101010101'
|
|
746
|
+
);
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### Script
|
|
750
|
+
|
|
751
|
+
Encoding/decoding bitcoin scripts
|
|
752
|
+
|
|
753
|
+
```ts
|
|
754
|
+
deepStrictEqual(
|
|
755
|
+
btc.Script.decode(
|
|
756
|
+
hex.decode(
|
|
757
|
+
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
758
|
+
)
|
|
759
|
+
).map((i) => (P.isBytes(i) ? hex.encode(i) : i)),
|
|
760
|
+
[
|
|
761
|
+
'OP_2',
|
|
762
|
+
'030000000000000000000000000000000000000000000000000000000000000001',
|
|
763
|
+
'030000000000000000000000000000000000000000000000000000000000000002',
|
|
764
|
+
'030000000000000000000000000000000000000000000000000000000000000003',
|
|
765
|
+
'OP_3',
|
|
766
|
+
'CHECKMULTISIG',
|
|
767
|
+
]
|
|
768
|
+
);
|
|
769
|
+
deepStrictEqual(
|
|
770
|
+
hex.encode(
|
|
771
|
+
btc.Script.encode([
|
|
772
|
+
'OP_2',
|
|
773
|
+
hex.decode('030000000000000000000000000000000000000000000000000000000000000001'),
|
|
774
|
+
hex.decode('030000000000000000000000000000000000000000000000000000000000000002'),
|
|
775
|
+
hex.decode('030000000000000000000000000000000000000000000000000000000000000003'),
|
|
776
|
+
'OP_3',
|
|
777
|
+
'CHECKMULTISIG',
|
|
778
|
+
])
|
|
779
|
+
),
|
|
780
|
+
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
781
|
+
);
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### OutScript
|
|
785
|
+
|
|
786
|
+
Encoding/decoding of output scripts
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
deepStrictEqual(
|
|
790
|
+
btc.OutScript.decode(
|
|
791
|
+
hex.decode(
|
|
792
|
+
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
793
|
+
)
|
|
794
|
+
),
|
|
795
|
+
{
|
|
796
|
+
type: 'ms',
|
|
797
|
+
m: 2,
|
|
798
|
+
pubkeys: [
|
|
799
|
+
'030000000000000000000000000000000000000000000000000000000000000001',
|
|
800
|
+
'030000000000000000000000000000000000000000000000000000000000000002',
|
|
801
|
+
'030000000000000000000000000000000000000000000000000000000000000003',
|
|
802
|
+
].map(hex.decode),
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
deepStrictEqual(
|
|
806
|
+
hex.encode(
|
|
807
|
+
btc.OutScript.encode({
|
|
808
|
+
type: 'ms',
|
|
809
|
+
m: 2,
|
|
810
|
+
pubkeys: [
|
|
811
|
+
'030000000000000000000000000000000000000000000000000000000000000001',
|
|
812
|
+
'030000000000000000000000000000000000000000000000000000000000000002',
|
|
813
|
+
'030000000000000000000000000000000000000000000000000000000000000003',
|
|
814
|
+
].map(hex.decode),
|
|
815
|
+
})
|
|
816
|
+
),
|
|
817
|
+
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
818
|
+
);
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
## Security
|
|
822
|
+
|
|
823
|
+
The library has been audited on Feb 21, 2023 by an independent security firm cure53: [PDF](./audit/2023-02-21-cure53-audit-report.pdf). The audit has been funded by Ryan Shea.
|
|
824
|
+
|
|
825
|
+
Target was v0.3.0 (commit 397ed56), see [changes since audit](https://github.com/paulmillr/scure-btc-signer/compare/0.3.0..main).
|
|
826
|
+
|
|
827
|
+
We consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every `npm install`. Our goal is to minimize this attack vector. As for dependencies used by the library:
|
|
828
|
+
|
|
829
|
+
- noble-curves, noble-hashes are audited cryptography libraries also developed by us and follow the same practices
|
|
830
|
+
- scure-base is used for bech32 / base64 and was also audited
|
|
831
|
+
- micro-packed is used for binary encoding, has not been audited
|
|
832
|
+
- devDependencies contain scure-bip32, micro-packed-debugger, micro-should (our packages). Locked versions of prettier (linter) and typescript which are rarely updated. Every update is checked with `npm-diff`. They are only used if you clone the git repo and want to add some feature to it. End-users won't use them
|
|
833
|
+
|
|
834
|
+
## License
|
|
835
|
+
|
|
836
|
+
MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.
|