@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.
- package/README.md +22 -0
- package/benchmark-compare/BENCHMARK.md +144 -0
- package/benchmark-compare/compare.bench.ts +1024 -0
- package/benchmark-compare/harness.ts +220 -0
- package/benchmark-compare/package.json +18 -0
- package/browser/address.d.ts.map +1 -1
- package/browser/block.d.ts.map +1 -1
- package/browser/chunks/{psbt-parallel-BBFlkmiv.js → psbt-parallel-B-dfm5GZ.js} +2430 -2523
- package/browser/crypto-hashes.d.ts +4 -0
- package/browser/crypto-hashes.d.ts.map +1 -0
- package/browser/crypto-hashes.native.d.ts +4 -0
- package/browser/crypto-hashes.native.d.ts.map +1 -0
- package/browser/crypto.d.ts.map +1 -1
- package/browser/index.d.ts +2 -2
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +571 -547
- package/browser/io/base58check.d.ts +26 -0
- package/browser/io/base58check.d.ts.map +1 -0
- package/browser/io/base64.d.ts +8 -0
- package/browser/io/base64.d.ts.map +1 -1
- package/browser/io/index.d.ts +2 -1
- package/browser/io/index.d.ts.map +1 -1
- package/browser/io/utils.d.ts.map +1 -1
- package/browser/payments/bip341.d.ts.map +1 -1
- package/browser/payments/p2op.d.ts.map +1 -1
- package/browser/payments/p2pkh.d.ts.map +1 -1
- package/browser/payments/p2sh.d.ts.map +1 -1
- package/browser/payments/p2tr.d.ts.map +1 -1
- package/browser/payments/p2wpkh.d.ts.map +1 -1
- package/browser/psbt/PsbtCache.d.ts.map +1 -1
- package/browser/psbt/PsbtSigner.d.ts.map +1 -1
- package/browser/psbt/PsbtTransaction.d.ts +2 -2
- package/browser/psbt/PsbtTransaction.d.ts.map +1 -1
- package/browser/psbt/bip371.d.ts.map +1 -1
- package/browser/psbt.d.ts +1 -1
- package/browser/psbt.d.ts.map +1 -1
- package/browser/react-native-quick-crypto.d.ts +11 -0
- package/browser/script.d.ts.map +1 -1
- package/browser/transaction.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.sequential.d.ts.map +1 -1
- package/browser/workers/index.js +5 -5
- package/build/address.d.ts.map +1 -1
- package/build/address.js +19 -12
- package/build/address.js.map +1 -1
- package/build/bip66.js +4 -4
- package/build/bip66.js.map +1 -1
- package/build/block.d.ts.map +1 -1
- package/build/block.js +9 -2
- package/build/block.js.map +1 -1
- package/build/crypto-hashes.d.ts +4 -0
- package/build/crypto-hashes.d.ts.map +1 -0
- package/build/crypto-hashes.js +4 -0
- package/build/crypto-hashes.js.map +1 -0
- package/build/crypto-hashes.native.d.ts +4 -0
- package/build/crypto-hashes.native.d.ts.map +1 -0
- package/build/crypto-hashes.native.js +15 -0
- package/build/crypto-hashes.native.js.map +1 -0
- package/build/crypto.d.ts.map +1 -1
- package/build/crypto.js +1 -2
- package/build/crypto.js.map +1 -1
- package/build/env.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/io/BinaryReader.js +1 -1
- package/build/io/BinaryReader.js.map +1 -1
- package/build/io/base58check.d.ts +26 -0
- package/build/io/base58check.d.ts.map +1 -0
- package/build/io/base58check.js +32 -0
- package/build/io/base58check.js.map +1 -0
- package/build/io/base64.d.ts +8 -0
- package/build/io/base64.d.ts.map +1 -1
- package/build/io/base64.js +14 -0
- package/build/io/base64.js.map +1 -1
- package/build/io/hex.js +1 -1
- package/build/io/hex.js.map +1 -1
- package/build/io/index.d.ts +2 -1
- package/build/io/index.d.ts.map +1 -1
- package/build/io/index.js +4 -2
- package/build/io/index.js.map +1 -1
- package/build/io/utils.d.ts.map +1 -1
- package/build/io/utils.js +3 -4
- package/build/io/utils.js.map +1 -1
- package/build/merkle.js.map +1 -1
- package/build/payments/bip341.d.ts.map +1 -1
- package/build/payments/bip341.js +4 -3
- package/build/payments/bip341.js.map +1 -1
- package/build/payments/p2op.d.ts.map +1 -1
- package/build/payments/p2op.js +6 -4
- package/build/payments/p2op.js.map +1 -1
- package/build/payments/p2pkh.d.ts.map +1 -1
- package/build/payments/p2pkh.js +3 -4
- package/build/payments/p2pkh.js.map +1 -1
- package/build/payments/p2sh.d.ts.map +1 -1
- package/build/payments/p2sh.js +3 -4
- package/build/payments/p2sh.js.map +1 -1
- package/build/payments/p2tr.d.ts.map +1 -1
- package/build/payments/p2tr.js +13 -6
- package/build/payments/p2tr.js.map +1 -1
- package/build/payments/p2wpkh.d.ts.map +1 -1
- package/build/payments/p2wpkh.js +7 -5
- package/build/payments/p2wpkh.js.map +1 -1
- package/build/payments/p2wsh.js.map +1 -1
- package/build/psbt/PsbtCache.d.ts.map +1 -1
- package/build/psbt/PsbtCache.js +8 -4
- package/build/psbt/PsbtCache.js.map +1 -1
- package/build/psbt/PsbtFinalizer.js +14 -8
- package/build/psbt/PsbtFinalizer.js.map +1 -1
- package/build/psbt/PsbtSigner.d.ts.map +1 -1
- package/build/psbt/PsbtSigner.js +3 -2
- package/build/psbt/PsbtSigner.js.map +1 -1
- package/build/psbt/PsbtTransaction.d.ts +2 -2
- package/build/psbt/PsbtTransaction.d.ts.map +1 -1
- package/build/psbt/PsbtTransaction.js.map +1 -1
- package/build/psbt/bip371.d.ts.map +1 -1
- package/build/psbt/bip371.js +4 -3
- package/build/psbt/bip371.js.map +1 -1
- package/build/psbt/utils.js.map +1 -1
- package/build/psbt.d.ts +1 -1
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js.map +1 -1
- package/build/push_data.js +1 -1
- package/build/push_data.js.map +1 -1
- package/build/script.d.ts.map +1 -1
- package/build/script.js +4 -3
- package/build/script.js.map +1 -1
- package/build/script_number.js +1 -1
- package/build/script_number.js.map +1 -1
- package/build/script_signature.js.map +1 -1
- package/build/transaction.d.ts.map +1 -1
- package/build/transaction.js +2 -1
- package/build/transaction.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/types.js.map +1 -1
- package/build/workers/WorkerSigningPool.js.map +1 -1
- package/build/workers/WorkerSigningPool.node.d.ts.map +1 -1
- package/build/workers/WorkerSigningPool.node.js +25 -3
- package/build/workers/WorkerSigningPool.node.js.map +1 -1
- package/build/workers/WorkerSigningPool.sequential.d.ts.map +1 -1
- package/build/workers/WorkerSigningPool.sequential.js +2 -0
- package/build/workers/WorkerSigningPool.sequential.js.map +1 -1
- package/build/workers/psbt-parallel.js.map +1 -1
- package/package.json +7 -5
- package/src/address.ts +18 -13
- package/src/bip66.ts +18 -18
- package/src/block.ts +7 -2
- package/src/crypto-hashes.native.ts +18 -0
- package/src/crypto-hashes.ts +3 -0
- package/src/crypto.ts +1 -2
- package/src/env.ts +6 -6
- package/src/index.ts +3 -0
- package/src/io/BinaryReader.ts +1 -1
- package/src/io/base58check.ts +35 -0
- package/src/io/base64.ts +15 -0
- package/src/io/hex.ts +1 -1
- package/src/io/index.ts +5 -2
- package/src/io/utils.ts +6 -7
- package/src/merkle.ts +3 -3
- package/src/payments/bip341.ts +5 -4
- package/src/payments/p2op.ts +6 -4
- package/src/payments/p2pkh.ts +4 -5
- package/src/payments/p2sh.ts +4 -5
- package/src/payments/p2tr.ts +18 -11
- package/src/payments/p2wpkh.ts +7 -5
- package/src/payments/p2wsh.ts +1 -1
- package/src/psbt/PsbtCache.ts +14 -11
- package/src/psbt/PsbtFinalizer.ts +14 -8
- package/src/psbt/PsbtSigner.ts +4 -3
- package/src/psbt/PsbtTransaction.ts +2 -2
- package/src/psbt/bip371.ts +4 -3
- package/src/psbt/utils.ts +1 -1
- package/src/psbt.ts +10 -8
- package/src/push_data.ts +5 -5
- package/src/react-native-quick-crypto.d.ts +11 -0
- package/src/script.ts +5 -4
- package/src/script_number.ts +6 -6
- package/src/script_signature.ts +2 -2
- package/src/transaction.ts +14 -13
- package/src/types.ts +1 -1
- package/src/workers/WorkerSigningPool.node.ts +28 -4
- package/src/workers/WorkerSigningPool.sequential.ts +2 -1
- package/src/workers/WorkerSigningPool.ts +3 -3
- package/src/workers/psbt-parallel.ts +2 -2
- package/test/address.spec.ts +1 -0
- package/test/bitcoin.core.spec.ts +9 -2
- package/test/browser/psbt.spec.ts +54 -29
- package/test/browser/workers-signing.spec.ts +8 -8
- package/test/crypto.spec.ts +1 -1
- package/test/env.spec.ts +2 -2
- package/test/integration/_regtest.ts +2 -2
- package/test/integration/blocks.spec.ts +1 -1
- package/test/integration/csv.spec.ts +1 -1
- package/test/integration/payments.spec.ts +2 -2
- package/test/integration/taproot.spec.ts +3 -3
- package/test/integration/transactions.spec.ts +6 -5
- package/test/psbt.spec.ts +49 -25
- package/test/transaction.spec.ts +6 -3
- package/test/workers-pool.spec.ts +5 -5
- package/test/workers-signing.spec.ts +8 -8
- 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
|
+
});
|