// Probe (room-state spec P3): membership-gated admission via REAL keet
// attestation. Validates the injected attest/verifyAttest path of StateBase
// against the actual keet-identity-key library (IdentityKey.from / bootstrap /
// attestData / verify) — the same primitive keet-core uses to attest member
// profiles (keet-core/index.js:1849 internalAttestData → IdentityKey.attestData).
//
// C = MOD → creator, writer/indexer
// P = PEER → keet member with a valid identity attestation → ADMITTED
// X = OUTSIDER → has a valid attestation for ITS OWN identity, but that
// identity is not in the member list → REFUSED (and it cannot
// forge P's identity: it lacks P's identity secret)
//
// Run: bare test/probe-state-attestation.js
// PASS = P admitted (write reaches C); X refused (non-writable).
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-attest-' + Math.random().toString(36).slice(2, 10)
const roomTag = crypto.hash(Buffer.from(ROOM_ID, 'utf-8'))
const DEADLINE_MS = 30000
// Build a real keet identity + attested device (mirrors keet-core's identity).
async function makeIdentity () {
const idKey = await IdentityKey.from({ mnemonic: IdentityKey.generateMnemonic() })
const identityPub = idKey.identityPublicKey // Buffer(32) — the keet identity
const deviceKp = crypto.keyPair() // this device's keypair
const deviceProof = await idKey.bootstrap(deviceKp.publicKey) // proof: identity attests device (async!)
return {
identityPub,
// attest(data) signs data with the device key + embeds the identity→device chain
attest: (data) => IdentityKey.attestData(data, deviceKp, deviceProof)
}
}
// Shared verifier — exactly how keet verifies a member attestation.
function verifyAttest (proof, data, expectedIdentity) {
try { return IdentityKey.verify(proof, data, { expectedIdentity }) !== null } catch { return false }
}
const C = await makeIdentity() // mod / creator
const P = await makeIdentity() // peer member
const X = await makeIdentity() // outsider (valid identity, NOT a member)
const MEMBERS = [
{ key: C.identityPub.toString('hex'), role: 2 }, // mod
{ key: P.identityPub.toString('hex'), role: 3 } // peer
]
const getMembers = async () => MEMBERS
const dirs = {}
for (const n of ['C', 'P', 'X']) {
dirs[n] = path.join(os.tmpdir(), 'probe-attest-' + n + '-' + Date.now())
fs.mkdirSync(dirs[n], { recursive: true })
}
console.log('roomId:', ROOM_ID)
console.log('C(mod):', C.identityPub.toString('hex').slice(0, 12) + '… P(peer):', P.identityPub.toString('hex').slice(0, 12) + '… X(outsider):', X.identityPub.toString('hex').slice(0, 12) + '…')
console.log('\nspinning up local DHT testnet...')
const testnet = await createTestnet(3)
const swarmOpts = { bootstrap: testnet.bootstrap }
const mk = (dir, id) => new StateBase(dir, roomTag, {
swarmOpts, selfKey: id.identityPub, attest: id.attest, verifyAttest, getMembers
})
const sbC = mk(dirs.C, C)
const sbP = mk(dirs.P, P)
const sbX = mk(dirs.X, X)
let exitCode = 1
function fail (m) { console.error('\n✗ FAIL:', m) }
try {
console.log('\n[C] ready (creator mints)…'); await sbC.ready()
console.log('[P] ready (member, awaits attestation-admission)…'); await sbP.ready()
console.log('[X] ready (outsider, should be refused)…'); await sbX.ready()
if (!sbC.stateBaseKey) throw new Error('creator did not mint')
console.log('\npolling for attestation-gated admission outcomes…')
const start = Date.now()
let lastLog = 0
let pWroteOk = false
while (Date.now() - start < DEADLINE_MS) {
const pW = !!(sbP.base && sbP.base.writable)
const xW = !!(sbX.base && sbX.base.writable)
if (pW && !pWroteOk) { try { await sbP.set('p:flag', 'admitted'); pWroteOk = true } catch {} }
let cSeesP = null
if (pWroteOk) cSeesP = await sbC.get('p:flag').catch(() => null)
const xAdopted = !!(sbX.base && sbX.stateBaseKey)
if (pW && pWroteOk && cSeesP && cSeesP.value === 'admitted' && xAdopted && !xW) {
console.log('\n✓ P admitted via real keet attestation (write reached C); X (valid identity, non-member) REFUSED')
exitCode = 0
break
}
const now = Date.now()
if (now - lastLog > 3000) { lastLog = now; console.log(' P.writable=', pW, ' C.sees=', cSeesP ? cSeesP.value : null, ' X.adopted=', xAdopted, ' X.writable=', xW) }
await new Promise(r => setTimeout(r, 500))
}
if (exitCode !== 0) fail('expected P attestation-admitted and X refused; P.writable=' + !!(sbP.base && sbP.base.writable) + ' X.writable=' + !!(sbX.base && sbX.base.writable))
if (sbX.base && sbX.base.writable) { fail('SECURITY: outsider X became writable'); exitCode = 1 }
} catch (err) {
fail(err.message); if (err.stack) console.error(err.stack)
} finally {
await Promise.race([
(async () => { for (const sb of [sbC, sbP, sbX]) await sb.close().catch(() => {}); await testnet.destroy().catch(() => {}) })(),
new Promise(r => setTimeout(r, 3000))
])
for (const n of ['C', 'P', 'X']) { try { fs.rmSync(dirs[n], { recursive: true, force: true }) } catch {} }
}
console.log(exitCode === 0 ? '\nPASS' : '\nFAIL')
Bare.exit(exitCode)