Seals
A seal is a small, signed envelope from the witness service that attests to some fact about a hash: that it was submitted at a particular time, that it represents the head of a chain, that two parties independently committed to the same conclusion, or that a particular ephemeral event happened. Each seal is portable — anyone with the witness’s public key can verify it offline, with no further calls to the witness or to synpareia.
This page is the deep-dive behind the homepage Witness section. For the wire-level surface (paths, request/response shapes), see the witness API reference. To run your own witness instead of using the reference instance, see self-hosted witness.
What a seal contains
Section titled “What a seal contains”Every seal is a JSON envelope with the same skeleton:
seal_type(orattestation_type) — what kind of fact is being attestedwitness_id— the witness’s stable identifiersealed_at/attestation_time— when the witness signed- A small, type-specific payload — usually one or two hashes plus type-bound metadata
witness_signature_b64— an Ed25519 signature over a canonical (JCS) serialisation of the envelope
The signed bytes are deterministic: a verifier reconstructs the same canonical form locally and checks the signature with the witness’s public key. Nothing about verification is interactive.
Blind by construction
Section titled “Blind by construction”The single most important property: the witness sees hashes, not content. The public seal endpoints accept a 32-byte hash and (in some cases) a chain identifier or commitment hash. They do not accept message bodies, conversation transcripts, or counterparty profiles, and they do not accept requester identity on the primary seal paths. Timestamp and state seals leave the requester_id column null — no DID, no IP, no client metadata. (Seals generated by the challenge/conclusion flows still bind to a DID internally, pending Witness Phase 2’s anonymous-credential replacement; the public seal API doesn’t expose that.)
This is sometimes called sparse-witness construction. It exists for two reasons. First, an agent should be able to use a third-party attester without trusting it with secrets — the witness’s value is its independence and its key, not its discretion. Second, it bounds the witness’s regulatory and operational surface: a service that has never held identifiable data cannot be compelled to disclose it, can run on a tiny operational footprint, and is straightforward to replicate or self-host. The trade-off is real — a blind witness cannot help with claims that need it to see something — and where richer attestation is needed (liveness, fair exchange) the protocol pushes the witness toward signing what each party reveals to it, not what it observes about them.
The seal types
Section titled “The seal types”Two persistent seal types and a family of ephemeral attestations:
| Type | What it attests | When you reach for it |
|---|---|---|
| Timestamp seal | A hash existed at this time | Anchoring a block, a profile card, or any content-hash to wall-clock time you can’t forge |
| State seal | A chain had this head at this time | Checkpointing a chain so later branches can’t fork the past undetected |
| Blind conclusion | Two parties independently committed to the same hash | Mutual attestation — both sides agreed on an outcome without either being able to reveal unilaterally |
| Liveness relay | A challenge-response exchange occurred between two DIDs | Proving an agent was alive and responsive at a particular moment, without persisting the challenge |
| Verify attestation | A signature over caller-supplied envelope bytes was valid | Getting an independent second-opinion that a signature checks out, useful when a verifier doesn’t want to ship Ed25519 code itself |
| Randomness / VRF | A verifiable random value derived from a caller-supplied input | Coin-flips, draw orderings, contest seeds — deterministic, reproducible by anyone with the witness’s public key |
| Query attestation | A signed answer to a structured query_key / query_context | When a downstream verifier wants a witness-signed answer about something the witness can compute statelessly |
| Fair exchange | Two reveal payloads released together, or neither | Avoiding the “I reveal first, you walk away” failure mode in commit-reveal between two parties |
Timestamp and state seals are persisted (you can fetch them again later by ID). The ephemeral attestations are stateless — the witness signs and returns the bytes; the caller keeps them. That asymmetry is deliberate: the persistent seals exist to be looked up by third parties, the ephemeral ones exist to be held by the parties they describe.
Verification
Section titled “Verification”Once a seal is in hand, verification is offline. The SDK ships verify_seal and verify_seal_block for the persistent types, and each ephemeral attestation has a .verify(witness_public_key) -> bool method. The check rebuilds the canonical envelope from the seal’s fields, runs Ed25519 verification against the witness’s public key, and returns a boolean. No network call, no shared secret, no synpareia infrastructure in the loop — just the bytes and the key.
A third-party verifier who has never spoken to either agent can do this. That’s the point.
Three axes worth being explicit about:
- Latency. A seal request is one HTTPS round-trip to the witness. The reference instance runs on Fly.io; expect tens of milliseconds within a region, low hundreds across continents. Verification is local and microsecond-scale.
- Money. The reference instance (
synpareia-witness.fly.dev) is public and free — no token, no signup. The pricing model is “sellers don’t pay”, and witness usage is expected to remain free for the typical agent footprint. Self-hosting is whatever your own hosting costs — the service is small (SQLite, no external deps) and runs comfortably on the cheapest tier of any PaaS. - Trust. You choose your witness. The reference instance is one option; a community-run witness, your organisation’s witness, or a per-application witness are all valid. A verifier downstream needs to know which witness signed (the
witness_idand public key are in the seal), but the protocol does not privilege any particular witness operator.
A short example
Section titled “A short example”from synpareia import generate, create_blockfrom synpareia.seal.verify import verify_seal_blockfrom synpareia.witness import SyncWitnessClient
profile = generate()block = create_block(profile, "message", "anchoring this in time")
# Request a timestamp seal — only the hash crosses the wireclient = SyncWitnessClient("https://synpareia-witness.fly.dev")info = client.get_witness_info()seal = client.timestamp_seal(block.content_hash)client.close()
# Verify offline — no further calls to the witnessvalid, err = verify_seal_block( seal, witness_public_key=info.public_key, expected_block_hash=block.content_hash,)assert valid, errThe seal can now travel anywhere — embedded in a chain as a seal block, shipped alongside an exported chain, or handed to a third party. A verifier with info.public_key and the seal bytes confirms the timestamp without ever talking to the witness.
Status today
Section titled “Status today”Timestamp seals, state seals, blind conclusions, liveness challenges, and all five ephemeral attestations are live on the reference witness service. The first sparse-witness phase has shipped — the public seal endpoints no longer accept a requester identity. Tightening the same property for the internal challenge and conclusion flows is the next piece of work; until that lands, those endpoints still carry a DID. The full status, and what’s coming next, is tracked in the project state document.
Built by Sam Hyland · Canberra, Australia