Skip to content

Limits: records, verify and the Ledger

What a sealed record, deedglass verify and the Ledger chain check show, and what they leave out.

  • Removing the newest deeds isn’t visible in the chain alone. A chain cut at seq 800 looks complete. Keep the head of each export you receive; S05’s sealed manifest records it.
  • The seal key holder can write any deed. Including any issued_at. A deed shows what this Deedglass deployment recorded, signed by that deployment, not by an independent party. Revoking a key makes every deed it sealed fail.
  • One signer per deed. A deed is not countersigned by a transparency service or a second party in v1.
  • Small payloads can be guessed from their digests. A digest of a yes/no answer or a short amount can be found by trying values.
  • New seal keys reach keys.json within 60 seconds, not at once, and clients may cache it for 300 seconds.
  • No keys revoke command yet. Revocation is a manual database change until it ships (docs/SEAL_KEYS.md).
  • A bare .jsonl can lose its newest deeds unnoticed. Only a sealed bundle manifest, or a head you kept from an earlier export, shows that deeds were cut from the end. deedglass verify warns when a bundle’s manifest is not sealed.
  • Keys inside a bundle show integrity, not authenticity. Whoever made the bundle chose those keys. Pin the issuer’s published keys (--keys, --pin) to know who sealed the deeds; --strict refuses to call an unpinned check valid.
  • verify checks seals and the chain, not the truth of a verdict. A VERIFIED written in a deed is what the Witness recorded then; verify does not re-run it.
  • Browsers verify Ed25519 with their own engines. The page checks key safety and s < L itself first and agrees with Go on every published vector and the 500-case parity corpus, but a browser engine could still differ on an input nobody has tried. The CLI is the reference verifier. Browsers without WebCrypto Ed25519 (before Safari 17, Firefox 129, Chrome 137) get “use the CLI”, never a verdict.
  • The browser verifier reads .json and .jsonl, not bundles, in v1.
  • Dual-sealed deeds (Ed25519 + ML-DSA-65, deed v2) are about 3.4 times slower to verify than Ed25519-only deeds, and miss the 5 s budget for 100,000 deeds. On an 8-core laptop, 100,000 dual-sealed deeds took 5.54 s (median of 3, peak memory 150 MiB) against 1.63 s for 100,000 v1 deeds in the same run (bench/results/2026-10-19-darwin-arm64-verify.json). Memory stays within 256 MiB. Every seal is checked, and Go’s crypto/mldsa expands the public matrix on every check.
  • The browser verifier checks ML-DSA-65 with its own code (web/verify/src/mldsa.ts, no dependency). It agrees with Go’s crypto/mldsa on every v2 vector and the 200-case v2 parity corpus, and its SHAKE output with Node’s on every length tested; it is not a FIPS 140 validated module. The CLI is the reference verifier.
  • The Python SDK reports v2 deeds as “cannot decide” (unsupported_version). It keeps key sets that list ML-DSA-65 keys and applies the accept policy to v1 deeds.
  • The export bundle’s manifest seal stays Ed25519 when the deeds are dual-sealed (ADR-109 §9, a follow-up).
  • The chain check runs inside the database it checks. It finds a changed, removed or reordered deed anywhere before the head, but not deeds deleted from the end of the chain. Keep the head (deedglass ledger check prints it) somewhere outside Deedglass and compare.
  • Someone with database superuser rights can change history. The append-only triggers stop every Deedglass role; a superuser can disable them. The chain check and every external deedglass verify then name the first changed deed; they can’t undo the change or say who made it.
  • Pruned payloads are gone. Deeds keep their digests, so a copy you kept elsewhere can still be matched, but Deedglass can no longer show the original arguments or results.
  • No search inside payloads. q matches the tool name and the error code only.
  • Exports stop at 1,000,000 deeds and checks at 2,000,000 deeds or 30 s per request; larger ranges are split (next_from_seq).
  • Payloads are held until the action is sealed and checked. An action waiting for approval, a read-back or a Proven check keeps its request and result as sent; the serve job replaces them a few seconds after (PHI_MODE.md).
  • Digests of short values can be guessed. They aren’t salted per org yet.
  • The decoder’s row images aren’t covered. Use witness.sensitive_columns for the Postgres connector’s Proven check and observe mode.
  • Turning it off doesn’t bring payloads back, and the setting is API-only (PATCH /v1/org) for now; the Console doesn’t show it.

Opt-in (TY-03, TIMESTAMPS.md); off unless timestamps.tsa_urls or timestamps.scitt_url is set.

  • Only what was timestamped, and only from then on. A timestamp shows that one head (seq, id, body hash) existed by its time. Records sealed after the last timestamp can be removed or rewritten without a trace; timestamp often (--every) to keep that window short.
  • Only for a reader who has the timestamp. verify compares the records with the timestamps it is given. Someone who can delete the records can usually delete a timestamps.json kept beside them, and a bundle’s own copy disappears with the bundle. Keep copies where the server’s operator can’t rewrite them, or register heads with a transparency service.
  • A TSA vouches for a time, not for the TSA. Without --tsa-roots, a token is checked against the chain it carries: that shows the file is intact, not who signed it. Choose TSAs run by different organisations; min_tsa (default 2) needs that many tokens per head. A TSA whose key is stolen, or that backdates, can give a false time.
  • Transparency services: one profile, checked against a local fake. The inclusion proof is checked as RFC 9942’s RFC9162_SHA256 with the leaf SHA-256(0x00 ‖ signed statement), and registration is one POST <url>/entries that answers 201 with the proof at once. A service that defines its leaf differently, registers asynchronously (202 and a later fetch), or signs with an algorithm other than ES256 or EdDSA doesn’t work yet. CI tests against a fake service on loopback; no live service has been tried.
  • RFC 3161 coverage. Tokens signed with RSA PKCS#1 v1.5, ECDSA (SHA-256/384/512) or Ed25519, with signed attributes and an ESS signing-cert attribute, are checked; RSA-PSS, SHA-1 and tokens without signed attributes are refused. Revocation of a TSA’s cert (CRL, OCSP) is not checked. Tested against a fake TSA and against tokens from OpenSSL 3.6’s TSA; no public TSA in CI (no network in tests).
  • The signed head uses the Ed25519 seal key. The transparency-service statement is sealed with the issuer’s Ed25519 key only, not ML-DSA-65 (ADR-109). It is checked by that key’s id in the key set; a revoked key fails it.
  • A failed timestamp never blocks an export. When too few TSAs answer, the export is written without timestamps.json and says why (deeds export prints the reason; the server logs export_not_timestamped).
  • The independent verifiers skip timestamps. verifiers/python and verifiers/js accept a bundle with timestamps.json and check its bytes against the manifest, but don’t check tokens or proofs or compare heads. Older Deedglass builds and older copies of those verifiers refuse such a bundle as bundle_invalid (an unknown entry).
  • Each head costs a request per TSA. --every asks only when the head has moved; an export asks once.