@vbyte/btc-dev 2.0.0 → 2.1.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -121
- package/README.md +36 -227
- package/dist/error.d.ts +11 -0
- package/dist/error.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/address/api.js +3 -2
- package/dist/lib/address/encode.js +6 -4
- package/dist/lib/address/p2pkh.js +4 -4
- package/dist/lib/address/p2sh.js +3 -3
- package/dist/lib/address/p2tr.js +3 -3
- package/dist/lib/address/p2wpkh.js +4 -4
- package/dist/lib/address/p2wsh.js +3 -3
- package/dist/lib/address/util.js +3 -1
- package/dist/lib/meta/locktime.js +3 -2
- package/dist/lib/meta/ref.js +4 -3
- package/dist/lib/meta/scribe.js +26 -6
- package/dist/lib/meta/sequence.js +8 -7
- package/dist/lib/script/decode.js +10 -9
- package/dist/lib/script/encode.js +4 -3
- package/dist/lib/script/words.js +4 -3
- package/dist/lib/sighash/segwit.js +9 -6
- package/dist/lib/sighash/taproot.js +7 -4
- package/dist/lib/sighash/util.js +4 -3
- package/dist/lib/signer/sign.js +7 -6
- package/dist/lib/signer/verify.js +8 -9
- package/dist/lib/taproot/cblock.js +3 -2
- package/dist/lib/taproot/encode.d.ts +1 -1
- package/dist/lib/taproot/encode.js +4 -3
- package/dist/lib/taproot/parse.js +9 -7
- package/dist/lib/taproot/tree.js +3 -2
- package/dist/lib/tx/create.js +1 -1
- package/dist/lib/tx/decode.js +13 -11
- package/dist/lib/tx/encode.js +1 -1
- package/dist/lib/tx/parse.js +3 -3
- package/dist/lib/tx/size.js +2 -4
- package/dist/lib/tx/util.js +2 -3
- package/dist/lib/tx/validate.js +36 -8
- package/dist/lib/witness/util.js +1 -1
- package/dist/main.cjs +1127 -1160
- package/dist/main.cjs.map +1 -1
- package/dist/module.mjs +1125 -1161
- package/dist/module.mjs.map +1 -1
- package/dist/package.json +13 -11
- package/dist/script.js +10 -12
- package/dist/script.js.map +1 -1
- package/docs/API.md +1145 -0
- package/docs/CONVENTIONS.md +316 -0
- package/docs/FAQ.md +396 -0
- package/docs/GUIDE.md +1102 -0
- package/package.json +13 -11
package/docs/GUIDE.md
ADDED
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
# @vbyte/btc-dev Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to Bitcoin development with `@vbyte/btc-dev`.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Quick Start](#quick-start)
|
|
9
|
+
- [Addresses](#addresses)
|
|
10
|
+
- [Transactions](#transactions)
|
|
11
|
+
- [Signing](#signing)
|
|
12
|
+
- [Taproot](#taproot)
|
|
13
|
+
- [Timelocks](#timelocks)
|
|
14
|
+
- [Scripts](#scripts)
|
|
15
|
+
- [Witness Data](#witness-data)
|
|
16
|
+
- [Import Patterns](#import-patterns)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### npm
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @vbyte/btc-dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### yarn
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
yarn add @vbyte/btc-dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### pnpm
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @vbyte/btc-dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### CDN (Browser)
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<script src="https://unpkg.com/@vbyte/btc-dev/dist/script.js"></script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Prerequisites
|
|
47
|
+
|
|
48
|
+
- Node.js 18 or later
|
|
49
|
+
- Basic understanding of Bitcoin concepts (addresses, transactions, UTXOs)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
Create a P2WPKH address and sign a transaction in under 3 minutes:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { ADDRESS, TX, SIGNER } from '@vbyte/btc-dev'
|
|
59
|
+
|
|
60
|
+
// Create a P2WPKH address from a compressed public key
|
|
61
|
+
const pubkey = '02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c'
|
|
62
|
+
const address = ADDRESS.p2wpkh(pubkey, 'main')
|
|
63
|
+
console.log(address.data) // bc1q...
|
|
64
|
+
|
|
65
|
+
// Build a transaction
|
|
66
|
+
const tx = TX.create({
|
|
67
|
+
version: 2,
|
|
68
|
+
locktime: 0,
|
|
69
|
+
vin: [{
|
|
70
|
+
txid: 'aa'.repeat(32),
|
|
71
|
+
vout: 0,
|
|
72
|
+
sequence: 0xffffffff,
|
|
73
|
+
prevout: { value: 100000n, script_pk: address.script.hex }
|
|
74
|
+
}],
|
|
75
|
+
vout: [{
|
|
76
|
+
value: 90000n,
|
|
77
|
+
script_pk: '0014' + '00'.repeat(20)
|
|
78
|
+
}]
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Sign and verify
|
|
82
|
+
const signature = SIGNER.sign_segwit_tx(secretKey, tx, {
|
|
83
|
+
txindex: 0,
|
|
84
|
+
pubkey: pubkey,
|
|
85
|
+
sigflag: 0x01
|
|
86
|
+
})
|
|
87
|
+
tx.vin[0].witness = [signature, pubkey]
|
|
88
|
+
|
|
89
|
+
const result = SIGNER.verify_tx(tx)
|
|
90
|
+
console.log('Valid:', result.valid)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Addresses
|
|
96
|
+
|
|
97
|
+
### Address Types Overview
|
|
98
|
+
|
|
99
|
+
| Type | Prefix (mainnet) | Function | Recommendation |
|
|
100
|
+
|------|------------------|----------|----------------|
|
|
101
|
+
| P2PKH | `1...` | `ADDRESS.p2pkh()` | Avoid for new projects |
|
|
102
|
+
| P2SH | `3...` | `ADDRESS.p2sh()` | Avoid for new projects |
|
|
103
|
+
| P2WPKH | `bc1q...` | `ADDRESS.p2wpkh()` | **Recommended** |
|
|
104
|
+
| P2WSH | `bc1q...` (longer) | `ADDRESS.p2wsh()` | For complex scripts |
|
|
105
|
+
| P2TR | `bc1p...` | `ADDRESS.p2tr()` | Best for privacy |
|
|
106
|
+
|
|
107
|
+
### P2WPKH (Recommended)
|
|
108
|
+
|
|
109
|
+
P2WPKH (Pay to Witness Public Key Hash) is the most efficient address type for single-signature use cases:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { ADDRESS } from '@vbyte/btc-dev'
|
|
113
|
+
|
|
114
|
+
// Create from a compressed public key (33 bytes)
|
|
115
|
+
const pubkey = '02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c'
|
|
116
|
+
const address = ADDRESS.p2wpkh(pubkey, 'main')
|
|
117
|
+
|
|
118
|
+
console.log(address.data) // bc1q...
|
|
119
|
+
console.log(address.format) // 'p2wpkh'
|
|
120
|
+
console.log(address.network) // 'main'
|
|
121
|
+
console.log(address.version) // 0
|
|
122
|
+
console.log(address.script.hex) // '0014' + pubkey_hash (20 bytes)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The scriptPubKey format is: `OP_0 <20-byte-pubkey-hash>` = `0x0014 + HASH160(compressed_pubkey)`
|
|
126
|
+
|
|
127
|
+
### P2TR (Taproot)
|
|
128
|
+
|
|
129
|
+
P2TR provides the best privacy since key-path spends look identical:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { ADDRESS } from '@vbyte/btc-dev'
|
|
133
|
+
|
|
134
|
+
// x-only public key (32 bytes, no 02/03 prefix)
|
|
135
|
+
const xOnlyPubkey = 'cc'.repeat(32)
|
|
136
|
+
const address = ADDRESS.p2tr(xOnlyPubkey, 'main')
|
|
137
|
+
|
|
138
|
+
console.log(address.data) // bc1p...
|
|
139
|
+
console.log(address.format) // 'p2tr'
|
|
140
|
+
console.log(address.version) // 1
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Converting compressed to x-only:**
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Compressed key: 02 or 03 prefix + 32 bytes
|
|
147
|
+
const compressedKey = '02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c'
|
|
148
|
+
|
|
149
|
+
// x-only key: just the 32 bytes (no prefix)
|
|
150
|
+
const xOnlyKey = compressedKey.slice(2) // Remove 02/03 prefix
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### P2WSH (Multisig)
|
|
154
|
+
|
|
155
|
+
P2WSH allows spending with a witness script:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { ADDRESS } from '@vbyte/btc-dev'
|
|
159
|
+
|
|
160
|
+
// 2-of-3 multisig script
|
|
161
|
+
const multisigScript = '5221' + pubkey1 + '21' + pubkey2 + '21' + pubkey3 + '53ae'
|
|
162
|
+
const address = ADDRESS.p2wsh(multisigScript, 'main')
|
|
163
|
+
|
|
164
|
+
console.log(address.data) // bc1q... (62 characters)
|
|
165
|
+
console.log(address.format) // 'p2wsh'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The scriptPubKey format is: `OP_0 <32-byte-script-hash>` = `0x0020 + SHA256(witness_script)`
|
|
169
|
+
|
|
170
|
+
### Legacy Addresses (P2PKH, P2SH)
|
|
171
|
+
|
|
172
|
+
For compatibility with old systems only:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { ADDRESS } from '@vbyte/btc-dev'
|
|
176
|
+
|
|
177
|
+
// P2PKH
|
|
178
|
+
const p2pkh = ADDRESS.p2pkh(pubkey, 'main')
|
|
179
|
+
console.log(p2pkh.data) // 1...
|
|
180
|
+
|
|
181
|
+
// P2SH
|
|
182
|
+
const p2sh = ADDRESS.p2sh(redeemScript, 'main')
|
|
183
|
+
console.log(p2sh.data) // 3...
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Network Prefixes
|
|
187
|
+
|
|
188
|
+
| Type | Mainnet | Testnet |
|
|
189
|
+
|------|---------|---------|
|
|
190
|
+
| P2PKH | `1` | `m` or `n` |
|
|
191
|
+
| P2SH | `3` | `2` |
|
|
192
|
+
| P2WPKH | `bc1q` | `tb1q` |
|
|
193
|
+
| P2WSH | `bc1q` | `tb1q` |
|
|
194
|
+
| P2TR | `bc1p` | `tb1p` |
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// Always specify network explicitly
|
|
198
|
+
const mainAddr = ADDRESS.p2wpkh(pubkey, 'main') // bc1q...
|
|
199
|
+
const testAddr = ADDRESS.p2wpkh(pubkey, 'test') // tb1q...
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Parsing Addresses
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { ADDRESS } from '@vbyte/btc-dev'
|
|
206
|
+
|
|
207
|
+
// Parse address string
|
|
208
|
+
const info = ADDRESS.parse_address('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4')
|
|
209
|
+
console.log(info.type) // 'p2wpkh'
|
|
210
|
+
console.log(info.network) // 'main'
|
|
211
|
+
console.log(info.hash) // pubkey hash
|
|
212
|
+
|
|
213
|
+
// Get address from scriptPubKey
|
|
214
|
+
const scriptPubKey = '0014751e76e8199196d454941c45d1b3a323f1433bd6'
|
|
215
|
+
const address = ADDRESS.get_address(scriptPubKey, 'main')
|
|
216
|
+
// 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### ScriptPubKey Patterns
|
|
220
|
+
|
|
221
|
+
Quick reference for identifying address types from scriptPubKey:
|
|
222
|
+
|
|
223
|
+
| Type | Pattern | Size |
|
|
224
|
+
|------|---------|------|
|
|
225
|
+
| P2PKH | `76a914{20-bytes}88ac` | 25 bytes |
|
|
226
|
+
| P2SH | `a914{20-bytes}87` | 23 bytes |
|
|
227
|
+
| P2WPKH | `0014{20-bytes}` | 22 bytes |
|
|
228
|
+
| P2WSH | `0020{32-bytes}` | 34 bytes |
|
|
229
|
+
| P2TR | `5120{32-bytes}` | 34 bytes |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Transactions
|
|
234
|
+
|
|
235
|
+
### Transaction Structure
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
interface TxData {
|
|
239
|
+
version: number // Transaction version (usually 1 or 2)
|
|
240
|
+
locktime: number // Block height or timestamp lock
|
|
241
|
+
vin: TxInput[] // Inputs (UTXOs being spent)
|
|
242
|
+
vout: TxOutput[] // Outputs (new UTXOs created)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
interface TxInput {
|
|
246
|
+
txid: string // Previous transaction ID (32 bytes)
|
|
247
|
+
vout: number // Output index in previous tx
|
|
248
|
+
sequence: number // Sequence number (for timelocks, RBF)
|
|
249
|
+
script_sig: string | null // Legacy unlock script
|
|
250
|
+
witness: string[] // Segwit/taproot witness data
|
|
251
|
+
prevout: TxOutput | null // Previous output data (for signing)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface TxOutput {
|
|
255
|
+
value: bigint // Amount in satoshis
|
|
256
|
+
script_pk: string // Locking script (scriptPubKey)
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Parsing Transactions
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { TX, SCRIPT } from '@vbyte/btc-dev'
|
|
264
|
+
|
|
265
|
+
const rawTx = '02000000000101...'
|
|
266
|
+
const tx = TX.parse(rawTx)
|
|
267
|
+
|
|
268
|
+
console.log('Version:', tx.version)
|
|
269
|
+
console.log('Locktime:', tx.locktime)
|
|
270
|
+
console.log('Inputs:', tx.vin.length)
|
|
271
|
+
console.log('Outputs:', tx.vout.length)
|
|
272
|
+
|
|
273
|
+
// Examine inputs
|
|
274
|
+
tx.vin.forEach((input, i) => {
|
|
275
|
+
console.log(`Input ${i}:`)
|
|
276
|
+
console.log(' TXID:', input.txid)
|
|
277
|
+
console.log(' Vout:', input.vout)
|
|
278
|
+
console.log(' Sequence:', input.sequence.toString(16))
|
|
279
|
+
console.log(' Witnesses:', input.witness.length)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// Examine outputs
|
|
283
|
+
tx.vout.forEach((output, i) => {
|
|
284
|
+
const type = SCRIPT.get_lock_script_type(output.script_pk)
|
|
285
|
+
console.log(`Output ${i}:`)
|
|
286
|
+
console.log(' Value:', output.value, 'sats')
|
|
287
|
+
console.log(' Type:', type)
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Building Transactions
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { TX } from '@vbyte/btc-dev'
|
|
295
|
+
|
|
296
|
+
const tx = TX.create({
|
|
297
|
+
version: 2,
|
|
298
|
+
locktime: 0,
|
|
299
|
+
vin: [{
|
|
300
|
+
txid: 'abcd'.repeat(16), // 32-byte TXID
|
|
301
|
+
vout: 0, // First output of that tx
|
|
302
|
+
sequence: 0xffffffff, // No RBF, no relative timelock
|
|
303
|
+
prevout: {
|
|
304
|
+
value: 100000n, // Previous output value (for signing)
|
|
305
|
+
script_pk: '0014aa...' // Previous output script
|
|
306
|
+
}
|
|
307
|
+
}],
|
|
308
|
+
vout: [{
|
|
309
|
+
value: 90000n, // Amount to send
|
|
310
|
+
script_pk: '0014bb...' // Recipient's scriptPubKey
|
|
311
|
+
}]
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
// Fee is implicit: 100000 - 90000 = 10000 satoshis
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Multiple inputs and outputs:**
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const tx = TX.create({
|
|
321
|
+
version: 2,
|
|
322
|
+
locktime: 0,
|
|
323
|
+
vin: [
|
|
324
|
+
{ txid: 'aa...', vout: 0, sequence: 0xffffffff,
|
|
325
|
+
prevout: { value: 50000n, script_pk: '0014...' } },
|
|
326
|
+
{ txid: 'bb...', vout: 1, sequence: 0xffffffff,
|
|
327
|
+
prevout: { value: 60000n, script_pk: '0014...' } }
|
|
328
|
+
],
|
|
329
|
+
vout: [
|
|
330
|
+
{ value: 80000n, script_pk: '0014...' }, // Payment
|
|
331
|
+
{ value: 25000n, script_pk: '0014...' } // Change
|
|
332
|
+
]
|
|
333
|
+
})
|
|
334
|
+
// Total in: 110000, Total out: 105000, Fee: 5000
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**RBF (Replace-By-Fee):**
|
|
338
|
+
|
|
339
|
+
Enable RBF by setting sequence < 0xfffffffe:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
{
|
|
343
|
+
txid: '...',
|
|
344
|
+
vout: 0,
|
|
345
|
+
sequence: 0xfffffffd // RBF enabled
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Encoding Transactions
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { TX } from '@vbyte/btc-dev'
|
|
353
|
+
|
|
354
|
+
// With witness data (for broadcasting)
|
|
355
|
+
const signedTxHex = TX.encode(tx, true).hex
|
|
356
|
+
|
|
357
|
+
// Without witness (for txid calculation)
|
|
358
|
+
const legacyTxHex = TX.encode(tx, false).hex
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Transaction Sizes and Fees
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { TX } from '@vbyte/btc-dev'
|
|
365
|
+
|
|
366
|
+
const size = TX.get_size(tx)
|
|
367
|
+
|
|
368
|
+
console.log('Base size:', size.base, 'bytes') // Without witness
|
|
369
|
+
console.log('Total size:', size.total, 'bytes') // With witness
|
|
370
|
+
console.log('Virtual size:', size.vsize, 'vbytes') // For fee calculation
|
|
371
|
+
console.log('Weight:', size.weight, 'WU') // Weight units
|
|
372
|
+
|
|
373
|
+
// Calculate fee rate
|
|
374
|
+
const inputTotal = tx.vin.reduce((sum, vin) => sum + vin.prevout.value, 0n)
|
|
375
|
+
const outputTotal = tx.vout.reduce((sum, vout) => sum + vout.value, 0n)
|
|
376
|
+
const fee = inputTotal - outputTotal
|
|
377
|
+
const feeRate = Number(fee) / size.vsize
|
|
378
|
+
console.log(`Fee rate: ${feeRate.toFixed(2)} sat/vB`)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Approximate sizes for common input types:**
|
|
382
|
+
|
|
383
|
+
| Input Type | Witness Size | vBytes (approx) |
|
|
384
|
+
|------------|--------------|-----------------|
|
|
385
|
+
| P2WPKH | 107 bytes | 68 vB |
|
|
386
|
+
| P2TR key-path | 64 bytes | 57.5 vB |
|
|
387
|
+
| P2WSH 2-of-2 | ~220 bytes | ~100 vB |
|
|
388
|
+
|
|
389
|
+
| Output Type | Size |
|
|
390
|
+
|-------------|------|
|
|
391
|
+
| P2WPKH | 31 bytes |
|
|
392
|
+
| P2TR | 43 bytes |
|
|
393
|
+
| P2WSH | 43 bytes |
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Signing
|
|
398
|
+
|
|
399
|
+
### P2WPKH Signing
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { TX, SIGNER } from '@vbyte/btc-dev'
|
|
403
|
+
|
|
404
|
+
// Build unsigned transaction
|
|
405
|
+
const tx = TX.create({
|
|
406
|
+
version: 2,
|
|
407
|
+
locktime: 0,
|
|
408
|
+
vin: [{
|
|
409
|
+
txid: prevTxid,
|
|
410
|
+
vout: 0,
|
|
411
|
+
sequence: 0xffffffff,
|
|
412
|
+
prevout: {
|
|
413
|
+
value: 100000n,
|
|
414
|
+
script_pk: '0014' + pubkeyHash // P2WPKH script
|
|
415
|
+
}
|
|
416
|
+
}],
|
|
417
|
+
vout: [{
|
|
418
|
+
value: 90000n,
|
|
419
|
+
script_pk: recipientScript
|
|
420
|
+
}]
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// Sign input 0
|
|
424
|
+
const signature = SIGNER.sign_segwit_tx(secretKey, tx, {
|
|
425
|
+
txindex: 0,
|
|
426
|
+
pubkey: compressedPubkey,
|
|
427
|
+
sigflag: 0x01 // SIGHASH_ALL
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
// Add witness
|
|
431
|
+
tx.vin[0].witness = [signature, compressedPubkey]
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### P2WSH Signing (Multisig)
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
import { TX, SIGNER } from '@vbyte/btc-dev'
|
|
438
|
+
|
|
439
|
+
// 2-of-2 multisig redeem script
|
|
440
|
+
const redeemScript = '5221' + pubkey1 + '21' + pubkey2 + '52ae'
|
|
441
|
+
|
|
442
|
+
// Sign with both keys
|
|
443
|
+
const sig1 = SIGNER.sign_segwit_tx(secretKey1, tx, {
|
|
444
|
+
txindex: 0,
|
|
445
|
+
script: redeemScript,
|
|
446
|
+
sigflag: 0x01
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
const sig2 = SIGNER.sign_segwit_tx(secretKey2, tx, {
|
|
450
|
+
txindex: 0,
|
|
451
|
+
script: redeemScript,
|
|
452
|
+
sigflag: 0x01
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
// Build witness: [OP_0, sig1, sig2, redeemScript]
|
|
456
|
+
tx.vin[0].witness = ['', sig1, sig2, redeemScript]
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Sighash Flags
|
|
460
|
+
|
|
461
|
+
Control what parts of the transaction the signature commits to:
|
|
462
|
+
|
|
463
|
+
| Flag | Value | Meaning |
|
|
464
|
+
|------|-------|---------|
|
|
465
|
+
| SIGHASH_DEFAULT | 0x00 | Taproot default (equivalent to ALL) |
|
|
466
|
+
| SIGHASH_ALL | 0x01 | Sign all inputs and outputs (safest) |
|
|
467
|
+
| SIGHASH_NONE | 0x02 | Sign inputs only, outputs modifiable |
|
|
468
|
+
| SIGHASH_SINGLE | 0x03 | Sign only the output at same index |
|
|
469
|
+
| ANYONECANPAY | 0x80 | Sign only own input (combine with above) |
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
// Standard: sign everything
|
|
473
|
+
SIGNER.sign_segwit_tx(key, tx, { txindex: 0, sigflag: 0x01 })
|
|
474
|
+
|
|
475
|
+
// Allow adding more inputs
|
|
476
|
+
SIGNER.sign_segwit_tx(key, tx, { txindex: 0, sigflag: 0x81 }) // ALL|ANYONECANPAY
|
|
477
|
+
|
|
478
|
+
// Sign only one input and its corresponding output
|
|
479
|
+
SIGNER.sign_segwit_tx(key, tx, { txindex: 0, sigflag: 0x83 }) // SINGLE|ANYONECANPAY
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Verifying Transactions
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import { SIGNER } from '@vbyte/btc-dev'
|
|
486
|
+
|
|
487
|
+
const result = SIGNER.verify_tx(tx)
|
|
488
|
+
|
|
489
|
+
if (result.valid) {
|
|
490
|
+
console.log('All signatures valid')
|
|
491
|
+
} else {
|
|
492
|
+
console.log('Verification failed:', result.error)
|
|
493
|
+
result.inputs.forEach(input => {
|
|
494
|
+
if (!input.valid) {
|
|
495
|
+
console.log(`Input ${input.index}: ${input.error}`)
|
|
496
|
+
}
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Or throw on failure
|
|
501
|
+
try {
|
|
502
|
+
SIGNER.verify_tx(tx, { throws: true })
|
|
503
|
+
console.log('Transaction is valid')
|
|
504
|
+
} catch (err) {
|
|
505
|
+
console.log('Invalid:', err.message)
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Signing Multiple Inputs
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
for (let i = 0; i < tx.vin.length; i++) {
|
|
513
|
+
const sig = SIGNER.sign_segwit_tx(secretKey, tx, {
|
|
514
|
+
txindex: i,
|
|
515
|
+
pubkey: pubkeys[i],
|
|
516
|
+
sigflag: 0x01
|
|
517
|
+
})
|
|
518
|
+
tx.vin[i].witness = [sig, pubkeys[i]]
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Taproot
|
|
525
|
+
|
|
526
|
+
### Key Concepts
|
|
527
|
+
|
|
528
|
+
**x-only Public Keys:**
|
|
529
|
+
|
|
530
|
+
Taproot uses 32-byte "x-only" public keys instead of 33-byte compressed keys:
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
// Compressed (33 bytes): prefix + x-coordinate
|
|
534
|
+
const compressed = '02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c'
|
|
535
|
+
|
|
536
|
+
// x-only (32 bytes): just the x-coordinate
|
|
537
|
+
const xOnly = compressed.slice(2)
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Tweaking:**
|
|
541
|
+
|
|
542
|
+
The internal key is "tweaked" with the merkle root of scripts to produce the taproot output key:
|
|
543
|
+
|
|
544
|
+
```
|
|
545
|
+
taproot_output_key = internal_key + tweak * G
|
|
546
|
+
tweak = tagged_hash("TapTweak", internal_key || merkle_root)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Key-Path Spending
|
|
550
|
+
|
|
551
|
+
The simplest taproot usage: spending with just a signature.
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import { TX, SIGNER, TAPROOT, ADDRESS } from '@vbyte/btc-dev'
|
|
555
|
+
|
|
556
|
+
// Internal public key (32 bytes, x-only)
|
|
557
|
+
const internalPubkey = 'cc'.repeat(32)
|
|
558
|
+
|
|
559
|
+
// For key-path only (no scripts), tweak with empty root
|
|
560
|
+
const taptweak = TAPROOT.encode_taptweak(internalPubkey)
|
|
561
|
+
|
|
562
|
+
// Build transaction
|
|
563
|
+
const tx = TX.create({
|
|
564
|
+
version: 2,
|
|
565
|
+
locktime: 0,
|
|
566
|
+
vin: [{
|
|
567
|
+
txid: '...',
|
|
568
|
+
vout: 0,
|
|
569
|
+
sequence: 0xffffffff,
|
|
570
|
+
prevout: {
|
|
571
|
+
value: 100000n,
|
|
572
|
+
script_pk: '5120' + tweakedPubkey // P2TR script
|
|
573
|
+
}
|
|
574
|
+
}],
|
|
575
|
+
vout: [{
|
|
576
|
+
value: 90000n,
|
|
577
|
+
script_pk: '5120...'
|
|
578
|
+
}]
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// Tweak the secret key for signing (use @vbyte/crypto)
|
|
582
|
+
import { tweak_seckey } from '@vbyte/crypto/ecc'
|
|
583
|
+
const tweakedSeckey = tweak_seckey(internalSeckey, taptweak, true)
|
|
584
|
+
|
|
585
|
+
// Sign
|
|
586
|
+
const signature = SIGNER.sign_taproot_tx(tweakedSeckey, tx, {
|
|
587
|
+
txindex: 0,
|
|
588
|
+
sigflag: 0x00 // SIGHASH_DEFAULT
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
// Key-path witness is just the signature
|
|
592
|
+
tx.vin[0].witness = [signature]
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Script-Path Spending
|
|
596
|
+
|
|
597
|
+
Spend by revealing a script from the taproot tree.
|
|
598
|
+
|
|
599
|
+
**Tapscripts:**
|
|
600
|
+
|
|
601
|
+
Tapscripts are Bitcoin scripts with BIP-342 modifications:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
import { TAPROOT } from '@vbyte/btc-dev'
|
|
605
|
+
|
|
606
|
+
// Simple tapscript: <pubkey> OP_CHECKSIG
|
|
607
|
+
const tapscript = '20' + xOnlyPubkey + 'ac'
|
|
608
|
+
|
|
609
|
+
// Encode as a tapleaf
|
|
610
|
+
const tapleaf = TAPROOT.encode_tapscript(tapscript)
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Building a Taproot Tree:**
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import { TAPROOT } from '@vbyte/btc-dev'
|
|
617
|
+
|
|
618
|
+
// Define scripts
|
|
619
|
+
const script1 = '20' + pubkey1 + 'ac' // <pk1> OP_CHECKSIG
|
|
620
|
+
const script2 = '20' + pubkey2 + 'ac' // <pk2> OP_CHECKSIG
|
|
621
|
+
|
|
622
|
+
// Encode as tapleaves
|
|
623
|
+
const leaf1 = TAPROOT.encode_tapscript(script1)
|
|
624
|
+
const leaf2 = TAPROOT.encode_tapscript(script2)
|
|
625
|
+
|
|
626
|
+
// Build the tree
|
|
627
|
+
const tree = [leaf1.hex, leaf2.hex]
|
|
628
|
+
|
|
629
|
+
// Get merkle root
|
|
630
|
+
const [root, target, path] = TAPROOT.merkleize(tree, leaf1.hex)
|
|
631
|
+
console.log('Root:', root)
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Complex Trees:**
|
|
635
|
+
|
|
636
|
+
Trees can be nested for more scripts:
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
// Balanced tree with 4 scripts
|
|
640
|
+
const tree = [
|
|
641
|
+
[leaf1, leaf2], // Left branch
|
|
642
|
+
[leaf3, leaf4] // Right branch
|
|
643
|
+
]
|
|
644
|
+
|
|
645
|
+
const [root] = TAPROOT.merkleize(tree)
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Building a Control Block:**
|
|
649
|
+
|
|
650
|
+
To spend via script-path, you need a control block proving the script is in the tree:
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { TAPROOT } from '@vbyte/btc-dev'
|
|
654
|
+
|
|
655
|
+
// Merkleize and get path to target script
|
|
656
|
+
const targetLeaf = TAPROOT.encode_tapscript(script1).hex
|
|
657
|
+
const [root, target, path] = TAPROOT.merkleize(tree, targetLeaf)
|
|
658
|
+
|
|
659
|
+
// Build control block
|
|
660
|
+
const cblock = TAPROOT.build_cblock(
|
|
661
|
+
internalPubkey,
|
|
662
|
+
path,
|
|
663
|
+
0, // parity (0 or 1)
|
|
664
|
+
0xc0 // leaf version
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
console.log(cblock.hex)
|
|
668
|
+
// Structure: <version|parity> || <internal_pubkey> || <merkle_path>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**Signing Script-Path:**
|
|
672
|
+
|
|
673
|
+
```typescript
|
|
674
|
+
import { SIGNER, TAPROOT } from '@vbyte/btc-dev'
|
|
675
|
+
|
|
676
|
+
// The tapleaf hash is the "extension" for signing
|
|
677
|
+
const tapleafHash = TAPROOT.encode_tapscript(script).hex
|
|
678
|
+
|
|
679
|
+
// Sign with the script's key (NOT the tweaked key)
|
|
680
|
+
const signature = SIGNER.sign_taproot_tx(scriptSeckey, tx, {
|
|
681
|
+
txindex: 0,
|
|
682
|
+
sigflag: 0x00,
|
|
683
|
+
extension: tapleafHash // Commits to the script being used
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
// Script-path witness: [signature(s), script, control_block]
|
|
687
|
+
tx.vin[0].witness = [signature, script, cblock.hex]
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Control Block Structure
|
|
691
|
+
|
|
692
|
+
The control block proves a script is in the taproot tree:
|
|
693
|
+
|
|
694
|
+
```
|
|
695
|
+
<1 byte: leaf_version | output_key_parity>
|
|
696
|
+
<32 bytes: internal_public_key>
|
|
697
|
+
<32*n bytes: merkle path (sibling hashes)>
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**Parsing:**
|
|
701
|
+
|
|
702
|
+
```typescript
|
|
703
|
+
import { TAPROOT } from '@vbyte/btc-dev'
|
|
704
|
+
|
|
705
|
+
const data = TAPROOT.parse_cblock(cblockHex)
|
|
706
|
+
|
|
707
|
+
console.log(data.version) // Leaf version (usually 0xc0)
|
|
708
|
+
console.log(data.parity) // Output key parity (0 or 1)
|
|
709
|
+
console.log(data.pubkey) // Internal public key
|
|
710
|
+
console.log(data.path) // Merkle path array
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
**Verifying:**
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
import { TAPROOT } from '@vbyte/btc-dev'
|
|
717
|
+
|
|
718
|
+
const tapkey = taprootOutputKey
|
|
719
|
+
const target = TAPROOT.encode_tapscript(script).hex
|
|
720
|
+
const isValid = TAPROOT.verify_taproot(tapkey, target, cblock)
|
|
721
|
+
|
|
722
|
+
if (!isValid) {
|
|
723
|
+
throw new Error('Control block verification failed')
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Sighash in Taproot
|
|
728
|
+
|
|
729
|
+
Taproot uses different sighash types (BIP-341):
|
|
730
|
+
|
|
731
|
+
| Flag | Value | Description |
|
|
732
|
+
|------|-------|-------------|
|
|
733
|
+
| SIGHASH_DEFAULT | 0x00 | Same as ALL, but signature is 64 bytes |
|
|
734
|
+
| SIGHASH_ALL | 0x01 | Sign all inputs and outputs |
|
|
735
|
+
| SIGHASH_NONE | 0x02 | Sign inputs only |
|
|
736
|
+
| SIGHASH_SINGLE | 0x03 | Sign corresponding output only |
|
|
737
|
+
| ANYONECANPAY | 0x80 | Sign only this input |
|
|
738
|
+
|
|
739
|
+
The default (0x00) produces 64-byte signatures. Any other flag produces 65-byte signatures.
|
|
740
|
+
|
|
741
|
+
```typescript
|
|
742
|
+
// Default: 64-byte signature
|
|
743
|
+
const sig = SIGNER.sign_taproot_tx(key, tx, { txindex: 0, sigflag: 0x00 })
|
|
744
|
+
console.log(sig.length) // 128 hex chars = 64 bytes
|
|
745
|
+
|
|
746
|
+
// SIGHASH_ALL: 65-byte signature
|
|
747
|
+
const sig2 = SIGNER.sign_taproot_tx(key, tx, { txindex: 0, sigflag: 0x01 })
|
|
748
|
+
console.log(sig2.length) // 130 hex chars = 65 bytes
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
### Best Practices
|
|
752
|
+
|
|
753
|
+
**Key-Path vs Script-Path:**
|
|
754
|
+
- **Key-path** when possible (cheapest, most private)
|
|
755
|
+
- **Script-path** for complex conditions, multisig, or fallback conditions
|
|
756
|
+
|
|
757
|
+
**Tree Design:**
|
|
758
|
+
- Put most-likely-used scripts closer to root (shorter proofs)
|
|
759
|
+
- Balance the tree for equal-probability scripts
|
|
760
|
+
- Maximum tree depth: 128 levels
|
|
761
|
+
|
|
762
|
+
**Privacy:**
|
|
763
|
+
- Key-path spends don't reveal script existence
|
|
764
|
+
- Script-path reveals only the used script, not others
|
|
765
|
+
- Use randomized internal keys for unlinkability
|
|
766
|
+
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
## Timelocks
|
|
770
|
+
|
|
771
|
+
### Transaction-Level Locktime (BIP-65)
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
import { META, TX } from '@vbyte/btc-dev'
|
|
775
|
+
|
|
776
|
+
// Lock until block 850000
|
|
777
|
+
const locktime = META.encode_locktime({
|
|
778
|
+
type: 'heightlock',
|
|
779
|
+
height: 850000
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
const tx = TX.create({
|
|
783
|
+
version: 2,
|
|
784
|
+
locktime, // Can't be mined before block 850000
|
|
785
|
+
vin: [...],
|
|
786
|
+
vout: [...]
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
// Decode locktime
|
|
790
|
+
const decoded = META.decode_locktime(tx.locktime)
|
|
791
|
+
console.log(decoded) // { type: 'heightlock', height: 850000 }
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
**Timestamp Locktime:**
|
|
795
|
+
|
|
796
|
+
```typescript
|
|
797
|
+
import { META } from '@vbyte/btc-dev'
|
|
798
|
+
|
|
799
|
+
// Lock until a specific Unix timestamp (must be > 500000000)
|
|
800
|
+
const locktime = META.encode_locktime({
|
|
801
|
+
type: 'timelock',
|
|
802
|
+
stamp: 1704067200 // 2024-01-01 00:00:00 UTC
|
|
803
|
+
})
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Input-Level Relative Timelock (BIP-68)
|
|
807
|
+
|
|
808
|
+
```typescript
|
|
809
|
+
import { META, TX } from '@vbyte/btc-dev'
|
|
810
|
+
|
|
811
|
+
// Wait 144 blocks (~24 hours) after input confirms
|
|
812
|
+
const sequence = META.encode_sequence({
|
|
813
|
+
mode: 'height',
|
|
814
|
+
height: 144
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
const tx = TX.create({
|
|
818
|
+
version: 2,
|
|
819
|
+
locktime: 0,
|
|
820
|
+
vin: [{
|
|
821
|
+
txid: '...',
|
|
822
|
+
vout: 0,
|
|
823
|
+
sequence // Relative timelock on this input
|
|
824
|
+
}],
|
|
825
|
+
vout: [...]
|
|
826
|
+
})
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
**Time-based relative locktime:**
|
|
830
|
+
|
|
831
|
+
```typescript
|
|
832
|
+
// Lock for specific time (in 512-second increments)
|
|
833
|
+
const sequence = META.encode_sequence({
|
|
834
|
+
mode: 'stamp',
|
|
835
|
+
stamp: 86400 // 24 hours = 86400 seconds
|
|
836
|
+
})
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
---
|
|
840
|
+
|
|
841
|
+
## Scripts
|
|
842
|
+
|
|
843
|
+
### Detect Script Type
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
import { SCRIPT } from '@vbyte/btc-dev'
|
|
847
|
+
|
|
848
|
+
const type = SCRIPT.get_lock_script_type(scriptPubKey)
|
|
849
|
+
// Returns: 'p2pkh' | 'p2sh' | 'p2wpkh' | 'p2wsh' | 'p2tr' | 'opreturn' | null
|
|
850
|
+
|
|
851
|
+
const version = SCRIPT.get_lock_script_version(scriptPubKey)
|
|
852
|
+
// Returns: 0 (segwit v0), 1 (taproot), or null (legacy)
|
|
853
|
+
|
|
854
|
+
// Type-specific checks
|
|
855
|
+
SCRIPT.is_p2wpkh_script(script) // true/false
|
|
856
|
+
SCRIPT.is_p2tr_script(script) // true/false
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### Encode/Decode Scripts
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
import { SCRIPT } from '@vbyte/btc-dev'
|
|
863
|
+
|
|
864
|
+
// Decode script to ASM
|
|
865
|
+
const asm = SCRIPT.decode(scriptHex)
|
|
866
|
+
console.log(asm) // ['OP_DUP', 'OP_HASH160', '89...', 'OP_EQUALVERIFY', 'OP_CHECKSIG']
|
|
867
|
+
|
|
868
|
+
// Encode ASM to hex
|
|
869
|
+
const hex = SCRIPT.encode(['OP_1', pubkey1, pubkey2, 'OP_2', 'OP_CHECKMULTISIG'])
|
|
870
|
+
console.log(hex)
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Create Multisig Script
|
|
874
|
+
|
|
875
|
+
```typescript
|
|
876
|
+
import { SCRIPT } from '@vbyte/btc-dev'
|
|
877
|
+
|
|
878
|
+
// 2-of-3 multisig
|
|
879
|
+
const script = SCRIPT.encode([
|
|
880
|
+
'OP_2',
|
|
881
|
+
pubkey1,
|
|
882
|
+
pubkey2,
|
|
883
|
+
pubkey3,
|
|
884
|
+
'OP_3',
|
|
885
|
+
'OP_CHECKMULTISIG'
|
|
886
|
+
])
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## Witness Data
|
|
892
|
+
|
|
893
|
+
### Parse Witness Data
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
import { WITNESS, TX } from '@vbyte/btc-dev'
|
|
897
|
+
|
|
898
|
+
const tx = TX.parse(rawTx)
|
|
899
|
+
|
|
900
|
+
tx.vin.forEach((input, i) => {
|
|
901
|
+
if (input.witness.length === 0) {
|
|
902
|
+
console.log(`Input ${i}: No witness (legacy)`)
|
|
903
|
+
return
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const witnessData = WITNESS.parse(input.witness)
|
|
907
|
+
|
|
908
|
+
console.log(`Input ${i}:`)
|
|
909
|
+
console.log(' Type:', witnessData.type)
|
|
910
|
+
console.log(' Version:', witnessData.version)
|
|
911
|
+
console.log(' Params:', witnessData.params.length)
|
|
912
|
+
|
|
913
|
+
if (witnessData.script) {
|
|
914
|
+
console.log(' Script:', witnessData.script)
|
|
915
|
+
}
|
|
916
|
+
if (witnessData.cblock) {
|
|
917
|
+
console.log(' Control block:', witnessData.cblock)
|
|
918
|
+
}
|
|
919
|
+
if (witnessData.annex) {
|
|
920
|
+
console.log(' Annex:', witnessData.annex)
|
|
921
|
+
}
|
|
922
|
+
})
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### Witness Types
|
|
926
|
+
|
|
927
|
+
| Type | Description | Witness Stack |
|
|
928
|
+
|------|-------------|---------------|
|
|
929
|
+
| `p2wpkh` | Segwit single-sig | `[signature, pubkey]` |
|
|
930
|
+
| `p2wsh` | Segwit script | `[...args, script]` |
|
|
931
|
+
| `p2tr` | Taproot key-path | `[signature]` |
|
|
932
|
+
| `p2ts` | Taproot script-path | `[...args, script, cblock]` |
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
## Import Patterns
|
|
937
|
+
|
|
938
|
+
### Namespace Imports (Recommended)
|
|
939
|
+
|
|
940
|
+
```typescript
|
|
941
|
+
import { ADDRESS, TX, SIGNER, SCRIPT, TAPROOT, SIGHASH, WITNESS, META } from '@vbyte/btc-dev'
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
### Tree-Shaking Imports
|
|
945
|
+
|
|
946
|
+
For smaller bundles, import specific functions:
|
|
947
|
+
|
|
948
|
+
```typescript
|
|
949
|
+
import { p2wpkh, p2tr } from '@vbyte/btc-dev/address'
|
|
950
|
+
import { parse_tx, encode_tx, create_tx } from '@vbyte/btc-dev/tx'
|
|
951
|
+
import { sign_segwit_tx, verify_tx } from '@vbyte/btc-dev/signer'
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
### Type Imports
|
|
955
|
+
|
|
956
|
+
```typescript
|
|
957
|
+
import type {
|
|
958
|
+
TxData,
|
|
959
|
+
TxInput,
|
|
960
|
+
TxOutput,
|
|
961
|
+
TxTemplate,
|
|
962
|
+
AddressInfo,
|
|
963
|
+
SigHashOptions,
|
|
964
|
+
WitnessData,
|
|
965
|
+
ChainNetwork
|
|
966
|
+
} from '@vbyte/btc-dev'
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
---
|
|
970
|
+
|
|
971
|
+
## Security Best Practices
|
|
972
|
+
|
|
973
|
+
### Private Key Handling
|
|
974
|
+
|
|
975
|
+
**Never expose secret keys:**
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
// WRONG - Never log secret keys
|
|
979
|
+
console.log('Secret key:', secretKey)
|
|
980
|
+
|
|
981
|
+
// WRONG - Never include in error messages
|
|
982
|
+
throw new Error(`Failed to sign with key: ${secretKey}`)
|
|
983
|
+
|
|
984
|
+
// CORRECT - Use secure handling
|
|
985
|
+
const signature = SIGNER.sign_segwit_tx(secretKey, txdata, options)
|
|
986
|
+
// Clear from memory when possible
|
|
987
|
+
secretKey = null
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**Secure key generation:**
|
|
991
|
+
|
|
992
|
+
- Always use cryptographically secure random number generators
|
|
993
|
+
- Never use predictable seeds or weak entropy sources
|
|
994
|
+
- Consider using hardware security modules (HSMs) for production systems
|
|
995
|
+
|
|
996
|
+
```typescript
|
|
997
|
+
// Use @noble/curves for key generation
|
|
998
|
+
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
999
|
+
import { randomBytes } from '@noble/hashes/utils'
|
|
1000
|
+
|
|
1001
|
+
const secretKey = randomBytes(32)
|
|
1002
|
+
const publicKey = secp256k1.getPublicKey(secretKey)
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
**Memory handling:**
|
|
1006
|
+
|
|
1007
|
+
- Clear sensitive data from memory after use
|
|
1008
|
+
- Be aware that JavaScript garbage collection may not immediately clear values
|
|
1009
|
+
- For high-security applications, consider using typed arrays that can be zeroed
|
|
1010
|
+
|
|
1011
|
+
### Input Validation
|
|
1012
|
+
|
|
1013
|
+
**Always validate transaction data before signing:**
|
|
1014
|
+
|
|
1015
|
+
```typescript
|
|
1016
|
+
// The library performs validation
|
|
1017
|
+
TX.assert_tx_data(txdata) // Throws on invalid
|
|
1018
|
+
TX.assert_tx_template(template) // Throws on invalid
|
|
1019
|
+
|
|
1020
|
+
// Validate at boundaries
|
|
1021
|
+
if (!tx.vin.every(vin => vin.prevout !== null)) {
|
|
1022
|
+
throw new Error('Missing prevout data for input')
|
|
1023
|
+
}
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
**Validate scripts:**
|
|
1027
|
+
|
|
1028
|
+
```typescript
|
|
1029
|
+
// Check script type
|
|
1030
|
+
const type = SCRIPT.get_lock_script_type(script)
|
|
1031
|
+
if (type === null) {
|
|
1032
|
+
throw new Error('Unknown or invalid script type')
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Validate script structure
|
|
1036
|
+
if (!SCRIPT.is_valid_script(script)) {
|
|
1037
|
+
throw new Error('Invalid script')
|
|
1038
|
+
}
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
### Sighash Flag Security
|
|
1042
|
+
|
|
1043
|
+
Understanding sighash flags is critical for security:
|
|
1044
|
+
|
|
1045
|
+
| Flag | Value | Security Implications |
|
|
1046
|
+
|------|-------|----------------------|
|
|
1047
|
+
| SIGHASH_ALL | 0x01 | **Safest** - Signs all inputs and outputs |
|
|
1048
|
+
| SIGHASH_NONE | 0x02 | **Dangerous** - Outputs can be modified by anyone |
|
|
1049
|
+
| SIGHASH_SINGLE | 0x03 | Signs only the output at the same index |
|
|
1050
|
+
| ANYONECANPAY | 0x80 | Only signs own input, others can be added |
|
|
1051
|
+
|
|
1052
|
+
```typescript
|
|
1053
|
+
// SAFE: SIGHASH_ALL - signs all inputs and outputs
|
|
1054
|
+
const safeSig = SIGNER.sign_segwit_tx(key, txdata, {
|
|
1055
|
+
txindex: 0,
|
|
1056
|
+
sigflag: 0x01 // SIGHASH_ALL
|
|
1057
|
+
})
|
|
1058
|
+
|
|
1059
|
+
// DANGEROUS: SIGHASH_NONE - outputs can be changed
|
|
1060
|
+
// Only use if you understand the implications
|
|
1061
|
+
const dangerousSig = SIGNER.sign_segwit_tx(key, txdata, {
|
|
1062
|
+
txindex: 0,
|
|
1063
|
+
sigflag: 0x02 // SIGHASH_NONE - anyone can redirect funds!
|
|
1064
|
+
})
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
### Signature Verification
|
|
1068
|
+
|
|
1069
|
+
Always verify signatures after signing:
|
|
1070
|
+
|
|
1071
|
+
```typescript
|
|
1072
|
+
// Sign the transaction
|
|
1073
|
+
const signature = SIGNER.sign_segwit_tx(secretKey, txdata, options)
|
|
1074
|
+
|
|
1075
|
+
// Verify the signature is valid
|
|
1076
|
+
txdata.vin[0].witness = [signature, pubkey]
|
|
1077
|
+
const result = SIGNER.verify_tx(txdata)
|
|
1078
|
+
|
|
1079
|
+
if (!result.valid) {
|
|
1080
|
+
throw new Error('Signature verification failed: ' + result.error)
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
### Security Checklist
|
|
1085
|
+
|
|
1086
|
+
Before deploying to production:
|
|
1087
|
+
|
|
1088
|
+
- [ ] All secret keys are securely generated
|
|
1089
|
+
- [ ] No secret keys are logged or exposed
|
|
1090
|
+
- [ ] Transaction data is validated before signing
|
|
1091
|
+
- [ ] Signature verification is performed after signing
|
|
1092
|
+
- [ ] Correct sighash flags are used for the use case
|
|
1093
|
+
- [ ] Network is explicitly specified
|
|
1094
|
+
- [ ] Dependencies are up to date
|
|
1095
|
+
- [ ] Error messages don't leak sensitive data
|
|
1096
|
+
|
|
1097
|
+
---
|
|
1098
|
+
|
|
1099
|
+
## Next Steps
|
|
1100
|
+
|
|
1101
|
+
- [API Reference](API.md) - Complete function documentation
|
|
1102
|
+
- [FAQ](FAQ.md) - Common questions and troubleshooting
|