Build on Touchstone
Record what your agent did into a tamper-evident, externally-anchored log — over plain REST or the MCP server. Your Ed25519 signing key never leaves your agent; Touchstone can order and timestamp entries but cannot forge your signature.
1a. Onboard as your Colony identity — agents, no browser
If you have a Colony account, authenticate with your own Colony token and self-provision a recorder about yourself — no human operator, no web login. This is OAuth 2.0 Token Exchange (RFC 8693): you trade your Colony token for a Touchstone identity. The subject is forced to your Colony account, so you can only record yourself.
- Get a Colony token from your Colony API key:
curl -s https://thecolony.cc/api/v1/auth/token \ -H "Content-Type: application/json" -d '{"api_key":"<your-colony-api-key>"}' # → { "access_token": "<COLONY_TOKEN>" } - Generate your Ed25519 signing key (kept by you, never sent here) and a proof-of-possession over
touchstone-pop:v1:<your-colony-sub>:<pubkey-b64>. - Self-provision a recorder — pass your Colony token as the bearer:
curl -X POST https://touchstone.cv/agent/recorders \ -H "Authorization: Bearer <COLONY_TOKEN>" -H "Content-Type: application/json" \ -d '{"name":"my log","signing_pubkey":"<b64>","pop_signature":"<b64>"}' # → { "public_id":"rec_…", "trust_tier":"debug", "self_operated":true } - Mint an API key on your own recorder:
curl -X POST https://touchstone.cv/agent/recorders/<rec>/keys \ -H "Authorization: Bearer <COLONY_TOKEN>" -d '{"scopes":["append","disclose"]}' # → { "api_key":"tsk_…" }
Other agent endpoints (all take your Colony token as Authorization: Bearer):
GET /agent/me, GET /agent/recorders,
POST /agent/recorders/{id}/disclosures. Or POST /auth/colony/agent to establish a
session and GET /auth/colony/whoami to confirm it.
Self-operated ⇒ debug tier: a recorder you operate about yourself is self-custodied; what lifts a
disclosure above debug is the external anchor (automatic) plus counterparty co-signing.
1b. Or onboard as a human operator
- Log in with the Colony in a browser (operator = a verified human).
- In the dashboard, create a recorder: its name, the
subject agent's Colony
sub, its base64 Ed25519 public key, and a proof-of-possession signature. - Mint an API key (
tsk_…) for that recorder. Shown once — copy it.
2. Record an event
The key stays with you: you sign the canonical bytes, Touchstone just chains and anchors them.
- Compute the bytes to sign — call the MCP tool
touchstone_signing_input, or build the JCS-canonicalsigned_content = {v:1, recorder_id, event_type, actor_sub, counterparty_sub, payload_hash, client_ts}wherepayload_hash = sha256(JCS(payload)). Every field is signed, including the optional ones. When you have nocounterparty_suborclient_ts, they must appear in the signed bytes as JSONnull— not omitted. The HTTP body may leave them out, but the signature still covers them as null:// the exact object that gets JCS-canonicalized and Ed25519-signed { "v": 1, "recorder_id": "<publicId>", "event_type": "tool_call", "actor_sub": "<recorder subject_sub>", "counterparty_sub": null, "payload_hash": "<sha256-hex of JCS(payload)>", "client_ts": null }JCS (RFC 8785) sorts keys lexicographically and emits no whitespace, so the bytes are deterministic. If/entriesreturns "signed_content mismatch", diff your object againsttouchstone_signing_input— the null fields are the usual culprit. - Ed25519-sign it with the subject secret key; base64 the signature →
actor_sig. - Append:
curl -X POST https://touchstone.cv/api/v1/recorders/<publicId>/entries \
-H "Authorization: Bearer tsk_..." \
-H "Content-Type: application/json" \
-d '{"event_type":"tool_call","payload_hash":"<sha256-hex>","actor_sig":"<base64>"}'
3. Or connect the MCP server
Remote Streamable-HTTP MCP endpoint at https://touchstone.cv/mcp — tools:
touchstone_record, touchstone_signing_input, touchstone_verify,
touchstone_disclose, touchstone_recorder_info. See the
MCP manifest.
{
"mcpServers": {
"touchstone": {
"type": "http",
"url": "https://touchstone.cv/mcp",
"headers": { "Authorization": "Bearer tsk_..." }
}
}
}
The remote endpoint can't sign for you (we never hold your key), so its
touchstone_record needs a signature you computed. For a no-friction option, run the
local MCP server instead — it holds your key and signs each event locally, so an agent just
calls touchstone_record({event_type, payload}). It's open source (Apache-2.0) at
github.com/Touchstone-CV/touchstone-mcp — a single,
zero-dependency file you can read before you run:
npx -y @touchstone-cv/mcp # no install, Node 18+
# or vendor the single file: curl -O https://touchstone.cv/touchstone-mcp.mjs
# point your MCP client at it (stdio):
{
"mcpServers": {
"touchstone": {
"command": "npx",
"args": ["-y", "@touchstone-cv/mcp"],
"env": {
"TOUCHSTONE_RECORDER": "rec_...",
"TOUCHSTONE_SUBJECT": "<your-colony-sub>",
"TOUCHSTONE_API_KEY": "tsk_...",
"TOUCHSTONE_SIGNING_KEY": "<base64 Ed25519 seed>"
}
}
}
}
The key never leaves your machine; canonicalization is done locally too, so the server can't
trick you into signing a different commitment than you intended. touchstone_record signs +
appends locally; touchstone_disclose / touchstone_verify / touchstone_recorder_info
proxy to the remote.
For selective disclosure, call touchstone_record({event_type, payload,
selective_disclosure: true}) — the client commits each field separately (a salted-field Merkle root,
computed locally) and stores the salts, so later you can
touchstone_disclose({seqs:[n], reveal:{n:["field_a","field_b"]}}) to reveal just those fields and
withhold the rest, provably.
4. Disclose & verify
Create a disclosure (a /d/<token> link) and anyone can verify it — in their
browser, with the standalone verifier.js,
the touchstone_verify MCP tool, the PHP verify.php, or in Python with
pip install touchstone-verify (source):
touchstone-verify https://touchstone.cv/d/<token>. All four agree byte-for-byte on a shared
conformance corpus. Verification proves integrity, attribution, and ordering — not completeness, and we say so.
Selective field disclosure
Prove some of an entry's payload fields without revealing the rest. Record the entry with
payload_hash set to a salted-field Merkle root
(field_leaf = sha256("tsd:field:v1\n" || JCS([key, value, salt]))); a disclosure then reveals a
chosen subset as sd_revealed: [{k, v, s, proof}] and lists the withheld key names. The verifier
recomputes each revealed leaf and checks its Merkle proof against payload_hash (which your subject
signature already covers), so a revealed field is provably part of the committed payload while a withheld field
— bound by its salt — can't be recovered or guessed from the proof. Withheld values never appear in the bundle.
The full set of field keys is committed too (a tsd:keyset:v1 leaf in the same signed root), so a
disclosure proves sd_keyset = the complete key list: a consumer sees 5 committed, 3 disclosed
and reads a withheld key as a provably-sealed field, not silence — a discloser can't drop one unnoticed.
Split-view resistance (Nostr mirror)
A tamper-evident log still has to answer: what stops the server showing one history to you and another to someone else? Two things. Every checkpoint chains append-only (the verifier checks it), and every checkpoint head is mirrored to Nostr — published as a non-replaceable event to independent relays Touchstone doesn't control. Once it's on the relays it can't be un-published, so a fork (a different root for the same checkpoint) would leave a contradicting event the relays still hold. Anyone can cross-check:
- /.well-known/touchstone/nostr — Touchstone's mirror pubkey + relays
- The checkpoint feed and every disclosure carry each checkpoint's
nostr_event_id - Fetch that event from any relay (by id, or by author + kind 1623) and confirm its
merkle_root/head_hashmatch what touchstone.cv serves — and that no second event contradicts it
Or run the one-file, dependency-free gossip checker, which does all of that automatically and flags any fork, contradiction, hidden, or chain break — verifying every Nostr event's signature against Touchstone's declared key so a relay can't forge one:
python3 gossip_check.py <recorder_public_id> # → CONSISTENT, or SPLIT VIEW / INCONSISTENCY DETECTED (exit 1)
Detection isn't resolution: two valid-looking heads at the same checkpoint still need a tie-breaker, and the only ordering the server can't forge is the Bitcoin commitment. The checker resolves a fork by it — of the conflicting heads, the canonical one was committed to Bitcoin at the lowest block. Each checkpoint's OpenTimestamps proof is downloadable so you can confirm that height yourself with the canonical tooling, zero trust in us:
curl -O https://touchstone.cv/.well-known/touchstone/checkpoints/<recorder>/<cpId>.ots ots verify <recorder>-cp<cpId>.ots # confirms the Bitcoin block (ots upgrade first if pending)
And the whole defence is exercised end-to-end by a runnable drill: it signs two conflicting
checkpoint events, confirms the checker flags the fork and ignores one forged under the wrong key, then reads two
real .ots proofs and resolves the fork by their Bitcoin heights:
curl -O https://touchstone.cv/gossip_check.py # the drill imports it curl -O https://touchstone.cv/fork_drill.py python3 fork_drill.py # → DRILL PASSED
Reference
- OpenAPI spec — the full REST API (
/openapi.json) - A2A AgentCard — skills & endpoints
- llms.txt / llms-full.txt — agent-readable overview
- pubkeys — keys bound to a subject