@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -121
  3. package/README.md +36 -227
  4. package/dist/error.d.ts +11 -0
  5. package/dist/error.js +20 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +1 -0
  8. package/dist/lib/address/api.js +3 -2
  9. package/dist/lib/address/encode.js +6 -4
  10. package/dist/lib/address/p2pkh.js +4 -4
  11. package/dist/lib/address/p2sh.js +3 -3
  12. package/dist/lib/address/p2tr.js +3 -3
  13. package/dist/lib/address/p2wpkh.js +4 -4
  14. package/dist/lib/address/p2wsh.js +3 -3
  15. package/dist/lib/address/util.js +3 -1
  16. package/dist/lib/meta/locktime.js +3 -2
  17. package/dist/lib/meta/ref.js +4 -3
  18. package/dist/lib/meta/scribe.js +26 -6
  19. package/dist/lib/meta/sequence.js +8 -7
  20. package/dist/lib/script/decode.js +10 -9
  21. package/dist/lib/script/encode.js +4 -3
  22. package/dist/lib/script/words.js +4 -3
  23. package/dist/lib/sighash/segwit.js +9 -6
  24. package/dist/lib/sighash/taproot.js +7 -4
  25. package/dist/lib/sighash/util.js +4 -3
  26. package/dist/lib/signer/sign.js +7 -6
  27. package/dist/lib/signer/verify.js +8 -9
  28. package/dist/lib/taproot/cblock.js +3 -2
  29. package/dist/lib/taproot/encode.d.ts +1 -1
  30. package/dist/lib/taproot/encode.js +4 -3
  31. package/dist/lib/taproot/parse.js +9 -7
  32. package/dist/lib/taproot/tree.js +3 -2
  33. package/dist/lib/tx/create.js +1 -1
  34. package/dist/lib/tx/decode.js +13 -11
  35. package/dist/lib/tx/encode.js +1 -1
  36. package/dist/lib/tx/parse.js +3 -3
  37. package/dist/lib/tx/size.js +2 -4
  38. package/dist/lib/tx/util.js +2 -3
  39. package/dist/lib/tx/validate.js +36 -8
  40. package/dist/lib/witness/util.js +1 -1
  41. package/dist/main.cjs +1127 -1160
  42. package/dist/main.cjs.map +1 -1
  43. package/dist/module.mjs +1125 -1161
  44. package/dist/module.mjs.map +1 -1
  45. package/dist/package.json +13 -11
  46. package/dist/script.js +10 -12
  47. package/dist/script.js.map +1 -1
  48. package/docs/API.md +1145 -0
  49. package/docs/CONVENTIONS.md +316 -0
  50. package/docs/FAQ.md +396 -0
  51. package/docs/GUIDE.md +1102 -0
  52. 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