Seed from Mnemonic
Description
Section titled “Description”This example demonstrates how to use seedFromMnemonic() to convert a 25-word Algorand mnemonic back to its original 32-byte seed. This is the reverse operation of mnemonicFromSeed(). Key concepts:
- seedFromMnemonic() reverses the mnemonic encoding process
- The checksum word is verified to ensure mnemonic integrity
- Round-trip conversion: seed -> mnemonic -> seed produces identical bytes
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example algo25/02-seed-from-mnemonic.ts/** * Example: Seed from Mnemonic * * This example demonstrates how to use seedFromMnemonic() to convert a 25-word * Algorand mnemonic back to its original 32-byte seed. This is the reverse * operation of mnemonicFromSeed(). * * Key concepts: * - seedFromMnemonic() reverses the mnemonic encoding process * - The checksum word is verified to ensure mnemonic integrity * - Round-trip conversion: seed -> mnemonic -> seed produces identical bytes * * Prerequisites: * - No LocalNet required */
import { mnemonicFromSeed, seedFromMnemonic } from '@algorandfoundation/algokit-utils/algo25'import { formatHex, printError, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
/** * Compare two Uint8Arrays for equality */function arrayEqual(a: Uint8Array, b: Uint8Array): boolean { if (a.length !== b.length) return false for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false } return true}
function main() { printHeader('Seed from Mnemonic Example')
// Step 1: Generate a random 32-byte seed printStep(1, 'Generate a Random 32-byte Seed')
const originalSeed = new Uint8Array(32) crypto.getRandomValues(originalSeed)
printInfo(`Original seed length: ${originalSeed.length} bytes (256 bits)`) printInfo(`Original seed hex: ${formatHex(originalSeed)}`)
// Step 2: Convert seed to mnemonic printStep(2, 'Convert Seed to 25-Word Mnemonic')
const mnemonic = mnemonicFromSeed(originalSeed) const words = mnemonic.split(' ')
printInfo(`Mnemonic has ${words.length} words`) printInfo('Mnemonic words:') // Display words in rows of 5 for readability for (let i = 0; i < words.length; i += 5) { const row = words.slice(i, i + 5) const numbered = row.map((w, j) => `${(i + j + 1).toString().padStart(2, ' ')}. ${w.padEnd(10)}`).join(' ') printInfo(` ${numbered}`) }
// Step 3: Recover seed from mnemonic printStep(3, 'Recover Seed from Mnemonic using seedFromMnemonic()')
const recoveredSeed = seedFromMnemonic(mnemonic)
printInfo(`Recovered seed length: ${recoveredSeed.length} bytes`) printInfo(`Recovered seed hex: ${formatHex(recoveredSeed)}`)
// Step 4: Compare original and recovered seeds printStep(4, 'Compare Original and Recovered Seeds')
printInfo('Original seed:') printInfo(` ${formatHex(originalSeed)}`) printInfo('Recovered seed:') printInfo(` ${formatHex(recoveredSeed)}`)
const seedsMatch = arrayEqual(originalSeed, recoveredSeed) printInfo(`Seeds are identical: ${seedsMatch ? 'Yes' : 'No'}`)
if (seedsMatch) { printSuccess('Round-trip verification passed: seedFromMnemonic(mnemonicFromSeed(seed)) === seed') } else { printError('Round-trip verification failed!') return }
// Step 5: Byte-by-byte comparison printStep(5, 'Byte-by-Byte Verification')
printInfo('Comparing first 8 bytes:') for (let i = 0; i < 8; i++) { const origByte = originalSeed[i].toString(16).padStart(2, '0') const recByte = recoveredSeed[i].toString(16).padStart(2, '0') const match = originalSeed[i] === recoveredSeed[i] ? '✓' : '✗' printInfo(` Byte ${i}: original=0x${origByte}, recovered=0x${recByte} ${match}`) } printInfo(' ... (all 32 bytes verified)')
// Step 6: Explain how seedFromMnemonic works printStep(6, 'How seedFromMnemonic() Works')
printInfo('The recovery process:') printInfo(' 1. Split the mnemonic into 25 words') printInfo(' 2. Separate the first 24 words (data) from the 25th word (checksum)') printInfo(' 3. Look up each data word in the BIP39 wordlist to get its 11-bit index') printInfo(' 4. Combine the 24 × 11 = 264 bits back into bytes') printInfo(' 5. Remove the last byte (8 padding bits) to get the 32-byte seed') printInfo(' 6. Recompute the checksum from the recovered seed') printInfo(' 7. Verify the computed checksum matches the 25th word') printInfo(' 8. Return the 32-byte seed if checksum is valid')
// Step 7: Demonstrate checksum validation printStep(7, 'Checksum Validation Protects Against Errors')
printInfo(`Checksum word (25th word): "${words[24]}"`) printInfo('The checksum is computed from SHA-512/256 hash of the seed.') printInfo('If any word is changed, the checksum will not match.')
// Create an invalid mnemonic by changing one word const tamperedWords = [...words] tamperedWords[0] = tamperedWords[0] === 'abandon' ? 'about' : 'abandon' const tamperedMnemonic = tamperedWords.join(' ')
printInfo('') printInfo('Attempting to decode a tampered mnemonic...') printInfo(` Changed word 1 from "${words[0]}" to "${tamperedWords[0]}"`)
try { seedFromMnemonic(tamperedMnemonic) printError('Unexpectedly succeeded with tampered mnemonic!') } catch (error) { printSuccess(`Checksum validation caught the error: "${(error as Error).message}"`) }
// Step 8: Summary printStep(8, 'Summary')
printInfo('seedFromMnemonic() converts a 25-word mnemonic back to a 32-byte seed:') printInfo(' - Input: Space-separated string of 25 words') printInfo(' - Output: 32-byte Uint8Array (the original seed)') printInfo(' - Validates the checksum to ensure integrity') printInfo(' - Throws an error if:') printInfo(' - Any word is not in the BIP39 wordlist') printInfo(' - The checksum word does not match') printInfo(' - Enables round-trip: seed → mnemonic → seed') printInfo(' - Use case: Recover a seed from a backed-up mnemonic phrase')
printSuccess('Seed from Mnemonic example completed successfully!')}
main()