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

247 lines
7.3 KiB
JavaScript

/**
* Debug - trace block 4 computation (first mismatch point)
*/
import { blake2b } from '../src/blake2b.js';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ARGON2_BLOCK_SIZE = 1024;
const ARGON2_PREHASH_DIGEST_LENGTH = 64;
const ARGON2_PREHASH_SEED_LENGTH = 72;
const ARGON2_SYNC_POINTS = 4;
const ARGON2_VERSION = 0x13;
const RANDOMX_ARGON_MEMORY = 262144;
const RANDOMX_ARGON_LANES = 1;
const RANDOMX_ARGON_SALT = new TextEncoder().encode("RandomX\x03");
function blake2bLong(outLen, input) {
const prefixed = new Uint8Array(4 + input.length);
prefixed[0] = outLen & 0xff;
prefixed[1] = (outLen >> 8) & 0xff;
prefixed[2] = (outLen >> 16) & 0xff;
prefixed[3] = (outLen >> 24) & 0xff;
prefixed.set(input, 4);
if (outLen <= 64) {
return blake2b(prefixed, outLen);
}
const result = new Uint8Array(outLen);
let v = blake2b(prefixed, 64);
result.set(v.subarray(0, 32), 0);
let pos = 32;
while (pos < outLen - 64) {
v = blake2b(v, 64);
result.set(v.subarray(0, 32), pos);
pos += 32;
}
const remaining = outLen - pos;
v = blake2b(v, remaining);
result.set(v, pos);
return result;
}
function initialHash(ctx) {
const parts = [];
const addU32 = (val) => {
const arr = new Uint8Array(4);
arr[0] = val & 0xff;
arr[1] = (val >> 8) & 0xff;
arr[2] = (val >> 16) & 0xff;
arr[3] = (val >> 24) & 0xff;
parts.push(arr);
};
addU32(ctx.lanes);
addU32(ctx.outLen);
addU32(ctx.mCost);
addU32(ctx.tCost);
addU32(ctx.version);
addU32(ctx.type);
addU32(ctx.password.length);
if (ctx.password.length > 0) parts.push(ctx.password);
addU32(ctx.salt.length);
if (ctx.salt.length > 0) parts.push(ctx.salt);
addU32(0);
addU32(0);
let totalLen = 0;
for (const p of parts) totalLen += p.length;
const input = new Uint8Array(totalLen);
let offset = 0;
for (const p of parts) {
input.set(p, offset);
offset += p.length;
}
return blake2b(input, ARGON2_PREHASH_DIGEST_LENGTH);
}
// JS implementation of indexAlpha for debugging
function indexAlpha(laneLength, segmentLength, pass, slice, index, pseudoRand, sameLane) {
let referenceAreaSize;
if (pass === 0) {
if (slice === 0) {
referenceAreaSize = index - 1;
} else {
if (sameLane) {
referenceAreaSize = slice * segmentLength + index - 1;
} else {
referenceAreaSize = slice * segmentLength + (index === 0 ? -1 : 0);
}
}
} else {
if (sameLane) {
referenceAreaSize = laneLength - segmentLength + index - 1;
} else {
referenceAreaSize = laneLength - segmentLength + (index === 0 ? -1 : 0);
}
}
console.log(` indexAlpha: pass=${pass}, slice=${slice}, index=${index}, sameLane=${sameLane}`);
console.log(` referenceAreaSize=${referenceAreaSize}`);
// Map pseudo_rand to [0, reference_area_size)
let relativePos = BigInt(pseudoRand) & 0xFFFFFFFFn;
console.log(` pseudo_rand & 0xFFFFFFFF = 0x${relativePos.toString(16)}`);
relativePos = (relativePos * relativePos) >> 32n;
console.log(` after square and shift: relativePos = 0x${relativePos.toString(16)}`);
relativePos = BigInt(referenceAreaSize) - 1n - ((BigInt(referenceAreaSize) * relativePos) >> 32n);
console.log(` final relativePos = ${relativePos}`);
// Starting position
let startPosition = 0;
if (pass !== 0) {
startPosition = (slice === ARGON2_SYNC_POINTS - 1) ? 0 : (slice + 1) * segmentLength;
}
console.log(` startPosition = ${startPosition}`);
const result = (startPosition + Number(relativePos)) % laneLength;
console.log(` refIndex = ${result}`);
return result;
}
console.log('=== Debug block 4 computation ===\n');
// From C reference:
// Block 4: prev=3, pseudo_rand=0x32a0ba3273a71a83, ref_lane=0, ref_index=2
console.log('C reference for block 4:');
console.log(' prev=3, pseudo_rand=0x32a0ba3273a71a83, ref_lane=0, ref_index=2\n');
// Load WASM
const wasmPath = join(__dirname, '../build/randomx.wasm');
const wasmBuffer = readFileSync(wasmPath);
const wasmMemory = new WebAssembly.Memory({
initial: 4096 + 512,
maximum: 8192
});
const imports = {
env: {
memory: wasmMemory,
abort: () => {}
}
};
const { instance } = await WebAssembly.instantiate(wasmBuffer, imports);
const wasm = instance.exports;
// Setup
const key = new TextEncoder().encode("test key 000");
const lanes = RANDOMX_ARGON_LANES;
const memoryBlocks = Math.floor(RANDOMX_ARGON_MEMORY / (ARGON2_BLOCK_SIZE / 1024));
const segmentLength = Math.floor(memoryBlocks / (lanes * ARGON2_SYNC_POINTS));
const laneLength = segmentLength * ARGON2_SYNC_POINTS;
const totalBlocks = lanes * laneLength;
const totalBytes = totalBlocks * ARGON2_BLOCK_SIZE;
// Initialize WASM
const memPtr = 0;
wasm.argon2d_init(memPtr, totalBlocks, laneLength, segmentLength);
// Create context
const ctx = {
password: key,
salt: RANDOMX_ARGON_SALT,
tCost: 3,
mCost: RANDOMX_ARGON_MEMORY,
lanes,
outLen: 0,
version: ARGON2_VERSION,
type: 0
};
// Generate H0 and fill first two blocks
const blockHash = initialHash(ctx);
const seed = new Uint8Array(ARGON2_PREHASH_SEED_LENGTH);
seed.set(blockHash);
const mem = new Uint8Array(wasmMemory.buffer);
const tempPtr = totalBytes + 1024;
seed[64] = 0; seed[65] = 0; seed[66] = 0; seed[67] = 0;
seed[68] = 0; seed[69] = 0; seed[70] = 0; seed[71] = 0;
const block0 = blake2bLong(ARGON2_BLOCK_SIZE, seed);
mem.set(block0, tempPtr);
wasm.argon2d_write_block(0, tempPtr);
seed[64] = 1;
const block1 = blake2bLong(ARGON2_BLOCK_SIZE, seed);
mem.set(block1, tempPtr);
wasm.argon2d_write_block(1, tempPtr);
const wasmView = new DataView(wasmMemory.buffer);
// Fill blocks 2 and 3 first
wasm.argon2d_fill_segment(0, 0, 0); // This fills blocks 2 to 65535
// Now let's trace block 4 computation
// For block 4:
// pass = 0, lane = 0, slice = 0
// segment starts at index 2 (starting_index = 2 for pass 0 slice 0)
// block 4 is at segment index 2 (i.e., startingIndex + 2 = 2 + 2 = 4)
//
// In fillSegment loop:
// i = 0: currOffset = 2, we fill block 2 using prev=1, ref=indexAlpha(...)
// i = 1: currOffset = 3, we fill block 3 using prev=2, ref=indexAlpha(...)
// i = 2: currOffset = 4, we fill block 4 using prev=3, ref=indexAlpha(...)
// Wait - block 4 is filled when i=2 in the segment
// At that point: index = i = 2
console.log('Tracing block 4 computation:\n');
// Read block 3 (which is the prev for block 4)
const block3_qword0 = wasmView.getBigUint64(3 * 1024, true);
console.log(`Block 3 first qword (WASM): 0x${block3_qword0.toString(16)}`);
console.log(`Block 3 first qword (C ref): 0x32a0ba3273a71a83\n`);
// Trace indexAlpha calculation
// For block 4: pass=0, slice=0, index=4 (the loop counter i when filling block 4)
// The loop starts at i=2 (startingIndex), so:
// i=2 -> block 2, i=3 -> block 3, i=4 -> block 4
const pass = 0;
const slice = 0;
const index = 4; // This is the loop index i when filling block 4
const pseudoRand = block3_qword0;
const sameLane = true; // always true in pass 0, slice 0
console.log('My indexAlpha calculation:');
const myRefIndex = indexAlpha(laneLength, segmentLength, pass, slice, index, pseudoRand, sameLane);
console.log();
console.log(`C reference refIndex: 2`);
console.log(`My refIndex: ${myRefIndex}`);
console.log(`Match: ${myRefIndex === 2}`);