Skip to content

Seed from Mnemonic

← Back to Mnemonic Utilities

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
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example algo25/02-seed-from-mnemonic.ts

View source on GitHub

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()