verify
@atiproto/atproto-attestation
Standalone verifier. Walks a record's signatures[] and checks each entry. Returns a structured result rather than throwing, so callers decide whether a partial pass is acceptable.
Signature
typescript
verify(input: VerifyInput): Promise<VerifyResult>interface VerifyInput {record: RecordMap;repository: string; // DID that owns the recordfields?: readonly string[]; // must match what the signer usedrole?: string; // filter to a specific rolekeyResolver?: KeyResolver; // defaults to did:key parsingrecordResolver?: RecordResolver; // required for remote entries}interface VerifyResult {valid: boolean; // true iff every checked entry verifiedentries: VerifyEntryResult[];}interface VerifyEntryResult {index: number;$type: string;ok: boolean;reason?: string; // populated when ok is false}
What is verified
- For an inline entry: the CID is recomputed over the record + the entry's own metadata + the supplied
repository, and the attached ECDSA / Ed25519 signature is verified against the public key resolved fromentry.key. - For a remote entry (
com.atproto.repo.strongRef): the proof record is fetched viarecordResolver, the CID is recomputed, andproof.cidmust equal it. Authority comes from the repo hosting the proof, so there are no signature bytes to check. - When
roleis supplied, entries whose role does not match are skipped, not failed. Inline entries are matched onentry.role; remote entries onproof.role. validisfalsewhen the after-filter set is empty.
Examples
Inline only
typescript
import { verify } from "@atiproto/atproto-attestation";const result = await verify({record: cart,repository: "did:plc:recipient",fields: ["items", "currency", "status"],});if (!result.valid) {for (const e of result.entries) {if (!e.ok) console.warn(`#${e.index} (${e.$type}): ${e.reason}`);}}
Require an AppView signature
typescript
const appViewOk = await verify({record: cart,repository: "did:plc:recipient",fields: ["items", "currency", "status"],role: "appview",});if (!appViewOk.valid) throw new Error("missing or bad AppView attestation");
Inline + remote, with resolvers
typescript
import { verify } from "@atiproto/atproto-attestation";import { didDocKeyResolver, fetchProofRecord } from "./resolvers";const result = await verify({record: cart,repository: "did:plc:recipient",fields: ["items", "currency", "status"],keyResolver: didDocKeyResolver,recordResolver: fetchProofRecord,});
See KeyResolver & RecordResolver for the resolver shapes and worked implementations.
Why a structured result
The verifier reports per-entry rather than throwing because real records carry multiple attestations (PoS, AppView, broker) and partial validity is often what callers want, e.g. accept a cart whose PoS signature is good, even if a stale AppView signature is left over. Callers can collapse the result to a boolean via result.valid when strictness is needed.