OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-base / test / attestation.test.mjs

71 lines · javascript ref:
// PHASE 0 #3 — keet-ATTESTATION ADMISSION. Real co-owner devices don't sign admission with the member
// key directly; a DEVICE (attested to the member identity) signs, and the verifier checks the
// attestation. atproto-base threads injectable attest/verifyAttest (the model hpp uses via
// atproto-room/lib/membership.js). Here a mock attestation scheme exercises the wiring: a device
// attested to its member is admitted; a device NOT attested is rejected.

import test from 'brittle'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import crypto from 'hypercore-crypto'
import { deriveAtprotoKey, createAtprotoIdentity } from 'atproto-identity'
import { AtprotoBase } from '../lib/base.js'

const MNEMONIC = 'test test test test test test test test test test test junk'
const ID = createAtprotoIdentity(MNEMONIC, { handle: 'x.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
const KEY = deriveAtprotoKey(MNEMONIC)
const post = (text) => ({ $type: 'app.bsky.feed.post', text, createdAt: '2026-07-23T00:00:00.000Z', langs: ['en'] })
const hex = (b) => b.toString('hex')
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
function mk (opts) { const dir = mkdtempSync(join(tmpdir(), 'att-')); const base = new AtprotoBase({ path: dir, did: ID.did, privateKey: KEY.privateKey, ackInterval: 100, ...opts }); return { base, dir, close: async () => { try { await base.close() } catch {} ; rmSync(dir, { recursive: true, force: true }) } } }
const pipe = (a, b) => { const s1 = a.base.base.replicate(true); const s2 = b.base.base.replicate(false); s1.pipe(s2).pipe(s1); return () => { try { s1.destroy() } catch {} ; try { s2.destroy() } catch {} } }
async function settle (...ws) { for (let i = 0; i < 20; i++) { for (const w of ws) { try { await w.base.base.update() } catch {} } await sleep(60) } }

// Mock keet-attestation: a member key ATTESTS a device (cert = member signs deviceKey). attest signs
// signData with the device; verifyAttest checks (a) the member attested the device, (b) the device
// signed signData. Contract: attest(signData)->Buffer; verifyAttest(proof, signData, identity)->bool.
const makeAttest = (deviceKp, memberKp) => {
  const cert = crypto.sign(deviceKp.publicKey, memberKp.secretKey)
  return async (signData) => Buffer.from(JSON.stringify({ device: hex(deviceKp.publicKey), cert: hex(cert), sig: hex(crypto.sign(signData, deviceKp.secretKey)) }))
}
const makeUnattested = (deviceKp) => { // cert signed by a RANDOM key → the member never attested this device
  const cert = crypto.sign(deviceKp.publicKey, crypto.keyPair().secretKey)
  return async (signData) => Buffer.from(JSON.stringify({ device: hex(deviceKp.publicKey), cert: hex(cert), sig: hex(crypto.sign(signData, deviceKp.secretKey)) }))
}
const verifyAttest = async (proof, signData, identity) => {
  try {
    const p = JSON.parse(proof.toString()); const device = Buffer.from(p.device, 'hex')
    if (!crypto.verify(device, Buffer.from(p.cert, 'hex'), identity)) return false // did `identity` attest this device?
    return crypto.verify(signData, Buffer.from(p.sig, 'hex'), device)              // did the device sign signData?
  } catch { return false }
}

test('keet-attestation: an attested device is admitted and its write converges', async (t) => {
  const cM = crypto.keyPair(); const cD = crypto.keyPair(); const jM = crypto.keyPair(); const jD = crypto.keyPair()
  const roster = [{ key: hex(cM.publicKey), role: 1 }, { key: hex(jM.publicKey), role: 1 }]

  const C = mk({ identity: { publicKey: cM.publicKey }, members: roster, attest: makeAttest(cD, cM), verifyAttest }); await C.base.ready()
  const J = mk({ bootstrap: C.base.base.key, identity: { publicKey: jM.publicKey }, attest: makeAttest(jD, jM), verifyAttest }); await J.base.ready()
  const un = pipe(C, J); t.teardown(async () => { un(); await C.close(); await J.close() })

  await C.base.projectMembers(roster); await settle(C, J)
  await J.base.putRecord('app.bsky.feed.post', '3j', post('attested joiner'))
  t.ok(J.base.writable, 'attested device was admitted (keet-attestation proof accepted)')
  await settle(C, J); await C.base.settled()
  t.ok((await C.base.indexedRecords()).find((r) => r.key.endsWith('3j')), "joiner's record converged into the shared view")
})

test('keet-attestation: a device NOT attested to the member is rejected', async (t) => {
  const cM = crypto.keyPair(); const cD = crypto.keyPair(); const jM = crypto.keyPair(); const jD = crypto.keyPair()
  const roster = [{ key: hex(cM.publicKey), role: 1 }, { key: hex(jM.publicKey), role: 1 }]

  const C = mk({ identity: { publicKey: cM.publicKey }, members: roster, attest: makeAttest(cD, cM), verifyAttest }); await C.base.ready()
  const J = mk({ bootstrap: C.base.base.key, identity: { publicKey: jM.publicKey }, attest: makeUnattested(jD), verifyAttest }); await J.base.ready()
  const un = pipe(C, J); t.teardown(async () => { un(); await C.close(); await J.close() })

  await C.base.projectMembers(roster); await settle(C, J)
  await t.exception(() => J.base.ensureWritable({ timeoutMs: 2500 }), 'unattested device is rejected (throws — not admitted)')
  t.absent(J.base.writable, 'never became a writer')
})