Provn

Anchoring

Batch receipts into a Merkle tree and publish the root on Robinhood Chain.

Prove / anchoring

Anchoring gives a batch of receipts a public timestamp on Robinhood Chain. It needs a funded signer in PROVN_ANCHOR_KEY. A deployment without one still seals and publishes batches, and reports them as unanchored.

The tree

  • Leaf: SHA-256 of the receipt payload bytes.
  • Parent: SHA-256 of the left child's bytes followed by the right child's.
  • Odd count: the last node moves up a level unchanged.
ts
import { createHash } from "node:crypto";

const sha256 = (b: Buffer) => createHash("sha256").update(b).digest();

export function merkleRoot(payloads: Buffer[]): Buffer {
  let level = payloads.map(sha256);
  while (level.length > 1) {
    const next: Buffer[] = [];
    for (let i = 0; i < level.length; i += 2) {
      next.push(i + 1 < level.length ? sha256(Buffer.concat([level[i], level[i + 1]])) : level[i]);
    }
    level = next;
  }
  return level[0];
}

The transaction

Provn sends a 0-value transaction whose calldata is a 6-byte tag followed by the 32-byte root.

text
0x50524f564e01 + <32-byte root>

50 52 4f 56 4e   "PROVN"
01               format version

Fetch the transaction from the public RPC and read its input. It starts with 0x50524f564e01, and the 64 hex characters after that are the root.

bash
curl https://rpc.mainnet.chain.robinhood.com \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionByHash","params":["0xANCHOR_TX_HASH"]}'

Inclusion proofs

GET /v1/receipts/:id/proof returns the Merkle path for a receipt and the anchor status of its batch. Hash the receipt payload, combine it with each step of the path, and compare the result with the root in the transaction. GET /v1/anchors lists batches.

bash
curl https://YOUR-PROVN-HOST/v1/receipts/req_4tQ8.../proof
curl https://YOUR-PROVN-HOST/v1/anchors

What an anchor shows

  • The receipt existed in this exact form no later than the block that holds the root.
  • Provn can't swap in a different receipt without changing the root.

What it does not show

  • Anything about the answer or the upstream host that the receipt itself doesn't show.
  • Any timestamp for a batch marked unanchored. That batch has a root but no transaction on chain.