OverviewFilesCommitsIssuesDocsReleasesPeers

favela-identity commits / a689170

the bytes a bridge claim is signed over, defined once
The claimant builds them and the bridge rebuilds them. If the two ever disagree by a byte, readAttested returns null and the bridge answers "proof does not attest that identity" — which reads as a hostile or broken caller, not as a version skew. There is no error that says "we disagree about the format", so there must not be two definitions. This package already exists for exactly that reason: its own header says the attestation coupling "lives HERE so there is one place to fix if it moves", and it is already a dependency of both favela-peer and ap-bridge. Claim and release have distinct prefixes so a claim proof can never verify as a release proof — releasing BURNS a name permanently, and a proof repurposable between the two would let a captured claim destroy what it was meant to create. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a6891707cdfd7390947542fa61f00cb7b286c7b3
by plankatron <fleeky@gmail.com> · 2026-09-11T16:19:58.000Z

Changed files (3) +47 -3

index.js +4 -0
……@@ -11,3 +11,7 @@
1111// That property is the reason admission to a bridge can be proved by a favela identity rather than
1212// by an atproto DID.
1313export { readAttested, readProof, signAttested } from './lib/attestation.js'
14
15// The bytes a peer signs to claim a name on a bridge. Here so the claimant and the verifier
16// cannot disagree about them — a mismatch is indistinguishable from a forged proof.
17export { claimBytes, releaseBytes, CLAIM, RELEASE } from './lib/bridge-claim.js'
lib/bridge-claim.js +37 -0
……@@ -0,0 +1,37 @@
1// bridge-claim.js — the exact bytes a peer signs to claim a name on a bridge.
2//
3// ⛔ ONE DEFINITION, BECAUSE A MISMATCH IS SILENT. The claimant builds these bytes and the bridge
4// rebuilds them; if the two ever disagree by a byte, `readAttested` returns null and the bridge
5// reports "proof does not attest that identity" — which reads as a hostile or broken caller, not as
6// a version skew. There is no error that says "we disagree about the format". So the format lives in
7// the one package both sides already depend on, next to the verifier that consumes it, rather than
8// being written twice and eventually written wrong.
9//
10// ⛔ VERSIONED AND PREFIXED. The prefix keeps a claim proof from ever verifying as a release proof —
11// releasing BURNS a name permanently, and a proof that could be repurposed between the two would let
12// a captured claim destroy what it was meant to create. The version means what is bound can change
13// later without both shapes being silently accepted at once.
14//
15// ⚠ Colons are safe separators here and that is a property of the inputs, not an accident: a bridge
16// username cannot contain one (it must be a usable path segment), and nonces and feed keys are hex.
17
18import b4a from 'b4a'
19
20export const CLAIM = 'favela-bridge:v1:claim'
21export const RELEASE = 'favela-bridge:v1:release'
22
23/**
24 * What a claimant signs. The REQUEST is inside it, not just the nonce: a proof covering only the
25 * challenge could be captured and replayed to claim a DIFFERENT name under the signer's root —
26 * consent proven, control proven, and the wrong name registered.
27 */
28export function claimBytes ({ nonce, username, feedKey }) {
29 return b4a.from([CLAIM, nonce, username, feedKey || ''].join(':'), 'utf8')
30}
31
32/** What a releaser signs. No feed: giving up a name says nothing about where a feed lives. */
33export function releaseBytes ({ nonce, username }) {
34 return b4a.from([RELEASE, nonce, username].join(':'), 'utf8')
35}
36
37export default { claimBytes, releaseBytes, CLAIM, RELEASE }
package.json +6 -3
……@@ -1,13 +1,15 @@
11{
22 "name": "favela-identity",
3 "version": "0.0.1",
3 "version": "0.0.2",
44 "description": "favela's identity primitives. Attestation today; the account layer (a User is durable, a DID is one credential, a bridge is a resource) as it lands.",
55 "type": "module",
66 "license": "MIT",
77 "main": "index.js",
88 "exports": {
99 ".": "./index.js",
10 "./lib/attestation.js": "./lib/attestation.js"
10 "./lib/attestation.js": "./lib/attestation.js",
11 "./lib/peer-client.js": "./lib/peer-client.js",
12 "./lib/bridge-claim.js": "./lib/bridge-claim.js"
1113 },
1214 "scripts": {
1315 "test": "brittle-bare test/*.test.js"
……@@ -18,7 +20,8 @@
1820 "compact-encoding": "^3.3.0",
1921 "bare-fs": "^4.0.0",
2022 "bare-path": "^3.0.0",
21 "hypercore-crypto": "^3.4.0"
23 "hypercore-crypto": "^3.4.0",
24 "bare-net": "^1.3.0"
2225 },
2326 "devDependencies": {
2427 "brittle": "^3.5.0"