Skip to main content
An attestation is Selo’s signed statement of what a release test found: which target and release were tested, the verdict, every check result, both payment legs and a hash of every evidence record. It is signed with Ed25519 under a published key, so anyone holding the file can check it offline without trusting the machine that delivered it.

When Selo issues one

  • Every release test response that carries a verdict includes an attestation object, as long as the Selo deployment has a signing key. api.useselo.xyz signs with the key selo-ed25519-728fcf85650b128f.
  • A duplicate request for the same job returns a byte-identical attestation. Ed25519 signatures are deterministic, and the payload is built from the stored job.
  • selo test --attestation <file.json> saves it, and the GitHub Action’s test mode saves it so that a later verify step can check it. The private report link does not include it, so keep the file.

The envelope

Envelope
object
required
The signed statement. Every field is listed below.
object
required

Payload fields

Release ids are asserted by the client

deployment.verification is always asserted_by_client. The release id is the label your CLI or workflow sent with the paid request. Selo signs that the test ran against the URL under that label; it does not inspect your infrastructure to confirm which build was serving. Bind the id to something you control, such as a commit SHA, and test the URL that the release actually serves.

The evidence manifest

Each evidence record’s sha256 is the SHA-256 of the canonical JSON of the sanitized record that Selo stored. The kinds are target_challenge, paid_response, downstream_payment, response_contract, catalog_record, replay_response and authorization_snapshot. The records themselves are private; the manifest commits to their exact content, and evidenceSha256 commits to the manifest.

Canonical JSON

Selo signs the canonical JSON of the payload, produced by this function from packages/core/src/canonical.ts:
packages/core/src/canonical.ts
The rules:
  • Object keys are sorted at every depth, by UTF-16 code unit (JavaScript’s default string order). Every payload key is ASCII.
  • Arrays keep their order.
  • No whitespace between tokens.
  • Strings are escaped as JSON.stringify escapes them; non-ASCII characters are written as-is.
  • The payload holds no floating-point numbers. Amounts are decimal strings.

The exact bytes signed

1

Serialize

Take the payload object exactly as it appears in the envelope and serialize it to canonical JSON.
2

Encode

Encode that string as UTF-8. These bytes are the message.
3

Sign

Selo signs the message with pure Ed25519 (no pre-hashing) and stores the 64-byte signature as base64url without padding in signature.value.

The key set

Selo publishes its public keys at a well-known URL, served with Cache-Control: public, max-age=300:
Terminal
selo-attestation-keys.json
  • publicKey is the raw 32-byte Ed25519 public key, base64url-encoded: the x member of an OKP JSON Web Key.
  • keyId is selo-ed25519- followed by the first 16 hex characters of the SHA-256 of those 32 bytes.
For a gate, pin the key. Save the key set as a file in your repository and verify against that file, so that a change at the URL cannot change what your gate trusts.

Verify with the CLI

Terminal
Run against the demo merchant’s fixed-v2 attestation from Algorand Mainnet, it prints:
Output
selo verify checks, in this order, and refuses at the first failure: A refusal prints not verified: <message> and exits 2. A verified attestation exits with its verdict: 0 for PASS, 1 for FAIL, 2 for INCONCLUSIVE. --keys takes an https URL or a local file; an http URL is refused as invalid input with exit 3. Checking the release id and target matters: a valid PASS for another release, another URL or no release at all cannot open your gate. See selo verify and Verify a signed verdict.

Verify with any Ed25519 library

You do not need Selo’s code. Rebuild the canonical JSON of payload, encode it as UTF-8, decode the base64url signature and public key, and run a standard Ed25519 verification. This Node.js script, tested with Node.js 24 against the Mainnet evidence files, does exactly that with the built-in node:crypto module:
verify-attestation.mjs
Terminal
The script checks the signature only. Compare payload.deployment.id, payload.testedTarget and payload.verdict with what you expect before you trust the result, as selo verify does.
In other languages, use a JSON encoder that sorts keys, omits whitespace and leaves non-ASCII characters unescaped, and compare its output with the rules above. In Python, json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode() matches Selo’s bytes for both Mainnet evidence files.

What a valid attestation proves

  • Selo’s key signed this exact verdict, these check results and this evidence manifest for this job.
  • The test ran against the stated URL and method, under the stated release label.
  • The payment transaction ids are the ones Selo recorded. Both are public on Algorand: look them up at https://allo.info/tx/<txId>.
  • Not one signed field has changed since Selo signed it.
  • Which build your infrastructure was serving. The release id is asserted by the caller.
  • That your endpoint still behaves the same way today. An attestation describes one run at completedAt.
  • The content of the evidence records, which stay private to Selo. The manifest only fixes their hashes.

Example from Algorand Mainnet

The demo merchant’s fixed-v2 release, tested on 26 October 2026:
g5-release-gate-fixed-v2-attestation.json

Verify a signed verdict

Step-by-step verification in a terminal and in CI.

selo verify

Every flag and exit code of the verify command.

Verdicts and the gate

How the signed verdict is computed.

Proof on Algorand Mainnet

Both demo releases, their transactions and their attestations.