The ETC Anchor

Your digest climbs three Merkle trees, and the last root goes into the input of one Ethereum Classic transaction. That transaction is the anchor every proof ends in.

The Merkle Tree

Etchd builds its trees the way python-opentimestamps does. Leaves pair left to right as sha256(left || right), and when a level has an odd count the last element moves up unchanged. A single leaf is its own root. Each step on the path appends or prepends the sibling and hashes with SHA-256. It costs 35 bytes in the proof.

Your proof walks all three in a row.

digest
  append(server nonce) sha256
  round tree hops              every digest in the same second
  prepend(u32be(second))       pending attestation sits here
  sha256
  calendar tree hops           every second of the minute
  prepend(prev_tick_root)
  append(beacon_root || witness_root || tick_header) sha256
                               tick_root
  batch tree hops              every tick in one ETC transaction
  prepend(u64be(last_n))       EtcTxAttestation
Diagram. A digest climbs the round tree, the calendar tree and the batch tree, and the last root goes into an Ethereum Classic transaction. Your proof climbs the round tree, the calendar tree and the batch tree, and the last root goes into the ETC transaction. Each step on the path costs 35 bytes, and the odd leaf at the end of a level moves up unchanged.

A proof with no tree hops is 297 bytes. One with a million digests in the same second, a full minute of rounds and a 1,000-tick backlog is 1,557 bytes. To see the trees of a real tick, open its page from the live view, which draws them next to the raw tick.

One Transaction a Minute

One transaction carries a Merkle root over every closed tick that is not on chain yet, which in normal running means one tick and one transaction a minute. The calldata is 40 bytes.

calldata = u64be(last_n) || batch_root
Diagram. Closed ticks fold into a batch root, which with the last tick number makes 40 bytes of calldata in one Ethereum Classic transaction. Every closed tick not yet on chain folds into a batch root. With the last tick number in front it is 40 bytes of calldata, sent in one transaction to EtchdAnchor, which stores nothing and emits it as a log.

Each one is a legacy EIP-155 transaction at a fixed 1 gwei with a gas limit of 30,000. It uses about 23,100 gas.

The Contract

The transaction goes to the anchor contract EtchdAnchor at 0xa6f241d25efa4e8582b808fa44cda1a68cd69fed on ETC mainnet, chain id 61, verified on Blockscout. It stores nothing. It copies the calldata and emits it as a LOG1 with the sender as the only topic. This is the whole source, from EtchdAnchor.sol.

EtchdAnchor.sol
// SPDX-License-Identifier: MITpragma solidity 0.8.33; /// @title etchd anchor/// @author Classix/// @notice Once every UTC minute etchd (https://etchd.app) sends this contract 40 bytes: the newest tick number///         covered (8 bytes, big-endian) and a Merkle root (32 bytes) over the ticks in that batch. Each tick root///         commits to every hash stamped, every chain head and every witness capture of its minute./// @dev The contract stores nothing and has no owner, so anyone can call it. It logs the calldata unchanged, with the///      sender's address as the only topic, so anchors can be found with eth_getLogs. Proofs never rely on the log.///      They point at the transaction and check its calldata. etchd publishes its signer addresses at///      https://etchd.app/v1/signers, which is how its anchors are told apart from anyone else's.contract EtchdAnchor {    fallback() external payable {        assembly ("memory-safe") {            // Copy the whole calldata (40 bytes from etchd) to memory at offset 0.            calldatacopy(0, 0, calldatasize())            // Emit it as a log with one topic, the address that sent the transaction.            log1(0, calldatasize(), caller())        }    }}

A Public Noticeboard

Nothing guards EtchdAnchor. It has no owner and no access check, and it copies whatever calldata it gets into a log whose only topic is the sender's address.

event = LOG1(topic0 = msg.sender, data = calldata)

Anyone can post to it, whether another calendar with roots of its own or someone with 40 bytes to put on ETC for some other reason. We left it open on purpose. ETC Transaction Format in the design notes gives the reasons.

Diagram. The EtchdAnchor contract logs whatever anyone sends it. A proof points at one exact transaction by block height and index, and etchd.app lists only the anchors the Etchd signer sent. Anyone can post to EtchdAnchor, and it logs every post with the sender as the topic. A proof names one transaction by block height and index and never searches the log. etchd.app lists only what the Etchd signer sent.

Records from different senders end up side by side in the same log, but a proof points at one exact transaction, the verifier checks who sent it, and our site shows only our own records.

One Exact Transaction

An ETC attestation names a block height and a position in that block, and the verifier compares the input of that one transaction with the 40 bytes your proof computes. The verifier does no search of the contract's log, and a post from someone else cannot stand in for one of ours, or the other way round.