OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / server-mnemonic.test.js

69 lines · javascript ref:
import test from 'brittle'
import crypto from 'hypercore-crypto'
import bip39 from 'bip39-mnemonic'
import { ServerIdentity } from '../../lib/p2p/identity/server-identity.js'
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'

const { entropyToMnemonic, mnemonicToEntropy, validateMnemonic } = bip39

test('server key seed encodes as 24-word BIP39 mnemonic', (t) => {
  const keypair = crypto.keyPair()
  const seed = keypair.secretKey.subarray(0, 32)
  const mnemonic = entropyToMnemonic(seed)
  const words = mnemonic.split(' ')
  t.is(words.length, 24, '32 bytes = 256 bits = 24 words')
  t.ok(validateMnemonic(mnemonic), 'valid BIP39 mnemonic')
})

test('mnemonic round-trips to same keypair', (t) => {
  const keypair = crypto.keyPair()
  const seed = keypair.secretKey.subarray(0, 32)
  const mnemonic = entropyToMnemonic(seed)
  const recoveredSeed = mnemonicToEntropy(mnemonic)
  const recoveredKeypair = crypto.keyPair(Buffer.from(recoveredSeed))
  t.alike(recoveredKeypair.publicKey, keypair.publicKey, 'same public key')
  t.alike(recoveredKeypair.secretKey, keypair.secretKey, 'same secret key')
})

test('ServerIdentity.toMnemonic() and initFromMnemonic() round-trip', async (t) => {
  const suffix = Date.now() + '-' + Math.random().toString(36).slice(2)
  const tmpDir = path.join(os.tmpdir(), 'pear-git-mnemonic-test-' + suffix)
  const tmpDir2 = path.join(os.tmpdir(), 'pear-git-mnemonic-test2-' + suffix)

  try {
    // Manually create identity (bypass _generateIdentity's StorageLocator check)
    const keypair = crypto.keyPair()
    fs.mkdirSync(tmpDir, { recursive: true })
    fs.writeFileSync(
      path.join(tmpDir, 'server-identity.json'),
      JSON.stringify({
        publicKey: keypair.publicKey.toString('hex'),
        secretKey: keypair.secretKey.toString('hex'),
        createdAt: Date.now()
      }, null, 2),
      { mode: 0o600 }
    )

    const identity = new ServerIdentity({ storagePath: tmpDir })
    await identity.init()

    const mnemonic = identity.toMnemonic()
    t.is(mnemonic.split(' ').length, 24, '24-word mnemonic')

    const restored = new ServerIdentity({ storagePath: tmpDir2 })
    await restored.initFromMnemonic(mnemonic)

    t.alike(restored.publicKey, identity.publicKey, 'same public key')
    t.alike(restored.secretKey, identity.secretKey, 'same secret key')

    // Verify the saved file has restoredFromMnemonic flag
    const saved = JSON.parse(fs.readFileSync(path.join(tmpDir2, 'server-identity.json'), 'utf-8'))
    t.is(saved.restoredFromMnemonic, true, 'restoredFromMnemonic flag set')
  } finally {
    try { fs.rmSync(tmpDir, { recursive: true }) } catch {}
    try { fs.rmSync(tmpDir2, { recursive: true }) } catch {}
  }
})