STRATA/1 specification
A minimal, contract-free convention for anchoring cryptographic commitments in the calldata of Robinhood Chain transactions, inheriting Ethereum's data availability and finality by way of the rollup's batch posting.
Version 1, frozen. This document is normative; the site is a reference implementation of it. Where the two disagree, the document is right and the site is a bug.
§1Overview
STRATA defines a proof-of-existence primitive. A party in possession of some bytes computes a 32-byte commitment to them, embeds that commitment in a fixed 38-byte payload, and sends the payload as calldata in a zero-value transaction to a well-known address. Once the transaction is included in a block, the chain constitutes evidence that the commitment — and therefore, under the collision resistance of keccak-256, the underlying bytes — existed no later than that block's timestamp.
The design goals, in order of priority:
- No trusted intermediary. No contract owner, no upgrade key, no server, no database. A record cannot be revoked, reordered, or censored after inclusion.
- No state. The protocol writes nothing to chain state. Its entire footprint is calldata, which is exactly the surface that is published to Ethereum.
- Recomputable from first principles. A reader with a JSON-RPC endpoint and a keccak-256 implementation can verify any record. No indexer, no SDK, no API key.
- Constant, minimal cost. Every record is the same size regardless of what it commits to: one file or ten thousand.
§2Payload
A STRATA record is a transaction whose to field is the sink, whose value is zero, and whose input is exactly 38 bytes laid out as follows.
offset len field encoding
0x00 4 magic 0x53545241 ASCII "STRA", fixed
0x04 1 version 0x01 this specification
0x05 1 kind 0x00 keccak-256 digest of a byte string
0x01 sorted-pair Merkle root over keccak-256 leaves
0x06 32 digest big-endian 32-byte commitment
total 38 bytes
A reader MUST reject a transaction as a STRATA record if any of the following hold:
- the
tofield is not the sink address, case-insensitively; - the input length is not exactly 38 bytes;
- the first four bytes are not the magic.
A reader SHOULD surface, rather than reject, a record whose version byte is unrecognised: future versions are expected to keep the magic and the version byte in place, so a version-1 reader can still report "this is a STRATA record of a version I do not understand" rather than silently discarding it.
The kind byte is advisory. It tells a reader how the digest was constructed so that a verifier can reproduce it, but the chain enforces nothing about it. A record whose kind is 0x01 and whose digest happens to be a plain keccak-256 hash is well-formed and indistinguishable; the semantics live entirely in the manifest the sealer keeps off chain.
§3The sink
All records are addressed to a single address, the sink:
0x00000000000000000000000000000000005ea1ed the low bytes spell "5EA1ED"
The sink has three properties that matter:
- It has no code.
- A transaction to it executes nothing. The gas cost is the intrinsic transaction cost plus calldata, and there is no execution path that can revert, so a well-formed seal cannot fail for protocol reasons.
- It has no known private key.
- The address is a low-numbered constant chosen for legibility, far outside the range any keypair generator will produce. Nothing sent to it can be moved. This is intentional: the sink is a destination, not an account.
- It is a filter, not a gate.
- Addressing every record to one place turns "find all STRATA records" into a single indexed query on any block explorer, and makes a record trivially distinguishable from ordinary traffic. It confers no authority: anyone may send to it, and nobody may stop them.
value = 0 and the reference implementation hard-codes it. Wallets that helpfully "round up" a transaction's value should not be used for sealing.§4Digest construction
For kind = 0x00, the digest is the keccak-256 hash of the committed byte string, with no prefix, no length encoding, and no domain separator:
digest = keccak256(bytes)
This is the same primitive Ethereum uses, so every existing tool already computes it: cast keccak, ethers.keccak256, web3.keccak, pysha3, js-sha3. Note that this is the original Keccak padding, not NIST SHA3-256; the two produce different output for the same input and are not interchangeable.
For text, the committed bytes are the UTF-8 encoding of the string, unnormalised. Two strings that differ only in Unicode normal form produce different digests. Applications that need stable text commitments should normalise (NFC) before hashing and say so in their manifest.
For files, the committed bytes are the file's contents verbatim. Filenames, timestamps and permissions are not committed to; if they matter, commit to an archive or to a manifest that names them.
§5Merkle manifests
For kind = 0x01, the digest is the root of a binary Merkle tree over the keccak-256 leaves of each committed item, using sorted-pair hashing:
parent(a, b) = keccak256( min(a,b) ‖ max(a,b) )
Sorting the pair before concatenation removes the need to carry a left/right bit in each proof step, which halves the bookkeeping in a verifier at the cost of not distinguishing sibling order. This is the same construction OpenZeppelin's MerkleProof library uses, so an on-chain verifier for STRATA manifests is four lines of Solidity.
Tree construction rules:
- Leaves are the keccak-256 digests of each item, in the order the sealer chose. Order is preserved in the manifest, so a verifier reproduces the same tree.
- At each level, nodes are paired left to right. If a level has an odd number of nodes, the final node is promoted unchanged to the next level. It is not duplicated. (Duplicating an odd leaf enables a known second-preimage forgery; promotion does not.)
- A single leaf is not a tree. A set of one item MUST be sealed as
kind = 0x00, so that the digest is the item's own hash.
Manifest format
The manifest is the off-chain half of a kind = 0x01 record. It is not consensus-critical and the chain never sees it; it exists so a third party can be handed one file out of a set and check it against the root.
{
"strata": 1,
"kind": "merkle",
"root": "0x<32 bytes>",
"algorithm": "keccak-256 / sorted-pair",
"leaves": [
{ "name": "contract-v3.pdf",
"size": 184320,
"leaf": "0x<32 bytes>",
"proof": [ "0x<sibling>", "0x<sibling>" ] }
]
}
To verify one item: hash the file, check it equals leaf, then fold the proof with sorted-pair hashing and check the result equals root. Then verify root against the chain per §7. The manifest is worth keeping alongside the files it describes; without it, a root commits to the set but cannot be opened for any individual member.
§6Writing a seal
A seal is an ordinary transaction. Any signer that can set an arbitrary data field on chain 4663 can produce one; the reference site is a convenience, not a dependency.
From a browser wallet
await ethereum.request({
method: 'eth_sendTransaction',
params: [{
from,
to: '0x00000000000000000000000000000000005ea1ed',
value: '0x0',
data: '0x53545241' + '01' + '00' + digestHex
}]
});
From the command line
# foundry cast send 0x00000000000000000000000000000000005ea1ed \ --rpc-url https://rpc.mainnet.chain.robinhood.com \ --private-key $KEY \ --value 0 \ 0x535452410100$(cast keccak "$(xxd -p file.bin | tr -d '\n')" | cut -c3-)
Network parameters
| Parameter | Value |
|---|---|
| Chain ID | 4663 (0x1237) |
| Name | Robinhood Chain mainnet |
| RPC | https://rpc.mainnet.chain.robinhood.com |
| Explorer | https://robinhoodchain.blockscout.com |
| Native currency | ETH, 18 decimals |
| Architecture | Arbitrum Nitro rollup settling to Ethereum |
The public RPC endpoint is rate limited and is not intended for production write loads. An application sealing at volume should run its own node or use a dedicated provider; the protocol is indifferent to which endpoint a writer uses.
§7Reading a seal
Verification is four RPC-free steps and two RPC calls. No part of it requires this site, and a verifier should not use this site if the thing being verified is adversarial to its operator.
- Fetch the transaction.
eth_getTransactionByHash. If the node returns null, the hash is unknown to that node; try another before concluding anything. - Check the envelope.
toequals the sink;inputis 38 bytes; magic matches. Reject otherwise. - Extract the digest. The last 32 bytes of
input. - Fetch the block.
eth_getBlockByNumberontx.blockNumber. Itstimestampis the upper bound on when the digest existed. Itsl1BlockNumbernames the Ethereum block the rollup referenced, which is the anchor for §8. - Recompute. Hash the bytes you hold and compare, constant-time or otherwise, to the extracted digest.
A verifier MUST confirm the transaction has a non-null blockNumber. A transaction in the mempool has been seen but not ordered, and carries no timestamp claim whatsoever.
§8Fees
The cost of a seal is the intrinsic transaction cost plus the calldata cost of 38 bytes, priced under the rollup's fee model. Nitro chains charge an L2 execution component and an L1 data component; the latter dominates for a transaction that does nothing but carry bytes, and it tracks Ethereum's base fee rather than Robinhood Chain's.
| Component | Cost |
|---|---|
| Intrinsic | 21,000 gas |
| Calldata | 38 bytes, 16 gas per non-zero byte, 4 per zero byte |
| Storage | none — the protocol writes no state |
| L1 data | compressed share of the batch, charged by the sequencer |
The site estimates the live figure with eth_estimateGas against the current base fee and displays it in layer.03 before you sign. In practice a seal has been costing a small fraction of a US cent; it is not a number worth designing around, but it is not zero, and an application sealing per-event at high frequency should batch into a Merkle root instead.
§9Settlement
Inclusion in a Robinhood Chain block is not the same as finality on Ethereum, and a rigorous verifier distinguishes three stages:
- Sequenced (seconds)
- The sequencer has ordered the transaction and emitted a block. This is what
eth_getTransactionReceiptreturns and what the site reports as "sealed". It is a commitment by the sequencer, backed by its reputation and the rollup's escape hatch, not yet by Ethereum. - Batched (minutes)
- The transaction's calldata has been compressed into a batch and posted to Ethereum. From this point the data is available to anyone reconstructing the chain from L1, and the record survives the sequencer disappearing entirely. The
l1BlockNumberfield on the block names the L1 block the rollup was tracking at the time, which bounds where to look. - Settled (the challenge window)
- The state root covering the block has been posted and its dispute period has elapsed without a successful challenge. For a proof-of-existence claim this stage adds little: the claim rests on data availability, which batching already provides, not on the correctness of any state transition.
This distinction is the practical answer to "how long should I wait?" — for a timestamp claim, until batched. STRATA reports the sequenced block immediately and gives you the L1 reference so the batch can be located; it does not currently watch for the batch itself, which is item two on the roadmap.
§10Threat model
Stating what a system does not do is more useful than restating what it does.
What STRATA resists
- Backdating. A record cannot be inserted into a past block. The timestamp is an upper bound produced by a party with no incentive to move it and no ability to do so retroactively.
- Silent revision. Nothing in the protocol can edit or delete a record. There is no owner, no pause, no upgrade.
- Operator disappearance. If this site goes offline, every existing record remains verifiable with
curland any keccak-256 implementation. §7 is the whole reader. - Content disclosure. A digest reveals nothing about its preimage to an observer who does not already have a candidate for it.
What STRATA does not resist
- Low-entropy preimages. A digest of "yes" or of a four-digit number is trivially brute-forced. If confidentiality matters, commit to
bytes ‖ saltwith a high-entropy salt and keep the salt with the manifest. Version 1 does not specify a salting scheme; it does not prevent one. - Priority disputes. A seal proves you had the bytes by time T. It does not prove you were first, that you authored them, or that anyone else did not have them earlier and simply not seal them.
- Claims about the future. A record is a lower bound on existence and nothing else. It says nothing about what the bytes mean or whether the claim inside them is true.
- A dishonest sequencer forcing a delay. The sequencer cannot alter a record but can decline to include one, which delays a seal rather than falsifying it. The rollup's force-inclusion mechanism bounds that delay; STRATA inherits that property and adds nothing to it.
- Loss of the preimage. The chain stores 32 bytes. If you lose the file, the record is unopenable and worthless. STRATA is not storage and is not a backup.
- Grinding on a chosen digest. keccak-256 collision resistance is the entire load-bearing assumption. If it falls, so does Ethereum, and this is the least of the consequences.
§11FAQ
Why Robinhood Chain rather than Ethereum directly?
Cost and the same guarantee. The bytes end up on Ethereum either way — that is what a rollup does — but they arrive compressed and amortised across a batch, so the marginal cost of one 38-byte record is a fraction of what the equivalent L1 transaction costs. The data-availability property a timestamp claim rests on is preserved.
Is there a token?
No. There is nothing to buy, nothing to stake, and no mechanism by which the protocol could have one: it is a byte layout and an address.
Can I seal the same digest twice?
Yes, and nothing stops you. Duplicate records are well-formed; the earliest one is the meaningful claim. A reader looking for the first occurrence of a digest should scan the sink's history and take the lowest block number.
Can someone else seal my digest?
Yes. Anyone who learns a digest can seal it from their own address. What they cannot do is produce the preimage, which is what an opened claim requires. If the ordering of claims matters to your application, bind the digest to an identity — hash bytes ‖ address, or sign the digest and commit to the signature.
What if the explorer index is down?
Layer.05 degrades to seals made from your own browser and says so. Verification in layer.04 is unaffected because it reads from an RPC node, not from the explorer. The index is a convenience over the chain; it is not the chain.
Is the site custodial in any way?
No. It never receives a file, never receives a private key, and holds no funds. Hashing happens in your tab, the transaction is signed by your wallet, and the destination is an address nobody controls.
§12Roadmap
Version 1 is frozen. Everything below is additive and none of it invalidates an existing record.
- Salted commitments (
kind = 0x02) - A specified salting scheme, so low-entropy preimages get a documented answer rather than a footnote in §10. The manifest carries the salt; the chain sees only the salted digest.
- Batch watcher
- Follow a sealed transaction from sequenced to batched and report the L1 batch transaction that carries it, closing the gap described in §9.
- Registry v2 (optional contract)
- A contract exposing
sealedAt(digest)and aSealedevent, for applications that need on-chain lookup or log-based indexing. It costs a storage write and is strictly optional; version 1 records remain valid and readable without it. Source is incontracts/StrataRegistry.sol. - Reader library
- A dependency-free reader, under 100 lines, published so that verification does not route through a web page at all.
§13Changelog
| Version | Status | Change |
|---|---|---|
| STRATA/1 | frozen | Initial specification. Magic, version, kind, digest. Kinds 0x00 and 0x01. Sink fixed. |
This document describes an experimental protocol on a public blockchain. Records are permanent and public. Nothing here is legal, financial, or evidentiary advice, and a blockchain timestamp is not a substitute for a jurisdiction's rules of evidence.