Files
Matt Hess 733ecd2681 Migrate all JS tests to Rust: 9-crate workspace, 703 tests, 0 JS remaining
Add root Cargo workspace with 9 crates: salvium-crypto (extended),
  salvium-types, salvium-consensus, salvium-wallet, salvium-tx,
  salvium-rpc, salvium-miner (extended), salvium-cli, salvium-multisig.

  New modules: chain_state, block_weight, alt_chain, validation,
  offline signing, stake lifecycle, wallet sync/query/encryption/utxo,
  randomx utilities, and full multisig crate with CARROT support.

  Delete 188 JS test/helper/debug files; archive integration test
  scripts to test/legacy-js/ for live testnet use. Testnet integration
  tests (transfer, stake, burn, convert, sweep) remain as #[ignore]-
  gated Rust tests runnable with --ignored against a live daemon.
2026-02-17 23:09:35 +00:00

248 lines
9.6 KiB
JavaScript

#!/usr/bin/env bun
/**
* Crypto Benchmark: JS vs WASM Backend
*
* Measures performance of CLSAG, TCLSAG, and Bulletproofs+
* operations across both backends to quantify speedup.
*/
import { setCryptoBackend } from '../src/crypto/index.js';
import { clsagSign, clsagVerify, tclsagSign, tclsagVerify, scSub } from '../src/transaction.js';
import { scalarMultBase, scalarMultPoint, pointAddCompressed, getGeneratorT } from '../src/crypto/index.js';
import { scRandom, commit, bytesToBigInt } from '../src/transaction/serialization.js';
import { keccak256 } from '../src/keccak.js';
import { bulletproofPlusProve, serializeProof, verifyRangeProof } from '../src/bulletproofs_plus.js';
const L_order = 2n ** 252n + 27742317777372353535851937790883648493n;
function generateClsagTestData(ringSize, secretIndex) {
const ringSecrets = [];
const ring = [];
const commitmentMasks = [];
const commitments = [];
for (let i = 0; i < ringSize; i++) {
const sk = scRandom();
ring.push(scalarMultBase(sk));
ringSecrets.push(sk);
const mask = scRandom();
commitments.push(scalarMultBase(mask));
commitmentMasks.push(mask);
}
const secretKey = ringSecrets[secretIndex];
const pseudoMask = scRandom();
const pseudoOutput = scalarMultBase(pseudoMask);
const commitmentMask = scSub(commitmentMasks[secretIndex], pseudoMask);
const message = keccak256(new Uint8Array(32));
return { message, ring, secretKey, commitments, commitmentMask, pseudoOutput, secretIndex };
}
function generateTclsagTestData(ringSize, secretIndex) {
const T = getGeneratorT();
const secretKeyX = scRandom();
const secretKeyY = scRandom();
function tclsagPubKey(x, y) {
return pointAddCompressed(scalarMultBase(x), scalarMultPoint(y, T));
}
const ring = [];
const commitmentMasks = [];
const commitments = [];
const amount = 1000000n;
for (let i = 0; i < ringSize; i++) {
ring.push(i === secretIndex ? tclsagPubKey(secretKeyX, secretKeyY) : tclsagPubKey(scRandom(), scRandom()));
const mask = scRandom();
commitments.push(commit(amount, mask));
commitmentMasks.push(mask);
}
const pseudoMask = scRandom();
const pseudoOutput = commit(amount, pseudoMask);
const maskBig = bytesToBigInt(commitmentMasks[secretIndex]);
const pseudoBig = bytesToBigInt(pseudoMask);
const zBig = ((maskBig - pseudoBig) % L_order + L_order) % L_order;
const commitmentMask = new Uint8Array(32);
let temp = zBig;
for (let i = 0; i < 32; i++) { commitmentMask[i] = Number(temp & 0xffn); temp >>= 8n; }
const message = scRandom();
return { message, ring, secretKeyX, secretKeyY, commitments, commitmentMask, pseudoOutput, secretIndex };
}
async function bench(name, fn, iterations = 1) {
// Warmup
await fn();
const start = performance.now();
for (let i = 0; i < iterations; i++) {
await fn();
}
const elapsed = performance.now() - start;
const avg = elapsed / iterations;
return { name, avg, iterations, total: elapsed };
}
async function main() {
console.log('============================================================');
console.log('Crypto Performance Benchmark: JS vs WASM');
console.log('============================================================\n');
const results = [];
// ─── CLSAG (ring size 16) ────────────────────────────────────────────────
console.log('Benchmarking CLSAG (ring=16)...');
{
const ringSize = 16;
const data = generateClsagTestData(ringSize, 7);
// JS
await setCryptoBackend('js');
const jsSign = await bench(`CLSAG sign (ring=${ringSize}) [JS]`, () => {
clsagSign(data.message, data.ring, data.secretKey, data.commitments, data.commitmentMask, data.pseudoOutput, data.secretIndex);
}, 3);
results.push(jsSign);
const jsSig = clsagSign(data.message, data.ring, data.secretKey, data.commitments, data.commitmentMask, data.pseudoOutput, data.secretIndex);
const jsVerify = await bench(`CLSAG verify (ring=${ringSize}) [JS]`, () => {
clsagVerify(data.message, jsSig, data.ring, data.commitments, data.pseudoOutput);
}, 3);
results.push(jsVerify);
// WASM
await setCryptoBackend('wasm');
const wasmSign = await bench(`CLSAG sign (ring=${ringSize}) [WASM]`, () => {
clsagSign(data.message, data.ring, data.secretKey, data.commitments, data.commitmentMask, data.pseudoOutput, data.secretIndex);
}, 10);
results.push(wasmSign);
const wasmSig = clsagSign(data.message, data.ring, data.secretKey, data.commitments, data.commitmentMask, data.pseudoOutput, data.secretIndex);
const wasmVerify = await bench(`CLSAG verify (ring=${ringSize}) [WASM]`, () => {
clsagVerify(data.message, wasmSig, data.ring, data.commitments, data.pseudoOutput);
}, 10);
results.push(wasmVerify);
}
// ─── TCLSAG (ring size 11) ──────────────────────────────────────────────
console.log('Benchmarking TCLSAG (ring=11)...');
{
const ringSize = 11;
const secretIndex = 5;
const data = generateTclsagTestData(ringSize, secretIndex);
// JS
await setCryptoBackend('js');
const jsSign = await bench(`TCLSAG sign (ring=${ringSize}) [JS]`, () => {
tclsagSign(data.message, data.ring, data.secretKeyX, data.secretKeyY, data.commitments, data.commitmentMask, data.pseudoOutput, secretIndex);
}, 3);
results.push(jsSign);
const jsSig = tclsagSign(data.message, data.ring, data.secretKeyX, data.secretKeyY, data.commitments, data.commitmentMask, data.pseudoOutput, secretIndex);
const jsVerify = await bench(`TCLSAG verify (ring=${ringSize}) [JS]`, () => {
tclsagVerify(data.message, jsSig, data.ring, data.commitments, data.pseudoOutput);
}, 3);
results.push(jsVerify);
// WASM
await setCryptoBackend('wasm');
const wasmSign = await bench(`TCLSAG sign (ring=${ringSize}) [WASM]`, () => {
tclsagSign(data.message, data.ring, data.secretKeyX, data.secretKeyY, data.commitments, data.commitmentMask, data.pseudoOutput, secretIndex);
}, 10);
results.push(wasmSign);
const wasmSig = tclsagSign(data.message, data.ring, data.secretKeyX, data.secretKeyY, data.commitments, data.commitmentMask, data.pseudoOutput, secretIndex);
const wasmVerify = await bench(`TCLSAG verify (ring=${ringSize}) [WASM]`, () => {
tclsagVerify(data.message, wasmSig, data.ring, data.commitments, data.pseudoOutput);
}, 10);
results.push(wasmVerify);
}
// ─── Bulletproofs+ (2 outputs) ───────────────────────────────────────────
console.log('Benchmarking Bulletproofs+ (2 outputs)...');
{
const amounts = [1000n, 2000n];
function randomBigIntScalar() {
const bytes = new Uint8Array(64);
crypto.getRandomValues(bytes);
let result = 0n;
for (let i = 0; i < 64; i++) result |= BigInt(bytes[i]) << BigInt(i * 8);
return result % L_order;
}
const masks = [randomBigIntScalar(), randomBigIntScalar()];
// JS
await setCryptoBackend('js');
const jsProve = await bench(`BP+ prove (2 outputs) [JS]`, () => {
bulletproofPlusProve(amounts, masks);
}, 1);
results.push(jsProve);
const jsProof = bulletproofPlusProve(amounts, masks);
const jsProofBytes = serializeProof(jsProof);
const jsCommitments = jsProof.V.map(v => v.toBytes());
const jsVerify = await bench(`BP+ verify (2 outputs) [JS]`, () => {
verifyRangeProof(jsCommitments, jsProofBytes);
}, 1);
results.push(jsVerify);
// WASM
await setCryptoBackend('wasm');
try {
const wasmProve = await bench(`BP+ prove (2 outputs) [WASM]`, () => {
bulletproofPlusProve(amounts, masks);
}, 5);
results.push(wasmProve);
const wasmProof = bulletproofPlusProve(amounts, masks);
if (wasmProof && wasmProof.V && wasmProof.proofBytes) {
const wasmVerify = await bench(`BP+ verify (2 outputs) [WASM]`, () => {
verifyRangeProof(wasmProof.V, wasmProof.proofBytes);
}, 5);
results.push(wasmVerify);
}
} catch (e) {
console.log(` [SKIP] WASM BP+ not yet working: ${e.message}`);
}
}
// ─── Results Table ───────────────────────────────────────────────────────
console.log('\n============================================================');
console.log('Results');
console.log('============================================================\n');
console.log('Operation'.padEnd(42) + 'Avg (ms)'.padStart(12) + 'Iters'.padStart(8));
console.log('-'.repeat(62));
for (const r of results) {
console.log(r.name.padEnd(42) + r.avg.toFixed(2).padStart(12) + String(r.iterations).padStart(8));
}
// Compute speedup ratios
console.log('\n--- Speedup Summary ---\n');
const pairs = [
['CLSAG sign'],
['CLSAG verify'],
['TCLSAG sign'],
['TCLSAG verify'],
['BP+ prove'],
['BP+ verify'],
];
for (const [op] of pairs) {
const jsResult = results.find(r => r.name.startsWith(op) && r.name.includes('[JS]'));
const wasmResult = results.find(r => r.name.startsWith(op) && r.name.includes('[WASM]'));
if (jsResult && wasmResult) {
const speedup = jsResult.avg / wasmResult.avg;
console.log(`${op}: ${speedup.toFixed(1)}x speedup (${jsResult.avg.toFixed(1)}ms -> ${wasmResult.avg.toFixed(1)}ms)`);
}
}
await setCryptoBackend('js');
}
main().catch(e => {
console.error('Fatal error:', e);
process.exit(1);
});