Attestra

Push and pull

Two ways to get a signed attestation in front of a chain, and how to choose between them.

Attestra produces one thing: a quorum-signed attestation over a fixed digest. What happens to that attestation next is a choice the caller makes, not a property of the fact itself. There are two ways to put it in front of a chain, and they differ in what gets written, who pays for it, and how long the fact remains checkable.

Pull: the attestation travels with the request

In the pull model, the attestation is not written anywhere. It rides along with whatever transaction needs it, and a contract checks the signature at the point of use, through AttestationVerifier. AttestationVerifier.verify recomputes the digest from the attestation fields, recovers each signer from the accompanying signature bundle, and checks the result against the SignerRegistry's threshold. If it passes, the calling transaction proceeds.

Nothing is stored and nothing is written by this check. The verifier's verify function is a view call: it costs no gas beyond the gas the calling transaction was already going to spend. Once that transaction completes, the chain retains no record that the check happened at all.

Push: Attestra writes the fact into the chain's own storage

In the push model, Attestra submits the attestation and its signature bundle as a transaction, and the target chain's own storage is updated. On Ethereum, this is AttestationStore.record; on Hyperledger Fabric, it is RecordAttestation in the registry chaincode. Both perform the same check pull does, recompute the digest, recover each signer, enforce the threshold, and only then write.

Unlike pull, push leaves something behind. AttestationStore.record stores a Record (status, existence, timestamp, signer count) keyed by keccak256(did, credentialId), and emits an AttestationRecorded event. Indexers and other contracts can read that record locally from then on, without holding the attestation or its signatures themselves and without calling Attestra again.

What the two share

Push and pull share everything that determines whether a bundle is valid. AttestationStore and AttestationVerifier compute the identical digest, the same nine-word preimage, keccak256 over the same 288 bytes, and enforce the identical quorum rule: signatures ordered by recovered signer address, strictly ascending, checked against the same SignerRegistry threshold. A bundle that clears one clears the other. Neither has a single-signature path; both were built with the single-signature overload deliberately excluded, because it would let a caller bypass the quorum entirely.

See The canonical digest for the preimage layout, and Verifying an attestation for why the ascending-order rule exists.

Comparing the two

PullPush
Who holds the attestationThe caller, for the duration of the requestThe target chain, in its own storage
What is writtenNothingAn AttestationRecorded record (Ethereum) or an equivalent chaincode write (Fabric)
Who pays gasThe calling transaction, no additional costThe push transaction itself, a record or RecordAttestation call
When the fact expiresWhenever the attestation's own expiresAt says, checked fresh every callWhenever the stored record is superseded or the reader decides it is stale; the chain does not enforce expiresAt on read
Who can read itOnly whoever holds the attestation and its signaturesAny indexer or contract with access to the target chain's storage

How to choose

Pull suits a check that happens once, per transaction, where the consumer already has the attestation in hand and freshness matters more than persistence. The attestation carries its own expiry inside the signed digest, so a pull check is always evaluated against the current moment: there is no stale copy sitting in storage that could outlive its validity window unnoticed.

Push suits a fact that many parties need to read, that should outlive the request that produced it, or where an on-chain event ought to trigger something downstream, an indexer updating a dashboard, another contract reacting to AttestationRecorded. Paying gas once to write the fact is cheaper than every consumer re-verifying the same bundle independently.

Push is self-authenticating

Anyone may submit a push transaction: a relayer, the credential holder, or any other party who has the signed attestation and its signature bundle. AttestationStore.record and its Fabric counterpart check the signatures, not the caller. Trust rests entirely on whether the bundle clears quorum, so the chain does not care who relayed it. This is what makes a push safe to delegate: a holder without gas can hand the bundle to a relayer, and the relayer's identity carries no special authority and needs none.

Confirming a push landed

A push response is Attestra's claim that a write happened. Reading the attestation back from the target chain's own storage is what turns that claim into something the caller can check independently. See Attestation.

Read-back today is implemented on Hyperledger Fabric only. On Ethereum, GET /v1/attestation returns 501, so confirmation there rests on what the push response already gave you: the transaction hash, or the emitted AttestationRecorded event, checked directly against the chain rather than through Attestra.

One caution

A failed push response does not prove nothing was written. By the time a 502 reaches the caller, the transaction may already have been broadcast, so the failure can mean the write is still pending, or that it reverted, not that it never happened. See Push for the detail on what a 502 can mean and how to recover from one.

On this page