When Selo issues one
- Every release test response that carries a verdict includes an
attestationobject, as long as the Selo deployment has a signing key.api.useselo.xyzsigns with the keyselo-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’stestmode saves it so that a laterverifystep 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’ssha256 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 frompackages/core/src/canonical.ts:
packages/core/src/canonical.ts
- 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.stringifyescapes 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 withCache-Control: public, max-age=300:
Terminal
selo-attestation-keys.json
publicKeyis the raw 32-byte Ed25519 public key, base64url-encoded: thexmember of an OKP JSON Web Key.keyIdisselo-ed25519-followed by the first 16 hex characters of the SHA-256 of those 32 bytes.
Verify with the CLI
Terminal
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 ofpayload, 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
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
What it proves
What it 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.
What it does not prove
What it does not prove
- 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’sfixed-v2 release, tested on 26 October 2026:
g5-release-gate-fixed-v2-attestation.json
g5-release-gate-fixed-v2-attestation.json
g5-release-gate-fixed-v2-attestation.json
Related
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.