Attestra

Add a chain

Implementing an adapter, registering it, and the on-chain verifier that a new chain still needs before an attestation can be checked there.

Adding a chain to Attestra has two halves, and only one of them lives in this repository. The off-chain half is a Go adapter that lets the resolver read the chain. The on-chain half is a verifier deployed to that chain, and it is the half that gets missed, because nothing on the off-chain side will complain if it is skipped.

The off-chain half: implement resolver.Adapter

Every adapter implements one required interface:

type Adapter interface {
	ResolveDID(did string) (*DIDDoc, error)
	Status(credentialID string) (*Status, error)
}

ResolveDID turns a DID into its document; Status answers whether a credential is valid or revoked on this chain. That is the whole requirement for a chain to be reachable at all.

Three further interfaces are optional, and an adapter implements whichever of them its chain actually supports:

type SchemaResolver interface {
	ResolveSchema(hash string) (*Schema, error)
}

type AttestationReader interface {
	ReadAttestation(did, credentialID string) (*StoredAttestation, error)
}

type PresentationVerifier interface {
	StatusType() string
	VerifyPresentation(raw []byte) (*VerifiedPresentation, error)
}

SchemaResolver backs /v1/schema. AttestationReader backs /v1/attestation, reading back an attestation the oracle previously pushed. PresentationVerifier backs /v1/verify; it registers under the credentialStatus.type value it understands, and the resolver dispatches to it by reading that field out of the presentation, never by assuming a chain.

Why a partial adapter is a legitimate thing to ship

The resolver checks each optional interface with a type assertion at call time. If an adapter has not implemented it, the resolver returns an error that the API layer maps to 501, not a wrong or empty answer dressed up as a success. A 404 on this path would claim a credential does not exist; a 200 with an empty body would claim it does and has no schema. Neither is true. 501 says the only correct thing available: this chain has not been wired up to answer this particular question yet.

That means a new adapter does not have to arrive complete. Implementing ResolveDID and Status alone is enough to register a chain and have it answer resolution and status lookups correctly; the endpoints it has not implemented fail honestly instead of failing silently.

Registration

An adapter is registered through configuration, not through code:

adapters:
  - kind: fabric
    method: acme
    endpoint: grpc://peer0.acme.example:7051
    registry: <contract-or-chaincode-reference>

kind selects which compiled adapter driver handles the entry (eidchain, ethr, fabric, and so on); method is the DID method it is registered under, the string a caller writes after did: to reach it. Changing which DID method routes to a given chain, or standing up a second instance of the same driver under a different method, is a configuration edit. No rebuild is needed to change the method a chain answers under.

The on-chain half

Implementing and registering an adapter lets Attestra read the new chain: it can resolve DIDs, check status, and answer whatever else the adapter implements. It does not let the chain verify an attestation. That requires a verifier contract or chaincode deployed on the new chain itself, computing the same canonical digest as every other verifier and enforcing the same quorum rule over the same signer registry. Without it, a bundle Attestra assembles for that chain has nowhere on that chain to be checked.

Both halves are required, and they are independent pieces of work: the adapter is Go code that ships with the oracle; the verifier is a separate deployment onto the target chain's own execution environment. See The canonical digest for the 32 bytes every verifier must agree on, and Verifying an attestation for the quorum rule every verifier enforces identically.

Three reference implementations of the verifier already exist, in Solidity, Rust and Go, one per chain currently supported. A fourth chain has three worked examples to follow rather than a blank page to start from.

On this page