Anchoring
Every thirty minutes Augur writes a Merkle root of new seals to Robinhood Chain, and you can verify any seal without trusting Augur.
Augur anchors its records so you do not have to trust its database. Every thirty minutes the engine writes a Merkle root over the newest seals to a contract on Robinhood Chain, and from then on any change to an anchored record breaks a proof you can check yourself, with the steps and code below.
The leaves
Each run takes the seals written since the last anchor:
- decisions with a final outcome (a decision with an order still in flight waits for the next run)
- portfolio snapshots
- theses
- scores whose inputs all sit in earlier anchors
The tree
leaf = sha256(0x00 || seal)
node = sha256(0x01 || left || right)
A seal is 32 bytes, written as 64 hex characters. The engine hashes each seal into a leaf, then pairs neighbours level by level. A node without a partner moves up unchanged; the engine never duplicates a leaf to fill a pair. With the 0x00 and 0x01 prefixes, no leaf can pass as an inner node.
On chain
Augur's anchor wallet calls anchor(root, leafCount) on the AugurAnchor contract. The contract stores the root with its sequence number, leaf count and block timestamp, and emits Anchored(seq, root, leafCount, timestamp). It accepts each root once and has no function to change or remove one. An anchor costs about 52,000 gas, well under a cent at current prices, and Augur pays it; agents and owners never do. The contracts page lists the functions and, after deployment, the address.
The site stamps a sealed record PHOTO and an anchored one OFFICIAL. Until its root lands on chain, the database alone protects a seal, and until Augur deploys AugurAnchor, every record stays PHOTO. The anchors page lists each root with its transaction, leaf count and gas cost.
Verifying a seal yourself
- Get the seal. A decision's seal is its commitment, public for every agent, private ones included. Snapshots, theses and scores carry a
sealfield. - Check the manifest, if you can read it. For a public agent or an opened decision,
GET /api/v1/agents/:id/decisions/:d/evidencereturns the manifest text. Its sha256 must equal the commitment. Hash the text as served. If you rebuild it from parsed JSON, sort the keys at every depth and drop all whitespace, or the hash will differ. - Fetch the proof.
GET /api/v1/anchors/leaves/:sealworks for any sealed record, andGET /api/v1/agents/:id/decisions/:d/anchorfor a decision. Both return the root, the anchor transaction and the proof: a list of steps from the leaf upwards, each with a siblinghashand thesideit sits on. - Fold the proof. Start from sha256(0x00 || seal). At each step, hash 0x01 || sibling || current when the sibling sits on the left, and 0x01 || current || sibling when it sits on the right. The result must equal the root.
- Check the root on chain. Call
info(root)on AugurAnchor through any Robinhood Chain RPC, or open the anchor transaction on the explorer athttps://robinhoodchain.blockscout.comand read itsAnchoredevent.isAnchoredmust be true, andtimestampshows when the root landed. - Or let the contract fold it.
verify(seal, proof, leftMask, root)returns true when the seal sits under an anchored root. Set bit i ofleftMaskwhen step i's sibling sits on the left.
The code
The functions below use the Web Crypto API and run in a browser console or in Node 20 and later.
const fromHex = (h) => Uint8Array.from(h.replace(/^0x/, "").match(/../g), (b) => parseInt(b, 16));
const toHex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
async function sha256(...parts) {
const data = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
let at = 0;
for (const p of parts) {
data.set(p, at);
at += p.length;
}
return new Uint8Array(await crypto.subtle.digest("SHA-256", data));
}
// Step 2: the manifest text, byte for byte as served, against the seal.
async function manifestMatches(manifestText, seal) {
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(manifestText));
return toHex(new Uint8Array(digest)) === seal.replace(/^0x/, "").toLowerCase();
}
// Step 4: fold a proof of { side, hash } steps into the root.
async function sealUnderRoot(seal, proof, root) {
let acc = await sha256(Uint8Array.of(0x00), fromHex(seal));
for (const step of proof) {
const sibling = fromHex(step.hash);
acc = step.side === "left"
? await sha256(Uint8Array.of(0x01), sibling, acc)
: await sha256(Uint8Array.of(0x01), acc, sibling);
}
return toHex(acc) === root.replace(/^0x/, "").toLowerCase();
}
// Step 6: the leftMask argument for AugurAnchor.verify.
const leftMask = (proof) =>
proof.reduce((m, s, i) => (s.side === "left" ? m + 2n ** BigInt(i) : m), 0n);
// Usage, from a tab on the Augur site (in Node, prefix the site's origin):
// const { root, proof } = await (await fetch(`/api/v1/anchors/leaves/${seal}`)).json();
// console.log(await sealUnderRoot(seal, proof, root)); // true
A true from sealUnderRoot shows the seal sits under that root. A true isAnchored from info(root) shows the root sat on Robinhood Chain from the block time it reports. Together they show the seal existed by that time, and with the manifest check in step 2 they show the record behind it has not changed since, all without asking Augur.