Files
salvium-rs/test/keys.test.js
T
Matt Hess 3658a0c33f New Features:
- generateSeed() - Cryptographically secure random seed generation
  - deriveKeys() - CryptoNote key derivation (spend/view secret & public keys)
  - deriveCarrotKeys() - CARROT key derivation (all 6 CARROT keys)
  - Blake2b tests with RFC 7693 test vectors
  - CryptoNote key derivation tests
  - CARROT key derivation tests
  - Updated exports in index.js
2026-01-14 23:59:27 +00:00

305 lines
9.3 KiB
JavaScript

/**
* Key Derivation Tests
*
* Tests for Ed25519 operations and key derivation (CryptoNote and CARROT).
*/
import {
scalarMultBase,
scalarMultPoint,
pointAddCompressed,
getGeneratorG,
getGeneratorT
} from '../src/ed25519.js';
// Get generator points
const G = getGeneratorG();
const T = getGeneratorT();
// Simple point validation (check it's 32 bytes and not all zeros)
function isValidPoint(p) {
if (!p || p.length !== 32) return false;
for (let i = 0; i < 32; i++) {
if (p[i] !== 0) return true;
}
return false;
}
import { deriveCarrotKeys, deriveKeys } from '../src/carrot.js';
import { bytesToHex, hexToBytes } from '../src/index.js';
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(`${name}`);
passed++;
} catch (error) {
console.log(`${name}`);
console.log(` Error: ${error.message}`);
failed++;
}
}
function assertEqual(actual, expected, message = '') {
if (actual !== expected) {
throw new Error(`${message} Expected ${expected}, got ${actual}`);
}
}
function assertNotEqual(actual, expected, message = '') {
if (actual === expected) {
throw new Error(`${message} Values should not be equal: ${actual}`);
}
}
function assertTrue(value, message = '') {
if (!value) {
throw new Error(`${message} Expected true, got ${value}`);
}
}
function assertFalse(value, message = '') {
if (value) {
throw new Error(`${message} Expected false, got ${value}`);
}
}
function assertLength(value, length, message = '') {
if (value.length !== length) {
throw new Error(`${message} Expected length ${length}, got ${value.length}`);
}
}
// Test vectors
const TEST_SEED = hexToBytes('8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94');
const SCALAR_ONE = hexToBytes('0100000000000000000000000000000000000000000000000000000000000000');
const SCALAR_TWO = hexToBytes('0200000000000000000000000000000000000000000000000000000000000000');
// ============================================================
// Generator Point Tests
// ============================================================
console.log('\n--- Generator Point Tests ---');
test('G (base point) is 32 bytes', () => {
assertLength(G, 32);
});
test('T (CARROT generator) is 32 bytes', () => {
assertLength(T, 32);
});
test('G and T are different points', () => {
assertNotEqual(bytesToHex(G), bytesToHex(T));
});
test('G is a valid curve point', () => {
assertTrue(isValidPoint(G));
});
test('T is a valid curve point', () => {
assertTrue(isValidPoint(T));
});
// ============================================================
// Scalar Multiplication Tests
// ============================================================
console.log('\n--- Scalar Multiplication Tests ---');
test('scalarMultBase returns 32 bytes', () => {
const result = scalarMultBase(SCALAR_ONE);
assertLength(result, 32);
});
test('scalarMultBase(1) equals G', () => {
const result = scalarMultBase(SCALAR_ONE);
assertEqual(bytesToHex(result), bytesToHex(G));
});
test('scalarMultBase is deterministic', () => {
const r1 = scalarMultBase(TEST_SEED);
const r2 = scalarMultBase(TEST_SEED);
assertEqual(bytesToHex(r1), bytesToHex(r2));
});
test('scalarMultBase with different scalars gives different points', () => {
const r1 = scalarMultBase(SCALAR_ONE);
const r2 = scalarMultBase(SCALAR_TWO);
assertNotEqual(bytesToHex(r1), bytesToHex(r2));
});
test('scalarMultPoint returns 32 bytes', () => {
const result = scalarMultPoint(SCALAR_TWO, G);
assertLength(result, 32);
});
test('scalarMultPoint(2, G) equals scalarMultBase(2)', () => {
const r1 = scalarMultPoint(SCALAR_TWO, G);
const r2 = scalarMultBase(SCALAR_TWO);
assertEqual(bytesToHex(r1), bytesToHex(r2));
});
// ============================================================
// Point Addition Tests
// ============================================================
console.log('\n--- Point Addition Tests ---');
test('pointAddCompressed returns 32 bytes', () => {
const result = pointAddCompressed(G, G);
assertLength(result, 32);
});
test('G + G equals 2*G', () => {
const added = pointAddCompressed(G, G);
const doubled = scalarMultBase(SCALAR_TWO);
assertEqual(bytesToHex(added), bytesToHex(doubled));
});
test('Point addition is commutative', () => {
const P1 = scalarMultBase(TEST_SEED);
const P2 = scalarMultBase(SCALAR_TWO);
const r1 = pointAddCompressed(P1, P2);
const r2 = pointAddCompressed(P2, P1);
assertEqual(bytesToHex(r1), bytesToHex(r2));
});
// ============================================================
// Point Validation Tests
// ============================================================
console.log('\n--- Point Validation Tests ---');
test('isValidPoint accepts valid points', () => {
assertTrue(isValidPoint(G));
assertTrue(isValidPoint(T));
assertTrue(isValidPoint(scalarMultBase(TEST_SEED)));
});
test('isValidPoint rejects all zeros', () => {
assertFalse(isValidPoint(new Uint8Array(32)));
});
test('isValidPoint rejects wrong length', () => {
assertFalse(isValidPoint(new Uint8Array(31)));
assertFalse(isValidPoint(new Uint8Array(33)));
});
// ============================================================
// CryptoNote Key Derivation Tests
// ============================================================
console.log('\n--- CryptoNote Key Derivation Tests ---');
test('deriveKeys returns all expected keys', () => {
const keys = deriveKeys(TEST_SEED);
assertLength(keys.spendSecretKey, 32);
assertLength(keys.spendPublicKey, 32);
assertLength(keys.viewSecretKey, 32);
assertLength(keys.viewPublicKey, 32);
});
test('deriveKeys is deterministic', () => {
const k1 = deriveKeys(TEST_SEED);
const k2 = deriveKeys(TEST_SEED);
assertEqual(bytesToHex(k1.spendSecretKey), bytesToHex(k2.spendSecretKey));
assertEqual(bytesToHex(k1.spendPublicKey), bytesToHex(k2.spendPublicKey));
assertEqual(bytesToHex(k1.viewSecretKey), bytesToHex(k2.viewSecretKey));
assertEqual(bytesToHex(k1.viewPublicKey), bytesToHex(k2.viewPublicKey));
});
test('deriveKeys produces different keys for different seeds', () => {
const k1 = deriveKeys(TEST_SEED);
const k2 = deriveKeys(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001'));
assertNotEqual(bytesToHex(k1.spendPublicKey), bytesToHex(k2.spendPublicKey));
assertNotEqual(bytesToHex(k1.viewPublicKey), bytesToHex(k2.viewPublicKey));
});
test('deriveKeys spend public key is valid point', () => {
const keys = deriveKeys(TEST_SEED);
assertTrue(isValidPoint(keys.spendPublicKey));
});
test('deriveKeys view public key is valid point', () => {
const keys = deriveKeys(TEST_SEED);
assertTrue(isValidPoint(keys.viewPublicKey));
});
test('deriveKeys accepts hex string input', () => {
const hexSeed = '8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94';
const keys = deriveKeys(hexSeed);
const keysFromBytes = deriveKeys(TEST_SEED);
assertEqual(bytesToHex(keys.spendSecretKey), bytesToHex(keysFromBytes.spendSecretKey));
});
test('deriveKeys throws for wrong seed length', () => {
let threw = false;
try {
deriveKeys(new Uint8Array(31));
} catch (e) {
threw = true;
}
assertTrue(threw, 'Should throw for 31-byte seed');
});
// ============================================================
// CARROT Key Derivation Tests
// ============================================================
console.log('\n--- CARROT Key Derivation Tests ---');
test('deriveCarrotKeys returns all expected keys', () => {
const keys = deriveCarrotKeys(TEST_SEED);
// All keys returned as 64-char hex strings (32 bytes)
assertLength(keys.proveSpendKey, 64);
assertLength(keys.generateImageKey, 64);
assertLength(keys.viewIncomingKey, 64);
assertLength(keys.generateAddressSecret, 64);
assertLength(keys.viewBalanceSecret, 64);
assertLength(keys.masterSecret, 64);
});
test('deriveCarrotKeys is deterministic', () => {
const k1 = deriveCarrotKeys(TEST_SEED);
const k2 = deriveCarrotKeys(TEST_SEED);
assertEqual(k1.proveSpendKey, k2.proveSpendKey);
assertEqual(k1.generateImageKey, k2.generateImageKey);
assertEqual(k1.viewIncomingKey, k2.viewIncomingKey);
assertEqual(k1.generateAddressSecret, k2.generateAddressSecret);
assertEqual(k1.viewBalanceSecret, k2.viewBalanceSecret);
});
test('deriveCarrotKeys produces different keys for different seeds', () => {
const k1 = deriveCarrotKeys(TEST_SEED);
const k2 = deriveCarrotKeys(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001'));
assertNotEqual(k1.proveSpendKey, k2.proveSpendKey);
assertNotEqual(k1.viewBalanceSecret, k2.viewBalanceSecret);
});
test('deriveCarrotKeys returns valid hex strings', () => {
const keys = deriveCarrotKeys(TEST_SEED);
// All keys should be valid 64-char hex strings
assertTrue(/^[0-9a-f]{64}$/.test(keys.proveSpendKey));
assertTrue(/^[0-9a-f]{64}$/.test(keys.viewBalanceSecret));
assertTrue(/^[0-9a-f]{64}$/.test(keys.generateImageKey));
});
// ============================================================
// Summary
// ============================================================
console.log('\n--- Key Derivation Test Summary ---');
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log(`Total: ${passed + failed}`);
if (failed > 0) {
console.log('\n⚠️ Some tests failed!');
process.exit(1);
} else {
console.log('\n✓ All key derivation tests passed!');
}