@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
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
|

|
|
4
7
|

|
|
5
8
|

|
|
@@ -40,21 +43,21 @@ Requires Node.js >= 24.0.0.
|
|
|
40
43
|
|
|
41
44
|
## Performance
|
|
42
45
|
|
|
43
|
-
Benchmarked against `bitcoinjs-lib` v7.0.1 on Node.js v25.
|
|
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.
|
|
44
47
|
|
|
45
|
-
| Operation | Inputs | @btc-vision/bitcoin | bitcoinjs-lib |
|
|
46
|
-
|
|
47
|
-
| PSBT Creation | 100 | 2.
|
|
48
|
-
| PSBT Creation | 500 | 9.
|
|
49
|
-
| P2WPKH Sign | 100 | 40ms |
|
|
50
|
-
| P2WPKH Sign | 500 |
|
|
51
|
-
| P2TR Sign | 100 |
|
|
52
|
-
| P2TR Sign | 500 |
|
|
53
|
-
| E2E P2WPKH | 100 |
|
|
54
|
-
| E2E P2TR | 100 | 22ms |
|
|
55
|
-
| Parallel Sign (4 workers) | 500 |
|
|
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** |
|
|
56
59
|
|
|
57
|
-
Parallel signing via `worker_threads` / Web Workers is exclusive to this fork. See [benchmark-compare/BENCHMARK.md](benchmark-compare/BENCHMARK.md) for detailed methodology and
|
|
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.
|
|
58
61
|
|
|
59
62
|
```bash
|
|
60
63
|
cd benchmark-compare && npm install && npm run bench
|
|
@@ -342,6 +345,7 @@ import { toBytes32, toBytes20, toSatoshi } from '@btc-vision/bitcoin';
|
|
|
342
345
|
| P2WPKH | `p2wpkh()` | `P2WPKH` | SegWit v0 Public Key Hash |
|
|
343
346
|
| P2WSH | `p2wsh()` | `P2WSH` | SegWit v0 Script Hash |
|
|
344
347
|
| P2TR | `p2tr()` | `P2TR` | Taproot (SegWit v1) |
|
|
348
|
+
| P2MR | `p2mr()` | `P2MR` | Pay-to-Merkle-Root (SegWit v2, BIP 360) |
|
|
345
349
|
| P2OP | `p2op()` | `P2OP` | OPNet (SegWit v16) |
|
|
346
350
|
| Embed | `p2data()` | `Embed` | OP_RETURN data |
|
|
347
351
|
|
|
@@ -494,6 +498,101 @@ If `react-native-worklets` is not installed, `createSigningPool()` returns a `Se
|
|
|
494
498
|
- `react-native-quick-crypto` is **not** required — the core library uses `@noble/hashes` and `@noble/curves` (pure JS)
|
|
495
499
|
- A future `@btc-vision/react-native-secp256k1` Nitro Module would provide native C++ performance via `initEccLib(createNativeBackend())`
|
|
496
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
|
+
|
|
497
596
|
## Running Tests
|
|
498
597
|
|
|
499
598
|
```bash
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# Benchmark: @btc-vision/bitcoin vs bitcoinjs-lib
|
|
1
|
+
# Benchmark: @btc-vision/bitcoin vs bitcoinjs-lib vs @scure/btc-signer
|
|
2
2
|
|
|
3
|
-
Comprehensive performance comparison between `@btc-vision/bitcoin` (this fork)
|
|
3
|
+
Comprehensive performance comparison between `@btc-vision/bitcoin` (this fork), the official `bitcoinjs-lib` v7.0.1, and `@scure/btc-signer`.
|
|
4
4
|
|
|
5
5
|
## Environment
|
|
6
6
|
|
|
7
7
|
| Property | Value |
|
|
8
8
|
|----------|-------|
|
|
9
|
-
| Node.js | v25.
|
|
10
|
-
| OS | Linux 6.8.0-
|
|
11
|
-
| Libraries | `@btc-vision/bitcoin` 7.0.0-
|
|
12
|
-
| ECC backends | `@noble/secp256k1` 3.x (pure JS), `tiny-secp256k1` 2.2.4 (WASM) |
|
|
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
13
|
|
|
14
14
|
## Methodology
|
|
15
15
|
|
|
16
16
|
- **Warmup**: 5 iterations (discarded) before each measurement
|
|
17
17
|
- **Iterations**: 30 for small inputs, 10 for 250 inputs, 5 for 500 inputs
|
|
18
18
|
- **Metric**: Median of all iterations (resistant to outlier spikes)
|
|
19
|
-
- **Fairness**:
|
|
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
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.
|
|
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
22
|
|
|
23
23
|
## Results
|
|
24
24
|
|
|
@@ -26,58 +26,59 @@ Comprehensive performance comparison between `@btc-vision/bitcoin` (this fork) a
|
|
|
26
26
|
|
|
27
27
|
| Configuration | Median |
|
|
28
28
|
|--------------|--------|
|
|
29
|
-
| Fork + Noble (pure JS) |
|
|
30
|
-
| Fork + tiny-secp256k1 (WASM) |
|
|
31
|
-
|
|
|
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 |
|
|
32
33
|
|
|
33
|
-
Both
|
|
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.
|
|
34
35
|
|
|
35
|
-
### 2. PSBT Creation (addInput + addOutput)
|
|
36
|
+
### 2. PSBT / Transaction Creation (addInput + addOutput)
|
|
36
37
|
|
|
37
|
-
| Inputs | Fork | Official | Speedup |
|
|
38
|
-
|
|
39
|
-
| 10 | 0.
|
|
40
|
-
| 50 | 1.
|
|
41
|
-
| 100 | 2.
|
|
42
|
-
| 250 | 5.
|
|
43
|
-
| 500 | 9.
|
|
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** |
|
|
44
45
|
|
|
45
|
-
The fork's PSBT creation scales linearly (O(n)). 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 **
|
|
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**.
|
|
46
47
|
|
|
47
48
|
### 3. P2WPKH Signing (create + sign, SegWit v0)
|
|
48
49
|
|
|
49
|
-
| Inputs | Fork (Noble) | Fork (tiny) | Official | Best Fork Speedup |
|
|
50
|
-
|
|
51
|
-
| 10 |
|
|
52
|
-
| 50 | 31.
|
|
53
|
-
| 100 |
|
|
54
|
-
| 250 |
|
|
55
|
-
| 500 | 378.
|
|
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** |
|
|
56
57
|
|
|
57
|
-
This benchmark includes PSBT creation + signing. The fork's advantage compounds: faster PSBT construction plus efficient sighash caching. With tiny-secp256k1, the raw signing speed matches the official library, but the PSBT overhead dominates at scale.
|
|
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.
|
|
58
59
|
|
|
59
60
|
### 4. P2TR Taproot Signing (Schnorr, SegWit v1)
|
|
60
61
|
|
|
61
|
-
| Inputs | Fork (Noble) | Fork (tiny) | Official | Best Fork Speedup |
|
|
62
|
-
|
|
63
|
-
| 10 | 22.
|
|
64
|
-
| 50 |
|
|
65
|
-
| 100 |
|
|
66
|
-
| 250 |
|
|
67
|
-
| 500 | 1.
|
|
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** |
|
|
68
69
|
|
|
69
|
-
With tiny-secp256k1, the fork is consistently faster due to its O(n) Taproot sighash caching (the official library recomputes more per input). Noble's pure-JS Schnorr
|
|
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.
|
|
70
71
|
|
|
71
72
|
### 5. End-to-End Lifecycle (100 inputs)
|
|
72
73
|
|
|
73
|
-
Full lifecycle: create PSBT + add inputs/outputs + sign + finalize + extract + serialize.
|
|
74
|
+
Full lifecycle: create PSBT/tx + add inputs/outputs + sign + finalize + extract + serialize.
|
|
74
75
|
|
|
75
|
-
| Type | Fork (Noble) | Fork (tiny) | Official | Best Fork Speedup |
|
|
76
|
-
|
|
77
|
-
| P2WPKH | 66.
|
|
78
|
-
| P2TR |
|
|
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** |
|
|
79
80
|
|
|
80
|
-
P2WPKH end-to-end is **
|
|
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).
|
|
81
82
|
|
|
82
83
|
### 6. Parallel Signing (fork-exclusive)
|
|
83
84
|
|
|
@@ -85,10 +86,24 @@ Worker-based parallel signing using `NodeWorkerSigningPool` with 4 `worker_threa
|
|
|
85
86
|
|
|
86
87
|
| Inputs | Fork Sequential | Fork Parallel (4w) | Official Sequential | Speedup vs Seq | Speedup vs Official |
|
|
87
88
|
|--------|----------------|-------------------|--------------------|----|------|
|
|
88
|
-
| 100 | 64.
|
|
89
|
-
| 500 |
|
|
89
|
+
| 100 | 64.27ms | 21.73ms | 310.42ms | 3.0x | **14.3x** |
|
|
90
|
+
| 500 | 380.27ms | 102.25ms | 6.47s | 3.7x | **63.3x** |
|
|
90
91
|
|
|
91
|
-
Parallel signing is **exclusive to the fork**. At 500 inputs, parallel signing completes in
|
|
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.
|
|
92
107
|
|
|
93
108
|
## The tiny-secp256k1 Reality
|
|
94
109
|
|
|
@@ -99,7 +114,7 @@ A common assumption is that `tiny-secp256k1` (WASM) is always faster. The raw si
|
|
|
99
114
|
- For P2TR-heavy workloads with few inputs, the per-signature difference matters
|
|
100
115
|
|
|
101
116
|
**Where it doesn't matter:**
|
|
102
|
-
- The PSBT construction overhead completely dominates at scale. The official library's O(n^2) behavior means PSBT creation alone takes
|
|
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
|
|
103
118
|
- P2WPKH signing with Noble is only ~1.5x slower per operation than WASM, and the fork's PSBT improvements more than compensate
|
|
104
119
|
|
|
105
120
|
**Where WASM is a liability:**
|
|
@@ -107,21 +122,21 @@ A common assumption is that `tiny-secp256k1` (WASM) is always faster. The raw si
|
|
|
107
122
|
- WASM initialization requires WebAssembly support -- unavailable in some React Native runtimes and edge environments
|
|
108
123
|
- Cold-start adds measurable overhead in serverless / Lambda contexts
|
|
109
124
|
|
|
110
|
-
**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 the official library. The Noble backend provides portability across Node.js, browsers, and React Native with no WASM dependency.
|
|
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.
|
|
111
126
|
|
|
112
127
|
## Summary
|
|
113
128
|
|
|
114
|
-
| Scenario | Inputs | @btc-vision/bitcoin | bitcoinjs-lib |
|
|
115
|
-
|
|
116
|
-
| PSBT Creation | 100 | 2.
|
|
117
|
-
| PSBT Creation | 500 | 9.
|
|
118
|
-
| P2WPKH Sign | 100 | 40ms |
|
|
119
|
-
| P2WPKH Sign | 500 |
|
|
120
|
-
| P2TR Sign | 100 |
|
|
121
|
-
| P2TR Sign | 500 |
|
|
122
|
-
| E2E P2WPKH | 100 |
|
|
123
|
-
| E2E P2TR | 100 | 22ms |
|
|
124
|
-
| Parallel (4w) | 500 |
|
|
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** |
|
|
125
140
|
|
|
126
141
|
## How to Reproduce
|
|
127
142
|
|