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.
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.
https://timestamps.inblock.iodid:pkh:eip155:1:0x01651f5417f76D2Cf6343b31550b911140e8910etimestamps.inblock.io / 142.93.168.4Before 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.
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.
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.
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.
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.
Run three checks against every witness before trusting it:
Linkable::calculate_link; assert
the result equals the map key.verify_inclusion(leaf, idx, size, &proof, &root,
HashType::Sha3_256) from
aqua_rs_sdk::primitives::merkle. The
payloads.merkle_root / merkle_proof /
batch_tree_size / batch_leaf_index fields
of the TimestampObject revision feed in directly.signature blob against
signature.pre_signature_canonical_json(); assert the
recovered EIP-55 address equals the address baked into the pinned
server_did. This is the trust-load-bearing check.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 | Auth | Purpose |
|---|---|---|
GET /.well-known/aqua-identity | public | server identity claim (signed Aqua tree); pin once |
GET /docs | public | this guide (HTML) |
GET /.well-known/aqua-skill.md | public | main agent skill (markdown, machine-readable) |
GET /.well-known/aqua-skill-auth.md | public | SIWE / CAIP-122 authentication deep-dive |
GET /health | public | liveness + uptime |
GET /v1/schedule | public | current / last-sealed epoch state |
GET /auth/challenge?did=... | public | CAIP-122 challenge |
POST /auth/session | public | exchange signed challenge for bearer |
POST /v1/leaves | bearer | submit hashes for the current epoch |
GET /v1/epochs | bearer | paginated epoch history |
GET /trees | bearer | tips owned by caller DID |
GET /trees/{tip} | bearer | aqua-node compatible witness fetch by tip |
GET /trees/by-leaf/{leaf}?method=evm|qtsa | bearer | witness fetch by submitted leaf |
GET /trees?epoch=N&method=evm|qtsa | bearer | witnesses for caller's leaves in epoch N |
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.