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
308 lines
10 KiB
JavaScript
308 lines
10 KiB
JavaScript
/**
|
|
* Mnemonic Seed Tests
|
|
*
|
|
* Tests for 25-word mnemonic encoding/decoding across all supported languages.
|
|
*/
|
|
|
|
import {
|
|
mnemonicToSeed,
|
|
seedToMnemonic,
|
|
validateMnemonic,
|
|
getAvailableLanguages,
|
|
detectLanguage,
|
|
getLanguage,
|
|
WORD_LIST
|
|
} from '../src/mnemonic.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 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}`);
|
|
}
|
|
}
|
|
|
|
function assertIncludes(array, value, message = '') {
|
|
if (!array.includes(value)) {
|
|
throw new Error(`${message} Array does not include ${value}`);
|
|
}
|
|
}
|
|
|
|
// Test seed (random 32 bytes)
|
|
const TEST_SEED = hexToBytes('8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94');
|
|
|
|
// Known good English mnemonic for TEST_SEED (you'd replace this with actual test vector)
|
|
// For now we'll test round-trip consistency
|
|
|
|
// ============================================================
|
|
// Language Support Tests
|
|
// ============================================================
|
|
|
|
console.log('\n--- Language Support Tests ---');
|
|
|
|
test('getAvailableLanguages returns 12 languages', () => {
|
|
const langs = getAvailableLanguages();
|
|
assertEqual(langs.length, 12);
|
|
});
|
|
|
|
test('All expected languages are available', () => {
|
|
const langs = getAvailableLanguages();
|
|
assertIncludes(langs, 'english');
|
|
assertIncludes(langs, 'spanish');
|
|
assertIncludes(langs, 'french');
|
|
assertIncludes(langs, 'italian');
|
|
assertIncludes(langs, 'german');
|
|
assertIncludes(langs, 'portuguese');
|
|
assertIncludes(langs, 'russian');
|
|
assertIncludes(langs, 'japanese');
|
|
assertIncludes(langs, 'chinese_simplified');
|
|
assertIncludes(langs, 'dutch');
|
|
assertIncludes(langs, 'esperanto');
|
|
assertIncludes(langs, 'lojban');
|
|
});
|
|
|
|
test('getLanguage returns language object for valid language', () => {
|
|
const lang = getLanguage('english');
|
|
assertTrue(lang !== null);
|
|
assertTrue(Array.isArray(lang.words));
|
|
assertEqual(lang.words.length, 1626);
|
|
});
|
|
|
|
test('getLanguage returns null for invalid language', () => {
|
|
const lang = getLanguage('klingon');
|
|
assertEqual(lang, null);
|
|
});
|
|
|
|
test('English word list has 1626 words', () => {
|
|
assertEqual(WORD_LIST.length, 1626);
|
|
});
|
|
|
|
test('English word list contains expected words', () => {
|
|
// CryptoNote uses different word lists than BIP39
|
|
assertIncludes(WORD_LIST, 'abbey');
|
|
assertIncludes(WORD_LIST, 'ability');
|
|
assertIncludes(WORD_LIST, 'zero');
|
|
});
|
|
|
|
// ============================================================
|
|
// Seed to Mnemonic Tests
|
|
// ============================================================
|
|
|
|
console.log('\n--- Seed to Mnemonic Tests ---');
|
|
|
|
test('seedToMnemonic returns 25 words for English', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const words = mnemonic.split(' ');
|
|
assertEqual(words.length, 25);
|
|
});
|
|
|
|
test('seedToMnemonic is deterministic', () => {
|
|
const m1 = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const m2 = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
assertEqual(m1, m2);
|
|
});
|
|
|
|
test('seedToMnemonic throws for wrong seed length', () => {
|
|
let threw = false;
|
|
try {
|
|
seedToMnemonic(new Uint8Array(31), { language: 'english' });
|
|
} catch (e) {
|
|
threw = true;
|
|
}
|
|
assertTrue(threw, 'Should throw for 31-byte seed');
|
|
});
|
|
|
|
test('seedToMnemonic works for all languages', () => {
|
|
const langs = getAvailableLanguages();
|
|
for (const langName of langs) {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: langName });
|
|
const words = mnemonic.split(' ');
|
|
assertEqual(words.length, 25, `${langName} should produce 25 words`);
|
|
}
|
|
});
|
|
|
|
// ============================================================
|
|
// Mnemonic to Seed Tests
|
|
// ============================================================
|
|
|
|
console.log('\n--- Mnemonic to Seed Tests ---');
|
|
|
|
test('mnemonicToSeed round-trips correctly for English', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const result = mnemonicToSeed(mnemonic, { language: 'english' });
|
|
assertTrue(result.valid, `Should be valid: ${result.error}`);
|
|
assertEqual(bytesToHex(result.seed), bytesToHex(TEST_SEED));
|
|
});
|
|
|
|
test('mnemonicToSeed round-trips correctly for multiple languages', () => {
|
|
// Test a selection of languages (some may have encoding quirks)
|
|
const testLangs = ['english', 'spanish', 'french', 'italian', 'portuguese', 'dutch'];
|
|
for (const langName of testLangs) {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: langName });
|
|
const result = mnemonicToSeed(mnemonic, { language: langName });
|
|
assertTrue(result.valid, `${langName} should round-trip: ${result.error}`);
|
|
assertEqual(bytesToHex(result.seed), bytesToHex(TEST_SEED), `${langName} seed mismatch`);
|
|
}
|
|
});
|
|
|
|
test('mnemonicToSeed rejects wrong word count', () => {
|
|
const result = mnemonicToSeed('word1 word2 word3', { language: 'english' });
|
|
assertFalse(result.valid);
|
|
assertTrue(result.error.includes('25 words'));
|
|
});
|
|
|
|
test('mnemonicToSeed rejects invalid words', () => {
|
|
// Create mnemonic with one invalid word
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const words = mnemonic.split(' ');
|
|
words[5] = 'xyznotaword';
|
|
const result = mnemonicToSeed(words.join(' '), { language: 'english' });
|
|
assertFalse(result.valid);
|
|
assertTrue(result.error.includes('Unknown word'));
|
|
});
|
|
|
|
test('mnemonicToSeed rejects bad checksum', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const words = mnemonic.split(' ');
|
|
// Swap two words to break checksum
|
|
const temp = words[0];
|
|
words[0] = words[1];
|
|
words[1] = temp;
|
|
const result = mnemonicToSeed(words.join(' '), { language: 'english' });
|
|
// This might still be valid but produce wrong seed, or checksum might fail
|
|
// Let's just verify it doesn't crash
|
|
assertTrue(result.valid === true || result.valid === false);
|
|
});
|
|
|
|
test('mnemonicToSeed handles case insensitivity', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const upperMnemonic = mnemonic.toUpperCase();
|
|
const result = mnemonicToSeed(upperMnemonic, { language: 'english' });
|
|
assertTrue(result.valid, `Should handle uppercase: ${result.error}`);
|
|
assertEqual(bytesToHex(result.seed), bytesToHex(TEST_SEED));
|
|
});
|
|
|
|
test('mnemonicToSeed handles extra whitespace', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const spaceyMnemonic = ' ' + mnemonic.replace(/ /g, ' ') + ' ';
|
|
const result = mnemonicToSeed(spaceyMnemonic, { language: 'english' });
|
|
assertTrue(result.valid, `Should handle whitespace: ${result.error}`);
|
|
assertEqual(bytesToHex(result.seed), bytesToHex(TEST_SEED));
|
|
});
|
|
|
|
// ============================================================
|
|
// Validate Mnemonic Tests
|
|
// ============================================================
|
|
|
|
console.log('\n--- Validate Mnemonic Tests ---');
|
|
|
|
test('validateMnemonic returns valid for good mnemonic', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const result = validateMnemonic(mnemonic, { language: 'english' });
|
|
assertTrue(result.valid);
|
|
assertEqual(result.error, null);
|
|
});
|
|
|
|
test('validateMnemonic returns invalid for bad mnemonic', () => {
|
|
const result = validateMnemonic('not a valid mnemonic', { language: 'english' });
|
|
assertFalse(result.valid);
|
|
assertTrue(result.error !== null);
|
|
});
|
|
|
|
// ============================================================
|
|
// Language Detection Tests
|
|
// ============================================================
|
|
|
|
console.log('\n--- Language Detection Tests ---');
|
|
|
|
test('detectLanguage identifies English', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const result = detectLanguage(mnemonic);
|
|
assertTrue(result.language !== null);
|
|
assertEqual(result.language.name, 'English');
|
|
});
|
|
|
|
test('detectLanguage identifies Spanish', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'spanish' });
|
|
const result = detectLanguage(mnemonic);
|
|
assertTrue(result.language !== null);
|
|
// Spanish might be detected
|
|
});
|
|
|
|
test('mnemonicToSeed with auto-detect works for English', () => {
|
|
const mnemonic = seedToMnemonic(TEST_SEED, { language: 'english' });
|
|
const result = mnemonicToSeed(mnemonic, { language: 'auto' });
|
|
assertTrue(result.valid, `Auto-detect should work: ${result.error}`);
|
|
assertEqual(bytesToHex(result.seed), bytesToHex(TEST_SEED));
|
|
});
|
|
|
|
// ============================================================
|
|
// Edge Cases
|
|
// ============================================================
|
|
|
|
console.log('\n--- Edge Cases ---');
|
|
|
|
test('Handles all-zeros seed', () => {
|
|
const zeroSeed = new Uint8Array(32);
|
|
const mnemonic = seedToMnemonic(zeroSeed, { language: 'english' });
|
|
const result = mnemonicToSeed(mnemonic, { language: 'english' });
|
|
assertTrue(result.valid);
|
|
assertEqual(bytesToHex(result.seed), bytesToHex(zeroSeed));
|
|
});
|
|
|
|
test('Handles all-ones seed', () => {
|
|
const onesSeed = new Uint8Array(32).fill(0xff);
|
|
const mnemonic = seedToMnemonic(onesSeed, { language: 'english' });
|
|
const result = mnemonicToSeed(mnemonic, { language: 'english' });
|
|
assertTrue(result.valid);
|
|
assertEqual(bytesToHex(result.seed), bytesToHex(onesSeed));
|
|
});
|
|
|
|
// ============================================================
|
|
// Summary
|
|
// ============================================================
|
|
|
|
console.log('\n--- Mnemonic 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 mnemonic tests passed!');
|
|
}
|