OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / handle-verdict.test.js

50 lines · javascript ref:
// Resolving a handle is TWO checks, in two places, and this is the second one.
//
// `Namespace.resolve` proves who MADE a claim — from the claim's own signed bytes, inside apply,
// with no state at all. What apply structurally cannot prove is that the identity owns the feed the
// claim points at: the namespace is a separate log with no access to a feed's `__founder__`. So a
// stranger CAN aim a name at someone else's feed, under their own root, and it verifies.
//
// ⛔ That lie is only detectable where the feed is held, which is what this decides. NAMING.md §3
// resolves a name to an identity ROOT and only then to a room key; skipping the second hop is what
// made claims unauthenticated in the first place.

import test from 'brittle'
import { handleVerdict } from '../lib/node.js'

const ROOT = 'a'.repeat(64)
const OTHER = 'b'.repeat(64)
const claim = (root = ROOT) => ({ handle: 'alice0', name: 'alice', number: 0, feed: 'f'.repeat(64), root })

test('the feed agrees — the handle means what it says', (t) => {
  const v = handleVerdict(claim(), { device: 'd'.repeat(64), identityRoot: ROOT })
  t.is(v.verdict, 'confirmed')
  t.is(v.founder, ROOT, 'and the founder is reported, not merely compared')
  t.is(v.handle, 'alice0', 'the claim travels through intact')
})

test('★ the feed is founded by SOMEONE ELSE — this is the attack', (t) => {
  // A stranger claims `alice` pointing at a feed they do not own. The claim itself is perfectly
  // valid: signed, unforgeable, un-liftable — and a lie. Nothing before this point can tell.
  const v = handleVerdict(claim(OTHER), { device: 'd'.repeat(64), identityRoot: ROOT })
  t.is(v.verdict, 'MISMATCH', 'refused')
  t.is(v.founder, ROOT, 'and BOTH roots are surfaced, so the disagreement is legible')
  t.is(v.root, OTHER)
})

test('a feed nobody founded names nobody', (t) => {
  t.is(handleVerdict(claim(), null).verdict, 'unfounded', 'no founder record at all')
  t.is(handleVerdict(claim(), {}).verdict, 'unfounded', 'a founder record with no root is the same')
  // ⚠ Not 'confirmed'. An empty feed agreeing with everyone is how a check becomes decorative.
  t.not(handleVerdict(claim(), {}).verdict, 'confirmed')
})

test('⛔ the comparison is on hex strings, and a near-miss is a MISMATCH', (t) => {
  // A Buffer compared by identity here would reject everything; a loose compare would accept
  // anything. Both failures are silent, so the boundary is pinned.
  t.is(handleVerdict(claim(ROOT), { identityRoot: ROOT.slice(0, 63) + 'b' }).verdict, 'MISMATCH',
    'one character apart is not the same identity')
  t.is(handleVerdict(claim(ROOT), { identityRoot: ROOT.toUpperCase() }).verdict, 'MISMATCH',
    'case is not normalised HERE — both sides come from attestation.js, which emits lower-case hex')
})