cert-machine · Verify Seal

Prove you held the draft, without showing it

Researchers now hold back directions for fear that a draft pasted into a frontier model comes back as someone else’s paper. A seal lets you prove, later, that you held these exact bytes by this date, and shows nothing until you choose to. Seal before anything goes into a cloud model. One file, Python’s standard library, nothing of the content leaves your machine.

planted forgeries certified
0 of 24
flipped bytes, wrong salts, renamed or added files, edited manifests: every one REFUTED or REFUSED
cases in the battery
42
the honest seals certified, the date rule, the unconfirmed anchors, determinism, the shell one-liner
red controls fired
5 of 5
a deliberately broken opener or verifier is caught every time
digests in the public registry
1
each a git commit date, one of the two anchors; the first is our own, below
in one minute

What a seal is, and when to make one

The worry. You have a draft, a statement, a direction, and you want help from a model, a colleague, a referee. Once the bytes leave your machine you cannot prove you had them first. Terence Tao said as much in his Math 2.0 talk: people hold back.

The seal. On your machine, the tool makes a deterministic archive of the file or folder, draws 32 random bytes (the salt), and computes one number from the two: the digest. The archive and the salt stay with you. Only the digest goes out, to two public anchors nobody here controls: OpenTimestamps, which ties it to a Bitcoin block, and this repository’s registry, where one commit holds it. The digest reveals nothing: without the salt there is nothing to guess, and a 32-byte salt is not guessed.

The reveal, when you choose. Hand over the archive and the salt. Anyone re-derives the digest with one line of shell, without this tool, and reads the anchors’ dates. Sealed file by file, you can show one file, the theorem statement, say, while the notes stay sealed: the manifest names no path and no salt.

What to do today. Seal the folder before the first paste into a cloud model. Keep the bundle somewhere you will find it; a lost salt is an unopenable seal. The commands are three lines, below.

what stays open

What a seal does not say

how

Three commands, and three more to show one file

The tool is one file, instruments/seal/seal.py, with its instruments/seal/README.md. Python 3.9 or later, standard library only; no network except the anchoring step, and that step sends the digest and nothing else.

python3 seal.py seal  <file-or-folder>            # -> <name>.seal/ : archive.tar, salt.hex, seal.json, digest.txt; prints the digest
python3 seal.py stamp <name>.seal                 # OpenTimestamps (ots stamp, free) + the registry line to post
python3 seal.py open  <name>.seal [--by YYYY-MM-DD] [--registry <clone of this repository>]

One file at a time (v2). Seal a folder file by file; later, show one file and keep the rest sealed.

python3 seal.py seal2  <folder>                   # -> <name>.seal2/ : manifest.json, salts.json (private), archive.tar (private), salt.hex, seal.json, digest.txt
python3 seal.py reveal <name>.seal2 <path>         # the package that shows ONE file: its bytes, its salt, the manifest, the seal and its anchors
python3 seal.py verify <package> [--by YYYY-MM-DD] [--registry <clone>]

Without this tool. The digest is one line of shell on the bundle (macOS; on Linux, sha256sum for shasum -a 256):

( printf 'verify-seal-v1\n'; xxd -r -p salt.hex; shasum -a 256 archive.tar   | cut -c1-64 | xxd -r -p ) | shasum -a 256
( printf 'verify-seal-v2\n'; xxd -r -p salt.hex; shasum -a 256 manifest.json | cut -c1-64 | xxd -r -p ) | shasum -a 256

The battery runs both lines against the Python digest on every build.

the decision

Three words, and what each one needs

why you can trust this

The commitment, and the forgeries it refused

The commitment. digest = SHA256("verify-seal-v1\n" || salt_32_bytes || SHA256(archive_bytes)). The archive is a tar of the regular files under the path, names relative and NFC-normalised, sorted by their UTF-8 bytes, mtime 0, uid and gid 0, mode 0644, no directories, no symbolic links (a symlink refuses the seal), no compression: the same contents give the same bytes on any machine. The salt is exactly 64 lowercase hex characters and nothing else; a trailing newline refuses, which caught a real bug on the battery’s first run.

One file at a time. Per file, with its own salt s_i: h_i = SHA256("verify-seal-v2-file\n" || s_i || SHA256(bytes_i)) and q_i = SHA256("verify-seal-v2-path\n" || s_i || path_i). The manifest holds only an index and those two hashes per file, in canonical JSON, and is sealed as above. The salts stay private; a reveal publishes one. So the manifest shows how many files were sealed and nothing else, and a revealed salt says nothing about the other files.

The battery (certs/seal-battery.json, written by instruments/seal/battery.py in a temporary folder with a temporary git registry; no network, no ots client, the OpenTimestamps path exercised only as present-but-unconfirmed): 24 planted forgeries, 0 certified; 42 cases; 5 red controls on a deliberately broken opener or verifier, each of which must turn a forgery green under it, or the battery itself fails. The tool’s sha256 is in the record (0198a91da9bc…); a changed tool must re-run the battery.

planted forgery (v1, whole bundle)wordwhat the opener said
one byte flipped in the archiveREFUTEDthe digest does not re-derive; what differs is the archive: its sha256 is not the one sealed (a byte, a file or a name inside it differs)
wrong salt (64 hex, another value)REFUTEDthe digest does not re-derive; what differs is the salt: salt.hex is not the salt drawn at sealing
truncated salt (60 hex)REFUSEDsalt.hex is not exactly 64 lowercase hex characters (60 character(s))
salt with a trailing newlineREFUSEDsalt.hex is not exactly 64 lowercase hex characters (65 character(s), ends in a newline)
salt missingREFUSEDsalt.hex is missing: without the salt the digest cannot be re-derived (a lost salt is an unopenable seal)
archive missingREFUSEDarchive.tar is missing: without the bytes there is nothing to re-derive (the researcher holds it)
a file added inside the folder (re-archived under the sealed salt)REFUTEDthe digest does not re-derive; what differs is the archive: its sha256 is not the one sealed (a byte, a file or a name inside it differs)
a file removed inside the folder (re-archived under the sealed salt)REFUTEDthe digest does not re-derive; what differs is the archive: its sha256 is not the one sealed (a byte, a file or a name inside it differs)
a file renamed inside the folder (re-archived under the sealed salt)REFUTEDthe digest does not re-derive; what differs is the archive: its sha256 is not the one sealed (a byte, a file or a name inside it differs)
seal.json's digest editedREFUTEDthe digest does not re-derive; what differs is the recorded digest itself: archive and salt are the sealed ones, so seal.json's digest was edited
seal.json's version editedREFUSEDseal.json version 'verify-seal-v0' is not verify-seal-v1 or verify-seal-v2; this opener reads only those
seal.json missingREFUSEDseal.json is missing; the bundle cannot be opened without the recorded digest
planted forgery (v2, one file revealed, or the whole bundle)wordwhat the verifier said
the revealed file's bytes differ by one bitREFUTEDthe revealed bytes do not re-derive the manifest's sha256 at index 3 (the name does): the bytes differ from the sealed ones
the right bytes revealed under another nameREFUTEDthe revealed name does not re-derive the manifest's path_hash at index 3 (the bytes do): the file was sealed under another name
the revealed file claimed at another index of the manifestREFUTEDneither the revealed bytes nor the revealed name re-derive the manifest's entry 0 under this salt: not the file that was sealed there
a revealed file absent from the manifest (index beyond it)REFUTEDreveal.json names index 99, but the sealed manifest holds 4 file(s): no such sealed file
another salt for the revealed fileREFUTEDneither the revealed bytes nor the revealed name re-derive the manifest's entry 3 under this salt: not the file that was sealed there
the manifest edited (a sha256 changed, canonical form kept): its seal no longer re-derivesREFUTEDthe manifest's digest does not re-derive; what differs is the manifest: its sha256 is not the one sealed — nothing under it is decided
the manifest re-serialised with spaces (not canonical)REFUSEDmanifest.json is not in canonical form (sorted keys, no spaces, one trailing newline): re-derive it with manifest_bytes
the manifest missing from the packageREFUSEDmanifest.json is missing
another manifest saltREFUTEDthe manifest's digest does not re-derive; what differs is the salt: salt.hex is not the manifest's salt — nothing under it is decided
the package's seal.json claims v1REFUSEDseal.json version 'verify-seal-v1' is not verify-seal-v2; this verifier reads only verify-seal-v2
one byte flipped in the whole bundle's archive (the manifest intact)REFUTEDthe manifest's seal re-derives but 1 file(s) do not: index 2 (notes/plan.txt): the bytes or the name do not re-derive the manifest's entry
one per-file salt replaced in salts.jsonREFUTEDthe manifest's seal re-derives but 1 file(s) do not: index 0 (données.txt): the bytes or the name do not re-derive the manifest's entry
  • Red control. an opener that certifies a mismatched digest IS caught (a flipped byte comes back CERTIFIED under it) Fired.
  • Red control. an opener that ignores the claimed date IS caught (a date before the anchor comes back CERTIFIED under it) Fired.
  • Red control. an opener that tolerates a newline after the salt IS caught (bytes.fromhex strips it; the strict rule is what refuses) Fired.
  • Red control. a verifier that skips the salted path check IS caught (the right bytes under another name come back CERTIFIED under it) Fired.
  • Red control. a verifier that skips the salted file hash IS caught (flipped bytes come back CERTIFIED under it) Fired.
the registry

Digests anchored here

The second anchor is seals/registry.jsonl: one line per digest, appended by the maintainer in one commit on a request through the issue form; tools/seal-registry.py refuses anything but a 64-hex digest and a repeat. The commit’s date is what open --registry <clone> reads.

the first seal

Our own proposal, sealed before it was read

What was sealed. claimbox, the claimant of this machine’s Lean proposals, proposed a lab-claim intake route on 5 November 2026 (its branch intake-2026-11-05, 10 files). Before cert-machine read a line of it, the branch’s files were sealed file by file in a private folder; the digest went to OpenTimestamps and to the registry above in one commit. Then one file was revealed, the route document, and the rest stay sealed. The separation this buys is the one the “same operator” caveat on every ETH page names: the author’s bytes are dated by anchors the judge does not control.

The verdict, re-run on every build of this page (certs/seal-first-use.json, the package seals/reveals/claimbox-intake-2026-11-05): CERTIFIED — this file re-derives its salted hash and its salted name at index 6 of a manifest whose seal re-derives; 1 anchor(s) confirmed; the file was held by 2026-11-05. The other 9 file(s) stay unrevealed and undecided.

  • The revealed file. lean/INTAKE.md, 12,329 bytes, index 6 of 10 in the manifest.
  • The manifest’s digest. 8e794bc681ea3338df608502c86c59fabd1ef77d14b2b8a9d05f815c69ddf562
  • Anchor 1, the registry. Confirmed: commit 0dcc77fb, dated 2026-11-05.
  • Anchor 2, OpenTimestamps. Stamped, pending: a Bitcoin attestation takes an hour or two, then ots upgrade reads the block; until then this anchor says nothing.
  • Held by. 2026-11-05, the earliest confirmed anchor.

What this does not say: that the route is right, or that it is ours alone. It says that these bytes existed, under this author, by this date, before the judge looked. The rest of claimbox’s proposal is decided the usual way, later, from the bytes.

contact

Write to Carlos

Sent by Web3Forms to Carlos’s inbox; nothing stored here.

Or write to carlos@carlostoledo.co, or open the public pilot form on GitHub.