Files
salvium-rs/test/randomx.test.js
T
Matt Hess 1066ca2110 Add full wallet implementation with storage, sync, and transaction support
New Features:
  - Wallet class with multi-account and subaddress support
  - PersistentWallet with blockchain sync and storage (Memory/IndexedDB)
  - Connection manager for daemon/wallet RPC failover
  - Query system for filtering outputs and transactions
  - Offline transaction signing (cold wallet support)
  - Multisig wallet support (N-of-M threshold signing)

  Daemon RPC:
  - Salvium-specific: getSupplyInfo(), getYieldInfo()
  - Mining control: startMining(), stopMining(), miningStatus()
  - Bandwidth/peer management, admin controls

  Transaction:
  - Full transaction building with UTXO selection
  - Transaction parsing and summarization
  - Fee estimation with priority levels
  - Decoy selection with gamma distribution

  Tests:
  - Wallet class tests (accounts, subaddresses, recovery)
  - Transaction builder and parser tests
  - UTXO selection tests
2026-01-18 21:39:05 +00:00

457 lines
14 KiB
JavaScript

#!/usr/bin/env node
/**
* Tests for RandomX Implementation
*
* Converted from vitest to simple test framework for consistency
*/
import {
RandomXContext,
rxSlowHash,
randomxHash,
calculateCommitment,
verifyHash,
checkDifficulty,
RandomXCache,
initDatasetItem,
Blake2Generator,
generateSuperscalar,
executeSuperscalar,
reciprocal,
argon2d
} from '../src/index.js';
import { blake2b } from '../src/blake2b.js';
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(`${name}`);
passed++;
} catch (e) {
console.log(`${name}: ${e.message}`);
failed++;
}
}
async function testAsync(name, fn) {
try {
await fn();
console.log(`${name}`);
passed++;
} catch (e) {
console.log(`${name}: ${e.message}`);
failed++;
}
}
function assertEqual(a, b, msg = '') {
if (a !== b) throw new Error(msg || `Expected ${b}, got ${a}`);
}
function assertNotEqual(a, b, msg = '') {
if (a === b) throw new Error(msg || `Expected values to differ, both are ${a}`);
}
function assertTrue(condition, msg = '') {
if (!condition) throw new Error(msg || 'Expected true');
}
function assertThrows(fn, expectedMsg = '') {
let threw = false;
let error = null;
try {
fn();
} catch (e) {
threw = true;
error = e;
}
if (!threw) throw new Error('Expected function to throw');
if (expectedMsg && !error.message.includes(expectedMsg)) {
throw new Error(`Expected error containing "${expectedMsg}", got "${error.message}"`);
}
}
function hexToBytes(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
return bytes;
}
function bytesToHex(bytes) {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
console.log('\n=== RandomX Tests ===\n');
// ============================================================================
// Blake2Generator Tests
// ============================================================================
console.log('--- Blake2Generator ---');
test('generates consistent bytes from seed', () => {
const seed = new Uint8Array(32);
seed[0] = 1;
const gen1 = new Blake2Generator(seed);
const gen2 = new Blake2Generator(seed);
const bytes1 = gen1.getBytes(16);
const bytes2 = gen2.getBytes(16);
assertEqual(bytesToHex(bytes1), bytesToHex(bytes2));
});
test('generates different bytes for different seeds', () => {
const seed1 = new Uint8Array(32);
seed1[0] = 1;
const seed2 = new Uint8Array(32);
seed2[0] = 2;
const gen1 = new Blake2Generator(seed1);
const gen2 = new Blake2Generator(seed2);
const bytes1 = gen1.getBytes(16);
const bytes2 = gen2.getBytes(16);
assertNotEqual(bytesToHex(bytes1), bytesToHex(bytes2));
});
test('getByte returns single bytes', () => {
const seed = new Uint8Array(32);
const gen = new Blake2Generator(seed);
const byte1 = gen.getByte();
const byte2 = gen.getByte();
assertTrue(typeof byte1 === 'number', 'getByte should return number');
assertTrue(byte1 >= 0 && byte1 <= 255, 'getByte should return 0-255');
});
test('getUInt32 returns 32-bit values', () => {
const seed = new Uint8Array(32);
const gen = new Blake2Generator(seed);
const val = gen.getUInt32();
assertTrue(typeof val === 'number', 'getUInt32 should return number');
assertTrue(val >= 0 && val <= 0xFFFFFFFF, 'getUInt32 should return 32-bit value');
});
// ============================================================================
// Superscalar Tests
// ============================================================================
console.log('\n--- Superscalar ---');
test('reciprocal produces consistent results', () => {
// Test known reciprocal value
const result = reciprocal(3);
assertTrue(typeof result === 'bigint', 'reciprocal should return bigint');
});
test('generateSuperscalar produces valid program', () => {
const seed = new Uint8Array(32);
const gen = new Blake2Generator(seed);
const program = generateSuperscalar(gen);
assertTrue(program !== null && typeof program === 'object', 'Should return program object');
assertTrue(Array.isArray(program.instructions), 'Should have instructions array');
assertTrue(program.instructions.length > 0, 'Should have instructions');
});
// ============================================================================
// Argon2d Tests
// ============================================================================
console.log('\n--- Argon2d ---');
test('argon2d function exists', () => {
assertTrue(typeof argon2d === 'function', 'argon2d should be a function');
});
test('argon2d produces output', () => {
const password = new Uint8Array(32);
const salt = new Uint8Array(32);
const result = argon2d(password, salt, 1, 16, 1, 32);
assertTrue(result instanceof Uint8Array, 'Should return Uint8Array');
assertEqual(result.length, 32, 'Should return requested length');
});
// ============================================================================
// RandomXCache Tests
// ============================================================================
console.log('\n--- RandomXCache ---');
test('RandomXCache constructs', () => {
const cache = new RandomXCache();
assertTrue(cache !== null, 'Should construct');
});
// ============================================================================
// checkDifficulty Tests
// ============================================================================
console.log('\n--- checkDifficulty ---');
test('checkDifficulty handles zero hash', () => {
const zeroHash = new Uint8Array(32);
// Zero hash should pass any difficulty
const result = checkDifficulty(zeroHash, 1n);
assertTrue(result === true, 'Zero hash should pass difficulty');
});
test('checkDifficulty handles max difficulty', () => {
const hash = new Uint8Array(32);
hash[0] = 0xff;
// Max hash shouldn't pass high difficulty
const result = checkDifficulty(hash, 0xffffffffffffffffn);
assertTrue(result === false, 'Max hash should fail high difficulty');
});
// ============================================================================
// calculateCommitment Tests
// ============================================================================
console.log('\n--- calculateCommitment ---');
test('calculateCommitment produces 32-byte output', () => {
const blockHash = new Uint8Array(32);
const previousHash = new Uint8Array(32);
const result = calculateCommitment(blockHash, previousHash);
assertTrue(result instanceof Uint8Array, 'Should return Uint8Array');
assertEqual(result.length, 32, 'Should be 32 bytes');
});
test('calculateCommitment is deterministic', () => {
const blockHash = new Uint8Array(32);
blockHash[0] = 0x42;
const previousHash = new Uint8Array(32);
previousHash[0] = 0x24;
const result1 = calculateCommitment(blockHash, previousHash);
const result2 = calculateCommitment(blockHash, previousHash);
assertEqual(bytesToHex(result1), bytesToHex(result2), 'Should be deterministic');
});
// ============================================================================
// Export Tests
// ============================================================================
console.log('\n--- Exports ---');
test('RandomXContext is exported', () => {
assertTrue(typeof RandomXContext === 'function', 'RandomXContext should be exported');
});
test('rxSlowHash is exported', () => {
assertTrue(typeof rxSlowHash === 'function', 'rxSlowHash should be exported');
});
test('randomxHash is exported', () => {
assertTrue(typeof randomxHash === 'function', 'randomxHash should be exported');
});
test('verifyHash is exported', () => {
assertTrue(typeof verifyHash === 'function', 'verifyHash should be exported');
});
test('checkDifficulty is exported', () => {
assertTrue(typeof checkDifficulty === 'function', 'checkDifficulty should be exported');
});
test('RandomXCache is exported', () => {
assertTrue(typeof RandomXCache === 'function', 'RandomXCache should be exported');
});
test('initDatasetItem is exported', () => {
assertTrue(typeof initDatasetItem === 'function', 'initDatasetItem should be exported');
});
test('Blake2Generator is exported', () => {
assertTrue(typeof Blake2Generator === 'function', 'Blake2Generator should be exported');
});
test('generateSuperscalar is exported', () => {
assertTrue(typeof generateSuperscalar === 'function', 'generateSuperscalar should be exported');
});
test('executeSuperscalar is exported', () => {
assertTrue(typeof executeSuperscalar === 'function', 'executeSuperscalar should be exported');
});
test('reciprocal is exported', () => {
assertTrue(typeof reciprocal === 'function', 'reciprocal should be exported');
});
test('argon2d is exported', () => {
assertTrue(typeof argon2d === 'function', 'argon2d should be exported');
});
// ============================================================================
// RandomX Integration Tests (requires ~1.5s for cache init)
// ============================================================================
console.log('\n--- RandomX Integration ---');
await testAsync('RandomXContext initializes and hashes', async () => {
const ctx = new RandomXContext();
const key = new Uint8Array(32);
key[0] = 0x01;
await ctx.init(key);
const input = new Uint8Array([0x74, 0x65, 0x73, 0x74]); // "test"
const hash = ctx.hash(input);
assertTrue(hash instanceof Uint8Array, 'Hash should be Uint8Array');
assertEqual(hash.length, 32, 'Hash should be 32 bytes');
});
await testAsync('RandomXContext produces consistent hashes', async () => {
const ctx = new RandomXContext();
const key = new Uint8Array(32);
await ctx.init(key);
const input = new Uint8Array([1, 2, 3, 4]);
const hash1 = ctx.hash(input);
const hash2 = ctx.hash(input);
assertEqual(bytesToHex(hash1), bytesToHex(hash2), 'Hashes should be consistent');
});
await testAsync('RandomXContext produces different hashes for different inputs', async () => {
const ctx = new RandomXContext();
const key = new Uint8Array(32);
await ctx.init(key);
const input1 = new Uint8Array([1, 2, 3, 4]);
const input2 = new Uint8Array([5, 6, 7, 8]);
const hash1 = ctx.hash(input1);
const hash2 = ctx.hash(input2);
assertNotEqual(bytesToHex(hash1), bytesToHex(hash2), 'Different inputs should produce different hashes');
});
await testAsync('verifyHash works correctly', async () => {
const key = new Uint8Array(32);
const input = new Uint8Array([0x61, 0x62, 0x63]); // "abc"
// First compute the correct hash
const ctx = new RandomXContext();
await ctx.init(key);
const hash = ctx.hash(input);
// verifyHash takes (key, input, expectedHash)
const isValid = await verifyHash(key, input, hash);
assertTrue(isValid, 'Correct hash should verify');
const wrongHash = new Uint8Array(32);
const isInvalid = await verifyHash(key, input, wrongHash);
assertTrue(!isInvalid, 'Wrong hash should not verify');
});
// ============================================================================
// Official RandomX Test Vectors (from tevador/RandomX reference implementation)
// https://github.com/tevador/RandomX/blob/master/src/tests/tests.cpp
// ============================================================================
console.log('\n--- Official Test Vectors ---');
// Test vector 1a: key="test key 000", input="This is a test"
await testAsync('Test vector 1a (reference implementation)', async () => {
const ctx = new RandomXContext();
await ctx.init('test key 000');
const hash = ctx.hashHex('This is a test');
assertEqual(
hash,
'639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f',
'Hash should match reference implementation'
);
});
// Test vector 1b: key="test key 000", input="Lorem ipsum dolor sit amet"
await testAsync('Test vector 1b (reference implementation)', async () => {
const ctx = new RandomXContext();
await ctx.init('test key 000');
const hash = ctx.hashHex('Lorem ipsum dolor sit amet');
assertEqual(
hash,
'300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969',
'Hash should match reference implementation'
);
});
// Test vector 1c: key="test key 000", input="sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
await testAsync('Test vector 1c (reference implementation)', async () => {
const ctx = new RandomXContext();
await ctx.init('test key 000');
const hash = ctx.hashHex('sed do eiusmod tempor incididunt ut labore et dolore magna aliqua');
assertEqual(
hash,
'c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8',
'Hash should match reference implementation'
);
});
// Test vector 1d: key="test key 001" (different key), same long input
await testAsync('Test vector 1d (different key)', async () => {
const ctx = new RandomXContext();
await ctx.init('test key 001');
const hash = ctx.hashHex('sed do eiusmod tempor incididunt ut labore et dolore magna aliqua');
assertEqual(
hash,
'e9ff4503201c0c2cca26d285c93ae883f9b1d30c9eb240b820756f2d5a7905fc',
'Hash should match reference implementation'
);
});
// Test vector 1e: key="test key 001", binary input
await testAsync('Test vector 1e (binary input)', async () => {
const ctx = new RandomXContext();
await ctx.init('test key 001');
const input = hexToBytes('0b0b98bea7e805e0010a2126d287a2a0cc833d312cb786385a7c2f9de69d25537f584a9bc9977b00000000666fd8753bf61a8631f12984e3fd44f4014eca629276817b56f32e9b68bd82f416');
const hash = ctx.hashHex(input);
assertEqual(
hash,
'c56414121acda1713c2f2a819d8ae38aed7c80c35c2a769298d34f03833cd5f1',
'Hash should match reference implementation'
);
});
// ============================================================================
// Summary
// ============================================================================
console.log(`\n--- Summary ---`);
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
if (failed === 0) {
console.log('\n✓ All RandomX tests passed!');
process.exit(0);
} else {
console.log('\n✗ Some tests failed');
process.exit(1);
}