Agent integration aqua-timestamp

How to obtain dual-anchored (EVM + eIDAS-qualified qTSA) timestamp witnesses from timestamps.inblock.io as an automated client.

This page mirrors the machine-readable skill at GET /.well-known/aqua-skill.md (with the SIWE auth deep-dive at /.well-known/aqua-skill-auth.md), in the same format Claude (and any other agent honoring the convention) consumes for ~/.claude/skills/<name>/SKILL.md. A client can fetch those URLs and drop them into its own skill library to learn how to call this service unattended.

What this service does

Submit a 32-byte hash. Get back two signed witness revisions: one anchored to Sepolia (cryptographic proof of existence) and one anchored to a Sectigo-qualified RFC 3161 TSA (legal / eIDAS proof of existence). Both chain off the hash you submitted via previous_revision, so they merge directly into your existing Aqua tree without rebasing the genesis.

1. Pin the server identity (do this once)

Before trusting any witness, verify the server's published identity claim and add its DID to your trust store:

curl -sS https://timestamps.inblock.io/.well-known/aqua-identity > server-identity.json

The response carries a valid Aqua tree: anchor -> service_claim_server object -> EIP-191 Signature, signed by the server's secp256k1 key. Verify it with aqua-rs-sdk's tree verifier; on success, pin server_did and never trust an unsigned witness again.

2. Authenticate with the API (CAIP-122 / SIWE)

Every protected endpoint requires a bearer token in the Authorization: Bearer <token> header. Obtain one by signing a CAIP-122 challenge with the same private key your DID is derived from. Three HTTP calls: challenge -> sign locally -> session -> bearer. No shared secrets, no API keys.

Quickstart (eip155 example):

curl -sS 'https://timestamps.inblock.io/auth/challenge?did=did:pkh:eip155:1:0xYOUR_ADDRESS'
#   -> { "nonce": "0x...", "message": "...", "expires_at": ... }

# Sign the `message` bytes locally with your DID's key.
# (See the deep-dive for the exact prehash / encoding per curve.)

curl -sS -X POST https://timestamps.inblock.io/auth/session \
  -H 'content-type: application/json' \
  -d '{"did":"did:pkh:...","nonce":"0x...","signature":"0x..."}'
#   -> { "token": "...", "did": "...", "valid_until": ... }

curl -sS -H 'authorization: Bearer <token>' ...

Deep-dive (read this before implementing): /.well-known/aqua-skill-auth.md — accepted DID methods (eip155, ed25519, p256), per-curve signing recipe with working Rust snippets, failure-mode catalogue, lifetimes, reference implementation pointer.

3. Submit a leaf

curl -sS -X POST https://timestamps.inblock.io/v1/leaves \
  -H 'authorization: Bearer <token>' \
  -H 'content-type: application/json' \
  -d '{"leaves":["0x<64 hex>"]}'

Up to 10000 hashes per request. Each leaf is 32 bytes (64 hex, optional 0x). The response carries epoch_id and epoch_closes_at; your leaf is guaranteed to land in either that epoch or the next one.

4. Wait for the seal

curl -sS https://timestamps.inblock.io/v1/schedule

Poll until last_sealed_epoch_id >= your_epoch_id. The default epoch is 10 minutes. A safe poll ceiling is 2 × (epoch_closes_at - now) + 30 s.

5. Fetch the witnesses

curl -sS -H 'authorization: Bearer <token>' \
  'https://timestamps.inblock.io/trees/by-leaf/0x<leaf>?method=evm'
curl -sS -H 'authorization: Bearer <token>' \
  'https://timestamps.inblock.io/trees/by-leaf/0x<leaf>?method=qtsa'

Each response is an aqua-node-compatible Tree: { revisions: BTreeMap, file_index: BTreeMap }. Deserialise directly into aqua_rs_sdk::schema::tree::Tree. 404 means the leaf is unknown; 403 means the leaf exists but another DID submitted it.

6. Verify offline

Run three checks against every witness before trusting it:

EVM extras: the on-chain transaction_hash can be cross-checked against the Sepolia RPC; the call data is 0x114ee197 (function selector witness(bytes32)) followed by the Merkle root.

qTSA extras: the transaction_hash field is the base64-encoded RFC 3161 TimeStampResp DER. Verify it under the Sectigo Qualified Time Stamping Root R45; the certificatePolicies OID 1.3.6.1.4.1.6449.1.2.1.9.1 confirms eIDAS-qualified status.

Endpoint catalogue

EndpointAuthPurpose
GET /.well-known/aqua-identitypublicserver identity claim (signed Aqua tree); pin once
GET /docspublicthis guide (HTML)
GET /.well-known/aqua-skill.mdpublicmain agent skill (markdown, machine-readable)
GET /.well-known/aqua-skill-auth.mdpublicSIWE / CAIP-122 authentication deep-dive
GET /healthpublicliveness + uptime
GET /v1/schedulepubliccurrent / last-sealed epoch state
GET /auth/challenge?did=...publicCAIP-122 challenge
POST /auth/sessionpublicexchange signed challenge for bearer
POST /v1/leavesbearersubmit hashes for the current epoch
GET /v1/epochsbearerpaginated epoch history
GET /treesbearertips owned by caller DID
GET /trees/{tip}beareraqua-node compatible witness fetch by tip
GET /trees/by-leaf/{leaf}?method=evm|qtsabearerwitness fetch by submitted leaf
GET /trees?epoch=N&method=evm|qtsabearerwitnesses for caller's leaves in epoch N

Reference client

A complete reference client (Rust) lives at crates/aqua-timestamp-e2e in the project repo. It runs the full flow + verification against this deployment for all three DID methods.