KeyResolver & RecordResolver

@atiproto/atproto-attestation

Two pluggable functions used by verify. They fetch verification material that lives outside the record. Ready-made implementations ship in the @atiproto/key-resolver and @atiproto/record-resolver packages.

KeyResolver

typescript
type KeyResolver = (keyRef: string) => Promise<KeyData> | KeyData;
interface KeyData {
type: "p256" | "p384" | "k256" | "ed25519";
bytes: Uint8Array;
}

Called for every inline signature entry. The argument is the string stored at signature.key, typically a did:key:… but in principle anything your protocol uses (DID + key id, JWK URL, etc).

The verifier's default resolver only understands bare did:key: strings. Use one of the prebuilt factories in @atiproto/key-resolver to verify against keys hosted in a DID document.

Prebuilt KeyResolvers

typescript
import { verify } from "@atiproto/atproto-attestation";
import { createCachedKeyResolver } from "@atiproto/key-resolver";
const keyResolver = createCachedKeyResolver();
await verify({
record,
repository,
fields,
keyResolver,
});

RecordResolver

typescript
type RecordResolver = (uri: string) => Promise<RecordMap> | RecordMap;
type RecordMap = { [key: string]: unknown };

Called for every strongRef entry on signatures[]. Given an at:// URI, return the proof record stored at that location. The returned record must include its $type (typically network.attested.proof) and the cid field that the verifier compares against the recomputed canonical CID.

Without a recordResolver, remote attestations fail with "Remote attestation requires input.recordResolver".

Prebuilt RecordResolvers

typescript
import { verify } from "@atiproto/atproto-attestation";
import { createCachedRecordResolver } from "@atiproto/record-resolver";
const recordResolver = createCachedRecordResolver();
await verify({
record,
repository,
fields,
recordResolver,
});

Full example

typescript
import { verify } from "@atiproto/atproto-attestation";
import { createCachedKeyResolver } from "@atiproto/key-resolver";
import { createCachedRecordResolver } from "@atiproto/record-resolver";
const keyResolver = createCachedKeyResolver();
const recordResolver = createCachedRecordResolver();
const result = await verify({
record: cart,
repository: "did:plc:recipient",
fields: ["items", "currency", "status"],
keyResolver,
recordResolver,
});

Custom resolvers

Both types are plain functions. If the prebuilt factories don't fit (e.g. you need a custom key reference format, or your proof records are mirrored to a non-XRPC store), write your own. Any function matching the shape above works.