Chains and DID methods
How Attestra reaches three different ledgers through one interface, and what is genuinely different about each.
The method is data, not code
Every request into Attestra names a DID, and every DID names its own method:
did:eid:..., did:ethr:..., a Fabric identifier resolved through its own
prefix. The oracle parses the method out of the DID and dispatches to the
adapter registered under it. It never assumes which chain a request is for;
it reads that from the DID.
That registration is configuration, not compilation. In the oracle's config
file, each entry under adapters carries a kind and a method: kind
selects the driver that knows how to query a chain (eidchain, ethr, or
fabric), and method is only the label that driver is registered under. A
chain's operator can rename its method, or point the same driver at a
different deployment, without a rebuild of the oracle.
The one rule the config loader enforces is that two adapters cannot share a method. If they did, the second registration would silently shadow the first in the resolver's internal map, and requests intended for one chain would quietly land on another. The loader refuses to start rather than let that happen.
Dispatch on the credential, not the chain
POST /v1/verify follows the same principle one level up. It does not ask
the caller which chain a presentation came from. It reads
credentialStatus.type out of the presented credential itself, a property
of the credential rather than of Attestra's code, and routes to whichever
verifier is registered for that type.
Today, exactly one verifier is registered: EidChainRevocation2024, for
eIDChain credentials. A presentation carrying any other status
type has no verifier to dispatch to and fails with 422. The dispatch
mechanism itself is chain-agnostic: registering a new type is one interface
implementation, not a change to /v1/verify. What is specific to this
deployment is which types happen to be registered right now, and that list
is short. See Verify for the full
mechanics.
The two dispatches, side by side
eIDChain
eIDChain is a Substrate chain, and identity there is addressed the
Substrate way: SS58-encoded addresses, keys expressed in a DID document as
publicKeyMultibase rather than as a chain-specific account reference.
Schemas on eIDChain are commitments, not documents. A CType pallet stores
only a hash and the registrant who submitted it; the definition itself
never goes on chain. So GET /v1/schema/{hash} against an eIDChain schema
returns the hash and the registrant, with an empty definition, and that
is not a partial answer, it is the whole of what the chain keeps.
eIDChain is also the only chain with a registered presentation verifier today. Its adapter recomputes the claim hash, checks holder binding and expiry, and reads the attester and revocation status straight from chain before an attestation is signed. See Verify for what that attestation contains and where each field comes from.
Ethereum
Ethereum identity here means did:ethr over ERC-1056: an identity is an
Ethereum address, and its current owner is read from the ERC-1056 registry
contract. Keys are expressed as blockchainAccountId, in
eip155:<chainId>:<address> form, rather than as a multibase-encoded key.
Credential status on Ethereum travels inside the credential rather than
being held in a separate on-chain record the oracle can look up by DID. So
a bare status lookup, GET /v1/status?chain=ethr, is not served: the
adapter's Status method returns unconditionally unsupported, and that
maps to a 404 on every call. This is not a gap in the deployment, it
reflects where the fact actually lives. Checking status against an Ethereum
credential goes through credentialStatus.type dispatch on /v1/verify
instead, or through POST /v1/credential-status for a W3C Bitstring
Status List, which is chain-independent.
Hyperledger Fabric
Fabric is permissioned, and that shapes the adapter more than any other difference in this list. A Fabric peer answers only a client that presents a mutually authenticated TLS connection and a valid MSP identity; there is no such thing as an anonymous read. Where the eIDChain and Ethereum adapters need only an RPC URL, the Fabric adapter is configured with a client certificate, a client key, and the peer's own TLS CA.
Fabric is also the only chain where the oracle can read an attestation
back after pushing it. GET /v1/attestation/{did}/{credentialId} is served
against Fabric and nowhere else in this deployment, which is also why that
endpoint defaults to chain=fabric when no chain is specified.
The capability matrix
Each adapter is a concrete implementation of a small set of interfaces
(Adapter, SchemaResolver, AttestationReader, PresentationVerifier),
and what a given chain can do through Attestra is exactly the set it
implements. The table below is not a limit on the architecture; it is an
inventory of this deployment.
| Capability | eIDChain | Ethereum | Fabric |
|---|---|---|---|
Adapter (resolve DID, status) | yes | yes* | yes |
SchemaResolver (/v1/schema) | yes | yes | yes |
AttestationReader (/v1/attestation) | no | no | yes |
PresentationVerifier (/v1/verify) | yes | no | no |
* Ethereum implements the Adapter interface, but its Status method
returns unsupported unconditionally, so a bare status lookup on Ethereum
always fails with 404.
Where a capability is not implemented, the request does not fail silently
or return a misleading answer. It returns 501. GET /v1/attestation
against chain=eid or chain=ethr returns 501 for exactly this reason:
AttestationReader is not implemented there. A 501 states plainly that
the capability is not available on this chain today, rather than the
oracle guessing at an answer it cannot actually check.
Adding a chain
A new chain joins Attestra as an adapter, not as a fork of the oracle.
Adapter is the only interface a new chain must implement: resolve a DID,
and report status. SchemaResolver, AttestationReader, and
PresentationVerifier are each optional, and a chain can add them later
without touching what already works.
Implementing the adapter is necessary but not sufficient. Any chain that will carry attestations also needs a verifier deployed on that chain, computing the same digest over the same nine fields and enforcing the same quorum rule the other chains use, because a signature is only checkable where something on that chain knows how to recompute it. Without that verifier, an attestation can be pushed but never checked there. See The canonical digest for the exact rule, and Add a chain for the steps.