@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
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Code Conventions
|
|
2
|
+
|
|
3
|
+
Coding conventions for the btc-dev Bitcoin development library. Reference this when writing or editing code.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Element | Convention | Example |
|
|
8
|
+
|---------|------------|---------|
|
|
9
|
+
| Functions | `snake_case` | `encode_address`, `parse_witness`, `create_tx_input` |
|
|
10
|
+
| Variables | `snake_case` | `txid`, `witness`, `sequence`, `prevout` |
|
|
11
|
+
| Types/Interfaces | `PascalCase` | `TxData`, `AddressInfo`, `SigHashOptions` |
|
|
12
|
+
| Constants | `SCREAMING_SNAKE_CASE` | `SIGHASH_DEFAULT`, `COINBASE`, `LOCK_SCRIPT_TYPE` |
|
|
13
|
+
| Files | `snake_case.ts` | `encode.ts`, `parse.ts`, `create.ts` |
|
|
14
|
+
| Directories | `snake_case` | `address/`, `script/`, `tx/`, `signer/` |
|
|
15
|
+
|
|
16
|
+
## Naming Conventions
|
|
17
|
+
|
|
18
|
+
### Functions and Variables
|
|
19
|
+
|
|
20
|
+
Use `snake_case` for all functions, variables, and object properties:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// Functions
|
|
24
|
+
export function create_tx_input(config: TxInputTemplate) { }
|
|
25
|
+
export function sign_segwit_tx(seckey: string, txdata: TxData) { }
|
|
26
|
+
|
|
27
|
+
// Variables
|
|
28
|
+
const sequence = normalize_sequence(config.sequence)
|
|
29
|
+
const witness = config.witness ?? []
|
|
30
|
+
const prevout = normalize_prevout(config.prevout)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Types and Interfaces
|
|
34
|
+
|
|
35
|
+
Use `PascalCase` with semantic suffixes:
|
|
36
|
+
|
|
37
|
+
| Suffix | Purpose | Example |
|
|
38
|
+
|--------|---------|---------|
|
|
39
|
+
| `Config` | User-provided configuration | `TaprootConfig`, `AddressConfig` |
|
|
40
|
+
| `Template` | Input structure for creation | `TxTemplate`, `TxInputTemplate` |
|
|
41
|
+
| `Data` | Generic data structure | `TxData`, `TxDecodedData` |
|
|
42
|
+
| `Info` | Enriched/parsed information | `AddressInfo`, `ScriptInfo` |
|
|
43
|
+
| `Options` | Optional parameters | `SigHashOptions` |
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Template (user input for creation)
|
|
47
|
+
export interface TxInputTemplate extends TxOutpoint {
|
|
48
|
+
coinbase? : string | null
|
|
49
|
+
prevout? : TxOutput | null
|
|
50
|
+
script_sig? : string | null
|
|
51
|
+
sequence? : number
|
|
52
|
+
witness? : string[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Data (normalized output)
|
|
56
|
+
export interface TxData {
|
|
57
|
+
locktime : number
|
|
58
|
+
vin : TxInput[]
|
|
59
|
+
vout : TxOutput[]
|
|
60
|
+
version : number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Options (configuration for operations)
|
|
64
|
+
export interface SigHashOptions {
|
|
65
|
+
extension? : string
|
|
66
|
+
sigflag? : number
|
|
67
|
+
txindex? : number
|
|
68
|
+
pubkey? : string
|
|
69
|
+
script? : string
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Constants
|
|
74
|
+
|
|
75
|
+
Use `SCREAMING_SNAKE_CASE`:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
export const SIGHASH_DEFAULT = 0x01
|
|
79
|
+
export const TAPLEAF_DEFAULT_VERSION = 0xc0
|
|
80
|
+
|
|
81
|
+
// Grouped constants use nested objects
|
|
82
|
+
export const COINBASE = {
|
|
83
|
+
TXID : '00'.repeat(32),
|
|
84
|
+
VOUT : 0xFFFFFFFF,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const LOCK_SCRIPT_TYPE = {
|
|
88
|
+
P2PKH : 'p2pkh',
|
|
89
|
+
P2SH : 'p2sh',
|
|
90
|
+
P2WPKH : 'p2wpkh',
|
|
91
|
+
P2WSH : 'p2wsh',
|
|
92
|
+
P2TR : 'p2tr',
|
|
93
|
+
OPRETURN : 'opreturn',
|
|
94
|
+
} as const
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API Namespace Pattern
|
|
98
|
+
|
|
99
|
+
Each module exports a namespace using `export * as NAMESPACE`:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// src/index.ts
|
|
103
|
+
export * as ADDRESS from './lib/address/index.js'
|
|
104
|
+
export * as SCRIPT from './lib/script/index.js'
|
|
105
|
+
export * as SIGNER from './lib/signer/index.js'
|
|
106
|
+
export * as TX from './lib/tx/index.js'
|
|
107
|
+
|
|
108
|
+
export * as CONST from './const.js'
|
|
109
|
+
export * as SCHEMA from './schema/index.js'
|
|
110
|
+
|
|
111
|
+
export type * from './types/index.js'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Usage pattern:**
|
|
115
|
+
```typescript
|
|
116
|
+
import { ADDRESS, TX, SCRIPT } from '@vbyte/btc-dev'
|
|
117
|
+
|
|
118
|
+
// Access module functions via namespace
|
|
119
|
+
const address_info = ADDRESS.P2PKH.decode(address)
|
|
120
|
+
const tx_data = TX.create_tx(template)
|
|
121
|
+
const script_hex = SCRIPT.encode_script(script)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Import Organization
|
|
125
|
+
|
|
126
|
+
Three groups in order, with aligned `from` keywords:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// 1. External dependencies
|
|
130
|
+
import { Buff } from '@vbyte/buff'
|
|
131
|
+
import { Assert } from '@vbyte/util'
|
|
132
|
+
import { ECC } from '@vbyte/crypto'
|
|
133
|
+
import { schnorr } from '@noble/curves/secp256k1'
|
|
134
|
+
|
|
135
|
+
// 2. Internal imports (path alias)
|
|
136
|
+
import { COINBASE, DEFAULT } from '@/const.js'
|
|
137
|
+
import { parse_tx } from '@/lib/tx/parse.js'
|
|
138
|
+
import { hash_segwit_tx } from '@/lib/sighash/segwit.js'
|
|
139
|
+
|
|
140
|
+
// 3. Type imports (separate block)
|
|
141
|
+
import type {
|
|
142
|
+
TxData,
|
|
143
|
+
TxInputTemplate,
|
|
144
|
+
SigHashOptions
|
|
145
|
+
} from '@/types/index.js'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Path Aliases
|
|
149
|
+
|
|
150
|
+
| Alias | Resolves To | Used In |
|
|
151
|
+
|-------|-------------|---------|
|
|
152
|
+
| `@/` | `src/` | All code (`src/`, `test/`) |
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// In src/ files
|
|
156
|
+
import { DEFAULT } from '@/const.js'
|
|
157
|
+
|
|
158
|
+
// In test/ files
|
|
159
|
+
import { TX } from '@/index.js'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Export Patterns
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Flat re-exports in index.ts
|
|
166
|
+
export * from './encode.js'
|
|
167
|
+
export * from './decode.js'
|
|
168
|
+
export * from './parse.js'
|
|
169
|
+
|
|
170
|
+
// Namespaced exports for modules
|
|
171
|
+
export * as SCHEMA from './schema/index.js'
|
|
172
|
+
|
|
173
|
+
// Type-only exports
|
|
174
|
+
export type * from './types/index.js'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Error Handling
|
|
178
|
+
|
|
179
|
+
### Assertions
|
|
180
|
+
|
|
181
|
+
Use `Assert` from `@vbyte/util`:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { Assert } from '@vbyte/util'
|
|
185
|
+
|
|
186
|
+
Assert.exists(config.prevout, 'prevout is required')
|
|
187
|
+
Assert.is_empty(config.coinbase, 'coinbase is not allowed')
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Zod Validation
|
|
191
|
+
|
|
192
|
+
Use Zod schemas for runtime validation:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { z } from 'zod'
|
|
196
|
+
|
|
197
|
+
export const tx_output = z.object({
|
|
198
|
+
value : z.bigint().min(0n).max(2_100_000_000_000_000n),
|
|
199
|
+
script_pk : hex,
|
|
200
|
+
}) satisfies z.ZodType<TxOutput>
|
|
201
|
+
|
|
202
|
+
export const tx_input = z.object({
|
|
203
|
+
coinbase : hex.nullable(),
|
|
204
|
+
txid : hex32,
|
|
205
|
+
vout : uint,
|
|
206
|
+
prevout : tx_output.nullable(),
|
|
207
|
+
script_sig : hex.nullable(),
|
|
208
|
+
sequence : uint,
|
|
209
|
+
witness : z.array(hex)
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Formatting
|
|
214
|
+
|
|
215
|
+
### Vertical Alignment
|
|
216
|
+
|
|
217
|
+
Align colons in interfaces, objects, and parameters:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Interface properties
|
|
221
|
+
export interface TxSize {
|
|
222
|
+
base : number
|
|
223
|
+
total : number
|
|
224
|
+
vsize : number
|
|
225
|
+
weight : number
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Function parameters
|
|
229
|
+
export function sign_segwit_tx (
|
|
230
|
+
seckey : string,
|
|
231
|
+
txdata : TxData,
|
|
232
|
+
options : SigHashOptions,
|
|
233
|
+
) : string { }
|
|
234
|
+
|
|
235
|
+
// Object literals
|
|
236
|
+
const input = {
|
|
237
|
+
txid : config.txid,
|
|
238
|
+
vout : config.vout,
|
|
239
|
+
prevout : normalize_prevout(config.prevout),
|
|
240
|
+
sequence : normalize_sequence(config.sequence),
|
|
241
|
+
witness : config.witness ?? []
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Indentation
|
|
246
|
+
|
|
247
|
+
- 2 spaces (no tabs)
|
|
248
|
+
- Multi-line parameters aligned
|
|
249
|
+
|
|
250
|
+
### Numbers
|
|
251
|
+
|
|
252
|
+
Use underscores for readability:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const MAX_SATS = 2_100_000_000_000_000n
|
|
256
|
+
const TIMEOUT_MS = 30_000
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### File Extensions
|
|
260
|
+
|
|
261
|
+
Always use `.js` in imports (ES module compatibility):
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { DEFAULT } from '@/const.js'
|
|
265
|
+
import type { TxData } from '../types/index.js'
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Domain Abbreviations
|
|
269
|
+
|
|
270
|
+
| Abbrev | Meaning |
|
|
271
|
+
|--------|---------|
|
|
272
|
+
| `psbt` | Partially Signed Bitcoin Transaction |
|
|
273
|
+
| `txid` | Transaction ID (hex string) |
|
|
274
|
+
| `utxo` | Unspent Transaction Output |
|
|
275
|
+
| `sats` | Satoshis (1 BTC = 100,000,000 sats) |
|
|
276
|
+
| `vout` | Output index in transaction |
|
|
277
|
+
| `vin` | Input index in transaction |
|
|
278
|
+
| `tx` | Transaction |
|
|
279
|
+
| `sig` | Signature |
|
|
280
|
+
| `seckey` | Secret/private key |
|
|
281
|
+
| `pubkey` | Public key |
|
|
282
|
+
| `script_pk` | Script pubkey (locking script) |
|
|
283
|
+
| `script_sig` | Script signature (unlocking script) |
|
|
284
|
+
| `prevout` | Previous output (spent by input) |
|
|
285
|
+
| `segwit` | Segregated Witness |
|
|
286
|
+
| `taproot` | Taproot (BIP340/341/342) |
|
|
287
|
+
|
|
288
|
+
## Comments
|
|
289
|
+
|
|
290
|
+
- Explain "why" not "what"
|
|
291
|
+
- Use `//` with space, sentence case
|
|
292
|
+
- JSDoc for exported functions
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
/**
|
|
296
|
+
* Sign a transaction input using segwit (BIP143) signature hashing.
|
|
297
|
+
* @param seckey - 32-byte secret key as hex string (64 characters)
|
|
298
|
+
* @param txdata - Transaction data
|
|
299
|
+
* @param options - Sighash options including txindex, sigflag, pubkey/script
|
|
300
|
+
* @returns ECDSA signature with sighash flag appended
|
|
301
|
+
* @throws Error if secret key format is invalid
|
|
302
|
+
*/
|
|
303
|
+
export function sign_segwit_tx (
|
|
304
|
+
seckey : string,
|
|
305
|
+
txdata : TxData,
|
|
306
|
+
options : SigHashOptions,
|
|
307
|
+
) {
|
|
308
|
+
// Validate inputs before processing.
|
|
309
|
+
validate_seckey(seckey)
|
|
310
|
+
validate_sighash_options(options, SIGHASH_SEGWIT)
|
|
311
|
+
// Hash and sign the transaction.
|
|
312
|
+
const tx = parse_tx(txdata)
|
|
313
|
+
const msg = hash_segwit_tx(tx, options)
|
|
314
|
+
return ECC.sign_ecdsa(seckey, msg).hex + format_sigflag(options.sigflag)
|
|
315
|
+
}
|
|
316
|
+
```
|
package/docs/FAQ.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Frequently Asked Questions
|
|
2
|
+
|
|
3
|
+
Common questions and troubleshooting for `@vbyte/btc-dev`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Why can't I import the library?
|
|
8
|
+
|
|
9
|
+
**ESM Only**: This library is ESM-only. Make sure your project is configured for ESM:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
// package.json
|
|
13
|
+
{
|
|
14
|
+
"type": "module"
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or use the `.mjs` extension for your files.
|
|
19
|
+
|
|
20
|
+
### Can I use this with CommonJS?
|
|
21
|
+
|
|
22
|
+
The library provides a CommonJS build at `dist/main.cjs`, but ESM is recommended. For CommonJS:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const { ADDRESS, TX } = require('@vbyte/btc-dev')
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Can I use this in a browser?
|
|
29
|
+
|
|
30
|
+
Yes. Use the UMD bundle:
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script src="https://unpkg.com/@vbyte/btc-dev/dist/script.js"></script>
|
|
34
|
+
<script>
|
|
35
|
+
const { ADDRESS, TX } = btcDev
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or use a bundler like Vite, Webpack, or Rollup with the ESM imports.
|
|
40
|
+
|
|
41
|
+
## TypeScript
|
|
42
|
+
|
|
43
|
+
### What TypeScript version is required?
|
|
44
|
+
|
|
45
|
+
TypeScript 5.0 or later is recommended. The library uses strict mode.
|
|
46
|
+
|
|
47
|
+
### How do I import types?
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import type { TxData, AddressInfo, SigHashOptions } from '@vbyte/btc-dev'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Why am I getting type errors?
|
|
54
|
+
|
|
55
|
+
Make sure your `tsconfig.json` has:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"compilerOptions": {
|
|
60
|
+
"moduleResolution": "bundler",
|
|
61
|
+
"module": "ESNext",
|
|
62
|
+
"target": "ES2020"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Addresses
|
|
68
|
+
|
|
69
|
+
### Which address type should I use?
|
|
70
|
+
|
|
71
|
+
- **P2WPKH** (`bc1q...`): Best for single-signature wallets. Good balance of efficiency and compatibility.
|
|
72
|
+
- **P2TR** (`bc1p...`): Best for privacy and future-proofing. Some older wallets don't support it yet.
|
|
73
|
+
- **P2WSH**: For multisig or complex scripts.
|
|
74
|
+
- **P2PKH/P2SH**: Only for legacy compatibility.
|
|
75
|
+
|
|
76
|
+
### Why does my address look different on testnet?
|
|
77
|
+
|
|
78
|
+
Testnet uses different prefixes:
|
|
79
|
+
|
|
80
|
+
| Type | Mainnet | Testnet |
|
|
81
|
+
|------|---------|---------|
|
|
82
|
+
| P2WPKH | `bc1q...` | `tb1q...` |
|
|
83
|
+
| P2TR | `bc1p...` | `tb1p...` |
|
|
84
|
+
|
|
85
|
+
Always specify the correct network: `'main'` or `'test'`.
|
|
86
|
+
|
|
87
|
+
### How do I convert a compressed pubkey to x-only for taproot?
|
|
88
|
+
|
|
89
|
+
Remove the first byte (02 or 03):
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const compressed = '02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c'
|
|
93
|
+
const xOnly = compressed.slice(2) // Remove 02/03 prefix
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Transactions
|
|
97
|
+
|
|
98
|
+
### Why does my signature verification fail?
|
|
99
|
+
|
|
100
|
+
Common causes:
|
|
101
|
+
|
|
102
|
+
1. **Wrong prevout data**: The prevout value and script must match exactly
|
|
103
|
+
2. **Wrong pubkey**: For P2WPKH, use the compressed public key
|
|
104
|
+
3. **Wrong sighash flag**: Make sure signing and verification use the same flag
|
|
105
|
+
4. **Missing witness**: Add the witness data after signing
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Check prevout is correct
|
|
109
|
+
console.log('Prevout value:', tx.vin[0].prevout.value)
|
|
110
|
+
console.log('Prevout script:', tx.vin[0].prevout.script_pk)
|
|
111
|
+
|
|
112
|
+
// Verify witness is added
|
|
113
|
+
console.log('Witness:', tx.vin[0].witness)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### How do I calculate the transaction fee?
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const inputTotal = tx.vin.reduce((sum, vin) => sum + vin.prevout.value, 0n)
|
|
120
|
+
const outputTotal = tx.vout.reduce((sum, vout) => sum + vout.value, 0n)
|
|
121
|
+
const fee = inputTotal - outputTotal
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### What's the difference between size, vsize, and weight?
|
|
125
|
+
|
|
126
|
+
- **Size**: Raw byte count
|
|
127
|
+
- **Weight**: Size calculation that discounts witness data (base * 4 + witness)
|
|
128
|
+
- **vSize**: Virtual size = weight / 4, used for fee calculation
|
|
129
|
+
|
|
130
|
+
Always use `vsize` for fee estimation.
|
|
131
|
+
|
|
132
|
+
### Why is my transaction rejected as "dust"?
|
|
133
|
+
|
|
134
|
+
Outputs below the dust limit (~546 sats for P2PKH, ~294 for P2WPKH) are rejected. Increase the output value.
|
|
135
|
+
|
|
136
|
+
### How do I enable RBF (Replace-By-Fee)?
|
|
137
|
+
|
|
138
|
+
Set sequence to less than 0xfffffffe on at least one input:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
{
|
|
142
|
+
txid: '...',
|
|
143
|
+
vout: 0,
|
|
144
|
+
sequence: 0xfffffffd // RBF enabled
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Signing
|
|
149
|
+
|
|
150
|
+
### What format should the secret key be?
|
|
151
|
+
|
|
152
|
+
A 64-character hex string (32 bytes):
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const secretKey = 'abcd1234...'.repeat(8) // 64 hex chars
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Why do I get "Invalid secret key format"?
|
|
159
|
+
|
|
160
|
+
The secret key must be:
|
|
161
|
+
- A string (not Buffer or Uint8Array)
|
|
162
|
+
- Exactly 64 hex characters
|
|
163
|
+
- Valid hex (0-9, a-f, A-F)
|
|
164
|
+
|
|
165
|
+
### What's the difference between segwit and taproot signing?
|
|
166
|
+
|
|
167
|
+
- **Segwit** (`sign_segwit_tx`): Uses ECDSA signatures, requires compressed pubkey
|
|
168
|
+
- **Taproot** (`sign_taproot_tx`): Uses Schnorr signatures, uses x-only pubkeys
|
|
169
|
+
|
|
170
|
+
### How do I sign multiple inputs?
|
|
171
|
+
|
|
172
|
+
Sign each input separately:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
for (let i = 0; i < tx.vin.length; i++) {
|
|
176
|
+
const sig = SIGNER.sign_segwit_tx(secretKey, tx, {
|
|
177
|
+
txindex: i,
|
|
178
|
+
pubkey: pubkeys[i],
|
|
179
|
+
sigflag: 0x01
|
|
180
|
+
})
|
|
181
|
+
tx.vin[i].witness = [sig, pubkeys[i]]
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Taproot
|
|
186
|
+
|
|
187
|
+
### When should I use key-path vs script-path?
|
|
188
|
+
|
|
189
|
+
- **Key-path**: Single signer, no complex conditions (cheapest, most private)
|
|
190
|
+
- **Script-path**: Multisig, timelocks, or alternative spending conditions
|
|
191
|
+
|
|
192
|
+
### Do I need to tweak the secret key?
|
|
193
|
+
|
|
194
|
+
For key-path spending, yes:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { tweak_seckey } from '@vbyte/crypto/ecc'
|
|
198
|
+
const taptweak = TAPROOT.encode_taptweak(internalPubkey, merkleRoot)
|
|
199
|
+
const tweakedSeckey = tweak_seckey(internalSeckey, taptweak, true)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
For script-path, use the original key for the script, not the tweaked key.
|
|
203
|
+
|
|
204
|
+
### What's a control block?
|
|
205
|
+
|
|
206
|
+
A control block proves that a script is part of the taproot tree. It contains:
|
|
207
|
+
- Leaf version and parity byte
|
|
208
|
+
- Internal public key
|
|
209
|
+
- Merkle path (sibling hashes)
|
|
210
|
+
|
|
211
|
+
## Scripts
|
|
212
|
+
|
|
213
|
+
### How do I detect the script type of an output?
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { SCRIPT } from '@vbyte/btc-dev'
|
|
217
|
+
|
|
218
|
+
const type = SCRIPT.get_lock_script_type(scriptPubKey)
|
|
219
|
+
// Returns: 'p2pkh' | 'p2sh' | 'p2wpkh' | 'p2wsh' | 'p2tr' | 'opreturn' | null
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### How do I create a multisig script?
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// 2-of-3 multisig
|
|
226
|
+
import { SCRIPT } from '@vbyte/btc-dev'
|
|
227
|
+
|
|
228
|
+
const script = SCRIPT.encode([
|
|
229
|
+
'OP_2',
|
|
230
|
+
pubkey1,
|
|
231
|
+
pubkey2,
|
|
232
|
+
pubkey3,
|
|
233
|
+
'OP_3',
|
|
234
|
+
'OP_CHECKMULTISIG'
|
|
235
|
+
])
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Error Handling
|
|
239
|
+
|
|
240
|
+
### What error types does the library throw?
|
|
241
|
+
|
|
242
|
+
The library provides three custom error classes for better error handling:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { ValidationError, DecodingError, ConfigError } from '@vbyte/btc-dev'
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const tx = TX.decode(malformedData)
|
|
249
|
+
} catch (err) {
|
|
250
|
+
if (err instanceof DecodingError) {
|
|
251
|
+
console.log('Malformed data at position:', err.position)
|
|
252
|
+
} else if (err instanceof ValidationError) {
|
|
253
|
+
console.log('Invalid input field:', err.field)
|
|
254
|
+
} else if (err instanceof ConfigError) {
|
|
255
|
+
console.log('Configuration error:', err.message)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
| Error Class | When Thrown | Properties |
|
|
261
|
+
|-------------|-------------|------------|
|
|
262
|
+
| `ValidationError` | Invalid input format, wrong length, type mismatch | `field?: string` |
|
|
263
|
+
| `DecodingError` | Malformed data, truncated input, invalid structure | `position?: number` |
|
|
264
|
+
| `ConfigError` | Invalid sigflag, unknown network, bad configuration | - |
|
|
265
|
+
|
|
266
|
+
### How do I catch specific errors?
|
|
267
|
+
|
|
268
|
+
Use `instanceof` to check the error type:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
try {
|
|
272
|
+
SIGNER.sign_segwit_tx('invalid', tx, options)
|
|
273
|
+
} catch (err) {
|
|
274
|
+
if (err instanceof ValidationError) {
|
|
275
|
+
// Handle invalid secret key format
|
|
276
|
+
console.log(`Validation failed for: ${err.field}`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Which functions throw which errors?
|
|
282
|
+
|
|
283
|
+
**ValidationError:**
|
|
284
|
+
- `sign_segwit_tx`, `sign_taproot_tx` - Invalid secret key format
|
|
285
|
+
- Most functions with input validation
|
|
286
|
+
|
|
287
|
+
**DecodingError:**
|
|
288
|
+
- `decode_tx` - Malformed transaction data
|
|
289
|
+
- `decode_script` - Invalid script (truncated pushdata, invalid opcodes)
|
|
290
|
+
|
|
291
|
+
**ConfigError:**
|
|
292
|
+
- `sign_segwit_tx`, `sign_taproot_tx` - Invalid sigflag
|
|
293
|
+
- Address functions - Unknown network
|
|
294
|
+
|
|
295
|
+
### How do I handle errors gracefully?
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
function safeDecode(hexData: string) {
|
|
299
|
+
try {
|
|
300
|
+
return { success: true, data: TX.decode(hexData) }
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (err instanceof DecodingError) {
|
|
303
|
+
return { success: false, error: `Decode error at ${err.position}: ${err.message}` }
|
|
304
|
+
}
|
|
305
|
+
return { success: false, error: err instanceof Error ? err.message : 'Unknown error' }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Security
|
|
311
|
+
|
|
312
|
+
### Is this library audited?
|
|
313
|
+
|
|
314
|
+
The cryptographic operations use audited libraries (`@noble/curves`, `@noble/hashes`). The Bitcoin-specific code has comprehensive tests but hasn't undergone a formal audit.
|
|
315
|
+
|
|
316
|
+
### How do I securely handle private keys?
|
|
317
|
+
|
|
318
|
+
Key points:
|
|
319
|
+
- Never log or expose secret keys
|
|
320
|
+
- Clear from memory when possible
|
|
321
|
+
- Use cryptographically secure random number generation
|
|
322
|
+
|
|
323
|
+
See the [Security Best Practices section in the Guide](GUIDE.md#security-best-practices) for detailed examples.
|
|
324
|
+
|
|
325
|
+
### What transaction size limits are enforced?
|
|
326
|
+
|
|
327
|
+
The library enforces limits to prevent denial-of-service attacks:
|
|
328
|
+
|
|
329
|
+
- **Maximum transaction size**: 4MB (Bitcoin consensus limit)
|
|
330
|
+
- **Maximum varint size**: 10MB (prevents memory exhaustion)
|
|
331
|
+
- **Maximum inputs/outputs**: 100,000 per transaction
|
|
332
|
+
- **Maximum taproot tree depth**: 128 levels
|
|
333
|
+
|
|
334
|
+
These limits are enforced automatically during decoding.
|
|
335
|
+
|
|
336
|
+
### What are the known limitations?
|
|
337
|
+
|
|
338
|
+
1. **OP_CODESEPARATOR**: Not fully supported in segwit scripts. The library will throw if encountered.
|
|
339
|
+
|
|
340
|
+
2. **P2SH-wrapped scripts**: Legacy P2SH spending is not fully implemented. Use native segwit (P2WPKH/P2WSH) instead.
|
|
341
|
+
|
|
342
|
+
3. **Multi-signature**: Complex multi-sig scripts require manual construction and may need custom handling.
|
|
343
|
+
|
|
344
|
+
### What cryptographic dependencies does this library use?
|
|
345
|
+
|
|
346
|
+
This library depends on well-maintained, audited cryptographic libraries:
|
|
347
|
+
|
|
348
|
+
- `@noble/curves` - Audited cryptographic library for elliptic curves
|
|
349
|
+
- `@noble/hashes` - Audited hash functions
|
|
350
|
+
- `@scure/btc-signer` - Bitcoin signing utilities from the same author
|
|
351
|
+
|
|
352
|
+
Keep dependencies updated:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
npm audit
|
|
356
|
+
npm update
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Debugging
|
|
360
|
+
|
|
361
|
+
### How do I inspect a raw transaction?
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { TX, SCRIPT } from '@vbyte/btc-dev'
|
|
365
|
+
|
|
366
|
+
const tx = TX.parse(rawTxHex)
|
|
367
|
+
|
|
368
|
+
console.log('Version:', tx.version)
|
|
369
|
+
console.log('Locktime:', tx.locktime)
|
|
370
|
+
|
|
371
|
+
tx.vin.forEach((vin, i) => {
|
|
372
|
+
console.log(`Input ${i}:`, vin.txid, vin.vout)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
tx.vout.forEach((vout, i) => {
|
|
376
|
+
const type = SCRIPT.get_lock_script_type(vout.script_pk)
|
|
377
|
+
console.log(`Output ${i}:`, vout.value.toString(), 'sats', type)
|
|
378
|
+
})
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### How do I decode a witness?
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { WITNESS } from '@vbyte/btc-dev'
|
|
385
|
+
|
|
386
|
+
const data = WITNESS.parse(tx.vin[0].witness)
|
|
387
|
+
console.log('Type:', data.type)
|
|
388
|
+
console.log('Version:', data.version)
|
|
389
|
+
console.log('Params:', data.params)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Getting Help
|
|
393
|
+
|
|
394
|
+
- Check [GitHub Issues](https://github.com/cmdruid/btc-dev/issues)
|
|
395
|
+
- Read the [API Reference](API.md)
|
|
396
|
+
- See the [Guide](GUIDE.md) for tutorials and examples
|