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