OverviewFilesCommitsIssuesDocsReleasesPeers

keet-daemon / test / probe-state-deterministic.js

106 lines · javascript ref:
// Probe (room-state v2): DETERMINISTIC base key from the room seed.
// Two daemons with the SAME roomSeed derive the SAME base.key with NO announce
// / key conveyance (keet's pattern: room key = getWriterKey of a seed-derived
// keypair). The elected creator (mod) writes genesis; the member (peer) opens
// the SAME derived base, replicates the creator's write, is attestation-
// admitted, and its own write reaches the creator. Rendezvous is purely the
// derived discoveryKey — no announce swarm.
//
// Run: bare test/probe-state-deterministic.js
// PASS = both base.keys equal the derived key AND writes flow both ways.

import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import crypto from 'hypercore-crypto'
import createTestnet from 'hyperdht/testnet.js'
import IdentityKey from 'keet-identity-key'
import { StateBase } from '../lib/state-base.js'

const ROOM_ID = 'probe-det-' + Math.random().toString(36).slice(2, 10)
const roomTag = crypto.hash(Buffer.from(ROOM_ID, 'utf-8'))
const roomSeed = crypto.randomBytes(32)        // the SHARED room secret (both have it)
const DEADLINE_MS = 30000

async function makeIdentity () {
  const idKey = await IdentityKey.from({ mnemonic: IdentityKey.generateMnemonic() })
  const deviceKp = crypto.keyPair()
  const deviceProof = await idKey.bootstrap(deviceKp.publicKey)
  return { identityPub: idKey.identityPublicKey, attest: (d) => IdentityKey.attestData(d, deviceKp, deviceProof) }
}
const verifyAttest = (proof, data, identity) => {
  try { return IdentityKey.verify(proof, data, { expectedIdentity: identity }) !== null } catch { return false }
}

const C = await makeIdentity()  // mod / creator
const M = await makeIdentity()  // peer / member
const MEMBERS = [
  { key: C.identityPub.toString('hex'), role: 2 },
  { key: M.identityPub.toString('hex'), role: 3 }
]
const getMembers = async () => MEMBERS

const dirC = path.join(os.tmpdir(), 'probe-det-C-' + Date.now())
const dirM = path.join(os.tmpdir(), 'probe-det-M-' + Date.now())
fs.mkdirSync(dirC, { recursive: true }); fs.mkdirSync(dirM, { recursive: true })

console.log('roomSeed:', roomSeed.toString('hex').slice(0, 16) + '…')
console.log('\nspinning up local DHT testnet...')
const testnet = await createTestnet(3)
const swarmOpts = { bootstrap: testnet.bootstrap }

// In production roomSeed === encryptionKey === the room's members-only secret,
// used BOTH for the deterministic base.key AND for encrypting the base at rest.
const mk = (dir, id) => new StateBase(dir, roomTag, { swarmOpts, roomSeed, encryptionKey: roomSeed, selfKey: id.identityPub, attest: id.attest, verifyAttest, getMembers })
const sbC = mk(dirC, C)
const sbM = mk(dirM, M)

let exitCode = 1
const fail = (m) => console.error('\n✗ FAIL:', m)

try {
  await sbC.ready()
  await sbM.ready()

  // 1. DETERMINISTIC: both derived the same base.key from roomSeed, with no announce.
  const cKey = sbC.stateBaseKey ? sbC.stateBaseKey.toString('hex') : null
  const mKey = sbM.stateBaseKey ? sbM.stateBaseKey.toString('hex') : null
  console.log('[det] C base.key:', cKey && cKey.slice(0, 16) + '…')
  console.log('[det] M base.key:', mKey && mKey.slice(0, 16) + '…')
  if (!cKey || cKey !== mKey) throw new Error('base.keys differ — derivation not deterministic (C=' + cKey + ' M=' + mKey + ')')
  console.log('[det] ✓ identical derived base.key (no announce, no conveyance)')

  console.log('[det] creator writes world:x = 7.0')
  await sbC.set('world:x', 7.0)

  console.log('\npolling: member replicates creator write + gets admitted + its write reaches creator…')
  const start = Date.now(); let lastLog = 0; let mWrote = false
  while (Date.now() - start < DEADLINE_MS) {
    const mReadsC = await sbM.get('world:x').catch(() => null)
    const mWritable = !!(sbM.base && sbM.base.writable)
    if (mWritable && !mWrote) { try { await sbM.set('m:y', 'from-member'); mWrote = true } catch {} }
    const cReadsM = mWrote ? await sbC.get('m:y').catch(() => null) : null
    if (mReadsC && mReadsC.value === 7.0 && mWritable && cReadsM && cReadsM.value === 'from-member') {
      console.log('\n✓ deterministic base + bidirectional writes: member read creator\'s 7.0 and member\'s write reached creator')
      exitCode = 0
      break
    }
    const now = Date.now()
    if (now - lastLog > 3000) { lastLog = now; console.log('  M.reads(world:x)=', mReadsC && mReadsC.value, ' M.writable=', mWritable, ' C.reads(m:y)=', cReadsM && cReadsM.value) }
    await new Promise(r => setTimeout(r, 500))
  }
  if (exitCode !== 0) fail('did not converge bidirectionally within deadline')
} catch (err) {
  fail(err.message); if (err.stack) console.error(err.stack)
} finally {
  await Promise.race([
    (async () => { for (const sb of [sbC, sbM]) await sb.close().catch(() => {}); await testnet.destroy().catch(() => {}) })(),
    new Promise(r => setTimeout(r, 3000))
  ])
  try { fs.rmSync(dirC, { recursive: true, force: true }) } catch {}
  try { fs.rmSync(dirM, { recursive: true, force: true }) } catch {}
}

console.log(exitCode === 0 ? '\nPASS' : '\nFAIL')
Bare.exit(exitCode)