3658a0c33f
- 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
305 lines
9.3 KiB
JavaScript
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!');
|
|
}
|