@btc-vision/bitcoin 7.0.0-alpha.9 → 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.
Files changed (268) hide show
  1. package/README.md +121 -0
  2. package/benchmark-compare/BENCHMARK.md +159 -0
  3. package/benchmark-compare/compare.bench.ts +1177 -0
  4. package/benchmark-compare/harness.ts +218 -0
  5. package/benchmark-compare/package.json +19 -0
  6. package/browser/address.d.ts +4 -4
  7. package/browser/address.d.ts.map +1 -1
  8. package/browser/block.d.ts.map +1 -1
  9. package/browser/chunks/{psbt-parallel-BBFlkmiv.js → psbt-parallel-jZ6QcCnM.js} +3487 -3183
  10. package/browser/crypto-hashes.d.ts +4 -0
  11. package/browser/crypto-hashes.d.ts.map +1 -0
  12. package/browser/crypto-hashes.native.d.ts +4 -0
  13. package/browser/crypto-hashes.native.d.ts.map +1 -0
  14. package/browser/crypto.d.ts.map +1 -1
  15. package/browser/index.d.ts +3 -3
  16. package/browser/index.d.ts.map +1 -1
  17. package/browser/index.js +687 -645
  18. package/browser/io/base58check.d.ts +2 -0
  19. package/browser/io/base58check.d.ts.map +1 -0
  20. package/browser/io/base64.d.ts +8 -0
  21. package/browser/io/base64.d.ts.map +1 -1
  22. package/browser/io/index.d.ts +2 -1
  23. package/browser/io/index.d.ts.map +1 -1
  24. package/browser/io/utils.d.ts.map +1 -1
  25. package/browser/networks.d.ts +1 -0
  26. package/browser/networks.d.ts.map +1 -1
  27. package/browser/payments/bip341.d.ts +17 -0
  28. package/browser/payments/bip341.d.ts.map +1 -1
  29. package/browser/payments/index.d.ts +3 -2
  30. package/browser/payments/index.d.ts.map +1 -1
  31. package/browser/payments/p2mr.d.ts +169 -0
  32. package/browser/payments/p2mr.d.ts.map +1 -0
  33. package/browser/payments/p2op.d.ts.map +1 -1
  34. package/browser/payments/p2pkh.d.ts.map +1 -1
  35. package/browser/payments/p2sh.d.ts.map +1 -1
  36. package/browser/payments/p2tr.d.ts.map +1 -1
  37. package/browser/payments/p2wpkh.d.ts.map +1 -1
  38. package/browser/payments/types.d.ts +11 -1
  39. package/browser/payments/types.d.ts.map +1 -1
  40. package/browser/psbt/PsbtCache.d.ts.map +1 -1
  41. package/browser/psbt/PsbtSigner.d.ts.map +1 -1
  42. package/browser/psbt/PsbtTransaction.d.ts +2 -2
  43. package/browser/psbt/PsbtTransaction.d.ts.map +1 -1
  44. package/browser/psbt/bip371.d.ts +30 -0
  45. package/browser/psbt/bip371.d.ts.map +1 -1
  46. package/browser/psbt/psbtutils.d.ts +1 -0
  47. package/browser/psbt/psbtutils.d.ts.map +1 -1
  48. package/browser/psbt.d.ts +1 -1
  49. package/browser/psbt.d.ts.map +1 -1
  50. package/browser/react-native-quick-crypto.d.ts +11 -0
  51. package/browser/script.d.ts.map +1 -1
  52. package/browser/transaction.d.ts.map +1 -1
  53. package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -1
  54. package/browser/workers/WorkerSigningPool.sequential.d.ts.map +1 -1
  55. package/browser/workers/index.js +6 -6
  56. package/build/address.d.ts +4 -4
  57. package/build/address.d.ts.map +1 -1
  58. package/build/address.js +30 -13
  59. package/build/address.js.map +1 -1
  60. package/build/bip66.js +4 -4
  61. package/build/bip66.js.map +1 -1
  62. package/build/block.d.ts.map +1 -1
  63. package/build/block.js +9 -2
  64. package/build/block.js.map +1 -1
  65. package/build/crypto-hashes.d.ts +4 -0
  66. package/build/crypto-hashes.d.ts.map +1 -0
  67. package/build/crypto-hashes.js +4 -0
  68. package/build/crypto-hashes.js.map +1 -0
  69. package/build/crypto-hashes.native.d.ts +4 -0
  70. package/build/crypto-hashes.native.d.ts.map +1 -0
  71. package/build/crypto-hashes.native.js +15 -0
  72. package/build/crypto-hashes.native.js.map +1 -0
  73. package/build/crypto.d.ts.map +1 -1
  74. package/build/crypto.js +1 -2
  75. package/build/crypto.js.map +1 -1
  76. package/build/env.js.map +1 -1
  77. package/build/index.d.ts +3 -3
  78. package/build/index.d.ts.map +1 -1
  79. package/build/index.js +1 -1
  80. package/build/index.js.map +1 -1
  81. package/build/io/BinaryReader.js +1 -1
  82. package/build/io/BinaryReader.js.map +1 -1
  83. package/build/io/base58check.d.ts +2 -0
  84. package/build/io/base58check.d.ts.map +1 -0
  85. package/build/io/base58check.js +2 -0
  86. package/build/io/base58check.js.map +1 -0
  87. package/build/io/base64.d.ts +8 -0
  88. package/build/io/base64.d.ts.map +1 -1
  89. package/build/io/base64.js +17 -0
  90. package/build/io/base64.js.map +1 -1
  91. package/build/io/hex.js +1 -1
  92. package/build/io/hex.js.map +1 -1
  93. package/build/io/index.d.ts +2 -1
  94. package/build/io/index.d.ts.map +1 -1
  95. package/build/io/index.js +4 -2
  96. package/build/io/index.js.map +1 -1
  97. package/build/io/utils.d.ts.map +1 -1
  98. package/build/io/utils.js +3 -4
  99. package/build/io/utils.js.map +1 -1
  100. package/build/merkle.js.map +1 -1
  101. package/build/networks.d.ts +1 -0
  102. package/build/networks.d.ts.map +1 -1
  103. package/build/networks.js +12 -0
  104. package/build/networks.js.map +1 -1
  105. package/build/payments/bip341.d.ts +17 -0
  106. package/build/payments/bip341.d.ts.map +1 -1
  107. package/build/payments/bip341.js +36 -4
  108. package/build/payments/bip341.js.map +1 -1
  109. package/build/payments/index.d.ts +3 -2
  110. package/build/payments/index.d.ts.map +1 -1
  111. package/build/payments/index.js +2 -1
  112. package/build/payments/index.js.map +1 -1
  113. package/build/payments/p2mr.d.ts +178 -0
  114. package/build/payments/p2mr.d.ts.map +1 -0
  115. package/build/payments/p2mr.js +555 -0
  116. package/build/payments/p2mr.js.map +1 -0
  117. package/build/payments/p2op.d.ts.map +1 -1
  118. package/build/payments/p2op.js +6 -4
  119. package/build/payments/p2op.js.map +1 -1
  120. package/build/payments/p2pkh.d.ts.map +1 -1
  121. package/build/payments/p2pkh.js +3 -4
  122. package/build/payments/p2pkh.js.map +1 -1
  123. package/build/payments/p2sh.d.ts.map +1 -1
  124. package/build/payments/p2sh.js +3 -4
  125. package/build/payments/p2sh.js.map +1 -1
  126. package/build/payments/p2tr.d.ts.map +1 -1
  127. package/build/payments/p2tr.js +13 -6
  128. package/build/payments/p2tr.js.map +1 -1
  129. package/build/payments/p2wpkh.d.ts.map +1 -1
  130. package/build/payments/p2wpkh.js +7 -5
  131. package/build/payments/p2wpkh.js.map +1 -1
  132. package/build/payments/p2wsh.js.map +1 -1
  133. package/build/payments/types.d.ts +11 -1
  134. package/build/payments/types.d.ts.map +1 -1
  135. package/build/payments/types.js +1 -0
  136. package/build/payments/types.js.map +1 -1
  137. package/build/psbt/PsbtCache.d.ts.map +1 -1
  138. package/build/psbt/PsbtCache.js +8 -4
  139. package/build/psbt/PsbtCache.js.map +1 -1
  140. package/build/psbt/PsbtFinalizer.js +14 -8
  141. package/build/psbt/PsbtFinalizer.js.map +1 -1
  142. package/build/psbt/PsbtSigner.d.ts.map +1 -1
  143. package/build/psbt/PsbtSigner.js +3 -2
  144. package/build/psbt/PsbtSigner.js.map +1 -1
  145. package/build/psbt/PsbtTransaction.d.ts +2 -2
  146. package/build/psbt/PsbtTransaction.d.ts.map +1 -1
  147. package/build/psbt/PsbtTransaction.js.map +1 -1
  148. package/build/psbt/bip371.d.ts +30 -0
  149. package/build/psbt/bip371.d.ts.map +1 -1
  150. package/build/psbt/bip371.js +84 -18
  151. package/build/psbt/bip371.js.map +1 -1
  152. package/build/psbt/psbtutils.d.ts +1 -0
  153. package/build/psbt/psbtutils.d.ts.map +1 -1
  154. package/build/psbt/psbtutils.js +2 -0
  155. package/build/psbt/psbtutils.js.map +1 -1
  156. package/build/psbt/utils.js.map +1 -1
  157. package/build/psbt.d.ts +1 -1
  158. package/build/psbt.d.ts.map +1 -1
  159. package/build/psbt.js +3 -2
  160. package/build/psbt.js.map +1 -1
  161. package/build/pubkey.js +1 -1
  162. package/build/pubkey.js.map +1 -1
  163. package/build/push_data.js +1 -1
  164. package/build/push_data.js.map +1 -1
  165. package/build/script.d.ts.map +1 -1
  166. package/build/script.js +4 -3
  167. package/build/script.js.map +1 -1
  168. package/build/script_number.js +1 -1
  169. package/build/script_number.js.map +1 -1
  170. package/build/script_signature.js.map +1 -1
  171. package/build/transaction.d.ts.map +1 -1
  172. package/build/transaction.js +2 -1
  173. package/build/transaction.js.map +1 -1
  174. package/build/tsconfig.build.tsbuildinfo +1 -1
  175. package/build/types.js.map +1 -1
  176. package/build/workers/WorkerSigningPool.js.map +1 -1
  177. package/build/workers/WorkerSigningPool.node.d.ts.map +1 -1
  178. package/build/workers/WorkerSigningPool.node.js +25 -3
  179. package/build/workers/WorkerSigningPool.node.js.map +1 -1
  180. package/build/workers/WorkerSigningPool.sequential.d.ts.map +1 -1
  181. package/build/workers/WorkerSigningPool.sequential.js +2 -0
  182. package/build/workers/WorkerSigningPool.sequential.js.map +1 -1
  183. package/build/workers/psbt-parallel.js.map +1 -1
  184. package/documentation/README.md +122 -0
  185. package/documentation/address.md +820 -0
  186. package/documentation/block.md +679 -0
  187. package/documentation/crypto.md +461 -0
  188. package/documentation/ecc.md +584 -0
  189. package/documentation/errors.md +656 -0
  190. package/documentation/io.md +942 -0
  191. package/documentation/networks.md +625 -0
  192. package/documentation/p2mr.md +380 -0
  193. package/documentation/payments.md +1485 -0
  194. package/documentation/psbt.md +1400 -0
  195. package/documentation/script.md +730 -0
  196. package/documentation/taproot.md +670 -0
  197. package/documentation/transaction.md +943 -0
  198. package/documentation/types.md +587 -0
  199. package/documentation/workers.md +1007 -0
  200. package/eslint.config.js +3 -0
  201. package/package.json +19 -14
  202. package/src/address.ts +40 -23
  203. package/src/bip66.ts +18 -18
  204. package/src/block.ts +7 -2
  205. package/src/crypto-hashes.native.ts +18 -0
  206. package/src/crypto-hashes.ts +3 -0
  207. package/src/crypto.ts +1 -2
  208. package/src/env.ts +6 -6
  209. package/src/index.ts +4 -0
  210. package/src/io/BinaryReader.ts +1 -1
  211. package/src/io/base58check.ts +1 -0
  212. package/src/io/base64.ts +20 -0
  213. package/src/io/hex.ts +1 -1
  214. package/src/io/index.ts +5 -2
  215. package/src/io/utils.ts +6 -7
  216. package/src/merkle.ts +3 -3
  217. package/src/networks.ts +13 -0
  218. package/src/payments/bip341.ts +41 -5
  219. package/src/payments/index.ts +4 -0
  220. package/src/payments/p2mr.ts +660 -0
  221. package/src/payments/p2op.ts +6 -4
  222. package/src/payments/p2pkh.ts +4 -5
  223. package/src/payments/p2sh.ts +4 -5
  224. package/src/payments/p2tr.ts +18 -11
  225. package/src/payments/p2wpkh.ts +7 -5
  226. package/src/payments/p2wsh.ts +1 -1
  227. package/src/payments/types.ts +12 -0
  228. package/src/psbt/PsbtCache.ts +14 -11
  229. package/src/psbt/PsbtFinalizer.ts +14 -8
  230. package/src/psbt/PsbtSigner.ts +4 -3
  231. package/src/psbt/PsbtTransaction.ts +2 -2
  232. package/src/psbt/bip371.ts +88 -16
  233. package/src/psbt/psbtutils.ts +2 -0
  234. package/src/psbt/utils.ts +1 -1
  235. package/src/psbt.ts +14 -10
  236. package/src/pubkey.ts +1 -1
  237. package/src/push_data.ts +5 -5
  238. package/src/react-native-quick-crypto.d.ts +11 -0
  239. package/src/script.ts +5 -4
  240. package/src/script_number.ts +6 -6
  241. package/src/script_signature.ts +2 -2
  242. package/src/transaction.ts +14 -13
  243. package/src/types.ts +1 -1
  244. package/src/workers/WorkerSigningPool.node.ts +28 -4
  245. package/src/workers/WorkerSigningPool.sequential.ts +2 -1
  246. package/src/workers/WorkerSigningPool.ts +3 -3
  247. package/src/workers/psbt-parallel.ts +2 -2
  248. package/test/address.spec.ts +1 -0
  249. package/test/bitcoin.core.spec.ts +10 -3
  250. package/test/browser/psbt.spec.ts +54 -29
  251. package/test/browser/workers-signing.spec.ts +8 -8
  252. package/test/crypto.spec.ts +1 -1
  253. package/test/env.spec.ts +2 -2
  254. package/test/fixtures/p2mr.json +270 -0
  255. package/test/integration/_regtest.ts +2 -2
  256. package/test/integration/blocks.spec.ts +1 -1
  257. package/test/integration/csv.spec.ts +1 -1
  258. package/test/integration/payments.spec.ts +2 -2
  259. package/test/integration/taproot.spec.ts +10 -6
  260. package/test/integration/transactions.spec.ts +6 -5
  261. package/test/opnetTestnet.spec.ts +302 -0
  262. package/test/payments.spec.ts +3 -1
  263. package/test/psbt.spec.ts +346 -27
  264. package/test/transaction.spec.ts +6 -3
  265. package/test/tsconfig.json +2 -2
  266. package/test/workers-pool.spec.ts +5 -5
  267. package/test/workers-signing.spec.ts +8 -8
  268. package/test/workers.spec.ts +3 -3
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @btc-vision/bitcoin
2
2
 
3
+ > **Experimental Feature: P2MR (Pay-to-Merkle-Root, BIP 360)**
4
+ > This library includes experimental support for P2MR, a SegWit version 2 output type that enables script-path-only spending from a bare Merkle root. P2MR addresses start with `bc1z`. See the [P2MR section](#p2mr-pay-to-merkle-root-bip-360) below for usage details. This feature is subject to change as BIP 360 is still a draft proposal.
5
+
3
6
  ![Bitcoin](https://img.shields.io/badge/Bitcoin-000?style=for-the-badge&logo=bitcoin&logoColor=white)
4
7
  ![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)
5
8
  ![NodeJS](https://img.shields.io/badge/Node%20js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)
@@ -38,6 +41,28 @@ npm install tiny-secp256k1
38
41
 
39
42
  Requires Node.js >= 24.0.0.
40
43
 
44
+ ## Performance
45
+
46
+ Benchmarked against `bitcoinjs-lib` v7.0.1 and `@scure/btc-signer` on Node.js v25.6.0 (Linux x64). The fork column uses the fastest backend for each scenario.
47
+
48
+ | Operation | Inputs | @btc-vision/bitcoin | @scure/btc-signer | bitcoinjs-lib | Fork vs Official |
49
+ |-----------|-------:|--------------------:|------------------:|--------------:|:----------------:|
50
+ | PSBT Creation | 100 | 2.08ms | 2.60ms | 303ms | **145x** |
51
+ | PSBT Creation | 500 | 9.73ms | 11.07ms | 6,870ms | **706x** |
52
+ | P2WPKH Sign | 100 | 40ms | 124ms | 348ms | **8.7x** |
53
+ | P2WPKH Sign | 500 | 283ms | 1,200ms | 7,250ms | **25.6x** |
54
+ | P2TR Sign | 100 | 21ms | 420ms | 44ms | **2.1x** |
55
+ | P2TR Sign | 500 | 102ms | 3,290ms | 522ms | **5.1x** |
56
+ | E2E P2WPKH | 100 | 41ms | 125ms | 342ms | **8.3x** |
57
+ | E2E P2TR | 100 | 22ms | 431ms | 55ms | **2.6x** |
58
+ | Parallel Sign (4 workers) | 500 | 102ms | N/A | 6,470ms | **63.3x** |
59
+
60
+ Parallel signing via `worker_threads` / Web Workers is exclusive to this fork. See [benchmark-compare/BENCHMARK.md](benchmark-compare/BENCHMARK.md) for detailed methodology, three-way analysis, and ECC backend comparison.
61
+
62
+ ```bash
63
+ cd benchmark-compare && npm install && npm run bench
64
+ ```
65
+
41
66
  ## Quick Start
42
67
 
43
68
  ### Initialize the ECC Library
@@ -320,6 +345,7 @@ import { toBytes32, toBytes20, toSatoshi } from '@btc-vision/bitcoin';
320
345
  | P2WPKH | `p2wpkh()` | `P2WPKH` | SegWit v0 Public Key Hash |
321
346
  | P2WSH | `p2wsh()` | `P2WSH` | SegWit v0 Script Hash |
322
347
  | P2TR | `p2tr()` | `P2TR` | Taproot (SegWit v1) |
348
+ | P2MR | `p2mr()` | `P2MR` | Pay-to-Merkle-Root (SegWit v2, BIP 360) |
323
349
  | P2OP | `p2op()` | `P2OP` | OPNet (SegWit v16) |
324
350
  | Embed | `p2data()` | `Embed` | OP_RETURN data |
325
351
 
@@ -472,6 +498,101 @@ If `react-native-worklets` is not installed, `createSigningPool()` returns a `Se
472
498
  - `react-native-quick-crypto` is **not** required — the core library uses `@noble/hashes` and `@noble/curves` (pure JS)
473
499
  - A future `@btc-vision/react-native-secp256k1` Nitro Module would provide native C++ performance via `initEccLib(createNativeBackend())`
474
500
 
501
+ ## P2MR (Pay-to-Merkle-Root, BIP 360)
502
+
503
+ P2MR is a SegWit version 2 output type defined in [BIP 360](https://github.com/nicbn/bips/blob/bip-p2mr/bip-0360.mediawiki). It commits to a bare Merkle root with no internal public key, providing script-path-only spending. This makes P2MR outputs resistant to quantum long-exposure attacks since no public key is revealed until spend time.
504
+
505
+ **Key differences from P2TR (Taproot):**
506
+
507
+ | | P2TR (v1) | P2MR (v2) |
508
+ |---|---|---|
509
+ | Address prefix | `bc1p` | `bc1z` |
510
+ | ScriptPubKey | `OP_1 <32-byte tweaked_pubkey>` | `OP_2 <32-byte merkle_root>` |
511
+ | Key-path spend | Yes | No |
512
+ | Script-path spend | Yes | Yes |
513
+ | Control block | `[1 + 32 + 32*m]` bytes (includes internal pubkey) | `[1 + 32*m]` bytes (no pubkey) |
514
+ | Parity bit | 0 or 1 | Always 1 |
515
+ | Sighash | BIP 342 | BIP 342 (same) |
516
+
517
+ ### Create a P2MR Output
518
+
519
+ ```typescript
520
+ import { payments } from '@btc-vision/bitcoin';
521
+
522
+ // From a script tree (single leaf)
523
+ const p2mr = payments.p2mr({
524
+ scriptTree: { output: leafScript },
525
+ });
526
+ console.log(p2mr.address); // bc1z...
527
+ console.log(p2mr.output); // OP_2 <merkle_root>
528
+ console.log(p2mr.hash); // raw merkle root bytes
529
+
530
+ // From a multi-leaf script tree
531
+ const p2mrMulti = payments.p2mr({
532
+ scriptTree: [
533
+ { output: leafA },
534
+ { output: leafB },
535
+ ],
536
+ });
537
+
538
+ // From a known address or hash
539
+ const fromAddr = payments.p2mr({
540
+ address: 'bc1z4rf73uru6qdyrv9w3nq9f3dwqlqmec4sdwj03hexu7n7r7dkehjs592djq',
541
+ });
542
+ ```
543
+
544
+ ### Spend a P2MR Output (PSBT)
545
+
546
+ P2MR spending uses the same PSBT taproot signing flow. The library automatically detects P2MR inputs and routes them to script-path finalization.
547
+
548
+ ```typescript
549
+ import {
550
+ Psbt, payments, initEccLib,
551
+ LEAF_VERSION_TAPSCRIPT, tapleafHash,
552
+ } from '@btc-vision/bitcoin';
553
+ import type { EccLib, Satoshi, Script } from '@btc-vision/bitcoin';
554
+
555
+ // ECC must be initialized before signing (see Quick Start above)
556
+ initEccLib(ecc as EccLib);
557
+
558
+ // Build the P2MR payment with a redeem script to get the witness/control block
559
+ const scriptTree = { output: leafScript, version: LEAF_VERSION_TAPSCRIPT };
560
+ const p2mr = payments.p2mr({
561
+ scriptTree,
562
+ redeem: { output: leafScript, redeemVersion: LEAF_VERSION_TAPSCRIPT },
563
+ });
564
+
565
+ const psbt = new Psbt();
566
+
567
+ // Add the P2MR input with taproot-style fields
568
+ psbt.addInput({
569
+ hash: txid,
570
+ index: vout,
571
+ witnessUtxo: {
572
+ value: amount as Satoshi,
573
+ script: p2mr.output as Script,
574
+ },
575
+ tapLeafScript: [{
576
+ leafVersion: LEAF_VERSION_TAPSCRIPT,
577
+ script: leafScript,
578
+ controlBlock: p2mr.witness![1]!, // control block (second witness element)
579
+ }],
580
+ tapMerkleRoot: p2mr.hash as Uint8Array,
581
+ });
582
+
583
+ psbt.addOutput({ address: destinationAddress, value: outputAmount as Satoshi });
584
+
585
+ // Sign using the leaf hash
586
+ const leafHash = tapleafHash({ output: leafScript, version: LEAF_VERSION_TAPSCRIPT });
587
+ psbt.signTaprootInput(0, keyPair, leafHash);
588
+
589
+ // Finalize and extract — P2MR always uses script-path finalization
590
+ psbt.finalizeInput(0);
591
+ const tx = psbt.extractTransaction();
592
+ ```
593
+
594
+ The final witness stack for a P2MR script-path spend is: `[signatures...] [leaf_script] [control_block]`, where the control block is `[leafVersion | 0x01, merkle_path...]` (no internal pubkey).
595
+
475
596
  ## Running Tests
476
597
 
477
598
  ```bash
@@ -0,0 +1,159 @@
1
+ # Benchmark: @btc-vision/bitcoin vs bitcoinjs-lib vs @scure/btc-signer
2
+
3
+ Comprehensive performance comparison between `@btc-vision/bitcoin` (this fork), the official `bitcoinjs-lib` v7.0.1, and `@scure/btc-signer`.
4
+
5
+ ## Environment
6
+
7
+ | Property | Value |
8
+ |----------|-------|
9
+ | Node.js | v25.6.0 |
10
+ | OS | Linux 6.8.0-100-generic x64 |
11
+ | Libraries | `@btc-vision/bitcoin` 7.0.0-rc.1, `bitcoinjs-lib` 7.0.1, `@scure/btc-signer` latest |
12
+ | ECC backends | `@noble/secp256k1` 3.x (pure JS), `tiny-secp256k1` 2.2.4 (WASM), `@noble/curves` (scure built-in) |
13
+
14
+ ## Methodology
15
+
16
+ - **Warmup**: 5 iterations (discarded) before each measurement
17
+ - **Iterations**: 30 for small inputs, 10 for 250 inputs, 5 for 500 inputs
18
+ - **Metric**: Median of all iterations (resistant to outlier spikes)
19
+ - **Fairness**: All libraries use identical key material derived from the same seed. Each scenario builds its own PSBTs/transactions with the correct types for each library.
20
+ - **Cold-start**: Library initialization measured via separate Node.js subprocess per iteration for true cold-start timing.
21
+ - **ECC backends**: The fork is tested with both Noble (pure JS) and tiny-secp256k1 (WASM). The official library only supports tiny-secp256k1. Scure uses its built-in `@noble/curves` backend.
22
+
23
+ ## Results
24
+
25
+ ### 1. Library Initialization (cold-start)
26
+
27
+ | Configuration | Median |
28
+ |--------------|--------|
29
+ | Fork + Noble (pure JS) | 53.44ms |
30
+ | Fork + tiny-secp256k1 (WASM) | 7.16ms |
31
+ | Scure (@noble/curves) | 26.27ms |
32
+ | Official + tiny-secp256k1 (WASM) | 7.16ms |
33
+
34
+ Both the fork and official library have identical initialization time when using the same WASM backend. Scure sits in the middle at ~26ms due to its pure-JS `@noble/curves` dependency. The Noble backend is ~53ms due to pure-JS module loading, but eliminates the WASM dependency entirely -- critical for React Native and edge runtimes without WASM support.
35
+
36
+ ### 2. PSBT / Transaction Creation (addInput + addOutput)
37
+
38
+ | Inputs | Fork | Scure | Official | Fork Speedup |
39
+ |--------|------|-------|----------|:------------:|
40
+ | 10 | 0.26ms | 0.58ms | 6.40ms | **24x** |
41
+ | 50 | 1.09ms | 1.56ms | 83.80ms | **77x** |
42
+ | 100 | 2.08ms | 2.60ms | 303.32ms | **145x** |
43
+ | 250 | 4.96ms | 5.86ms | 1.73s | **349x** |
44
+ | 500 | 9.73ms | 11.07ms | 6.87s | **706x** |
45
+
46
+ The fork's PSBT creation scales linearly (O(n)). Scure's `Transaction` also scales linearly but is ~1.1-1.2x slower than the fork. The official library exhibits O(n^2) behavior -- each `addInput()` call triggers increasingly expensive internal validation passes over all existing inputs. At 500 inputs, the official library takes over **6.87 seconds** just to build the PSBT, while the fork completes in under **10 milliseconds** and scure in **11ms**.
47
+
48
+ ### 3. P2WPKH Signing (create + sign, SegWit v0)
49
+
50
+ | Inputs | Fork (Noble) | Fork (tiny) | Scure | Official | Best Fork Speedup |
51
+ |--------|-------------|-------------|-------|----------|:-----------------:|
52
+ | 10 | 6.89ms | 4.04ms | 11.05ms | 9.36ms | **2.3x** |
53
+ | 50 | 31.33ms | 19.52ms | 54.85ms | 101.59ms | **5.2x** |
54
+ | 100 | 65.27ms | 39.96ms | 123.83ms | 348.15ms | **8.7x** |
55
+ | 250 | 171.40ms | 110.99ms | 413.48ms | 1.89s | **17x** |
56
+ | 500 | 378.57ms | 282.93ms | 1.20s | 7.25s | **25.6x** |
57
+
58
+ This benchmark includes PSBT/transaction creation + signing. The fork's advantage compounds: faster PSBT construction plus efficient sighash caching. Scure is ~3x slower than the fork (tiny) but still ~6x faster than official at 500 inputs. With tiny-secp256k1, the fork's raw signing speed matches the official library, but the PSBT overhead dominates at scale.
59
+
60
+ ### 4. P2TR Taproot Signing (Schnorr, SegWit v1)
61
+
62
+ | Inputs | Fork (Noble) | Fork (tiny) | Scure | Official | Best Fork Speedup |
63
+ |--------|-------------|-------------|-------|----------|:-----------------:|
64
+ | 10 | 22.55ms | 2.44ms | 39.17ms | 3.62ms | **1.5x** |
65
+ | 50 | 105.59ms | 10.72ms | 197.94ms | 18.72ms | **1.7x** |
66
+ | 100 | 211.00ms | 21.33ms | 420.13ms | 44.27ms | **2.1x** |
67
+ | 250 | 520.22ms | 51.69ms | 1.24s | 171.77ms | **3.3x** |
68
+ | 500 | 1.04s | 102.31ms | 3.29s | 521.88ms | **5.1x** |
69
+
70
+ With tiny-secp256k1, the fork is consistently faster due to its O(n) Taproot sighash caching (the official library recomputes more per input). Scure is the slowest here at ~3.29s for 500 inputs, as its pure-JS Schnorr implementation carries significant per-signature overhead. Noble's pure-JS Schnorr is also slower than WASM for raw signing, which shows at small input counts, but the fork's architectural advantages compound at scale.
71
+
72
+ ### 5. End-to-End Lifecycle (100 inputs)
73
+
74
+ Full lifecycle: create PSBT/tx + add inputs/outputs + sign + finalize + extract + serialize.
75
+
76
+ | Type | Fork (Noble) | Fork (tiny) | Scure | Official | Best Fork Speedup |
77
+ |------|-------------|-------------|-------|----------|:-----------------:|
78
+ | P2WPKH | 66.62ms | 41.13ms | 125.32ms | 341.76ms | **8.3x** |
79
+ | P2TR | 210.98ms | 21.60ms | 431.07ms | 55.12ms | **2.6x** |
80
+
81
+ P2WPKH end-to-end is **8.3x faster** with the fork (tiny-secp256k1 backend). Scure is ~3x slower than the fork but still ~2.7x faster than official for P2WPKH. For P2TR, the fork with tiny-secp256k1 is **2.6x faster** than official. Scure's pure-JS Schnorr signing makes it the slowest for Taproot at 431ms vs 22ms (fork tiny) and 55ms (official).
82
+
83
+ ### 6. Parallel Signing (fork-exclusive)
84
+
85
+ Worker-based parallel signing using `NodeWorkerSigningPool` with 4 `worker_threads`.
86
+
87
+ | Inputs | Fork Sequential | Fork Parallel (4w) | Official Sequential | Speedup vs Seq | Speedup vs Official |
88
+ |--------|----------------|-------------------|--------------------|----|------|
89
+ | 100 | 64.27ms | 21.73ms | 310.42ms | 3.0x | **14.3x** |
90
+ | 500 | 380.27ms | 102.25ms | 6.47s | 3.7x | **63.3x** |
91
+
92
+ Parallel signing is **exclusive to the fork**. At 500 inputs, parallel signing completes in 102ms vs the official library's 6.47s sequential signing -- a **63.3x improvement**.
93
+
94
+ ## Scure vs Official vs Fork
95
+
96
+ `@scure/btc-signer` sits between the fork and official library in most scenarios:
97
+
98
+ **Where Scure beats Official:**
99
+ - Transaction creation is O(n) like the fork, making it dramatically faster than official's O(n^2) PSBT creation at scale (11ms vs 6.87s at 500 inputs)
100
+ - P2WPKH signing is ~6x faster than official at 500 inputs, though ~4x slower than the fork
101
+
102
+ **Where Scure is slowest:**
103
+ - P2TR (Taproot) signing is the weakest spot -- 3.29s for 500 inputs vs 102ms (fork tiny) and 522ms (official). Its pure-JS Schnorr implementation has significant per-signature overhead
104
+ - No parallel signing support
105
+
106
+ **Bottom line:** Scure is a solid zero-dependency pure-JS option that avoids official's O(n^2) PSBT creation bottleneck, but its signing performance (especially Taproot) falls behind both the fork and official library.
107
+
108
+ ## The tiny-secp256k1 Reality
109
+
110
+ A common assumption is that `tiny-secp256k1` (WASM) is always faster. The raw signing numbers tell a nuanced story:
111
+
112
+ **Where WASM wins:**
113
+ - Raw Schnorr signing throughput is ~2x faster per operation than Noble's pure JS implementation
114
+ - For P2TR-heavy workloads with few inputs, the per-signature difference matters
115
+
116
+ **Where it doesn't matter:**
117
+ - The PSBT construction overhead completely dominates at scale. The official library's O(n^2) behavior means PSBT creation alone takes 6.87s at 500 inputs vs 10ms in the fork -- a difference so large that the choice of signing backend is irrelevant
118
+ - P2WPKH signing with Noble is only ~1.5x slower per operation than WASM, and the fork's PSBT improvements more than compensate
119
+
120
+ **Where WASM is a liability:**
121
+ - ~1.2MB WASM binary added to bundle size vs ~12KB for Noble
122
+ - WASM initialization requires WebAssembly support -- unavailable in some React Native runtimes and edge environments
123
+ - Cold-start adds measurable overhead in serverless / Lambda contexts
124
+
125
+ **Bottom line:** The fork with Noble (pure JS) outperforms the official library with WASM for all real-world P2WPKH workloads. For P2TR-heavy workloads, the fork with tiny-secp256k1 is the fastest option and still outperforms both the official library and scure. The Noble backend provides portability across Node.js, browsers, and React Native with no WASM dependency.
126
+
127
+ ## Summary
128
+
129
+ | Scenario | Inputs | @btc-vision/bitcoin | @scure/btc-signer | bitcoinjs-lib | Fork vs Official |
130
+ |----------|--------|--------------------:|------------------:|--------------:|:----------------:|
131
+ | PSBT Creation | 100 | 2.08ms | 2.60ms | 303ms | **145x** |
132
+ | PSBT Creation | 500 | 9.73ms | 11.07ms | 6,870ms | **706x** |
133
+ | P2WPKH Sign | 100 | 40ms | 124ms | 348ms | **8.7x** |
134
+ | P2WPKH Sign | 500 | 283ms | 1,200ms | 7,250ms | **25.6x** |
135
+ | P2TR Sign | 100 | 21ms | 420ms | 44ms | **2.1x** |
136
+ | P2TR Sign | 500 | 102ms | 3,290ms | 522ms | **5.1x** |
137
+ | E2E P2WPKH | 100 | 41ms | 125ms | 342ms | **8.3x** |
138
+ | E2E P2TR | 100 | 22ms | 431ms | 55ms | **2.6x** |
139
+ | Parallel (4w) | 500 | 102ms | N/A | 6,470ms | **63.3x** |
140
+
141
+ ## How to Reproduce
142
+
143
+ ```bash
144
+ cd benchmark-compare
145
+ npm install
146
+ npm run bench
147
+
148
+ # With GC control (recommended for stable results):
149
+ npm run bench:gc
150
+ ```
151
+
152
+ The benchmark requires the parent project to be built first:
153
+
154
+ ```bash
155
+ cd ..
156
+ npm run build
157
+ cd benchmark-compare
158
+ npm run bench
159
+ ```