OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / knock-identity.test.js

113 lines · javascript ref:
// ⛔ A knock proves WHO is knocking: an identity root, never a bare feed key.
//
// THE DEFECT (hyper-social #77 §1, 2026-10-12). `applyKnock` recorded `__knocks__/<op.feed>` for
// whatever feed the op named, and `applyListFeed` read that row as the named feed's CONSENT to be
// listed. Nothing tied the device that wrote the knock to the feed it named, so anyone holding a
// registry's log could knock "as" any feed and get it listed without its owner ever asking. The
// comment on applyListFeed claimed the opposite ("the knock is written by the knocker's own device").
//
// Why the fix names a ROOT and not a feed: inside apply, this log cannot read another feed's log, so
// "this device belongs to feed X" cannot be proven here. "This device chains to identity root R" can,
// from the proof's own bytes, exactly as add-device does. A feed key is a locator; the identity root is
// who (favela-markdown `uri.js`: the authority is the identity root). So the knock is keyed by the
// proven root, a listing records that root, and a READER checks the listed feed's founder against it
// (Peer.directory).

import test from 'brittle'
import { identity } from './helpers.js'
import { applyOp, isRosterKey, KNOCKS, LISTING, LISTING_ROOT } from '../lib/feed.js'
import { proofsFor } from './helpers.js'

const kv = () => {
  const m = new Map()
  return {
    m,
    async set (k, v) { m.set(k, v) },
    async get (k) { return m.has(k) ? m.get(k) : null },
    entries () { return [...m.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) }
  }
}

const OWNER = 'd1'.repeat(32) // the registry's own device
const STRANGER = 'ff'.repeat(32) // the knocking WRITER core, on the registry's log
const OTHER_WRITER = 'ee'.repeat(32)
const THEIR_FEED = 'ab'.repeat(32)
const VICTIM_FEED = 'cd'.repeat(32)

const PROOF = await proofsFor([OWNER])
const STRANGER_ID = await identity()
const STRANGER_DEVICE = await STRANGER_ID.device()
const STRANGER_ROOT = STRANGER_ID.rootHex

const addDevice = (device) => ({ t: 'add-device', device, proof: PROOF[device] })
const run = async (ops, view = kv()) => {
  for (const [device, op] of ops) {
    const genesis = !view.entries().some(([k]) => isRosterKey(k))
    await applyOp(view, op, { device, genesis })
  }
  return view
}
const knocksIn = (v) => v.entries().filter(([k]) => k.startsWith(KNOCKS))

test('⛔ an unproven knock records nothing', async (t) => {
  const v = await run([[OWNER, addDevice(OWNER)], [STRANGER, { t: 'knock', feed: VICTIM_FEED }]])
  t.alike(knocksIn(v), [], 'no proof, no knock — a bare feed key is not consent from that feed')
})

test('a proven knock is keyed by the knocker\'s identity root and names its feed', async (t) => {
  const v = await run([
    [OWNER, addDevice(OWNER)],
    [STRANGER, { t: 'knock', feed: THEIR_FEED, proof: STRANGER_DEVICE.bind(STRANGER) }]
  ])
  t.alike(await v.get(KNOCKS + '/' + STRANGER_ROOT), { device: STRANGER, feed: THEIR_FEED },
    'the row is the proven root: who knocked, with the feed they asked about')
})

test('⛔ a proof bound to a different writer is refused', async (t) => {
  const v = await run([
    [OWNER, addDevice(OWNER)],
    [STRANGER, { t: 'knock', feed: THEIR_FEED, proof: STRANGER_DEVICE.bind(OTHER_WRITER) }]
  ])
  t.alike(knocksIn(v), [], 'a proof is never a bearer token for another core')
})

test('knocking twice is one row per identity, the latest feed named', async (t) => {
  const v = await run([
    [OWNER, addDevice(OWNER)],
    [STRANGER, { t: 'knock', feed: THEIR_FEED, proof: STRANGER_DEVICE.bind(STRANGER) }],
    [STRANGER, { t: 'knock', feed: THEIR_FEED, proof: STRANGER_DEVICE.bind(STRANGER) }]
  ])
  t.is(knocksIn(v).length, 1, 'keyed by root, so a flood is one row')
})

test('⛔ a listing needs a proven knock from that root naming that same feed', async (t) => {
  const knock = [STRANGER, { t: 'knock', feed: THEIR_FEED, proof: STRANGER_DEVICE.bind(STRANGER) }]
  const list = (feed) => [OWNER, { t: 'list-feed', root: STRANGER_ROOT, feed, $oid: 'o', size: 1 }]

  const wrongFeed = await run([[OWNER, addDevice(OWNER)], knock, list(VICTIM_FEED)])
  t.is(await wrongFeed.get(LISTING + '/' + VICTIM_FEED), null, 'the root asked about ITS feed, not this one')

  const ok = await run([[OWNER, addDevice(OWNER)], knock, list(THEIR_FEED)])
  t.alike(await ok.get(LISTING + '/' + THEIR_FEED), { $oid: 'o', size: 1 }, 'listed: the pointer stays exactly { $oid, size }')
  t.alike(await ok.get(LISTING_ROOT + '/' + THEIR_FEED), { root: STRANGER_ROOT },
    'and the root is recorded beside it, so a reader can check the feed\'s founder')
})

test('⛔ the old spoof: a feed listed on a knock it never wrote', async (t) => {
  // The exact attack: knock naming the victim's feed, then a registry lists it as consent.
  const v = await run([
    [OWNER, addDevice(OWNER)],
    [STRANGER, { t: 'knock', feed: VICTIM_FEED }],
    [OWNER, { t: 'list-feed', feed: VICTIM_FEED, $oid: 'o', size: 1 }]
  ])
  t.is(await v.get(LISTING + '/' + VICTIM_FEED), null, 'refused: nobody proved they own the victim\'s feed')
})

test('dismissing is by root, and needs admission', async (t) => {
  const knock = [STRANGER, { t: 'knock', feed: THEIR_FEED, proof: STRANGER_DEVICE.bind(STRANGER) }]
  const v = await run([[OWNER, addDevice(OWNER)], knock, [STRANGER, { t: 'dismiss-knock', root: STRANGER_ROOT }]])
  t.ok(await v.get(KNOCKS + '/' + STRANGER_ROOT), 'a stranger cannot clear a knock')
  await applyOp(v, { t: 'dismiss-knock', root: STRANGER_ROOT }, { device: OWNER, genesis: false })
  t.alike(await v.get(KNOCKS + '/' + STRANGER_ROOT), { $deleted: true }, 'the owner can')
})