Files
salvium-rs/test/legacy-js/debug-commitment-factor.js
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

124 lines
4.9 KiB
JavaScript

#!/usr/bin/env bun
/**
* Check if outPk = 8 * commit(amount, mask), or other systematic relationships.
* Also dump the full blake2b transcript for manual comparison.
*/
import { setCryptoBackend, blake2b, commit, scalarMultPoint, scalarMultBase, pointAddCompressed } from '../src/crypto/index.js';
import { getCryptoBackend } from '../src/crypto/provider.js';
import { MemoryStorage } from '../src/wallet-store.js';
import { readFileSync } from 'fs';
await setCryptoBackend('wasm');
function hexToBytes(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
return bytes;
}
function bytesToHex(bytes) {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
const backend = getCryptoBackend();
const storage = new MemoryStorage();
storage.load(JSON.parse(readFileSync(`${process.env.HOME}/testnet-wallet/wallet-a-sync.json`, 'utf-8')));
const allOutputs = await storage.getOutputs({ isSpent: false });
const carrotOutputs = allOutputs.filter(o => o.isCarrot && o.commitment && o.mask);
console.log(`CARROT outputs with commitment+mask: ${carrotOutputs.length}\n`);
const o = carrotOutputs[0];
const amount = BigInt(o.amount);
const mask = hexToBytes(o.mask);
const outPk = hexToBytes(o.commitment);
console.log(`TX: ${o.txHash?.slice(0,16)}... idx=${o.outputIndex}`);
console.log(`Amount: ${amount}`);
console.log(`Mask: ${bytesToHex(mask)}`);
console.log(`OutPk: ${bytesToHex(outPk)}`);
// Standard commitment
const C = commit(amount, mask);
console.log(`\nC = commit(amount, mask): ${bytesToHex(C)}`);
console.log(`C == outPk: ${bytesToHex(C) === bytesToHex(outPk)}`);
// Check 8*C
const eight = new Uint8Array(32);
eight[0] = 8;
const C8 = scalarMultPoint(eight, C);
console.log(`\n8*C = scalarmult8(C): ${bytesToHex(C8)}`);
console.log(`8*C == outPk: ${bytesToHex(C8) === bytesToHex(outPk)}`);
// Check if outPk = mask*G + amount*H is using a different mask
// Try with WASM scReduce64 instead of JS BigInt scReduce
const domain = 'Carrot commitment mask';
const domainBytes = new TextEncoder().encode(domain);
const Ks = hexToBytes('74861e06908f0af207df6f20b78164836b5e831bef870b63e690cdc595d50544');
const ctx = hexToBytes(o.carrotSharedSecret);
// Build transcript manually
const amountBytes = new Uint8Array(8);
let a = amount;
for (let i = 0; i < 8; i++) { amountBytes[i] = Number(a & 0xffn); a >>= 8n; }
for (const enoteType of [0, 1]) {
const typeBytes = new Uint8Array([enoteType]);
const transcript = new Uint8Array(1 + domainBytes.length + 8 + 32 + 1);
let off = 0;
transcript[off++] = domainBytes.length;
transcript.set(domainBytes, off); off += domainBytes.length;
transcript.set(amountBytes, off); off += 8;
transcript.set(Ks, off); off += 32;
transcript.set(typeBytes, off); off += 1;
console.log(`\n=== enoteType=${enoteType} ===`);
console.log(`Transcript (${transcript.length} bytes): ${bytesToHex(transcript)}`);
// blake2b keyed hash, 64-byte output, key = ctx
const hash64 = blake2b(transcript, 64, ctx);
console.log(`blake2b(transcript, 64, key=ctx): first32=${bytesToHex(hash64).slice(0,64)}...`);
// JS BigInt scReduce
const L = (1n << 252n) + 27742317777372353535851937790883648493n;
let n = 0n;
for (let i = 63; i >= 0; i--) n = (n << 8n) | BigInt(hash64[i]);
n = n % L;
const jsReduced = new Uint8Array(32);
let tmp = n;
for (let i = 0; i < 32; i++) { jsReduced[i] = Number(tmp & 0xffn); tmp >>= 8n; }
// WASM scReduce64
const wasmReduced = backend.scReduce64(new Uint8Array(hash64));
console.log(`JS scReduce: ${bytesToHex(jsReduced)}`);
console.log(`WASM scReduce: ${bytesToHex(wasmReduced)}`);
console.log(`Match: ${bytesToHex(jsReduced) === bytesToHex(wasmReduced)}`);
// Commit with both
const cJS = commit(amount, jsReduced);
const cWASM = commit(amount, wasmReduced);
console.log(`commit(amount, jsReduced): ${bytesToHex(cJS)}`);
console.log(`commit(amount, wasmReduced): ${bytesToHex(cWASM)}`);
console.log(`JS matches outPk: ${bytesToHex(cJS) === bytesToHex(outPk)}`);
console.log(`WASM matches outPk: ${bytesToHex(cWASM) === bytesToHex(outPk)}`);
// Check 8*C
const cJS8 = scalarMultPoint(eight, cJS);
console.log(`8*commit(JS) matches outPk: ${bytesToHex(cJS8) === bytesToHex(outPk)}`);
}
// Also: what if we need amount as varint/compact form instead of 8-byte LE?
console.log(`\n=== Extra: amount encoding tests ===`);
console.log(`Amount hex (8-byte LE): ${bytesToHex(amountBytes)}`);
// Try as 4-byte LE (fits in uint32)
const amount4 = new Uint8Array(4);
a = amount;
for (let i = 0; i < 4; i++) { amount4[i] = Number(a & 0xffn); a >>= 8n; }
console.log(`Amount hex (4-byte LE): ${bytesToHex(amount4)}`);
// What if Salvium uses 32-byte amount in transcript?
const amount32 = new Uint8Array(32);
a = BigInt(o.amount);
for (let i = 0; i < 32 && a > 0n; i++) { amount32[i] = Number(a & 0xffn); a >>= 8n; }
console.log(`Amount hex (32-byte LE): ${bytesToHex(amount32)}`);