@btc-vision/bitcoin 7.0.0-beta.0 → 7.0.0-beta.1
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/README.md +112 -13
- package/benchmark-compare/BENCHMARK.md +74 -59
- package/benchmark-compare/compare.bench.ts +249 -96
- package/benchmark-compare/harness.ts +23 -25
- package/benchmark-compare/package.json +1 -0
- package/browser/address.d.ts +4 -4
- package/browser/address.d.ts.map +1 -1
- package/browser/chunks/{psbt-parallel-B-dfm5GZ.js → psbt-parallel-jZ6QcCnM.js} +3128 -2731
- package/browser/index.d.ts +1 -1
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +603 -585
- package/browser/io/base58check.d.ts +1 -25
- package/browser/io/base58check.d.ts.map +1 -1
- package/browser/io/base64.d.ts.map +1 -1
- package/browser/networks.d.ts +1 -0
- package/browser/networks.d.ts.map +1 -1
- package/browser/payments/bip341.d.ts +17 -0
- package/browser/payments/bip341.d.ts.map +1 -1
- package/browser/payments/index.d.ts +3 -2
- package/browser/payments/index.d.ts.map +1 -1
- package/browser/payments/p2mr.d.ts +169 -0
- package/browser/payments/p2mr.d.ts.map +1 -0
- package/browser/payments/types.d.ts +11 -1
- package/browser/payments/types.d.ts.map +1 -1
- package/browser/psbt/bip371.d.ts +30 -0
- package/browser/psbt/bip371.d.ts.map +1 -1
- package/browser/psbt/psbtutils.d.ts +1 -0
- package/browser/psbt/psbtutils.d.ts.map +1 -1
- package/browser/psbt.d.ts.map +1 -1
- package/browser/workers/index.js +9 -9
- package/build/address.d.ts +4 -4
- package/build/address.d.ts.map +1 -1
- package/build/address.js +11 -1
- package/build/address.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/io/base58check.d.ts +1 -25
- package/build/io/base58check.d.ts.map +1 -1
- package/build/io/base58check.js +1 -31
- package/build/io/base58check.js.map +1 -1
- package/build/io/base64.d.ts.map +1 -1
- package/build/io/base64.js +3 -0
- package/build/io/base64.js.map +1 -1
- package/build/networks.d.ts +1 -0
- package/build/networks.d.ts.map +1 -1
- package/build/networks.js +12 -0
- package/build/networks.js.map +1 -1
- package/build/payments/bip341.d.ts +17 -0
- package/build/payments/bip341.d.ts.map +1 -1
- package/build/payments/bip341.js +32 -1
- package/build/payments/bip341.js.map +1 -1
- package/build/payments/index.d.ts +3 -2
- package/build/payments/index.d.ts.map +1 -1
- package/build/payments/index.js +2 -1
- package/build/payments/index.js.map +1 -1
- package/build/payments/p2mr.d.ts +178 -0
- package/build/payments/p2mr.d.ts.map +1 -0
- package/build/payments/p2mr.js +555 -0
- package/build/payments/p2mr.js.map +1 -0
- package/build/payments/types.d.ts +11 -1
- package/build/payments/types.d.ts.map +1 -1
- package/build/payments/types.js +1 -0
- package/build/payments/types.js.map +1 -1
- package/build/psbt/bip371.d.ts +30 -0
- package/build/psbt/bip371.d.ts.map +1 -1
- package/build/psbt/bip371.js +80 -15
- package/build/psbt/bip371.js.map +1 -1
- package/build/psbt/psbtutils.d.ts +1 -0
- package/build/psbt/psbtutils.d.ts.map +1 -1
- package/build/psbt/psbtutils.js +2 -0
- package/build/psbt/psbtutils.js.map +1 -1
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js +3 -2
- package/build/psbt.js.map +1 -1
- package/build/pubkey.js +1 -1
- package/build/pubkey.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/documentation/README.md +122 -0
- package/documentation/address.md +820 -0
- package/documentation/block.md +679 -0
- package/documentation/crypto.md +461 -0
- package/documentation/ecc.md +584 -0
- package/documentation/errors.md +656 -0
- package/documentation/io.md +942 -0
- package/documentation/networks.md +625 -0
- package/documentation/p2mr.md +380 -0
- package/documentation/payments.md +1485 -0
- package/documentation/psbt.md +1400 -0
- package/documentation/script.md +730 -0
- package/documentation/taproot.md +670 -0
- package/documentation/transaction.md +943 -0
- package/documentation/types.md +587 -0
- package/documentation/workers.md +1007 -0
- package/eslint.config.js +3 -0
- package/package.json +17 -14
- package/src/address.ts +22 -10
- package/src/index.ts +1 -0
- package/src/io/base58check.ts +1 -35
- package/src/io/base64.ts +5 -0
- package/src/networks.ts +13 -0
- package/src/payments/bip341.ts +36 -1
- package/src/payments/index.ts +4 -0
- package/src/payments/p2mr.ts +660 -0
- package/src/payments/types.ts +12 -0
- package/src/psbt/bip371.ts +84 -13
- package/src/psbt/psbtutils.ts +2 -0
- package/src/psbt.ts +4 -2
- package/src/pubkey.ts +1 -1
- package/test/bitcoin.core.spec.ts +1 -1
- package/test/fixtures/p2mr.json +270 -0
- package/test/integration/taproot.spec.ts +7 -3
- package/test/opnetTestnet.spec.ts +302 -0
- package/test/payments.spec.ts +3 -1
- package/test/psbt.spec.ts +297 -2
- package/test/tsconfig.json +2 -2
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
# Types - Branded Types, Type Guards, and Assertions
|
|
2
|
+
|
|
3
|
+
The type system in `@btc-vision/bitcoin` uses TypeScript branded types to provide compile-time safety for Bitcoin primitives that are structurally identical at runtime. A 32-byte `PrivateKey` and a 32-byte `MessageHash` are both `Uint8Array`, but the branding mechanism prevents one from being passed where the other is expected. Runtime type guards and assertion helpers complement these types by performing validation at boundaries where data enters the system.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
| Category | Purpose |
|
|
8
|
+
|----------|---------|
|
|
9
|
+
| Branded types | Nominal typing over `Uint8Array` and `bigint` to prevent accidental misuse of structurally identical values |
|
|
10
|
+
| Type guards | Runtime `is*()` functions returning `value is T` for safe narrowing |
|
|
11
|
+
| Assertion helpers | `assert*()` functions throwing `TypeError` / `RangeError` with descriptive messages |
|
|
12
|
+
| Conversion functions | `to*()` functions that validate and cast plain values into branded types |
|
|
13
|
+
| Stack types | Types representing Bitcoin script execution stack elements |
|
|
14
|
+
| Taptree types | Recursive types representing Taproot/P2MR script trees |
|
|
15
|
+
| Constants | Protocol-level numeric limits |
|
|
16
|
+
| Utilities | Helper functions for comparing stack arrays |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Branded Types
|
|
21
|
+
|
|
22
|
+
Branded types use the `Brand<T, B>` utility to create nominal types from structural base types. The brand is a phantom property that exists only at the type level and has zero runtime cost.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// The underlying branding mechanism (from @btc-vision/ecpair)
|
|
26
|
+
declare const __brand: unique symbol;
|
|
27
|
+
type Brand<T, B extends string> = T & {
|
|
28
|
+
readonly [__brand]: B;
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Because `__brand` is a `unique symbol`, TypeScript treats each branded type as distinct even though they share the same runtime representation. You cannot assign a `PrivateKey` to a `MessageHash` parameter without an explicit cast, despite both being 32-byte `Uint8Array` values at runtime.
|
|
33
|
+
|
|
34
|
+
### Branded Type Reference
|
|
35
|
+
|
|
36
|
+
| Type | Base | Size | Description |
|
|
37
|
+
|------|------|------|-------------|
|
|
38
|
+
| `Bytes32` | `Uint8Array` | 32 bytes | Generic 32-byte buffer. Used for transaction hashes, Merkle roots, and other 256-bit values. |
|
|
39
|
+
| `Bytes20` | `Uint8Array` | 20 bytes | Generic 20-byte buffer. Used for HASH160 results (RIPEMD160(SHA256(x))), P2PKH/P2SH hashes. |
|
|
40
|
+
| `PublicKey` | `Uint8Array` | 33 or 65 bytes | SEC1-encoded secp256k1 public key. 33 bytes compressed (prefix `0x02`/`0x03`) or 65 bytes uncompressed (prefix `0x04`/`0x06`/`0x07`). |
|
|
41
|
+
| `XOnlyPublicKey` | `Uint8Array` | 32 bytes | BIP 340 x-only public key for Taproot and Schnorr signatures. Must be non-zero and less than the secp256k1 field prime `p`. |
|
|
42
|
+
| `Satoshi` | `bigint` | N/A | Bitcoin amount in satoshis. Must be `>= 0n` and `<= 2_100_000_000_000_000n` (21 million BTC). |
|
|
43
|
+
| `PrivateKey` | `Uint8Array` | 32 bytes | secp256k1 private key scalar. Must be non-zero and less than the curve order `n`. |
|
|
44
|
+
| `Signature` | `Uint8Array` | 8-73 bytes | DER-encoded ECDSA signature. |
|
|
45
|
+
| `SchnorrSignature` | `Uint8Array` | 64 bytes | BIP 340 Schnorr signature. |
|
|
46
|
+
| `MessageHash` | `Uint8Array` | 32 bytes | 32-byte hash of the message being signed. Semantically distinct from `PrivateKey` and `Bytes32`. **Not exported from the main `@btc-vision/bitcoin` entry point.** Import from `@btc-vision/bitcoin/types` instead. |
|
|
47
|
+
| `Script` | `Uint8Array` | Variable | Bitcoin script bytecode. |
|
|
48
|
+
|
|
49
|
+
### Import
|
|
50
|
+
|
|
51
|
+
Most branded types are re-exported from the main entry point:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import type {
|
|
55
|
+
Bytes32,
|
|
56
|
+
Bytes20,
|
|
57
|
+
PublicKey,
|
|
58
|
+
XOnlyPublicKey,
|
|
59
|
+
Satoshi,
|
|
60
|
+
PrivateKey,
|
|
61
|
+
Signature,
|
|
62
|
+
SchnorrSignature,
|
|
63
|
+
Script,
|
|
64
|
+
} from '@btc-vision/bitcoin';
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> **Note:** `MessageHash` is **not** re-exported from `@btc-vision/bitcoin`. Use the `@btc-vision/bitcoin/types` subpath:
|
|
68
|
+
>
|
|
69
|
+
> ```typescript
|
|
70
|
+
> import type { MessageHash } from '@btc-vision/bitcoin/types';
|
|
71
|
+
> ```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Type Guard Functions
|
|
76
|
+
|
|
77
|
+
Type guards are runtime `is*()` functions that return a type predicate (`value is T`). They perform the minimum checks necessary to guarantee the branded type's invariants hold.
|
|
78
|
+
|
|
79
|
+
### Primitive Guards
|
|
80
|
+
|
|
81
|
+
> **Note:** The primitive guard functions listed below (`isUInt8`, `isUInt32`, `isNumber`, `isUint8Array`, `isUint8ArrayN`, `isArray`) are defined in `src/types.ts` but are **not re-exported from the main `@btc-vision/bitcoin` entry point**. To use them, import from the `@btc-vision/bitcoin/types` subpath.
|
|
82
|
+
|
|
83
|
+
| Function | Signature | Description |
|
|
84
|
+
|----------|-----------|-------------|
|
|
85
|
+
| `isUInt8` | `(value: unknown) => value is number` | True if `value` is an integer in `[0, 255]`. |
|
|
86
|
+
| `isUInt32` | `(value: unknown) => value is number` | True if `value` is an integer in `[0, 4_294_967_295]`. |
|
|
87
|
+
| `isNumber` | `(value: unknown) => value is number` | True if `value` is a finite number (excludes `NaN`, `Infinity`, `-Infinity`). |
|
|
88
|
+
| `isUint8Array` | `(value: unknown) => value is Uint8Array` | True if `value` is a `Uint8Array` instance. |
|
|
89
|
+
| `isUint8ArrayN` | `<N>(value: unknown, n: N) => value is Uint8Array & { readonly length: N }` | True if `value` is a `Uint8Array` of exactly `n` bytes. |
|
|
90
|
+
| `isArray` | `(value: unknown) => value is unknown[]` | True if `value` is an array (`Array.isArray`). |
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// These are NOT available from '@btc-vision/bitcoin'. Use the subpath:
|
|
94
|
+
import { isUInt8, isUInt32, isNumber, isUint8Array, isUint8ArrayN, isArray } from '@btc-vision/bitcoin/types';
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### isHex
|
|
98
|
+
|
|
99
|
+
The `isHex` function exported from `@btc-vision/bitcoin` comes from `io/hex.ts` (not from `types.ts`). It has a different signature and behavior than the internal `types.ts` version:
|
|
100
|
+
|
|
101
|
+
| Function | Signature | Description |
|
|
102
|
+
|----------|-----------|-------------|
|
|
103
|
+
| `isHex` (exported, from `io/hex.ts`) | `(value: string) => boolean` | Returns `true` if `value` is a valid hex string of even length. **Strips `0x`/`0X` prefixes** before checking. Note: the parameter type is `string` (not `unknown`), and the return type is `boolean` (not a type guard predicate). |
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { isHex } from '@btc-vision/bitcoin';
|
|
107
|
+
|
|
108
|
+
isHex('deadbeef'); // true
|
|
109
|
+
isHex('0xdeadbeef'); // true (0x prefix is stripped before validation)
|
|
110
|
+
isHex('DEADBEEF'); // true
|
|
111
|
+
isHex('deadbee'); // false (odd length after stripping)
|
|
112
|
+
isHex('deadbeeg'); // false (invalid character)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> **Internal note:** `src/types.ts` also defines an `isHex` function with signature `(value: unknown) => value is string` that does **not** strip `0x` prefixes. This version is not re-exported from the main entry point; the `io/hex.ts` version takes precedence in `@btc-vision/bitcoin`.
|
|
116
|
+
|
|
117
|
+
### Branded Type Guards
|
|
118
|
+
|
|
119
|
+
| Function | Signature | Checks |
|
|
120
|
+
|----------|-----------|--------|
|
|
121
|
+
| `isBytes32` | `(value: unknown) => value is Bytes32` | `Uint8Array` with `length === 32`. |
|
|
122
|
+
| `isBytes20` | `(value: unknown) => value is Bytes20` | `Uint8Array` with `length === 20`. |
|
|
123
|
+
| `isXOnlyPublicKey` | `(value: unknown) => value is XOnlyPublicKey` | `Uint8Array` with `length === 32`, non-zero, and `< EC_P` (secp256k1 field prime). |
|
|
124
|
+
| `isPoint` | `(value: unknown) => value is PublicKey` | Valid SEC1-encoded point: compressed (33 bytes, prefix `0x02`/`0x03`) or uncompressed (65 bytes, prefix `0x04`/`0x06`/`0x07`), with x (and y if uncompressed) coordinates non-zero and `< EC_P`. |
|
|
125
|
+
| `isSatoshi` | `(value: unknown) => value is Satoshi` | `bigint` in range `[0n, SATOSHI_MAX]`. |
|
|
126
|
+
| `isPrivateKey` | `(value: unknown) => value is PrivateKey` | `Uint8Array` with `length === 32`, non-zero, and `< EC_N` (secp256k1 curve order). |
|
|
127
|
+
| `isSchnorrSignature` | `(value: unknown) => value is SchnorrSignature` | `Uint8Array` with `length === 64`. |
|
|
128
|
+
| `isSignature` | `(value: unknown) => value is Signature` | `Uint8Array` with `length` in `[8, 73]` (DER-encoded ECDSA range). |
|
|
129
|
+
| `isScript` | `(value: unknown) => value is Script` | `Uint8Array` instance (any length). |
|
|
130
|
+
|
|
131
|
+
> **Note:** `isMessageHash` is **not exported from the main `@btc-vision/bitcoin` entry point**. Import from the subpath:
|
|
132
|
+
>
|
|
133
|
+
> ```typescript
|
|
134
|
+
> import { isMessageHash } from '@btc-vision/bitcoin/types';
|
|
135
|
+
> ```
|
|
136
|
+
>
|
|
137
|
+
> | Function | Signature | Checks |
|
|
138
|
+
> |----------|-----------|--------|
|
|
139
|
+
> | `isMessageHash` | `(value: unknown) => value is MessageHash` | `Uint8Array` with `length === 32`. |
|
|
140
|
+
|
|
141
|
+
### Taproot Type Guards
|
|
142
|
+
|
|
143
|
+
> **Note:** `isTapleaf` and `isTaptree` are defined in `src/types.ts` but are **not re-exported from the main `@btc-vision/bitcoin` entry point**. Import from the subpath:
|
|
144
|
+
>
|
|
145
|
+
> ```typescript
|
|
146
|
+
> import { isTapleaf, isTaptree } from '@btc-vision/bitcoin/types';
|
|
147
|
+
> ```
|
|
148
|
+
|
|
149
|
+
| Function | Signature | Checks |
|
|
150
|
+
|----------|-----------|--------|
|
|
151
|
+
| `isTapleaf` | `(value: unknown) => value is Tapleaf` | Object with an `output` property that is a `Uint8Array`. Optional `version` must be a number satisfying `(version & TAPLEAF_VERSION_MASK) === version`. |
|
|
152
|
+
| `isTaptree` | `(value: unknown) => value is Taptree` | Recursively validates: either a `Tapleaf` or a two-element array where both elements are valid `Taptree` nodes. |
|
|
153
|
+
|
|
154
|
+
### Usage Examples
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { isBytes32, isPoint, isSatoshi, isHex } from '@btc-vision/bitcoin';
|
|
158
|
+
|
|
159
|
+
// Validate a hash before use
|
|
160
|
+
function processHash(input: unknown): void {
|
|
161
|
+
if (!isBytes32(input)) {
|
|
162
|
+
throw new Error('Expected a 32-byte hash');
|
|
163
|
+
}
|
|
164
|
+
// input is now narrowed to Bytes32
|
|
165
|
+
console.log(input.length); // 32
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate a public key
|
|
169
|
+
function checkKey(key: unknown): void {
|
|
170
|
+
if (isPoint(key)) {
|
|
171
|
+
// key is PublicKey (33 or 65 bytes, valid SEC1 encoding)
|
|
172
|
+
console.log('Valid public key, length:', key.length);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Validate satoshi amounts from user input
|
|
177
|
+
function parseFee(value: unknown): void {
|
|
178
|
+
if (!isSatoshi(value)) {
|
|
179
|
+
throw new Error('Invalid satoshi amount');
|
|
180
|
+
}
|
|
181
|
+
// value is now Satoshi
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Validate hex strings (note: isHex from main entry accepts string, not unknown)
|
|
185
|
+
const input: string = '0a1b2c3d';
|
|
186
|
+
if (isHex(input)) {
|
|
187
|
+
// Safe to decode as hex
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Assertion Helpers
|
|
194
|
+
|
|
195
|
+
Assertion helpers throw descriptive errors when validation fails. They use TypeScript's `asserts value is T` return type to narrow the value in subsequent code. Use these at function entry points for fail-fast validation.
|
|
196
|
+
|
|
197
|
+
### assertXOnlyPublicKey
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
function assertXOnlyPublicKey(
|
|
201
|
+
value: unknown,
|
|
202
|
+
name: string,
|
|
203
|
+
): asserts value is XOnlyPublicKey;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Throws `TypeError` if `value` is not a 32-byte `Uint8Array`. Throws `RangeError` if the value is zero or `>= EC_P` (secp256k1 field prime). The `name` parameter is included in the error message for debugging.
|
|
207
|
+
|
|
208
|
+
> **Source code note:** The actual error message thrown when the value exceeds `EC_P` reads `"${name} exceeds curve order"`, which is technically misleading -- the check is against the field prime (`EC_P`), not the curve order (`EC_N`). The validation logic itself is correct; only the error message text is inaccurate.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { assertXOnlyPublicKey } from '@btc-vision/bitcoin';
|
|
212
|
+
|
|
213
|
+
function tweakKey(internalKey: Uint8Array): void {
|
|
214
|
+
assertXOnlyPublicKey(internalKey, 'internalKey');
|
|
215
|
+
// internalKey is now XOnlyPublicKey
|
|
216
|
+
// If invalid, throws:
|
|
217
|
+
// TypeError: internalKey must be Uint8Array, got ...
|
|
218
|
+
// TypeError: internalKey must be 32 bytes, got ...
|
|
219
|
+
// RangeError: internalKey cannot be zero
|
|
220
|
+
// RangeError: internalKey exceeds curve order (note: actually checks EC_P, not EC_N)
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### assertPrivateKey
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
function assertPrivateKey(
|
|
228
|
+
value: unknown,
|
|
229
|
+
name: string,
|
|
230
|
+
): asserts value is PrivateKey;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Throws `TypeError` if `value` is not a 32-byte `Uint8Array`. Throws `RangeError` if the value is zero or `>= EC_N` (secp256k1 curve order). The `name` parameter is included in the error message.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { assertPrivateKey } from '@btc-vision/bitcoin';
|
|
237
|
+
|
|
238
|
+
function signMessage(key: Uint8Array, msg: Uint8Array): void {
|
|
239
|
+
assertPrivateKey(key, 'signingKey');
|
|
240
|
+
// key is now PrivateKey
|
|
241
|
+
// If invalid, throws:
|
|
242
|
+
// TypeError: signingKey must be Uint8Array, got ...
|
|
243
|
+
// TypeError: signingKey must be 32 bytes, got ...
|
|
244
|
+
// RangeError: signingKey cannot be zero
|
|
245
|
+
// RangeError: signingKey exceeds curve order
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Conversion Functions
|
|
252
|
+
|
|
253
|
+
Conversion functions validate a plain value and return it cast to the corresponding branded type. They throw on invalid input, making them suitable for trusted boundaries where you have a value you believe is correct but need the compiler to agree.
|
|
254
|
+
|
|
255
|
+
### toBytes32
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
function toBytes32(value: Uint8Array): Bytes32;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Throws `TypeError` if `value.length !== 32`.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { toBytes32 } from '@btc-vision/bitcoin';
|
|
265
|
+
import * as crypto from '@btc-vision/bitcoin/crypto';
|
|
266
|
+
|
|
267
|
+
const hash = crypto.sha256(data);
|
|
268
|
+
const typed: Bytes32 = toBytes32(hash);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### toBytes20
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
function toBytes20(value: Uint8Array): Bytes20;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Throws `TypeError` if `value.length !== 20`.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { toBytes20 } from '@btc-vision/bitcoin';
|
|
281
|
+
|
|
282
|
+
const hash160Result = hash160(pubkey);
|
|
283
|
+
const typed: Bytes20 = toBytes20(hash160Result);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### toSatoshi
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
function toSatoshi(value: bigint): Satoshi;
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Throws `RangeError` if `value < 0n` or `value > SATOSHI_MAX`.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { toSatoshi } from '@btc-vision/bitcoin';
|
|
296
|
+
|
|
297
|
+
const fee: Satoshi = toSatoshi(10_000n);
|
|
298
|
+
const amount: Satoshi = toSatoshi(50_000_000n); // 0.5 BTC
|
|
299
|
+
|
|
300
|
+
// These throw:
|
|
301
|
+
toSatoshi(-1n); // RangeError: Satoshi cannot be negative, got -1
|
|
302
|
+
toSatoshi(2_100_000_000_000_001n); // RangeError: Satoshi exceeds maximum supply ...
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### toMessageHash
|
|
306
|
+
|
|
307
|
+
> **Note:** `toMessageHash` is **not exported from the main `@btc-vision/bitcoin` entry point**. Import from the subpath:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
function toMessageHash(value: Uint8Array): MessageHash;
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Throws `TypeError` if `value.length !== 32`.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { toMessageHash } from '@btc-vision/bitcoin/types';
|
|
317
|
+
|
|
318
|
+
const sigHash = computeSigHash(tx, index);
|
|
319
|
+
const typed: MessageHash = toMessageHash(sigHash);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Stack Types
|
|
325
|
+
|
|
326
|
+
Stack types represent elements on the Bitcoin script execution stack.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
type StackElement = Uint8Array | number;
|
|
330
|
+
type Stack = readonly StackElement[];
|
|
331
|
+
type StackFunction = () => Stack;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
| Type | Description |
|
|
335
|
+
|------|-------------|
|
|
336
|
+
| `StackElement` | A single value on the script stack: either raw bytes (`Uint8Array`) or a small integer (`number`, representing script numbers / opcodes). |
|
|
337
|
+
| `Stack` | A readonly array of `StackElement` values, representing the full stack state. |
|
|
338
|
+
| `StackFunction` | A lazily evaluated stack: a zero-argument function returning a `Stack`. Used when witness data is computed on demand. |
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import type { Stack, StackElement, StackFunction } from '@btc-vision/bitcoin';
|
|
342
|
+
|
|
343
|
+
// Direct stack
|
|
344
|
+
const witness: Stack = [
|
|
345
|
+
new Uint8Array([0x01, 0x02]),
|
|
346
|
+
42,
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
// Lazy stack (computed when needed)
|
|
350
|
+
const lazyWitness: StackFunction = () => [
|
|
351
|
+
computeSignature(),
|
|
352
|
+
redeemScript,
|
|
353
|
+
];
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Taptree Types
|
|
359
|
+
|
|
360
|
+
Taptree types define the recursive structure of Taproot (and P2MR) script trees.
|
|
361
|
+
|
|
362
|
+
### Tapleaf
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
export interface Tapleaf {
|
|
366
|
+
readonly output: Uint8Array;
|
|
367
|
+
readonly version?: number;
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
A single leaf in the script tree. `output` is the compiled script bytecode. `version` is the leaf version (defaults to `0xc0`, the standard tapscript version). The version must satisfy `(version & TAPLEAF_VERSION_MASK) === version`, meaning the lowest bit must be zero.
|
|
372
|
+
|
|
373
|
+
### Taptree
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
A recursive binary tree type. Each node is either a `Tapleaf` (terminal) or a two-element tuple of child nodes. This mirrors the Merkle tree structure defined in BIP 341.
|
|
380
|
+
|
|
381
|
+
> **Note:** The `Tapleaf` and `Taptree` **types** are exported from `@btc-vision/bitcoin`, but the **runtime guard functions** `isTapleaf` and `isTaptree` are **not**. Import the guards from the subpath:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import type { Tapleaf, Taptree } from '@btc-vision/bitcoin';
|
|
385
|
+
import { isTapleaf, isTaptree } from '@btc-vision/bitcoin/types';
|
|
386
|
+
|
|
387
|
+
// Single leaf
|
|
388
|
+
const leaf: Tapleaf = { output: scriptBytes };
|
|
389
|
+
|
|
390
|
+
// Two-leaf tree
|
|
391
|
+
const tree: Taptree = [
|
|
392
|
+
{ output: script1 },
|
|
393
|
+
{ output: script2 },
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
// Nested tree (3 leaves)
|
|
397
|
+
const nested: Taptree = [
|
|
398
|
+
[
|
|
399
|
+
{ output: scriptA },
|
|
400
|
+
{ output: scriptB },
|
|
401
|
+
],
|
|
402
|
+
{ output: scriptC },
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
// Runtime validation
|
|
406
|
+
if (isTapleaf(value)) {
|
|
407
|
+
console.log(value.output); // Uint8Array
|
|
408
|
+
}
|
|
409
|
+
if (isTaptree(value)) {
|
|
410
|
+
// value is a valid tree structure
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Constants
|
|
417
|
+
|
|
418
|
+
| Constant | Value | Exported from main entry? | Description |
|
|
419
|
+
|----------|-------|---------------------------|-------------|
|
|
420
|
+
| `SATOSHI_MAX` | `2_100_000_000_000_000n` (`21n * 10n ** 14n`) | **No** | Maximum total supply of Bitcoin in satoshis (21 million BTC). Used by `isSatoshi()` and `toSatoshi()` for upper bound validation. |
|
|
421
|
+
| `TAPLEAF_VERSION_MASK` | `0xfe` | Yes | Bitmask for valid tapleaf versions. The lowest bit is reserved for the parity flag in the control block, so valid leaf versions have bit 0 clear. |
|
|
422
|
+
|
|
423
|
+
> **Note:** `SATOSHI_MAX` is **not re-exported from the main `@btc-vision/bitcoin` entry point**. Import it from the subpath:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { TAPLEAF_VERSION_MASK } from '@btc-vision/bitcoin';
|
|
427
|
+
import { SATOSHI_MAX } from '@btc-vision/bitcoin/types';
|
|
428
|
+
|
|
429
|
+
// SATOSHI_MAX
|
|
430
|
+
console.log(SATOSHI_MAX); // 2100000000000000n
|
|
431
|
+
|
|
432
|
+
// TAPLEAF_VERSION_MASK
|
|
433
|
+
const LEAF_VERSION_TAPSCRIPT = 0xc0;
|
|
434
|
+
console.log((LEAF_VERSION_TAPSCRIPT & TAPLEAF_VERSION_MASK) === LEAF_VERSION_TAPSCRIPT); // true
|
|
435
|
+
|
|
436
|
+
const invalid = 0xc1; // bit 0 set
|
|
437
|
+
console.log((invalid & TAPLEAF_VERSION_MASK) === invalid); // false
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Utility Functions
|
|
443
|
+
|
|
444
|
+
### stacksEqual
|
|
445
|
+
|
|
446
|
+
> **Note:** `stacksEqual` is defined in `src/types.ts` but is **not re-exported from the main `@btc-vision/bitcoin` entry point**. Import from the subpath:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
function stacksEqual(a: Uint8Array[], b: Uint8Array[]): boolean;
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Compares two arrays of `Uint8Array` for deep equality. Returns `true` if both arrays have the same length and every element at each index is byte-equal. Uses the library's internal `equals()` function, which performs an **early-return comparison** (returns `false` immediately on the first mismatched byte). This is **not** constant-time.
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { stacksEqual } from '@btc-vision/bitcoin/types';
|
|
456
|
+
|
|
457
|
+
const a = [new Uint8Array([1, 2]), new Uint8Array([3, 4])];
|
|
458
|
+
const b = [new Uint8Array([1, 2]), new Uint8Array([3, 4])];
|
|
459
|
+
const c = [new Uint8Array([1, 2]), new Uint8Array([5, 6])];
|
|
460
|
+
|
|
461
|
+
console.log(stacksEqual(a, b)); // true
|
|
462
|
+
console.log(stacksEqual(a, c)); // false
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Internal Constants
|
|
468
|
+
|
|
469
|
+
The module defines two internal constants used by type guards and assertions but not exported:
|
|
470
|
+
|
|
471
|
+
| Constant | Value (hex) | Description |
|
|
472
|
+
|----------|-------------|-------------|
|
|
473
|
+
| `EC_P` | `fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f` | The secp256k1 field prime. X-only public keys and point coordinates must be strictly less than this value. |
|
|
474
|
+
| `EC_N` | `fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141` | The secp256k1 curve order. Private keys must be strictly less than this value and non-zero. |
|
|
475
|
+
|
|
476
|
+
These are used by `isXOnlyPublicKey()`, `isPoint()`, `isPrivateKey()`, `assertXOnlyPublicKey()`, and `assertPrivateKey()` to enforce cryptographic validity beyond simple length checks.
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Complete Example
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import {
|
|
484
|
+
// Branded types (type-only imports)
|
|
485
|
+
type Bytes32,
|
|
486
|
+
type Bytes20,
|
|
487
|
+
type PublicKey,
|
|
488
|
+
type XOnlyPublicKey,
|
|
489
|
+
type Satoshi,
|
|
490
|
+
type PrivateKey,
|
|
491
|
+
type SchnorrSignature,
|
|
492
|
+
type Script,
|
|
493
|
+
|
|
494
|
+
// Type guards (exported from main entry)
|
|
495
|
+
isBytes32,
|
|
496
|
+
isPoint,
|
|
497
|
+
isXOnlyPublicKey,
|
|
498
|
+
isPrivateKey,
|
|
499
|
+
isSatoshi,
|
|
500
|
+
isSchnorrSignature,
|
|
501
|
+
isHex,
|
|
502
|
+
|
|
503
|
+
// Assertions
|
|
504
|
+
assertXOnlyPublicKey,
|
|
505
|
+
assertPrivateKey,
|
|
506
|
+
|
|
507
|
+
// Conversions (exported from main entry)
|
|
508
|
+
toBytes32,
|
|
509
|
+
toBytes20,
|
|
510
|
+
toSatoshi,
|
|
511
|
+
|
|
512
|
+
// Constants (only TAPLEAF_VERSION_MASK from main entry)
|
|
513
|
+
TAPLEAF_VERSION_MASK,
|
|
514
|
+
} from '@btc-vision/bitcoin';
|
|
515
|
+
|
|
516
|
+
// Items not on the main entry point -- use subpath import:
|
|
517
|
+
import {
|
|
518
|
+
type MessageHash,
|
|
519
|
+
toMessageHash,
|
|
520
|
+
isMessageHash,
|
|
521
|
+
stacksEqual,
|
|
522
|
+
isTapleaf,
|
|
523
|
+
isTaptree,
|
|
524
|
+
SATOSHI_MAX,
|
|
525
|
+
} from '@btc-vision/bitcoin/types';
|
|
526
|
+
|
|
527
|
+
// --- Creating branded types from raw data ---
|
|
528
|
+
|
|
529
|
+
// From a known-good 32-byte hash
|
|
530
|
+
const txid: Bytes32 = toBytes32(rawHashBytes);
|
|
531
|
+
|
|
532
|
+
// From a bigint amount
|
|
533
|
+
const fee: Satoshi = toSatoshi(1000n);
|
|
534
|
+
const onebtc: Satoshi = toSatoshi(100_000_000n);
|
|
535
|
+
|
|
536
|
+
// --- Validating untrusted input ---
|
|
537
|
+
|
|
538
|
+
function processInput(data: unknown): Bytes32 {
|
|
539
|
+
if (!isBytes32(data)) {
|
|
540
|
+
throw new Error('Expected 32-byte value');
|
|
541
|
+
}
|
|
542
|
+
return data; // TypeScript knows this is Bytes32
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// --- Using assertions for fail-fast validation ---
|
|
546
|
+
|
|
547
|
+
function buildTaprootOutput(internalKey: unknown, privateKey: unknown): void {
|
|
548
|
+
assertXOnlyPublicKey(internalKey, 'internalKey');
|
|
549
|
+
assertPrivateKey(privateKey, 'privateKey');
|
|
550
|
+
|
|
551
|
+
// Both are now narrowed to their branded types.
|
|
552
|
+
// Any invalid input would have thrown with a descriptive error.
|
|
553
|
+
const xonly: XOnlyPublicKey = internalKey;
|
|
554
|
+
const key: PrivateKey = privateKey;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// --- Conditional narrowing with type guards ---
|
|
558
|
+
|
|
559
|
+
function handleKey(key: Uint8Array): string {
|
|
560
|
+
if (isXOnlyPublicKey(key)) {
|
|
561
|
+
return 'x-only (32 bytes, valid field element)';
|
|
562
|
+
}
|
|
563
|
+
if (isPoint(key)) {
|
|
564
|
+
return `SEC1 public key (${key.length} bytes)`;
|
|
565
|
+
}
|
|
566
|
+
if (isPrivateKey(key)) {
|
|
567
|
+
return 'private key (32 bytes, valid scalar)';
|
|
568
|
+
}
|
|
569
|
+
return 'unknown key format';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// --- Satoshi bounds checking ---
|
|
573
|
+
|
|
574
|
+
console.log(isSatoshi(0n)); // true
|
|
575
|
+
console.log(isSatoshi(SATOSHI_MAX)); // true
|
|
576
|
+
console.log(isSatoshi(-1n)); // false
|
|
577
|
+
console.log(isSatoshi(SATOSHI_MAX + 1n)); // false
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Source File
|
|
583
|
+
|
|
584
|
+
All types, guards, assertions, and conversions are defined in:
|
|
585
|
+
|
|
586
|
+
- `/src/types.ts` - Main module re-exporting branded types and defining all runtime functions
|
|
587
|
+
- `/src/branded.ts` - Re-exports branded type definitions from `@btc-vision/ecpair`
|