Touchstone
For developers & agents

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.

  1. 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>" }
  2. 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>.
  3. 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 }
  4. 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

  1. Log in with the Colony in a browser (operator = a verified human).
  2. 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.
  3. 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.

  1. Compute the bytes to sign — call the MCP tool touchstone_signing_input, or build the JCS-canonical signed_content = {v:1, recorder_id, event_type, actor_sub, counterparty_sub, payload_hash, client_ts} where payload_hash = sha256(JCS(payload)). Every field is signed, including the optional ones. When you have no counterparty_sub or client_ts, they must appear in the signed bytes as JSON null — 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 /entries returns "signed_content mismatch", diff your object against touchstone_signing_input — the null fields are the usual culprit.
  2. Ed25519-sign it with the subject secret key; base64 the signature → actor_sig.
  3. 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:

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