@btc-vision/bitcoin 7.0.0-alpha.9 → 7.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +22 -0
  2. package/benchmark-compare/BENCHMARK.md +144 -0
  3. package/benchmark-compare/compare.bench.ts +1024 -0
  4. package/benchmark-compare/harness.ts +220 -0
  5. package/benchmark-compare/package.json +18 -0
  6. package/browser/address.d.ts.map +1 -1
  7. package/browser/block.d.ts.map +1 -1
  8. package/browser/chunks/{psbt-parallel-BBFlkmiv.js → psbt-parallel-B-dfm5GZ.js} +2430 -2523
  9. package/browser/crypto-hashes.d.ts +4 -0
  10. package/browser/crypto-hashes.d.ts.map +1 -0
  11. package/browser/crypto-hashes.native.d.ts +4 -0
  12. package/browser/crypto-hashes.native.d.ts.map +1 -0
  13. package/browser/crypto.d.ts.map +1 -1
  14. package/browser/index.d.ts +2 -2
  15. package/browser/index.d.ts.map +1 -1
  16. package/browser/index.js +571 -547
  17. package/browser/io/base58check.d.ts +26 -0
  18. package/browser/io/base58check.d.ts.map +1 -0
  19. package/browser/io/base64.d.ts +8 -0
  20. package/browser/io/base64.d.ts.map +1 -1
  21. package/browser/io/index.d.ts +2 -1
  22. package/browser/io/index.d.ts.map +1 -1
  23. package/browser/io/utils.d.ts.map +1 -1
  24. package/browser/payments/bip341.d.ts.map +1 -1
  25. package/browser/payments/p2op.d.ts.map +1 -1
  26. package/browser/payments/p2pkh.d.ts.map +1 -1
  27. package/browser/payments/p2sh.d.ts.map +1 -1
  28. package/browser/payments/p2tr.d.ts.map +1 -1
  29. package/browser/payments/p2wpkh.d.ts.map +1 -1
  30. package/browser/psbt/PsbtCache.d.ts.map +1 -1
  31. package/browser/psbt/PsbtSigner.d.ts.map +1 -1
  32. package/browser/psbt/PsbtTransaction.d.ts +2 -2
  33. package/browser/psbt/PsbtTransaction.d.ts.map +1 -1
  34. package/browser/psbt/bip371.d.ts.map +1 -1
  35. package/browser/psbt.d.ts +1 -1
  36. package/browser/psbt.d.ts.map +1 -1
  37. package/browser/react-native-quick-crypto.d.ts +11 -0
  38. package/browser/script.d.ts.map +1 -1
  39. package/browser/transaction.d.ts.map +1 -1
  40. package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -1
  41. package/browser/workers/WorkerSigningPool.sequential.d.ts.map +1 -1
  42. package/browser/workers/index.js +5 -5
  43. package/build/address.d.ts.map +1 -1
  44. package/build/address.js +19 -12
  45. package/build/address.js.map +1 -1
  46. package/build/bip66.js +4 -4
  47. package/build/bip66.js.map +1 -1
  48. package/build/block.d.ts.map +1 -1
  49. package/build/block.js +9 -2
  50. package/build/block.js.map +1 -1
  51. package/build/crypto-hashes.d.ts +4 -0
  52. package/build/crypto-hashes.d.ts.map +1 -0
  53. package/build/crypto-hashes.js +4 -0
  54. package/build/crypto-hashes.js.map +1 -0
  55. package/build/crypto-hashes.native.d.ts +4 -0
  56. package/build/crypto-hashes.native.d.ts.map +1 -0
  57. package/build/crypto-hashes.native.js +15 -0
  58. package/build/crypto-hashes.native.js.map +1 -0
  59. package/build/crypto.d.ts.map +1 -1
  60. package/build/crypto.js +1 -2
  61. package/build/crypto.js.map +1 -1
  62. package/build/env.js.map +1 -1
  63. package/build/index.d.ts +2 -2
  64. package/build/index.d.ts.map +1 -1
  65. package/build/index.js +1 -1
  66. package/build/index.js.map +1 -1
  67. package/build/io/BinaryReader.js +1 -1
  68. package/build/io/BinaryReader.js.map +1 -1
  69. package/build/io/base58check.d.ts +26 -0
  70. package/build/io/base58check.d.ts.map +1 -0
  71. package/build/io/base58check.js +32 -0
  72. package/build/io/base58check.js.map +1 -0
  73. package/build/io/base64.d.ts +8 -0
  74. package/build/io/base64.d.ts.map +1 -1
  75. package/build/io/base64.js +14 -0
  76. package/build/io/base64.js.map +1 -1
  77. package/build/io/hex.js +1 -1
  78. package/build/io/hex.js.map +1 -1
  79. package/build/io/index.d.ts +2 -1
  80. package/build/io/index.d.ts.map +1 -1
  81. package/build/io/index.js +4 -2
  82. package/build/io/index.js.map +1 -1
  83. package/build/io/utils.d.ts.map +1 -1
  84. package/build/io/utils.js +3 -4
  85. package/build/io/utils.js.map +1 -1
  86. package/build/merkle.js.map +1 -1
  87. package/build/payments/bip341.d.ts.map +1 -1
  88. package/build/payments/bip341.js +4 -3
  89. package/build/payments/bip341.js.map +1 -1
  90. package/build/payments/p2op.d.ts.map +1 -1
  91. package/build/payments/p2op.js +6 -4
  92. package/build/payments/p2op.js.map +1 -1
  93. package/build/payments/p2pkh.d.ts.map +1 -1
  94. package/build/payments/p2pkh.js +3 -4
  95. package/build/payments/p2pkh.js.map +1 -1
  96. package/build/payments/p2sh.d.ts.map +1 -1
  97. package/build/payments/p2sh.js +3 -4
  98. package/build/payments/p2sh.js.map +1 -1
  99. package/build/payments/p2tr.d.ts.map +1 -1
  100. package/build/payments/p2tr.js +13 -6
  101. package/build/payments/p2tr.js.map +1 -1
  102. package/build/payments/p2wpkh.d.ts.map +1 -1
  103. package/build/payments/p2wpkh.js +7 -5
  104. package/build/payments/p2wpkh.js.map +1 -1
  105. package/build/payments/p2wsh.js.map +1 -1
  106. package/build/psbt/PsbtCache.d.ts.map +1 -1
  107. package/build/psbt/PsbtCache.js +8 -4
  108. package/build/psbt/PsbtCache.js.map +1 -1
  109. package/build/psbt/PsbtFinalizer.js +14 -8
  110. package/build/psbt/PsbtFinalizer.js.map +1 -1
  111. package/build/psbt/PsbtSigner.d.ts.map +1 -1
  112. package/build/psbt/PsbtSigner.js +3 -2
  113. package/build/psbt/PsbtSigner.js.map +1 -1
  114. package/build/psbt/PsbtTransaction.d.ts +2 -2
  115. package/build/psbt/PsbtTransaction.d.ts.map +1 -1
  116. package/build/psbt/PsbtTransaction.js.map +1 -1
  117. package/build/psbt/bip371.d.ts.map +1 -1
  118. package/build/psbt/bip371.js +4 -3
  119. package/build/psbt/bip371.js.map +1 -1
  120. package/build/psbt/utils.js.map +1 -1
  121. package/build/psbt.d.ts +1 -1
  122. package/build/psbt.d.ts.map +1 -1
  123. package/build/psbt.js.map +1 -1
  124. package/build/push_data.js +1 -1
  125. package/build/push_data.js.map +1 -1
  126. package/build/script.d.ts.map +1 -1
  127. package/build/script.js +4 -3
  128. package/build/script.js.map +1 -1
  129. package/build/script_number.js +1 -1
  130. package/build/script_number.js.map +1 -1
  131. package/build/script_signature.js.map +1 -1
  132. package/build/transaction.d.ts.map +1 -1
  133. package/build/transaction.js +2 -1
  134. package/build/transaction.js.map +1 -1
  135. package/build/tsconfig.build.tsbuildinfo +1 -1
  136. package/build/types.js.map +1 -1
  137. package/build/workers/WorkerSigningPool.js.map +1 -1
  138. package/build/workers/WorkerSigningPool.node.d.ts.map +1 -1
  139. package/build/workers/WorkerSigningPool.node.js +25 -3
  140. package/build/workers/WorkerSigningPool.node.js.map +1 -1
  141. package/build/workers/WorkerSigningPool.sequential.d.ts.map +1 -1
  142. package/build/workers/WorkerSigningPool.sequential.js +2 -0
  143. package/build/workers/WorkerSigningPool.sequential.js.map +1 -1
  144. package/build/workers/psbt-parallel.js.map +1 -1
  145. package/package.json +7 -5
  146. package/src/address.ts +18 -13
  147. package/src/bip66.ts +18 -18
  148. package/src/block.ts +7 -2
  149. package/src/crypto-hashes.native.ts +18 -0
  150. package/src/crypto-hashes.ts +3 -0
  151. package/src/crypto.ts +1 -2
  152. package/src/env.ts +6 -6
  153. package/src/index.ts +3 -0
  154. package/src/io/BinaryReader.ts +1 -1
  155. package/src/io/base58check.ts +35 -0
  156. package/src/io/base64.ts +15 -0
  157. package/src/io/hex.ts +1 -1
  158. package/src/io/index.ts +5 -2
  159. package/src/io/utils.ts +6 -7
  160. package/src/merkle.ts +3 -3
  161. package/src/payments/bip341.ts +5 -4
  162. package/src/payments/p2op.ts +6 -4
  163. package/src/payments/p2pkh.ts +4 -5
  164. package/src/payments/p2sh.ts +4 -5
  165. package/src/payments/p2tr.ts +18 -11
  166. package/src/payments/p2wpkh.ts +7 -5
  167. package/src/payments/p2wsh.ts +1 -1
  168. package/src/psbt/PsbtCache.ts +14 -11
  169. package/src/psbt/PsbtFinalizer.ts +14 -8
  170. package/src/psbt/PsbtSigner.ts +4 -3
  171. package/src/psbt/PsbtTransaction.ts +2 -2
  172. package/src/psbt/bip371.ts +4 -3
  173. package/src/psbt/utils.ts +1 -1
  174. package/src/psbt.ts +10 -8
  175. package/src/push_data.ts +5 -5
  176. package/src/react-native-quick-crypto.d.ts +11 -0
  177. package/src/script.ts +5 -4
  178. package/src/script_number.ts +6 -6
  179. package/src/script_signature.ts +2 -2
  180. package/src/transaction.ts +14 -13
  181. package/src/types.ts +1 -1
  182. package/src/workers/WorkerSigningPool.node.ts +28 -4
  183. package/src/workers/WorkerSigningPool.sequential.ts +2 -1
  184. package/src/workers/WorkerSigningPool.ts +3 -3
  185. package/src/workers/psbt-parallel.ts +2 -2
  186. package/test/address.spec.ts +1 -0
  187. package/test/bitcoin.core.spec.ts +9 -2
  188. package/test/browser/psbt.spec.ts +54 -29
  189. package/test/browser/workers-signing.spec.ts +8 -8
  190. package/test/crypto.spec.ts +1 -1
  191. package/test/env.spec.ts +2 -2
  192. package/test/integration/_regtest.ts +2 -2
  193. package/test/integration/blocks.spec.ts +1 -1
  194. package/test/integration/csv.spec.ts +1 -1
  195. package/test/integration/payments.spec.ts +2 -2
  196. package/test/integration/taproot.spec.ts +3 -3
  197. package/test/integration/transactions.spec.ts +6 -5
  198. package/test/psbt.spec.ts +49 -25
  199. package/test/transaction.spec.ts +6 -3
  200. package/test/workers-pool.spec.ts +5 -5
  201. package/test/workers-signing.spec.ts +8 -8
  202. package/test/workers.spec.ts +3 -3
@@ -0,0 +1,1024 @@
1
+ /**
2
+ * Comprehensive benchmark: @btc-vision/bitcoin vs bitcoinjs-lib
3
+ *
4
+ * Run:
5
+ * cd benchmark-compare && npm run bench
6
+ * cd benchmark-compare && npm run bench:gc # with GC control
7
+ *
8
+ * Scenarios:
9
+ * 1. Library Initialization (cold-start)
10
+ * 2. PSBT Creation (varying input counts)
11
+ * 3. P2WPKH Signing
12
+ * 4. P2TR Taproot Signing
13
+ * 5. End-to-End Lifecycle
14
+ * 6. Parallel Signing (fork-only)
15
+ */
16
+
17
+ import { execSync } from 'child_process';
18
+ import { randomBytes } from 'crypto';
19
+ import { writeFileSync, unlinkSync } from 'fs';
20
+ import { join, dirname } from 'path';
21
+ import { fileURLToPath } from 'url';
22
+ import {
23
+ type BenchResult,
24
+ type ComparisonRow,
25
+ buildSummary,
26
+ fmt,
27
+ measure,
28
+ printComparison,
29
+ printForkOnly,
30
+ } from './harness.js';
31
+
32
+ // ── Official bitcoinjs-lib ──────────────────────────────────────────────────
33
+ import * as bitcoin from 'bitcoinjs-lib';
34
+ import ECPairFactory from 'ecpair';
35
+ import * as tinysecp from 'tiny-secp256k1';
36
+
37
+ // ── Fork (@btc-vision/bitcoin) ─────────────────────────────────────────────
38
+ import {
39
+ Psbt as ForkPsbt,
40
+ initEccLib,
41
+ payments as forkPayments,
42
+ networks as forkNetworks,
43
+ toXOnly,
44
+ crypto as forkCrypto,
45
+ } from '../build/index.js';
46
+ import type { Satoshi, Script as ForkScript } from '../build/index.js';
47
+ import {
48
+ createNobleBackend,
49
+ createLegacyBackend,
50
+ ECPairSigner,
51
+ } from '@btc-vision/ecpair';
52
+
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ // Initialization helpers
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+
57
+ function initOfficial(): void {
58
+ bitcoin.initEccLib(tinysecp);
59
+ }
60
+
61
+ function initForkNoble(): void {
62
+ initEccLib(createNobleBackend());
63
+ }
64
+
65
+ function initForkTiny(): void {
66
+ initEccLib(createLegacyBackend(tinysecp));
67
+ }
68
+
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+ // Shared key material (deterministic from same seed for fairness)
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+
73
+ const SEED = Buffer.from(
74
+ 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35',
75
+ 'hex',
76
+ );
77
+
78
+ // Official ECPair
79
+ const officialECPair = ECPairFactory(tinysecp);
80
+ const officialKeyPair = officialECPair.fromPrivateKey(SEED);
81
+
82
+ // Fork Noble key pair
83
+ const nobleBackend = createNobleBackend();
84
+ const forkNobleKeyPair = ECPairSigner.fromPrivateKey(nobleBackend, SEED);
85
+
86
+ // Fork tiny-secp256k1 key pair
87
+ const tinyBackend = createLegacyBackend(tinysecp);
88
+ const forkTinyKeyPair = ECPairSigner.fromPrivateKey(tinyBackend, SEED);
89
+
90
+ // Tweaked signers for P2TR (key-path spend)
91
+ // Must be created after ECC init — we'll create them lazily
92
+ let _forkNobleTweaked: ReturnType<typeof forkNobleKeyPair.tweak> | null = null;
93
+ let _forkTinyTweaked: ReturnType<typeof forkTinyKeyPair.tweak> | null = null;
94
+ let _officialTweaked: ReturnType<typeof officialKeyPair.tweak> | null = null;
95
+
96
+ function getForkNobleTweakedSigner() {
97
+ if (!_forkNobleTweaked) {
98
+ const xonly = toXOnly(forkNobleKeyPair.publicKey);
99
+ _forkNobleTweaked = forkNobleKeyPair.tweak(forkCrypto.taggedHash('TapTweak', xonly));
100
+ }
101
+ return _forkNobleTweaked;
102
+ }
103
+
104
+ function getForkTinyTweakedSigner() {
105
+ if (!_forkTinyTweaked) {
106
+ const xonly = toXOnly(forkTinyKeyPair.publicKey);
107
+ _forkTinyTweaked = forkTinyKeyPair.tweak(forkCrypto.taggedHash('TapTweak', xonly));
108
+ }
109
+ return _forkTinyTweaked;
110
+ }
111
+
112
+ function getOfficialTweakedSigner() {
113
+ if (!_officialTweaked) {
114
+ const xonly = bitcoin.toXOnly(officialKeyPair.publicKey);
115
+ _officialTweaked = officialKeyPair.tweak(bitcoin.crypto.taggedHash('TapTweak', xonly));
116
+ }
117
+ return _officialTweaked;
118
+ }
119
+
120
+ // ─────────────────────────────────────────────────────────────────────────────
121
+ // Helper: build a fake prev tx output script for P2WPKH
122
+ // ─────────────────────────────────────────────────────────────────────────────
123
+
124
+ function makeWitnessUtxoOfficial(pubkey: Uint8Array): { script: Uint8Array; value: bigint } {
125
+ const p2wpkh = bitcoin.payments.p2wpkh({ pubkey });
126
+ return {
127
+ script: p2wpkh.output!,
128
+ value: 100_000n,
129
+ };
130
+ }
131
+
132
+ function makeWitnessUtxoFork(pubkey: Uint8Array): { script: Uint8Array; value: bigint } {
133
+ const p2wpkh = forkPayments.p2wpkh({ pubkey });
134
+ return {
135
+ script: p2wpkh.output!,
136
+ value: 100_000n,
137
+ };
138
+ }
139
+
140
+ // Helper: build a fake prev tx output for P2TR
141
+ function makeTaprootWitnessUtxoOfficial(pubkey: Uint8Array): { script: Uint8Array; value: bigint } {
142
+ const xonly = bitcoin.toXOnly(pubkey);
143
+ const p2tr = bitcoin.payments.p2tr({ internalPubkey: xonly });
144
+ return {
145
+ script: p2tr.output!,
146
+ value: 100_000n,
147
+ };
148
+ }
149
+
150
+ function makeTaprootWitnessUtxoFork(pubkey: Uint8Array): { script: Uint8Array; value: bigint } {
151
+ const xonly = toXOnly(pubkey);
152
+ const p2tr = forkPayments.p2tr({ internalPubkey: xonly });
153
+ return {
154
+ script: p2tr.output!,
155
+ value: 100_000n,
156
+ };
157
+ }
158
+
159
+ // ─────────────────────────────────────────────────────────────────────────────
160
+ // Helper: random tx hash
161
+ // ─────────────────────────────────────────────────────────────────────────────
162
+
163
+ function randomTxHash(): Buffer {
164
+ return randomBytes(32);
165
+ }
166
+
167
+ // ─────────────────────────────────────────────────────────────────────────────
168
+ // Scenario 1: Library Initialization (subprocess isolation)
169
+ // ─────────────────────────────────────────────────────────────────────────────
170
+
171
+ async function scenarioInit(): Promise<ComparisonRow[]> {
172
+ console.log('\n--- Scenario 1: Library Initialization (cold-start) ---\n');
173
+
174
+ const iterations = 5;
175
+
176
+ function measureColdStart(label: string, scriptBody: string): BenchResult {
177
+ const scriptPath = join(__dirname, `_bench_init_${Date.now()}.mjs`);
178
+ writeFileSync(scriptPath, scriptBody, 'utf-8');
179
+
180
+ const times: number[] = [];
181
+ for (let i = 0; i < iterations; i++) {
182
+ try {
183
+ const output = execSync(`node ${scriptPath}`, {
184
+ cwd: __dirname,
185
+ encoding: 'utf-8',
186
+ timeout: 15000,
187
+ });
188
+ const ms = parseFloat(output.trim().split('\n').pop()!);
189
+ if (!isNaN(ms)) times.push(ms);
190
+ } catch {
191
+ // skip failed iterations
192
+ }
193
+ }
194
+
195
+ try { unlinkSync(scriptPath); } catch { /* ignore */ }
196
+
197
+ if (times.length === 0) {
198
+ return { name: label, mean: NaN, median: NaN, min: NaN, max: NaN, p95: NaN, stddev: NaN, samples: 0 };
199
+ }
200
+
201
+ times.sort((a, b) => a - b);
202
+ const sum = times.reduce((a, b) => a + b, 0);
203
+ const mean = sum / times.length;
204
+ const median = times[Math.floor(times.length / 2)]!;
205
+ const min = times[0]!;
206
+ const max = times[times.length - 1]!;
207
+ const p95 = times[Math.floor(times.length * 0.95)]!;
208
+ const variance = times.reduce((acc, t) => acc + (t - mean) ** 2, 0) / times.length;
209
+ const stddev = Math.sqrt(variance);
210
+
211
+ return { name: label, mean, median, min, max, p95, stddev, samples: times.length };
212
+ }
213
+
214
+ const forkNobleInit = measureColdStart(
215
+ 'Fork (Noble)',
216
+ `const t0 = performance.now();
217
+ import { initEccLib } from '../build/index.js';
218
+ import { createNobleBackend } from '@btc-vision/ecpair';
219
+ initEccLib(createNobleBackend());
220
+ console.log((performance.now() - t0).toFixed(4));`,
221
+ );
222
+ console.log(` Fork (Noble): ${fmt(forkNobleInit.median)}`);
223
+
224
+ const forkTinyInit = measureColdStart(
225
+ 'Fork (tiny-secp256k1)',
226
+ `const t0 = performance.now();
227
+ import { initEccLib } from '../build/index.js';
228
+ import { createLegacyBackend } from '@btc-vision/ecpair';
229
+ import * as tinysecp from 'tiny-secp256k1';
230
+ initEccLib(createLegacyBackend(tinysecp));
231
+ console.log((performance.now() - t0).toFixed(4));`,
232
+ );
233
+ console.log(` Fork (tiny-secp256k1): ${fmt(forkTinyInit.median)}`);
234
+
235
+ const officialInit = measureColdStart(
236
+ 'Official (tiny-secp256k1)',
237
+ `const t0 = performance.now();
238
+ import * as bitcoin from 'bitcoinjs-lib';
239
+ import * as tinysecp from 'tiny-secp256k1';
240
+ import ECPairFactory from 'ecpair';
241
+ bitcoin.initEccLib(tinysecp);
242
+ ECPairFactory(tinysecp);
243
+ console.log((performance.now() - t0).toFixed(4));`,
244
+ );
245
+ console.log(` Official (tiny-secp): ${fmt(officialInit.median)}`);
246
+
247
+ return [{
248
+ scenario: 'Library Init',
249
+ detail: 'cold-start',
250
+ forkNoble: forkNobleInit,
251
+ fork: forkTinyInit,
252
+ official: officialInit,
253
+ }];
254
+ }
255
+
256
+ // ─────────────────────────────────────────────────────────────────────────────
257
+ // Scenario 2: PSBT Creation
258
+ // ─────────────────────────────────────────────────────────────────────────────
259
+
260
+ async function scenarioPsbtCreation(): Promise<ComparisonRow[]> {
261
+ console.log('\n--- Scenario 2: PSBT Creation ---\n');
262
+
263
+ // Make sure both ECC libs are initialized
264
+ initOfficial();
265
+ initForkNoble();
266
+
267
+ const inputCounts = [10, 50, 100, 250, 500];
268
+ const rows: ComparisonRow[] = [];
269
+
270
+ for (const numInputs of inputCounts) {
271
+ const iters = numInputs >= 500 ? 5 : numInputs >= 250 ? 10 : 30;
272
+
273
+ // Pre-generate hashes
274
+ const hashes = Array.from({ length: numInputs }, () => randomTxHash());
275
+
276
+ const forkResult = await measure(
277
+ `Fork PSBT ${numInputs}in`,
278
+ () => {
279
+ const witnessUtxo = makeWitnessUtxoFork(forkNobleKeyPair.publicKey);
280
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
281
+ for (let i = 0; i < numInputs; i++) {
282
+ psbt.addInput({
283
+ hash: hashes[i]!,
284
+ index: 0,
285
+ witnessUtxo: {
286
+ script: witnessUtxo.script as ForkScript,
287
+ value: witnessUtxo.value as Satoshi,
288
+ },
289
+ });
290
+ }
291
+ for (let j = 0; j < 5; j++) {
292
+ psbt.addOutput({
293
+ script: witnessUtxo.script as ForkScript,
294
+ value: 10_000n as Satoshi,
295
+ });
296
+ }
297
+ },
298
+ { iterations: iters },
299
+ );
300
+
301
+ const officialResult = await measure(
302
+ `Official PSBT ${numInputs}in`,
303
+ () => {
304
+ const witnessUtxo = makeWitnessUtxoOfficial(officialKeyPair.publicKey);
305
+ const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
306
+ for (let i = 0; i < numInputs; i++) {
307
+ psbt.addInput({
308
+ hash: hashes[i]!,
309
+ index: 0,
310
+ witnessUtxo,
311
+ });
312
+ }
313
+ for (let j = 0; j < 5; j++) {
314
+ psbt.addOutput({
315
+ script: witnessUtxo.script,
316
+ value: 10_000n,
317
+ });
318
+ }
319
+ },
320
+ { iterations: iters },
321
+ );
322
+
323
+ console.log(
324
+ ` ${String(numInputs).padStart(4)} inputs: Fork=${fmt(forkResult.median)}, Official=${fmt(officialResult.median)}`,
325
+ );
326
+
327
+ rows.push({
328
+ scenario: 'PSBT Creation',
329
+ detail: `${numInputs} inputs`,
330
+ forkNoble: forkResult,
331
+ fork: null,
332
+ official: officialResult,
333
+ });
334
+ }
335
+
336
+ return rows;
337
+ }
338
+
339
+ // ─────────────────────────────────────────────────────────────────────────────
340
+ // Scenario 3: P2WPKH Signing
341
+ // ─────────────────────────────────────────────────────────────────────────────
342
+
343
+ async function scenarioP2wpkhSigning(): Promise<ComparisonRow[]> {
344
+ console.log('\n--- Scenario 3: P2WPKH Signing ---\n');
345
+
346
+ initOfficial();
347
+
348
+ const inputCounts = [10, 50, 100, 250, 500];
349
+ const rows: ComparisonRow[] = [];
350
+
351
+ for (const numInputs of inputCounts) {
352
+ const iters = numInputs >= 500 ? 5 : numInputs >= 250 ? 10 : 30;
353
+ const hashes = Array.from({ length: numInputs }, () => randomTxHash());
354
+
355
+ // --- Fork with Noble ---
356
+ initForkNoble();
357
+ const forkNobleResult = await measure(
358
+ `Fork Noble P2WPKH ${numInputs}`,
359
+ () => {
360
+ const witnessUtxo = makeWitnessUtxoFork(forkNobleKeyPair.publicKey);
361
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
362
+ for (let i = 0; i < numInputs; i++) {
363
+ psbt.addInput({
364
+ hash: hashes[i]!,
365
+ index: 0,
366
+ witnessUtxo: {
367
+ script: witnessUtxo.script as ForkScript,
368
+ value: witnessUtxo.value as Satoshi,
369
+ },
370
+ });
371
+ }
372
+ for (let j = 0; j < 5; j++) {
373
+ psbt.addOutput({
374
+ script: witnessUtxo.script as ForkScript,
375
+ value: 10_000n as Satoshi,
376
+ });
377
+ }
378
+ psbt.signAllInputs(forkNobleKeyPair);
379
+ },
380
+ { iterations: iters },
381
+ );
382
+
383
+ // --- Fork with tiny-secp256k1 ---
384
+ initForkTiny();
385
+ const forkTinyResult = await measure(
386
+ `Fork tiny P2WPKH ${numInputs}`,
387
+ () => {
388
+ const witnessUtxo = makeWitnessUtxoFork(forkTinyKeyPair.publicKey);
389
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
390
+ for (let i = 0; i < numInputs; i++) {
391
+ psbt.addInput({
392
+ hash: hashes[i]!,
393
+ index: 0,
394
+ witnessUtxo: {
395
+ script: witnessUtxo.script as ForkScript,
396
+ value: witnessUtxo.value as Satoshi,
397
+ },
398
+ });
399
+ }
400
+ for (let j = 0; j < 5; j++) {
401
+ psbt.addOutput({
402
+ script: witnessUtxo.script as ForkScript,
403
+ value: 10_000n as Satoshi,
404
+ });
405
+ }
406
+ psbt.signAllInputs(forkTinyKeyPair);
407
+ },
408
+ { iterations: iters },
409
+ );
410
+
411
+ // --- Official with tiny-secp256k1 ---
412
+ initOfficial();
413
+ const officialResult = await measure(
414
+ `Official P2WPKH ${numInputs}`,
415
+ () => {
416
+ const witnessUtxo = makeWitnessUtxoOfficial(officialKeyPair.publicKey);
417
+ const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
418
+ for (let i = 0; i < numInputs; i++) {
419
+ psbt.addInput({
420
+ hash: hashes[i]!,
421
+ index: 0,
422
+ witnessUtxo,
423
+ });
424
+ }
425
+ for (let j = 0; j < 5; j++) {
426
+ psbt.addOutput({
427
+ script: witnessUtxo.script,
428
+ value: 10_000n,
429
+ });
430
+ }
431
+ psbt.signAllInputs(officialKeyPair);
432
+ },
433
+ { iterations: iters },
434
+ );
435
+
436
+ console.log(
437
+ ` ${String(numInputs).padStart(4)} inputs: Noble=${fmt(forkNobleResult.median)}, tiny=${fmt(forkTinyResult.median)}, Official=${fmt(officialResult.median)}`,
438
+ );
439
+
440
+ rows.push({
441
+ scenario: 'P2WPKH Signing',
442
+ detail: `${numInputs} inputs`,
443
+ forkNoble: forkNobleResult,
444
+ fork: forkTinyResult,
445
+ official: officialResult,
446
+ });
447
+ }
448
+
449
+ return rows;
450
+ }
451
+
452
+ // ─────────────────────────────────────────────────────────────────────────────
453
+ // Scenario 4: P2TR Taproot Signing
454
+ // ─────────────────────────────────────────────────────────────────────────────
455
+
456
+ async function scenarioP2trSigning(): Promise<ComparisonRow[]> {
457
+ console.log('\n--- Scenario 4: P2TR Taproot Signing ---\n');
458
+
459
+ const inputCounts = [10, 50, 100, 250, 500];
460
+ const rows: ComparisonRow[] = [];
461
+
462
+ for (const numInputs of inputCounts) {
463
+ const iters = numInputs >= 500 ? 5 : numInputs >= 250 ? 10 : 30;
464
+ const hashes = Array.from({ length: numInputs }, () => randomTxHash());
465
+
466
+ // --- Fork with Noble ---
467
+ initForkNoble();
468
+ const nobleTweaked = getForkNobleTweakedSigner();
469
+ const forkNobleXOnly = toXOnly(forkNobleKeyPair.publicKey);
470
+ const forkNobleResult = await measure(
471
+ `Fork Noble P2TR ${numInputs}`,
472
+ () => {
473
+ const witnessUtxo = makeTaprootWitnessUtxoFork(forkNobleKeyPair.publicKey);
474
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
475
+ for (let i = 0; i < numInputs; i++) {
476
+ psbt.addInput({
477
+ hash: hashes[i]!,
478
+ index: 0,
479
+ witnessUtxo: {
480
+ script: witnessUtxo.script as ForkScript,
481
+ value: witnessUtxo.value as Satoshi,
482
+ },
483
+ tapInternalKey: forkNobleXOnly,
484
+ });
485
+ }
486
+ for (let j = 0; j < 5; j++) {
487
+ psbt.addOutput({
488
+ script: witnessUtxo.script as ForkScript,
489
+ value: 10_000n as Satoshi,
490
+ });
491
+ }
492
+ psbt.signAllInputs(nobleTweaked);
493
+ },
494
+ { iterations: iters },
495
+ );
496
+
497
+ // --- Fork with tiny-secp256k1 ---
498
+ initForkTiny();
499
+ const tinyTweaked = getForkTinyTweakedSigner();
500
+ const forkTinyXOnly = toXOnly(forkTinyKeyPair.publicKey);
501
+ const forkTinyResult = await measure(
502
+ `Fork tiny P2TR ${numInputs}`,
503
+ () => {
504
+ const witnessUtxo = makeTaprootWitnessUtxoFork(forkTinyKeyPair.publicKey);
505
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
506
+ for (let i = 0; i < numInputs; i++) {
507
+ psbt.addInput({
508
+ hash: hashes[i]!,
509
+ index: 0,
510
+ witnessUtxo: {
511
+ script: witnessUtxo.script as ForkScript,
512
+ value: witnessUtxo.value as Satoshi,
513
+ },
514
+ tapInternalKey: forkTinyXOnly,
515
+ });
516
+ }
517
+ for (let j = 0; j < 5; j++) {
518
+ psbt.addOutput({
519
+ script: witnessUtxo.script as ForkScript,
520
+ value: 10_000n as Satoshi,
521
+ });
522
+ }
523
+ psbt.signAllInputs(tinyTweaked);
524
+ },
525
+ { iterations: iters },
526
+ );
527
+
528
+ // --- Official with tiny-secp256k1 ---
529
+ initOfficial();
530
+ const officialTweaked = getOfficialTweakedSigner();
531
+ const xonlyOfficial = bitcoin.toXOnly(officialKeyPair.publicKey);
532
+ const officialResult = await measure(
533
+ `Official P2TR ${numInputs}`,
534
+ () => {
535
+ const witnessUtxo = makeTaprootWitnessUtxoOfficial(officialKeyPair.publicKey);
536
+ const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
537
+ for (let i = 0; i < numInputs; i++) {
538
+ psbt.addInput({
539
+ hash: hashes[i]!,
540
+ index: 0,
541
+ witnessUtxo,
542
+ tapInternalKey: xonlyOfficial,
543
+ });
544
+ }
545
+ for (let j = 0; j < 5; j++) {
546
+ psbt.addOutput({
547
+ script: witnessUtxo.script,
548
+ value: 10_000n,
549
+ });
550
+ }
551
+ psbt.signAllInputs(officialTweaked);
552
+ },
553
+ { iterations: iters },
554
+ );
555
+
556
+ console.log(
557
+ ` ${String(numInputs).padStart(4)} inputs: Noble=${fmt(forkNobleResult.median)}, tiny=${fmt(forkTinyResult.median)}, Official=${fmt(officialResult.median)}`,
558
+ );
559
+
560
+ rows.push({
561
+ scenario: 'P2TR Signing',
562
+ detail: `${numInputs} inputs`,
563
+ forkNoble: forkNobleResult,
564
+ fork: forkTinyResult,
565
+ official: officialResult,
566
+ });
567
+ }
568
+
569
+ return rows;
570
+ }
571
+
572
+ // ─────────────────────────────────────────────────────────────────────────────
573
+ // Scenario 5: End-to-End Lifecycle
574
+ // ─────────────────────────────────────────────────────────────────────────────
575
+
576
+ async function scenarioEndToEnd(): Promise<ComparisonRow[]> {
577
+ console.log('\n--- Scenario 5: End-to-End Lifecycle (100 inputs) ---\n');
578
+
579
+ const numInputs = 100;
580
+ const iters = 20;
581
+ const hashes = Array.from({ length: numInputs }, () => randomTxHash());
582
+
583
+ // --- Fork Noble (P2WPKH) ---
584
+ initForkNoble();
585
+ const forkNobleE2E = await measure(
586
+ 'Fork Noble E2E P2WPKH',
587
+ () => {
588
+ const witnessUtxo = makeWitnessUtxoFork(forkNobleKeyPair.publicKey);
589
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
590
+ for (let i = 0; i < numInputs; i++) {
591
+ psbt.addInput({
592
+ hash: hashes[i]!,
593
+ index: 0,
594
+ witnessUtxo: {
595
+ script: witnessUtxo.script as ForkScript,
596
+ value: witnessUtxo.value as Satoshi,
597
+ },
598
+ });
599
+ }
600
+ for (let j = 0; j < 5; j++) {
601
+ psbt.addOutput({
602
+ script: witnessUtxo.script as ForkScript,
603
+ value: 10_000n as Satoshi,
604
+ });
605
+ }
606
+ psbt.signAllInputs(forkNobleKeyPair);
607
+ psbt.finalizeAllInputs();
608
+ const tx = psbt.extractTransaction();
609
+ tx.toHex();
610
+ },
611
+ { iterations: iters },
612
+ );
613
+
614
+ // --- Fork tiny-secp256k1 (P2WPKH) ---
615
+ initForkTiny();
616
+ const forkTinyE2E = await measure(
617
+ 'Fork tiny E2E P2WPKH',
618
+ () => {
619
+ const witnessUtxo = makeWitnessUtxoFork(forkTinyKeyPair.publicKey);
620
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
621
+ for (let i = 0; i < numInputs; i++) {
622
+ psbt.addInput({
623
+ hash: hashes[i]!,
624
+ index: 0,
625
+ witnessUtxo: {
626
+ script: witnessUtxo.script as ForkScript,
627
+ value: witnessUtxo.value as Satoshi,
628
+ },
629
+ });
630
+ }
631
+ for (let j = 0; j < 5; j++) {
632
+ psbt.addOutput({
633
+ script: witnessUtxo.script as ForkScript,
634
+ value: 10_000n as Satoshi,
635
+ });
636
+ }
637
+ psbt.signAllInputs(forkTinyKeyPair);
638
+ psbt.finalizeAllInputs();
639
+ const tx = psbt.extractTransaction();
640
+ tx.toHex();
641
+ },
642
+ { iterations: iters },
643
+ );
644
+
645
+ // --- Official (P2WPKH) ---
646
+ initOfficial();
647
+ const officialE2E = await measure(
648
+ 'Official E2E P2WPKH',
649
+ () => {
650
+ const witnessUtxo = makeWitnessUtxoOfficial(officialKeyPair.publicKey);
651
+ const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
652
+ for (let i = 0; i < numInputs; i++) {
653
+ psbt.addInput({
654
+ hash: hashes[i]!,
655
+ index: 0,
656
+ witnessUtxo,
657
+ });
658
+ }
659
+ for (let j = 0; j < 5; j++) {
660
+ psbt.addOutput({
661
+ script: witnessUtxo.script,
662
+ value: 10_000n,
663
+ });
664
+ }
665
+ psbt.signAllInputs(officialKeyPair);
666
+ psbt.finalizeAllInputs();
667
+ const tx = psbt.extractTransaction();
668
+ tx.toHex();
669
+ },
670
+ { iterations: iters },
671
+ );
672
+
673
+ console.log(` P2WPKH 100in: Noble=${fmt(forkNobleE2E.median)}, tiny=${fmt(forkTinyE2E.median)}, Official=${fmt(officialE2E.median)}`);
674
+
675
+ // --- Fork Noble (P2TR) ---
676
+ initForkNoble();
677
+ const e2eNobleTweaked = getForkNobleTweakedSigner();
678
+ const e2eNobleXOnly = toXOnly(forkNobleKeyPair.publicKey);
679
+ const forkNobleE2ETr = await measure(
680
+ 'Fork Noble E2E P2TR',
681
+ () => {
682
+ const witnessUtxo = makeTaprootWitnessUtxoFork(forkNobleKeyPair.publicKey);
683
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
684
+ for (let i = 0; i < numInputs; i++) {
685
+ psbt.addInput({
686
+ hash: hashes[i]!,
687
+ index: 0,
688
+ witnessUtxo: {
689
+ script: witnessUtxo.script as ForkScript,
690
+ value: witnessUtxo.value as Satoshi,
691
+ },
692
+ tapInternalKey: e2eNobleXOnly,
693
+ });
694
+ }
695
+ for (let j = 0; j < 5; j++) {
696
+ psbt.addOutput({
697
+ script: witnessUtxo.script as ForkScript,
698
+ value: 10_000n as Satoshi,
699
+ });
700
+ }
701
+ psbt.signAllInputs(e2eNobleTweaked);
702
+ psbt.finalizeAllInputs();
703
+ const tx = psbt.extractTransaction();
704
+ tx.toHex();
705
+ },
706
+ { iterations: iters },
707
+ );
708
+
709
+ // --- Fork tiny-secp256k1 (P2TR) ---
710
+ initForkTiny();
711
+ const e2eTinyTweaked = getForkTinyTweakedSigner();
712
+ const e2eTinyXOnly = toXOnly(forkTinyKeyPair.publicKey);
713
+ const forkTinyE2ETr = await measure(
714
+ 'Fork tiny E2E P2TR',
715
+ () => {
716
+ const witnessUtxo = makeTaprootWitnessUtxoFork(forkTinyKeyPair.publicKey);
717
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
718
+ for (let i = 0; i < numInputs; i++) {
719
+ psbt.addInput({
720
+ hash: hashes[i]!,
721
+ index: 0,
722
+ witnessUtxo: {
723
+ script: witnessUtxo.script as ForkScript,
724
+ value: witnessUtxo.value as Satoshi,
725
+ },
726
+ tapInternalKey: e2eTinyXOnly,
727
+ });
728
+ }
729
+ for (let j = 0; j < 5; j++) {
730
+ psbt.addOutput({
731
+ script: witnessUtxo.script as ForkScript,
732
+ value: 10_000n as Satoshi,
733
+ });
734
+ }
735
+ psbt.signAllInputs(e2eTinyTweaked);
736
+ psbt.finalizeAllInputs();
737
+ const tx = psbt.extractTransaction();
738
+ tx.toHex();
739
+ },
740
+ { iterations: iters },
741
+ );
742
+
743
+ // --- Official (P2TR) ---
744
+ initOfficial();
745
+ const e2eOfficialTweaked = getOfficialTweakedSigner();
746
+ const xonly = bitcoin.toXOnly(officialKeyPair.publicKey);
747
+ const officialE2ETr = await measure(
748
+ 'Official E2E P2TR',
749
+ () => {
750
+ const witnessUtxo = makeTaprootWitnessUtxoOfficial(officialKeyPair.publicKey);
751
+ const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
752
+ for (let i = 0; i < numInputs; i++) {
753
+ psbt.addInput({
754
+ hash: hashes[i]!,
755
+ index: 0,
756
+ witnessUtxo,
757
+ tapInternalKey: xonly,
758
+ });
759
+ }
760
+ for (let j = 0; j < 5; j++) {
761
+ psbt.addOutput({
762
+ script: witnessUtxo.script,
763
+ value: 10_000n,
764
+ });
765
+ }
766
+ psbt.signAllInputs(e2eOfficialTweaked);
767
+ psbt.finalizeAllInputs();
768
+ const tx = psbt.extractTransaction();
769
+ tx.toHex();
770
+ },
771
+ { iterations: iters },
772
+ );
773
+
774
+ console.log(` P2TR 100in: Noble=${fmt(forkNobleE2ETr.median)}, tiny=${fmt(forkTinyE2ETr.median)}, Official=${fmt(officialE2ETr.median)}`);
775
+
776
+ return [
777
+ {
778
+ scenario: 'E2E P2WPKH',
779
+ detail: '100 inputs',
780
+ forkNoble: forkNobleE2E,
781
+ fork: forkTinyE2E,
782
+ official: officialE2E,
783
+ },
784
+ {
785
+ scenario: 'E2E P2TR',
786
+ detail: '100 inputs',
787
+ forkNoble: forkNobleE2ETr,
788
+ fork: forkTinyE2ETr,
789
+ official: officialE2ETr,
790
+ },
791
+ ];
792
+ }
793
+
794
+ // ─────────────────────────────────────────────────────────────────────────────
795
+ // Scenario 6: Parallel Signing (fork-only)
796
+ // ─────────────────────────────────────────────────────────────────────────────
797
+
798
+ async function scenarioParallelSigning(): Promise<{
799
+ rows: { scenario: string; detail: string; result: BenchResult }[];
800
+ results: Record<string, BenchResult>;
801
+ }> {
802
+ console.log('\n--- Scenario 6: Parallel Signing (fork-only) ---\n');
803
+
804
+ const { NodeWorkerSigningPool } = await import('../build/workers/WorkerSigningPool.node.js');
805
+ const { signPsbtParallel } = await import('../build/workers/psbt-parallel.js');
806
+
807
+ initForkNoble();
808
+
809
+ const inputCounts = [100, 500];
810
+ const rows: { scenario: string; detail: string; result: BenchResult }[] = [];
811
+ const results: Record<string, BenchResult> = {};
812
+
813
+ for (const numInputs of inputCounts) {
814
+ const iters = numInputs >= 500 ? 5 : 10;
815
+ const hashes = Array.from({ length: numInputs }, () => randomTxHash());
816
+
817
+ // --- Fork Sequential ---
818
+ initForkNoble();
819
+ const seqResult = await measure(
820
+ `Fork Sequential ${numInputs}`,
821
+ () => {
822
+ const witnessUtxo = makeWitnessUtxoFork(forkNobleKeyPair.publicKey);
823
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
824
+ for (let i = 0; i < numInputs; i++) {
825
+ psbt.addInput({
826
+ hash: hashes[i]!,
827
+ index: 0,
828
+ witnessUtxo: {
829
+ script: witnessUtxo.script as ForkScript,
830
+ value: witnessUtxo.value as Satoshi,
831
+ },
832
+ });
833
+ }
834
+ for (let j = 0; j < 5; j++) {
835
+ psbt.addOutput({
836
+ script: witnessUtxo.script as ForkScript,
837
+ value: 10_000n as Satoshi,
838
+ });
839
+ }
840
+ psbt.signAllInputs(forkNobleKeyPair);
841
+ },
842
+ { iterations: iters, warmup: 2 },
843
+ );
844
+
845
+ // --- Fork Parallel (4 workers) ---
846
+ NodeWorkerSigningPool.resetInstance();
847
+ const pool = NodeWorkerSigningPool.getInstance({ workerCount: 4 });
848
+ await pool.initialize();
849
+ pool.preserveWorkers();
850
+
851
+ // Warmup the pool
852
+ {
853
+ const warmupPsbt = new ForkPsbt({ network: forkNetworks.bitcoin });
854
+ const witnessUtxo = makeWitnessUtxoFork(forkNobleKeyPair.publicKey);
855
+ for (let i = 0; i < 10; i++) {
856
+ warmupPsbt.addInput({
857
+ hash: randomTxHash(),
858
+ index: 0,
859
+ witnessUtxo: {
860
+ script: witnessUtxo.script as ForkScript,
861
+ value: witnessUtxo.value as Satoshi,
862
+ },
863
+ });
864
+ }
865
+ warmupPsbt.addOutput({
866
+ script: witnessUtxo.script as ForkScript,
867
+ value: 10_000n as Satoshi,
868
+ });
869
+ await signPsbtParallel(warmupPsbt, forkNobleKeyPair, pool);
870
+ }
871
+
872
+ const parResult = await measure(
873
+ `Fork Parallel(4) ${numInputs}`,
874
+ async () => {
875
+ const witnessUtxo = makeWitnessUtxoFork(forkNobleKeyPair.publicKey);
876
+ const psbt = new ForkPsbt({ network: forkNetworks.bitcoin });
877
+ for (let i = 0; i < numInputs; i++) {
878
+ psbt.addInput({
879
+ hash: hashes[i]!,
880
+ index: 0,
881
+ witnessUtxo: {
882
+ script: witnessUtxo.script as ForkScript,
883
+ value: witnessUtxo.value as Satoshi,
884
+ },
885
+ });
886
+ }
887
+ for (let j = 0; j < 5; j++) {
888
+ psbt.addOutput({
889
+ script: witnessUtxo.script as ForkScript,
890
+ value: 10_000n as Satoshi,
891
+ });
892
+ }
893
+ await signPsbtParallel(psbt, forkNobleKeyPair, pool);
894
+ },
895
+ { iterations: iters, warmup: 2 },
896
+ );
897
+
898
+ await pool.shutdown();
899
+
900
+ // --- Official Sequential (for comparison) ---
901
+ initOfficial();
902
+ const officialSeqResult = await measure(
903
+ `Official Sequential ${numInputs}`,
904
+ () => {
905
+ const witnessUtxo = makeWitnessUtxoOfficial(officialKeyPair.publicKey);
906
+ const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
907
+ for (let i = 0; i < numInputs; i++) {
908
+ psbt.addInput({
909
+ hash: hashes[i]!,
910
+ index: 0,
911
+ witnessUtxo,
912
+ });
913
+ }
914
+ for (let j = 0; j < 5; j++) {
915
+ psbt.addOutput({
916
+ script: witnessUtxo.script,
917
+ value: 10_000n,
918
+ });
919
+ }
920
+ psbt.signAllInputs(officialKeyPair);
921
+ },
922
+ { iterations: iters, warmup: 2 },
923
+ );
924
+
925
+ const speedup = seqResult.median / parResult.median;
926
+
927
+ console.log(
928
+ ` ${String(numInputs).padStart(4)} inputs: Sequential=${fmt(seqResult.median)}, Parallel(4)=${fmt(parResult.median)}, Official=${fmt(officialSeqResult.median)}, Speedup=${speedup.toFixed(2)}x`,
929
+ );
930
+
931
+ rows.push(
932
+ { scenario: 'Fork Sequential', detail: `${numInputs} inputs`, result: seqResult },
933
+ { scenario: 'Fork Parallel (4 workers)', detail: `${numInputs} inputs`, result: parResult },
934
+ { scenario: 'Official Sequential', detail: `${numInputs} inputs`, result: officialSeqResult },
935
+ );
936
+
937
+ results[`parallel_seq_${numInputs}`] = seqResult;
938
+ results[`parallel_par4_${numInputs}`] = parResult;
939
+ results[`parallel_official_${numInputs}`] = officialSeqResult;
940
+ }
941
+
942
+ return { rows, results };
943
+ }
944
+
945
+ // ─────────────────────────────────────────────────────────────────────────────
946
+ // Main
947
+ // ─────────────────────────────────────────────────────────────────────────────
948
+
949
+ // dirname workaround for ESM
950
+ const __filename = fileURLToPath(import.meta.url);
951
+ const __dirname = dirname(__filename);
952
+
953
+ async function main(): Promise<void> {
954
+ console.log('='.repeat(80));
955
+ console.log(' @btc-vision/bitcoin vs bitcoinjs-lib — Benchmark Comparison');
956
+ console.log(` Node ${process.version} | ${process.platform} ${process.arch}`);
957
+ console.log(` GC control: ${typeof globalThis.gc === 'function' ? 'YES' : 'NO (run with --expose-gc for best results)'}`);
958
+ console.log('='.repeat(80));
959
+
960
+ const allRows: ComparisonRow[] = [];
961
+ const allResults: Record<string, BenchResult> = {};
962
+
963
+ // Scenario 1: Initialization
964
+ const initRows = await scenarioInit();
965
+ allRows.push(...initRows);
966
+ for (const r of initRows) {
967
+ if (r.forkNoble) allResults[`init_fork_noble`] = r.forkNoble;
968
+ if (r.fork) allResults[`init_fork_tiny`] = r.fork;
969
+ if (r.official) allResults[`init_official`] = r.official;
970
+ }
971
+
972
+ // Scenario 2: PSBT Creation
973
+ const createRows = await scenarioPsbtCreation();
974
+ allRows.push(...createRows);
975
+ for (const r of createRows) {
976
+ if (r.forkNoble) allResults[`create_fork_${r.detail}`] = r.forkNoble;
977
+ if (r.official) allResults[`create_official_${r.detail}`] = r.official;
978
+ }
979
+
980
+ // Scenario 3: P2WPKH Signing
981
+ const wpkhRows = await scenarioP2wpkhSigning();
982
+ allRows.push(...wpkhRows);
983
+ for (const r of wpkhRows) {
984
+ if (r.forkNoble) allResults[`wpkh_noble_${r.detail}`] = r.forkNoble;
985
+ if (r.fork) allResults[`wpkh_tiny_${r.detail}`] = r.fork;
986
+ if (r.official) allResults[`wpkh_official_${r.detail}`] = r.official;
987
+ }
988
+
989
+ // Scenario 4: P2TR Signing
990
+ const trRows = await scenarioP2trSigning();
991
+ allRows.push(...trRows);
992
+ for (const r of trRows) {
993
+ if (r.forkNoble) allResults[`tr_noble_${r.detail}`] = r.forkNoble;
994
+ if (r.fork) allResults[`tr_tiny_${r.detail}`] = r.fork;
995
+ if (r.official) allResults[`tr_official_${r.detail}`] = r.official;
996
+ }
997
+
998
+ // Scenario 5: End-to-End
999
+ const e2eRows = await scenarioEndToEnd();
1000
+ allRows.push(...e2eRows);
1001
+ for (const r of e2eRows) {
1002
+ if (r.forkNoble) allResults[`e2e_noble_${r.detail}_${r.scenario}`] = r.forkNoble;
1003
+ if (r.fork) allResults[`e2e_tiny_${r.detail}_${r.scenario}`] = r.fork;
1004
+ if (r.official) allResults[`e2e_official_${r.detail}_${r.scenario}`] = r.official;
1005
+ }
1006
+
1007
+ // Scenario 6: Parallel Signing
1008
+ const parallelData = await scenarioParallelSigning();
1009
+ Object.assign(allResults, parallelData.results);
1010
+
1011
+ // ── Print summary tables ──
1012
+ printComparison('Summary: @btc-vision/bitcoin vs bitcoinjs-lib', allRows);
1013
+ printForkOnly('Parallel Signing (fork-exclusive)', parallelData.rows);
1014
+
1015
+ // ── JSON summary ──
1016
+ const summary = buildSummary(allResults);
1017
+ console.log('\n--- JSON Summary ---');
1018
+ console.log(JSON.stringify(summary, null, 2));
1019
+ }
1020
+
1021
+ main().catch((err) => {
1022
+ console.error('Benchmark failed:', err);
1023
+ process.exit(1);
1024
+ });