OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer commits / e639ffc

bridge-front: ask a bridge to carry an identity this node holds
Carried, not named. A bridge that fronts an identity serves what that identity SIGNED and holds no key for it, so this asks for availability rather than authority. `id` stays opaque to this daemon — what it means is the bridge's business, and boundary.test.js is what keeps that true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
e639ffca6e005bd71c20b720eb846403f2cc6b2e
by plankatron <fleeky@gmail.com> · 2026-09-11T16:34:57.000Z

Changed files (2) +26 -3

lib/bridge.js +14 -2
……@@ -19,7 +19,7 @@ import ProtomuxRPC from 'protomux-rpc'
1919import c from 'compact-encoding'
2020import b4a from 'b4a'
2121import { signAttested } from 'favela-identity/lib/attestation.js'
22import { claimBytes, releaseBytes } from 'favela-identity/lib/bridge-claim.js'
22import { claimBytes, releaseBytes, frontBytes } from 'favela-identity/lib/bridge-claim.js'
2323
2424export const PROTOCOL = 'favela/bridge-register/1'
2525const HEX64 = /^[0-9a-f]{64}$/i
……@@ -92,7 +92,19 @@ export function buildRelease ({ nonce, username, root, cred }) {
9292 return { username, root, nonce, proof: signAttested(releaseBytes({ nonce, username }), cred) }
9393}
9494
95export default { connectBridge, buildClaim, buildRelease, PROTOCOL }
95/**
96 * Ask a bridge to CARRY an identity this node holds, rather than to name one.
97 *
98 * ⚠ `id` is opaque to this daemon on purpose. It is a credential this node holds on some other
99 * network; what it means there is the bridge's business, and boundary.test.js is what keeps that
100 * true.
101 */
102export function buildFront ({ nonce, id, root, cred }) {
103 if (!root) throw new Error('this node cannot sign yet — link this device to its identity first')
104 return { id, root, nonce, proof: signAttested(frontBytes({ nonce, id }), cred) }
105}
106
107export default { connectBridge, buildClaim, buildRelease, buildFront, PROTOCOL }
96108
97109// ── which bridges carry this node ────────────────────────────────────────────────────────────────
98110//
lib/rpc.js +12 -1
……@@ -104,7 +104,7 @@ const MUTATIONS = new Set(['post', 'follow', 'unfollow', 'like', 'unlike', 'repl
104104 // ⛔ Asking a bridge to carry this identity puts a name for it on somebody's domain, which is the
105105 // same class of act as turning a network on — so it needs an ATTESTED device, not merely a
106106 // signed-in browser.
107 'bridge-claim', 'bridge-release'])
107 'bridge-claim', 'bridge-release', 'bridge-front'])
108108
109109/**
110110 * The room, or a refusal that says which of the two possible things is wrong.
……@@ -417,6 +417,17 @@ export async function dispatch (node, cmd, a) {
417417 rememberBridge(node.storage, r.body && r.body.acct, a.bridge)
418418 return { ...r.body, pending: r.status === 202 ? (r.body && r.body.pending) || true : false }
419419 }
420 // ⛔ CARRIED, not named. A bridge that fronts an identity serves what that identity SIGNED — it
421 // holds no key for it — so this asks for availability, not for authority.
422 case 'bridge-front': {
423 const { connectBridge, buildFront } = await import('./bridge.js')
424 const { rpc } = await connectBridge(node.swarm, a.bridge)
425 const { nonce } = await rpc.request('challenge', {})
426 const r = await rpc.request('front', buildFront({ nonce, id: a.id, root: node.rootHex(), cred: node.cred }))
427 if (r.status !== 200 && r.status !== 202) throw new Error((r.body && r.body.error) || ('bridge refused: ' + r.status))
428 return r.body
429 }
430
420431 case 'bridge-release': {
421432 const { connectBridge, buildRelease, bridgeFor, forgetBridge } = await import('./bridge.js')
422433 // ⚠ The remembered key wins over one supplied by the caller: the node knows which bridge it