The BitGraphed file

The file is its own proof. A BitGraphed file is the committed bytes followed by one structural block that holds the bitgraph/1 proof and everything a reader needs to check it with no URL, no operator, and no trust beyond mathematics, the AWS Nitro root and two public block hashes: the floor anchor and its Ethereum header, the closing anchor, the Base block the record existed by, its settlement on Ethereum when known, and the enclave’s attestation laid out as evidence anyone can check with openssl. Everything travels in the one file, offline, with nothing resolved from this site. It holds a single-file position, an earlier form made while the floor was an Ethereum anchor; positions made now stand on a Base floor and travel in an export (below). Since bitgraph-carrier/4, one file of a tree travels the same way: the file with its place in the tree and the whole proof inside.

The envelope rule

The outer file is an envelope. Its own hash is committed nowhere and proves nothing: anyone can repackage the same committed bytes with the same block and get a different outer hash, and that is fine. The envelope is never the recorded thing; the bytes inside it are. A verifier strips the block by structure, hashes what remains, and checks that digest against the proof. Change one byte of the committed bytes and the pair is detectably invalid.

Dropping a BitGraphed file on this site does the same thing: the block is stripped and the bytes inside are checked, and the MCP server and the SDK likewise never record the envelope in place of what it carries. An envelope can still be given a position of its own through any path that hashes it as it is. A reader of that file then strips one block and finds the envelope, which is why a reader strips exactly one.

The time window

The floor is always inside. A BitGraphed file holds an earlier single-file position, so its floor is an Ethereum anchor: the anchor the enclave fixed when the position was allocated and signed into the proof, matched to the signed block number and hash, with that block’s raw header beside it. Recomputing the header’s hash offline shows it is the header the enclave signed, so the time read from it belongs to that block; one lookup on any Ethereum node or explorer, at any time, shows the block is on Ethereum. Together they say the record was placed after this block. A file made with bitgraph-fuse/2 says more: its commitment binds that block’s hash, so the bytes themselves could not have been finished before the block existed. (A position made now names a Base block instead, signed into the proof as commit.slotFloor and bound by bitgraph-fuse/3; see the proof format.)

Three things follow the commit, and each is carried once it exists:

  • The closing anchor. The next Ethereum anchor in the sequence: the record was committed before the anchoring of that block, a bound in position, not a clock time. Every position made while anchors ran has one; the format calls it ceiling.
  • The ceiling in time. A Base block that includes a Merkle root over the record’s proof hash: the record existed by that block’s time. It lands seconds after the commit, and it is carried as the same bitgraph-ceiling/1 file the proof page serves, whole, so the raw Base transaction, its inclusion proof and the Base header are inside. Records made before 2026-09-29 have none, and the file says so.
  • Settlement. When known, Ethereum’s own record of that Base block. The current form, which an export carries, goes through Base’s output root (bitgraph-output-root/1): an Ethereum header and the proof that the output root in it covers the Base block. A BitGraphed file carries the earlier pointer form (bitgraph-settlement/1): the Ethereum header, the batcher transaction’s inclusion proof and the blob commitments for the Base batch data holding the ceiling transaction. No blob bytes travel in either; BitGraph archives them at bitgraph.ing/api/ceilings/blobs/<versioned hash>.bin because Ethereum prunes them after about 18 days. Either form says the record existed by that Ethereum block, whatever Base’s own state says.

Neither the closing anchor nor the ceiling in time can be inside at the moment of the commit, so each is either present or stated as not fetched, in those words. Completion is a one-step patch from public data: drop the file back on the site, or run bitgraph complete, and what has landed since is fetched in. The committed bytes never change, so the proof is unaffected. Nothing already inside is ever overwritten; a file offered a different ceiling than the one it holds refuses it, because a conflicting embedded ceiling is evidence worth keeping.

Every proof inside also carries its Nitro attestation, whose timestamp is the enclave platform’s signed clock. It states the instant of the commit, and the blocks bound that instant from outside.

The attestation, checkable with openssl

The one thing a reader with no BitGraph code used to be unable to check was the AWS attestation: a COSE-signed CBOR document whose certificate chain reaches the AWS Nitro root, with certificates that live about three hours. A bitgraph-carrier/2 block carries the same document laid out as evidence, so the check is five openssl commands and three greps, with no CBOR knowledge:

Shell (files written from the block’s attestation witness)
openssl x509 -in root.pem -outform DER | openssl dgst -sha256     # 641a0321…9bb5b: the AWS Nitro Enclaves Root CA G1
openssl verify -attime <atTimeUnix> -CAfile root.pem -untrusted chain.pem leaf.pem   # leaf.pem: OK
openssl x509 -in leaf.pem -pubkey -noout > leaf.pub
openssl dgst -sha384 -verify leaf.pub -signature sig.der sigstructure.bin      # Verified OK
xxd -p sigstructure.bin | tr -d '\n' | grep -c <pcr0>        # 1: PCR0 is inside the signed bytes
xxd -p sigstructure.bin | tr -d '\n' | grep -c <user_data>   # 1: user_data is inside the signed bytes

-attime is the document’s own timestamp: the certificates are evaluated at the instant AWS signed, which is the only instant that means anything, and a check at “now” fails on every attestation older than a few hours. Then PCR0 must equal the proof’s environment.measurement, and user_data must equal the SHA-256 of the proof’s canonical signed body (its proofHash on an ordinary proof). The witness carries each decoded value beside the value it must equal, and the procedure with its expected output. A verifier never trusts the witness: it recomputes it from the attestation document inside the proof and refuses a witness that differs.

What a verifier says

verifyCarrier answers one line per claim, each with what it rests on, at two levels. Offline, every claim holds by mathematics and the AWS root, with the block headers taken as the ones matching their hashes. Confirmed needs one question answered by any node the caller names: is that header the chain’s own block? The lookups are supplied by the caller; the verifier never fetches. The verdict is FALSE the moment any evidence contradicts a claim, TRUE when every carried claim holds, and UNDETERMINED when the block is unreadable, which is a damaged or newer block, never a verdict on the file. The reading is written from the results, never read from the file.

bitgraph verify bitgraph-demonstration.bitgraph.txt --eth-rpc … --base-rpc …
TRUE
  ok  The committed bytes are the ones the proof names [SHA-256]
  ok  The proof is signed and its position record is bound to it [Ed25519]
  ok  The bytes carry the position commitment [SHA-256 over the signed position record and its nonce]
  ok  AWS hardware signed the attestation [ES384 (P-384)]
  ok  The chain reaches the AWS Nitro root [the AWS Nitro Enclaves Root CA G1 (641A0321…)]
  ok  Every certificate was valid at the document's own instant [the document's signed timestamp]
  ok  The attestation is bound to this proof [user_data inside the signed document]
  ok  The floor is the anchor the enclave fixed for this position [the proof's signed commit.slotAnchor]
  ok  The floor block's header is the one with that hash [Ethereum block 26088457, header as given]
  ok  The record was committed before the closing anchor [the enclave's counter order]
  ok  The transaction is in the Base block [Merkle-Patricia proof; Base block 51979918, header as given]
  ??  How far Base had settled the block: reported by BitGraph's Base node, not proven by the file
  ok  The floor block is Ethereum's own [the Ethereum node the caller named]
  ok  The ceiling block is Base's own [the Base node the caller named]

  These exact bytes were finished after their position was opened: made after Ethereum block 26088457
  (2026-09-30T06:19:23Z), and existed by Base block 51979918 (2026-09-30T06:19:43Z); committed before the
  anchoring of Ethereum block 26088459, a bound in position. Position 4546 of epoch UX8Vhg+L…. Rests on:
  SHA-256, Ed25519, the AWS Nitro root (641A0321…), Ethereum block 26088457, Base block 51979918. Every
  block was confirmed against a node, so nothing here rests on a header taken as given.

The file also declares the pins it was made under: the enclave’s PCR0, the ceiling writer’s address, the chains. A verifier compares them with its own list and reports a difference; it never reads them as authority. Without a PCR0 list of its own it reports the measurement and says to compare it with the ones BitGraph publishes and reproduces.

What survives, what does not

The block rides after the file’s own end. Most formats never read past their own data, so a BitGraphed photo still opens as a photo. PDF and ZIP-based files (docx, xlsx, pptx) are read from the end, and tested readers still open them because they search back for their own end marker; a ZIP-based file stops opening once the block passes 65,535 bytes (its end record must sit within the last 64 KiB; measured 2026-09-30: Spotlight and Python fail at 65,536, and QuickLook hangs), so the builder keeps every block under 60,000 bytes for them, leaving the openssl witness out first (the attestation itself is still inside the proof) and keeping the proof beside the file, in an export, when even that is too large. MP4 and MOV have no such limit: every reader tested ignored a trailing block up to the format’s own 8 MiB cap. A block is about 50 KB with the witness and 35 KB without. Copying preserves it byte for byte. Re-encoding does not: export from an editor, and the block is gone the way any trailing data is. The original recording is unaffected either way, and the file can be rebuilt from its position page.

The block, for people writing readers

The block is found from the end of the file only, never by scanning: the last eight bytes are the magic, the four before them the payload length, and the leading magic and length must agree.

bitgraph-carrier/2
<committed bytes>                       exactly what was hashed and committed
"BGPROOF" 0x01                          8 bytes  magic + block version
<uint32 BE>                             payload length
<payload>                               UTF-8 JSON, the object below
<uint32 BE>                             payload length again
"BGPROOF" 0x01                          8 bytes  magic again

Strip exactly one block. The committed bytes can themselves end in a block, when an envelope was given a position and then carried, so a reader must never repeat the strip.

payload
{
  "carrier": "bitgraph-carrier/2",      // a reader that knows only /1 reports an unknown version, never a verdict
  "proof":   { ... },                   // the bitgraph/1 proof, unchanged
  "floor": {
    "status":  "present",               // always; a floorless file is not made
    "anchor":  { ... },                 // the anchor named by commit.slotAnchor
    "witness": { "headerRlpHex": "...", "blockNumber": 0, "blockHash": "0x..." }
  },
  "ceiling": { "status": "unfetched" }, // the closing anchor, a bound in POSITION; or: { "status": "present",
                                        //   "basis": "counter-order", "anchor": { ... }, "witness": { ... } }
  "ceilingInTime": { "status": "present", "sidecar": { "version": "bitgraph-ceiling/1", ... } },
                                        // or { "status": "unfetched", "searched": { "at": "<ISO>" } }
  "settlement": { "status": "present", "pointer": { "version": "bitgraph-settlement/1", ... } },   // optional; the pointer form, no blob bytes
  "pins": { "pcr0": "<hex>", "ceilingWriter": "0x...", "chains": { "ethereum": 1, "base": 8453 } },  // declared, never trusted
  "attestation": { "format": "aws-nitro-witness/1", "sigStructureB64": "...", "signatureDerHex": "...",
                   "chainPem": [ ... ], "rootPem": "...", "rootSha256": "641A0321...", "atTimeUnix": 0,
                   "decoded": { "pcr0": "...", "userDataB64": "...", "timestampMs": 0, ... },
                   "expected": { "pcr0": "...", "signedBodyHashB64": "..." }, "procedure": [ ... ] }   // optional
}

bitgraph-carrier/1 files, made before 2026-09-30, carry the first three fields only and verify exactly as before. An ordinary reader never needs to know the block is there: it sees the file it always saw. The formats inside are specified with the proof: the position record and its commitment, the ceiling file, and settlement. SPEC.md is the normative text; carriers are its section 15.5.

The BitGraphed file and the export

A BitGraphed file of a single-file position (bitgraph-carrier/1 to /3) carries committed bytes that are the file itself, and is still offered for them (bitgraph bitgraphed <path>, or the proof page’s download). In a tree/1 BitGraph the proof names the tree’s root document, not any file, so a member’s file (bitgraph-carrier/4) also carries the member’s place in the tree: the root document and the member’s leaf, index, count and path, exactly what that file’s own export carries. A reader hashes the bytes before the block, requires them to be the leaf’s committed bytes (the file itself when it was kept as is; the file with the commitment placed in it otherwise), walks the path to the root, and requires the root document to hash to the signed digest and to carry the position’s commitment. Then everything above applies, on whichever floor the proof signs. A placed member’s bytes were finished after the floor block; a member kept as is was recorded after it, and the bytes themselves are not dated. A file kept as is in a kind of file a trailing block would break (text, SVG, JSON, XML, HTML) travels with its export instead, and a member placed in a container is a tar and is named as one. A reader that knows only versions 1 to 3 reports a version 4 block as one it cannot read: a newer block, never a verdict on the file. The proof page offers a member’s BitGraphed file once the file is in hand and placed.

What BitGraph makes now is a tree/1: one position over one or more files, each a leaf, one file a tree of one. Its evidence travels as an export/1 JSON beside the files, with SPEC.md, and holds no copy of any file. The two answer the same questions in the same words, one line per claim, from bitgraph verify <file> for a BitGraphed file and bitgraph verify <file> <export.json> for a tree member. A reader that must stay inside one file takes the BitGraphed file; a reader with several files at one position takes the export.