@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,670 @@
|
|
|
1
|
+
# BIP 341 Taproot Utilities
|
|
2
|
+
|
|
3
|
+
The `bip341.ts` module provides low-level primitives for constructing and verifying Taproot (BIP 341) Merkle script trees. These utilities are shared by both P2TR (SegWit v1) and P2MR (SegWit v2) payment types. The module handles tree hashing, key tweaking, Merkle proof generation, and root reconstruction from control blocks.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
| Property | Value |
|
|
8
|
+
|----------|-------|
|
|
9
|
+
| BIP | 341 |
|
|
10
|
+
| Module | `src/payments/bip341.ts` |
|
|
11
|
+
| Leaf version constant | `LEAF_VERSION_TAPSCRIPT` = `0xc0` |
|
|
12
|
+
| Max tree depth | `MAX_TAPTREE_DEPTH` = `128` |
|
|
13
|
+
| Tagged hash prefixes | `TapLeaf`, `TapBranch`, `TapTweak` |
|
|
14
|
+
| Used by | P2TR (`p2tr.ts`), P2MR (`p2mr.ts`) |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Constants
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import {
|
|
22
|
+
LEAF_VERSION_TAPSCRIPT,
|
|
23
|
+
MAX_TAPTREE_DEPTH,
|
|
24
|
+
} from '@btc-vision/bitcoin';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Constant | Value | Description |
|
|
28
|
+
|----------|-------|-------------|
|
|
29
|
+
| `LEAF_VERSION_TAPSCRIPT` | `0xc0` (192) | Default leaf version for Tapscript leaves. Encoded in the control byte as `leaf_version \| parity_bit`. Must satisfy `(version & 0xfe) === version` (even, lowest bit reserved for parity). |
|
|
30
|
+
| `MAX_TAPTREE_DEPTH` | `128` | Maximum depth of a Taproot Merkle tree. The Merkle proof path length `m` must satisfy `0 <= m <= 128`. |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Type Definitions
|
|
35
|
+
|
|
36
|
+
### Tapleaf
|
|
37
|
+
|
|
38
|
+
A single script leaf in the tree. Defined in `src/types.ts`.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
interface Tapleaf {
|
|
42
|
+
readonly output: Uint8Array; // compiled script bytes
|
|
43
|
+
readonly version?: number; // leaf version (defaults to LEAF_VERSION_TAPSCRIPT)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Field | Type | Required | Description |
|
|
48
|
+
|-------|------|----------|-------------|
|
|
49
|
+
| `output` | `Uint8Array` | Yes | The compiled Bitcoin script for this leaf. |
|
|
50
|
+
| `version` | `number` | No | Leaf version. Defaults to `0xc0`. Must satisfy `(version & 0xfe) === version`. |
|
|
51
|
+
|
|
52
|
+
### Taptree
|
|
53
|
+
|
|
54
|
+
A recursive binary tree of script leaves. Defined in `src/types.ts`.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
A `Taptree` is either:
|
|
61
|
+
- A single `Tapleaf` (a leaf node), or
|
|
62
|
+
- A two-element tuple `[left, right]` where each element is itself a `Taptree` or `Tapleaf` (a branch node).
|
|
63
|
+
|
|
64
|
+
### HashTree
|
|
65
|
+
|
|
66
|
+
The hashed representation of a `Taptree`. Defined in `src/payments/bip341.ts`.
|
|
67
|
+
|
|
68
|
+
> **Note:** `HashLeaf`, `HashBranch`, and `HashTree` are **not exported from the main `@btc-vision/bitcoin` entry point**. The `HashTree` type is re-exported from `@btc-vision/bitcoin` via the payments barrel export, but `HashLeaf` and `HashBranch` are internal types. In practice, you interact with `HashTree` values returned by `toHashTree()` and passed to `findScriptPath()`.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface HashLeaf {
|
|
72
|
+
hash: Bytes32;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface HashBranch {
|
|
76
|
+
hash: Bytes32;
|
|
77
|
+
left: HashTree;
|
|
78
|
+
right: HashTree;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type HashTree = HashLeaf | HashBranch;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A `HashTree` is produced by `toHashTree()` and consumed by `findScriptPath()`. Each node contains a 32-byte hash. Branch nodes additionally store references to their left and right children, which enables recursive path-finding for Merkle proof construction.
|
|
85
|
+
|
|
86
|
+
### TweakedPublicKey
|
|
87
|
+
|
|
88
|
+
The result of tweaking an internal public key with a Merkle root. This interface is **not exported from the main `@btc-vision/bitcoin` entry point**. It is defined internally in `src/payments/bip341.ts` and used as the return type of `tweakKey()`.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
interface TweakedPublicKey {
|
|
92
|
+
parity: number; // 0 or 1
|
|
93
|
+
x: XOnlyPublicKey; // 32-byte x-only tweaked pubkey
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Taptree Structure
|
|
100
|
+
|
|
101
|
+
A Taptree is a binary tree where scripts live at the leaves. The tree is hashed bottom-up: each leaf is tagged-hashed individually, and each pair of siblings is combined via a branch hash. The root hash is used to tweak the internal public key (P2TR) or serves directly as the witness program (P2MR).
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Root Hash
|
|
105
|
+
/ \
|
|
106
|
+
Branch AB Branch CD
|
|
107
|
+
/ \ / \
|
|
108
|
+
Leaf A Leaf B Leaf C Leaf D
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Each leaf hash is computed as:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
tapleafHash = TaggedHash("TapLeaf", [version_byte] || [varint(script.length)] || script)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Each branch hash is computed by sorting the two children lexicographically, then:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
tapBranchHash = TaggedHash("TapBranch", sorted_left || sorted_right)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The lexicographic sorting ensures the tree is canonical -- swapping left and right children produces the same root hash. This means the tree structure matters only for proof length, not for commitment identity.
|
|
124
|
+
|
|
125
|
+
### Depth and Proof Cost
|
|
126
|
+
|
|
127
|
+
The depth of a leaf in the tree determines the length of its Merkle proof. A leaf at depth `d` requires `d` sibling hashes (each 32 bytes) in the control block. Frequently-used scripts should be placed closer to the root to minimize witness size.
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Depth 0: 1 leaf -> 0 proof hashes -> 0 bytes of Merkle path
|
|
131
|
+
Depth 1: 2 leaves -> 1 proof hash -> 32 bytes of Merkle path
|
|
132
|
+
Depth 2: 4 leaves -> 2 proof hashes -> 64 bytes of Merkle path
|
|
133
|
+
Depth 3: 8 leaves -> 3 proof hashes -> 96 bytes of Merkle path
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Functions
|
|
139
|
+
|
|
140
|
+
### toHashTree()
|
|
141
|
+
|
|
142
|
+
Builds a `HashTree` from a `Taptree` by recursively hashing leaves and branches.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
function toHashTree(scriptTree: Taptree): HashTree;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Parameter | Type | Description |
|
|
149
|
+
|-----------|------|-------------|
|
|
150
|
+
| `scriptTree` | `Taptree` | The binary tree of script leaves. |
|
|
151
|
+
| **Returns** | `HashTree` | The hashed tree with `hash`, `left`, and `right` at each branch. |
|
|
152
|
+
|
|
153
|
+
**Algorithm:**
|
|
154
|
+
|
|
155
|
+
1. If the node is a `Tapleaf`, compute its `tapleafHash` and return a `HashLeaf`.
|
|
156
|
+
2. If the node is a branch `[left, right]`, recursively compute `toHashTree(left)` and `toHashTree(right)`.
|
|
157
|
+
3. Sort the two child hashes lexicographically (byte comparison).
|
|
158
|
+
4. Compute `tapBranchHash(sortedLeft.hash, sortedRight.hash)`.
|
|
159
|
+
5. Return a `HashBranch` with the combined hash and ordered children.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { toHashTree, tapleafHash } from '@btc-vision/bitcoin';
|
|
163
|
+
import * as script from '@btc-vision/bitcoin/script';
|
|
164
|
+
|
|
165
|
+
const scriptTree = [
|
|
166
|
+
{ output: script.compile([script.opcodes.OP_1]) },
|
|
167
|
+
{ output: script.compile([script.opcodes.OP_2]) },
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const hashTree = toHashTree(scriptTree);
|
|
171
|
+
console.log(hashTree.hash); // 32-byte Merkle root
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### tapleafHash()
|
|
177
|
+
|
|
178
|
+
Computes the tagged hash of a single script leaf.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
function tapleafHash(leaf: Tapleaf): Bytes32;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Parameter | Type | Description |
|
|
185
|
+
|-----------|------|-------------|
|
|
186
|
+
| `leaf` | `Tapleaf` | Object with `output` (script bytes) and optional `version`. |
|
|
187
|
+
| **Returns** | `Bytes32` | 32-byte `TapLeaf` tagged hash. |
|
|
188
|
+
|
|
189
|
+
**Computation:**
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
TaggedHash("TapLeaf", [version] || [compact_size(script.length)] || script)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Where `version` defaults to `LEAF_VERSION_TAPSCRIPT` (`0xc0`) if not specified on the leaf.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { tapleafHash, LEAF_VERSION_TAPSCRIPT } from '@btc-vision/bitcoin';
|
|
199
|
+
|
|
200
|
+
const leaf = {
|
|
201
|
+
output: redeemScript,
|
|
202
|
+
version: LEAF_VERSION_TAPSCRIPT,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const hash = tapleafHash(leaf);
|
|
206
|
+
// hash is a 32-byte Bytes32
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### tapBranchHash()
|
|
212
|
+
|
|
213
|
+
Computes the tagged hash of two child hashes (a branch node).
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
function tapBranchHash(a: Uint8Array, b: Uint8Array): Bytes32;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Parameter | Type | Description |
|
|
220
|
+
|-----------|------|-------------|
|
|
221
|
+
| `a` | `Uint8Array` | First child hash (left branch). |
|
|
222
|
+
| `b` | `Uint8Array` | Second child hash (right branch). |
|
|
223
|
+
| **Returns** | `Bytes32` | 32-byte `TapBranch` tagged hash. |
|
|
224
|
+
|
|
225
|
+
**Computation:**
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
TaggedHash("TapBranch", a || b)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Note: The caller is responsible for passing `a` and `b` in the correct (sorted) order. The `toHashTree()` function handles sorting automatically, but if you call `tapBranchHash` directly, you must sort the inputs lexicographically yourself.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { tapBranchHash, tapleafHash } from '@btc-vision/bitcoin';
|
|
235
|
+
|
|
236
|
+
const hashA = tapleafHash({ output: scriptA });
|
|
237
|
+
const hashB = tapleafHash({ output: scriptB });
|
|
238
|
+
|
|
239
|
+
// Sort before hashing (compare returns < 0 if hashA < hashB)
|
|
240
|
+
const branch = (compare(hashA, hashB) < 0)
|
|
241
|
+
? tapBranchHash(hashA, hashB)
|
|
242
|
+
: tapBranchHash(hashB, hashA);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
### tapTweakHash()
|
|
248
|
+
|
|
249
|
+
Computes the tweak hash used to derive the output public key from an internal key.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
function tapTweakHash(pubKey: XOnlyPublicKey, h: Bytes32 | undefined): Bytes32;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
| Parameter | Type | Description |
|
|
256
|
+
|-----------|------|-------------|
|
|
257
|
+
| `pubKey` | `XOnlyPublicKey` | 32-byte x-only internal public key. |
|
|
258
|
+
| `h` | `Bytes32 \| undefined` | Merkle root hash, or `undefined` for key-path-only (no script tree). |
|
|
259
|
+
| **Returns** | `Bytes32` | 32-byte `TapTweak` tagged hash. |
|
|
260
|
+
|
|
261
|
+
**Computation:**
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
If h is defined: TaggedHash("TapTweak", pubKey || h)
|
|
265
|
+
If h is undefined: TaggedHash("TapTweak", pubKey)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
When no script tree is present, the tweak commits only to the internal public key. This is the key-path-only case for P2TR.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### tweakKey()
|
|
273
|
+
|
|
274
|
+
Tweaks an internal x-only public key with an optional Merkle root to produce the output public key.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
function tweakKey(
|
|
278
|
+
pubKey: XOnlyPublicKey,
|
|
279
|
+
h: Bytes32 | undefined,
|
|
280
|
+
): TweakedPublicKey | null;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
| Parameter | Type | Description |
|
|
284
|
+
|-----------|------|-------------|
|
|
285
|
+
| `pubKey` | `XOnlyPublicKey` | 32-byte x-only internal public key. |
|
|
286
|
+
| `h` | `Bytes32 \| undefined` | Merkle root hash, or `undefined` for key-path-only. |
|
|
287
|
+
| **Returns** | `TweakedPublicKey \| null` | The tweaked public key and parity, or `null` on failure. |
|
|
288
|
+
|
|
289
|
+
**Algorithm:**
|
|
290
|
+
|
|
291
|
+
1. Validate that `pubKey` is a 32-byte `Uint8Array`.
|
|
292
|
+
2. If `h` is provided, validate it is 32 bytes.
|
|
293
|
+
3. Compute `tweakHash = tapTweakHash(pubKey, h)`.
|
|
294
|
+
4. Call the ECC library's `xOnlyPointAddTweak(pubKey, tweakHash)`.
|
|
295
|
+
5. Return `{ parity, x }` where `x` is the new x-only output key.
|
|
296
|
+
|
|
297
|
+
The parity bit is encoded in the control block's first byte (lowest bit) so the verifier can reconstruct the full point.
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { tweakKey, toHashTree } from '@btc-vision/bitcoin';
|
|
301
|
+
import type { XOnlyPublicKey } from '@btc-vision/bitcoin';
|
|
302
|
+
|
|
303
|
+
const hashTree = toHashTree(scriptTree);
|
|
304
|
+
const tweaked = tweakKey(internalPubkey, hashTree.hash);
|
|
305
|
+
|
|
306
|
+
if (tweaked) {
|
|
307
|
+
console.log(tweaked.x); // 32-byte output pubkey
|
|
308
|
+
console.log(tweaked.parity); // 0 or 1
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### rootHashFromPath()
|
|
315
|
+
|
|
316
|
+
Reconstructs the Merkle root from a **P2TR** control block and a leaf hash by walking up the Merkle proof path.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
function rootHashFromPath(
|
|
320
|
+
controlBlock: Uint8Array,
|
|
321
|
+
leafHash: Uint8Array,
|
|
322
|
+
): Bytes32;
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
| Parameter | Type | Description |
|
|
326
|
+
|-----------|------|-------------|
|
|
327
|
+
| `controlBlock` | `Uint8Array` | The P2TR control block (minimum 33 bytes). |
|
|
328
|
+
| `leafHash` | `Uint8Array` | The leaf hash being verified. |
|
|
329
|
+
| **Returns** | `Bytes32` | The reconstructed 32-byte Merkle root. |
|
|
330
|
+
| **Throws** | `TypeError` | If `controlBlock.length < 33`. |
|
|
331
|
+
|
|
332
|
+
**P2TR control block layout:**
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
[control_byte (1)] [internal_pubkey (32)] [merkle_path (32 * m)]
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The Merkle path starts at byte offset 33. For each 32-byte sibling hash `ej` in the path, the function computes:
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
if kj < ej: kj = tapBranchHash(kj, ej)
|
|
342
|
+
else: kj = tapBranchHash(ej, kj)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Where `kj` starts as `leafHash` and ends as the Merkle root after processing all `m` sibling hashes.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { rootHashFromPath, tapleafHash } from '@btc-vision/bitcoin';
|
|
349
|
+
|
|
350
|
+
const leafHash = tapleafHash({ output: redeemScript });
|
|
351
|
+
const merkleRoot = rootHashFromPath(controlBlock, leafHash);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### rootHashFromPathP2MR()
|
|
357
|
+
|
|
358
|
+
Reconstructs the Merkle root from a **P2MR** control block and a leaf hash. P2MR control blocks have no internal public key, so the Merkle path starts at byte offset 1 instead of 33.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
function rootHashFromPathP2MR(
|
|
362
|
+
controlBlock: Uint8Array,
|
|
363
|
+
leafHash: Uint8Array,
|
|
364
|
+
): Bytes32;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
| Parameter | Type | Description |
|
|
368
|
+
|-----------|------|-------------|
|
|
369
|
+
| `controlBlock` | `Uint8Array` | The P2MR control block (minimum 1 byte). |
|
|
370
|
+
| `leafHash` | `Uint8Array` | The leaf hash being verified. |
|
|
371
|
+
| **Returns** | `Bytes32` | The reconstructed 32-byte Merkle root. |
|
|
372
|
+
| **Throws** | `TypeError` | If `controlBlock.length < 1`. |
|
|
373
|
+
|
|
374
|
+
**P2MR control block layout:**
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
[control_byte (1)] [merkle_path (32 * m)]
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The algorithm is identical to `rootHashFromPath`, except the path extraction starts at offset 1 instead of 33, and `m = (controlBlock.length - 1) / 32`.
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { rootHashFromPathP2MR, tapleafHash } from '@btc-vision/bitcoin';
|
|
384
|
+
|
|
385
|
+
const leafHash = tapleafHash({ output: redeemScript });
|
|
386
|
+
const merkleRoot = rootHashFromPathP2MR(controlBlock, leafHash);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
### findScriptPath()
|
|
392
|
+
|
|
393
|
+
Finds the Merkle proof path for a specific leaf hash within a `HashTree`. Returns the array of sibling hashes needed to prove inclusion of the leaf in the tree.
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
function findScriptPath(
|
|
397
|
+
node: HashTree,
|
|
398
|
+
hash: Bytes32,
|
|
399
|
+
): Bytes32[] | undefined;
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
| Parameter | Type | Description |
|
|
403
|
+
|-----------|------|-------------|
|
|
404
|
+
| `node` | `HashTree` | The root of the hash tree (from `toHashTree()`). |
|
|
405
|
+
| `hash` | `Bytes32` | The leaf hash to search for. |
|
|
406
|
+
| **Returns** | `Bytes32[] \| undefined` | Array of sibling hashes from leaf to root, or `undefined` if not found. |
|
|
407
|
+
|
|
408
|
+
**Algorithm:**
|
|
409
|
+
|
|
410
|
+
1. If `node` is a branch, recursively search the left subtree.
|
|
411
|
+
2. If found on the left, append the right sibling's hash and return.
|
|
412
|
+
3. Otherwise, recursively search the right subtree.
|
|
413
|
+
4. If found on the right, append the left sibling's hash and return.
|
|
414
|
+
5. If `node` is a leaf and its hash matches, return an empty array `[]`.
|
|
415
|
+
6. If no match is found anywhere, return `undefined`.
|
|
416
|
+
|
|
417
|
+
The returned array of sibling hashes is ordered from leaf level (index 0) to just below the root (last index). This is the Merkle path that goes into the control block.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { toHashTree, findScriptPath, tapleafHash } from '@btc-vision/bitcoin';
|
|
421
|
+
|
|
422
|
+
const hashTree = toHashTree(scriptTree);
|
|
423
|
+
const leafHash = tapleafHash({ output: targetScript });
|
|
424
|
+
|
|
425
|
+
const path = findScriptPath(hashTree, leafHash);
|
|
426
|
+
if (path) {
|
|
427
|
+
console.log(`Proof length: ${path.length} hashes (${path.length * 32} bytes)`);
|
|
428
|
+
// path[0] = sibling at leaf level
|
|
429
|
+
// path[path.length - 1] = sibling just below root
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Differences Between P2TR and P2MR Control Block Handling
|
|
436
|
+
|
|
437
|
+
Both P2TR and P2MR use the same Merkle tree construction (`toHashTree`, `tapleafHash`, `tapBranchHash`, `findScriptPath`). The difference lies in how the control block is structured and how the root hash is used.
|
|
438
|
+
|
|
439
|
+
| Aspect | P2TR (BIP 341) | P2MR (BIP 360) |
|
|
440
|
+
|--------|----------------|----------------|
|
|
441
|
+
| SegWit version | 1 (`OP_1`) | 2 (`OP_2`) |
|
|
442
|
+
| Control block minimum size | 33 bytes | 1 byte |
|
|
443
|
+
| Control block layout | `[ctrl_byte] [internal_pubkey (32)] [path...]` | `[ctrl_byte] [path...]` |
|
|
444
|
+
| Total control block size | `33 + 32*m` bytes | `1 + 32*m` bytes |
|
|
445
|
+
| Internal pubkey in control block | Yes (bytes 1-32) | No |
|
|
446
|
+
| Root hash function | `rootHashFromPath()` | `rootHashFromPathP2MR()` |
|
|
447
|
+
| Merkle path offset | Byte 33 | Byte 1 |
|
|
448
|
+
| Key-path spend | Yes (single Schnorr signature) | No |
|
|
449
|
+
| Output commitment | `tweakKey(internalPubkey, merkleRoot).x` | `merkleRoot` directly |
|
|
450
|
+
| Control byte parity bit | 0 or 1 (from tweaked key parity) | Always 1 |
|
|
451
|
+
| Address prefix | `bc1p` | `bc1z` |
|
|
452
|
+
|
|
453
|
+
### Why the Difference?
|
|
454
|
+
|
|
455
|
+
P2TR embeds the internal public key in the control block so the verifier can:
|
|
456
|
+
1. Extract the internal pubkey from the control block.
|
|
457
|
+
2. Reconstruct the Merkle root from the leaf hash and proof path.
|
|
458
|
+
3. Tweak the internal pubkey with the root to derive the expected output pubkey.
|
|
459
|
+
4. Compare against the output pubkey in the scriptPubKey.
|
|
460
|
+
|
|
461
|
+
P2MR eliminates the internal pubkey entirely. The output IS the Merkle root, so the verifier only needs to:
|
|
462
|
+
1. Reconstruct the Merkle root from the leaf hash and proof path.
|
|
463
|
+
2. Compare directly against the witness program in the scriptPubKey.
|
|
464
|
+
|
|
465
|
+
This removal of the key-path spend eliminates the quantum-vulnerable elliptic curve operation from the spending path.
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Step-by-Step Example: Building a Script Tree
|
|
470
|
+
|
|
471
|
+
This example walks through building a 3-leaf script tree, computing all hashes, constructing the control block, and spending via the script path.
|
|
472
|
+
|
|
473
|
+
### 1. Define the Script Tree
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import {
|
|
477
|
+
toHashTree,
|
|
478
|
+
findScriptPath,
|
|
479
|
+
tapleafHash,
|
|
480
|
+
tapBranchHash,
|
|
481
|
+
tweakKey,
|
|
482
|
+
LEAF_VERSION_TAPSCRIPT,
|
|
483
|
+
} from '@btc-vision/bitcoin';
|
|
484
|
+
import * as script from '@btc-vision/bitcoin/script';
|
|
485
|
+
import type { Taptree, XOnlyPublicKey } from '@btc-vision/bitcoin';
|
|
486
|
+
import { concat } from '@btc-vision/bitcoin';
|
|
487
|
+
|
|
488
|
+
// Three scripts: a checksig, a timelock, and a hash preimage check
|
|
489
|
+
const scriptA = script.fromASM('<pubkeyA> OP_CHECKSIG');
|
|
490
|
+
const scriptB = script.fromASM('OP_10 OP_CHECKSEQUENCEVERIFY OP_DROP <pubkeyB> OP_CHECKSIG');
|
|
491
|
+
const scriptC = script.fromASM('OP_SHA256 <hash> OP_EQUAL');
|
|
492
|
+
|
|
493
|
+
// Arrange as a binary tree:
|
|
494
|
+
// root
|
|
495
|
+
// / \
|
|
496
|
+
// branch leafC
|
|
497
|
+
// / \
|
|
498
|
+
// leafA leafB
|
|
499
|
+
|
|
500
|
+
const scriptTree: Taptree = [
|
|
501
|
+
[
|
|
502
|
+
{ output: scriptA },
|
|
503
|
+
{ output: scriptB },
|
|
504
|
+
],
|
|
505
|
+
{ output: scriptC },
|
|
506
|
+
];
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 2. Build the Hash Tree
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
const hashTree = toHashTree(scriptTree);
|
|
513
|
+
|
|
514
|
+
// Individual leaf hashes
|
|
515
|
+
const hashA = tapleafHash({ output: scriptA });
|
|
516
|
+
const hashB = tapleafHash({ output: scriptB });
|
|
517
|
+
const hashC = tapleafHash({ output: scriptC });
|
|
518
|
+
|
|
519
|
+
// hashTree.hash is the Merkle root
|
|
520
|
+
console.log(hashTree.hash); // 32-byte root hash
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
The tree structure after hashing (children sorted lexicographically):
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
hashTree.hash
|
|
527
|
+
/ \
|
|
528
|
+
tapBranchHash(hashA, hashB) hashC
|
|
529
|
+
/ \
|
|
530
|
+
hashA hashB
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### 3. Find a Merkle Proof Path
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
// Find the proof path for scriptB
|
|
537
|
+
const leafHashB = tapleafHash({ output: scriptB });
|
|
538
|
+
const pathB = findScriptPath(hashTree, leafHashB);
|
|
539
|
+
|
|
540
|
+
if (pathB) {
|
|
541
|
+
// pathB = [hashA, hashC]
|
|
542
|
+
// pathB[0] = sibling of leafB at depth 2 (which is hashA)
|
|
543
|
+
// pathB[1] = sibling of the branch at depth 1 (which is hashC)
|
|
544
|
+
console.log(`Path length: ${pathB.length}`); // 2
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### 4. Construct the P2TR Control Block
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
// Tweak the internal pubkey with the Merkle root
|
|
552
|
+
const internalPubkey: XOnlyPublicKey = /* 32-byte x-only key */;
|
|
553
|
+
const tweaked = tweakKey(internalPubkey, hashTree.hash);
|
|
554
|
+
|
|
555
|
+
if (tweaked && pathB) {
|
|
556
|
+
const controlByte = LEAF_VERSION_TAPSCRIPT | tweaked.parity; // 0xc0 | parity
|
|
557
|
+
const controlBlock = concat([
|
|
558
|
+
new Uint8Array([controlByte]),
|
|
559
|
+
internalPubkey, // 32 bytes
|
|
560
|
+
...pathB, // 32 bytes per sibling
|
|
561
|
+
]);
|
|
562
|
+
// controlBlock length = 1 + 32 + (2 * 32) = 97 bytes
|
|
563
|
+
|
|
564
|
+
// The witness for spending via scriptB:
|
|
565
|
+
const witness = [
|
|
566
|
+
/* scriptB inputs (e.g., signature) */,
|
|
567
|
+
scriptB, // the script being executed
|
|
568
|
+
controlBlock, // Merkle proof + internal pubkey
|
|
569
|
+
];
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### 5. Construct the P2MR Control Block
|
|
574
|
+
|
|
575
|
+
For P2MR, there is no internal pubkey in the control block, and the Merkle root is the witness program directly.
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
if (pathB) {
|
|
579
|
+
const controlByte = LEAF_VERSION_TAPSCRIPT | 0x01; // parity always 1 for P2MR
|
|
580
|
+
const controlBlock = concat([
|
|
581
|
+
new Uint8Array([controlByte]),
|
|
582
|
+
...pathB, // 32 bytes per sibling (no internal pubkey)
|
|
583
|
+
]);
|
|
584
|
+
// controlBlock length = 1 + (2 * 32) = 65 bytes
|
|
585
|
+
|
|
586
|
+
// The witness for spending via scriptB:
|
|
587
|
+
const witness = [
|
|
588
|
+
/* scriptB inputs (e.g., signature) */,
|
|
589
|
+
scriptB, // the script being executed
|
|
590
|
+
controlBlock, // Merkle proof only
|
|
591
|
+
];
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### 6. Verify: Reconstruct the Root from the Control Block
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
import { rootHashFromPath, rootHashFromPathP2MR } from '@btc-vision/bitcoin';
|
|
599
|
+
|
|
600
|
+
// P2TR verification
|
|
601
|
+
const reconstructedRootP2TR = rootHashFromPath(p2trControlBlock, leafHashB);
|
|
602
|
+
// Then: tweakKey(extractedInternalPubkey, reconstructedRootP2TR) must equal the output pubkey
|
|
603
|
+
|
|
604
|
+
// P2MR verification
|
|
605
|
+
const reconstructedRootP2MR = rootHashFromPathP2MR(p2mrControlBlock, leafHashB);
|
|
606
|
+
// Then: reconstructedRootP2MR must equal the witness program directly
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## Tagged Hash Reference
|
|
612
|
+
|
|
613
|
+
All hashing in BIP 341 uses tagged hashes as defined in BIP 340. A tagged hash is:
|
|
614
|
+
|
|
615
|
+
```
|
|
616
|
+
TaggedHash(tag, msg) = SHA256(SHA256(tag) || SHA256(tag) || msg)
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
The `SHA256(tag) || SHA256(tag)` prefix is precomputed for each known tag.
|
|
620
|
+
|
|
621
|
+
| Tag | Used By | Input |
|
|
622
|
+
|-----|---------|-------|
|
|
623
|
+
| `TapLeaf` | `tapleafHash()` | `[version_byte] \|\| [compact_size(script.length)] \|\| script` |
|
|
624
|
+
| `TapBranch` | `tapBranchHash()` | `left_hash \|\| right_hash` (sorted) |
|
|
625
|
+
| `TapTweak` | `tapTweakHash()` | `internal_pubkey \|\| merkle_root` (or just `internal_pubkey`) |
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Type Guard Functions
|
|
630
|
+
|
|
631
|
+
The `types.ts` module provides type guards for runtime validation of tree structures.
|
|
632
|
+
|
|
633
|
+
> **Note:** `isTapleaf` and `isTaptree` are **not re-exported from the main `@btc-vision/bitcoin` entry point**. Import them from the subpath:
|
|
634
|
+
>
|
|
635
|
+
> ```typescript
|
|
636
|
+
> import { isTapleaf, isTaptree } from '@btc-vision/bitcoin/types';
|
|
637
|
+
> ```
|
|
638
|
+
|
|
639
|
+
### isTapleaf()
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
function isTapleaf(value: unknown): value is Tapleaf;
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Returns `true` if `value` is an object with an `output` property that is a `Uint8Array`, and an optional `version` that satisfies `(version & 0xfe) === version`.
|
|
646
|
+
|
|
647
|
+
### isTaptree()
|
|
648
|
+
|
|
649
|
+
```typescript
|
|
650
|
+
function isTaptree(value: unknown): value is Taptree;
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
Returns `true` if `value` is either a valid `Tapleaf` or a two-element array where both elements are valid `Taptree` nodes. This check is recursive.
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## API Summary
|
|
658
|
+
|
|
659
|
+
| Function | Input | Output | Description |
|
|
660
|
+
|----------|-------|--------|-------------|
|
|
661
|
+
| `toHashTree(scriptTree)` | `Taptree` | `HashTree` | Builds the full hashed Merkle tree. |
|
|
662
|
+
| `tapleafHash(leaf)` | `Tapleaf` | `Bytes32` | Computes a single leaf hash. |
|
|
663
|
+
| `tapBranchHash(a, b)` | Two `Uint8Array` | `Bytes32` | Computes a branch hash from two children. |
|
|
664
|
+
| `tapTweakHash(pubKey, h)` | `XOnlyPublicKey`, `Bytes32?` | `Bytes32` | Computes the tweak scalar. |
|
|
665
|
+
| `tweakKey(pubKey, h)` | `XOnlyPublicKey`, `Bytes32?` | `TweakedPublicKey \| null` | Tweaks the internal key to get the output key. |
|
|
666
|
+
| `rootHashFromPath(cb, leaf)` | P2TR control block, leaf hash | `Bytes32` | Reconstructs root from P2TR control block. |
|
|
667
|
+
| `rootHashFromPathP2MR(cb, leaf)` | P2MR control block, leaf hash | `Bytes32` | Reconstructs root from P2MR control block. |
|
|
668
|
+
| `findScriptPath(node, hash)` | `HashTree`, `Bytes32` | `Bytes32[] \| undefined` | Finds the Merkle proof path for a leaf. |
|
|
669
|
+
| `isTapleaf(value)` | `unknown` | `boolean` | Type guard for `Tapleaf`. |
|
|
670
|
+
| `isTaptree(value)` | `unknown` | `boolean` | Type guard for `Taptree`. |
|