OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-bridge / pds / lib / sync-get-record.js

48 lines · javascript ref:
// com.atproto.sync.getRecord — build the proof CAR for ONE record.
//
// WHY THIS EXISTS AT ALL. The membrane served getRepo (the whole repo) and repo.getRecord (hydrated
// JSON: no commit, no signature, no MST proof, therefore unverifiable) but NOT the proof-carrying
// single-record read in between. So no client could verify one of our records without pulling the
// entire repo — including our own favela inbound lens, which got a 404 pointing at us. A gap in our
// federation surface, found by aiming our own verifier at ourselves.
//
// WHAT A CONSUMER DOES WITH IT. Load the commit, check its signature against the DID document's key,
// walk the covering path to <collection>/<rkey>, and confirm the record block hashes to the CID the
// proof arrives at. That is only possible if we ship the commit + the path + the record — which is
// exactly what this slices out of the full repo CAR.
//
// EXISTENCE *OR* NON-EXISTENCE. Per the lexicon this proves either. An absent record is NOT a 404: it
// returns the same commit + covering path with no record block, which proves the key is genuinely not
// in the tree. A 404 would be indistinguishable from "the repo is not here".
//
// Separated from server-bare.js so it is testable under plain node — that file imports bare-http1 and
// so can only run under bare.

import { readCar, writeCar, loadCommit, getBlock } from 'atproto-repo/lib/repo.js'
import { nodeStoreFromBlocks, findRpathProof } from 'atproto-repo/lib/mst.js'

// carBytes: the full repo CAR (front.getRepo()). Returns { carBytes, found } or throws.
export async function buildRecordProofCar (repoCarBytes, collection, rkey) {
  const car = await readCar(repoCarBytes)
  const commit = loadCommit(car)
  const ns = nodeStoreFromBlocks(car.blocks)
  const { value: recordCid, proof } = findRpathProof(ns, commit.value.data, `${collection}/${rkey}`)

  // The signed commit is the root and the anchor for every other check.
  const blocks = [{ cid: commit.cid, bytes: getBlock(car, commit.cid).bytes }]

  // The covering path — the MST nodes a verifier walks from the signed root down to the key. Without
  // these the record block is unattached to anything signed.
  for (const cidStr of proof) {
    const b = car.blocks.get(cidStr)
    if (b) blocks.push({ cid: b.cid, bytes: b.bytes })
  }

  if (recordCid) {
    const rb = car.blocks.get(recordCid.toString())
    if (rb) blocks.push({ cid: rb.cid, bytes: rb.bytes })
  }

  return { carBytes: await writeCar(commit.cid, blocks), found: !!recordCid }
}