Skip to main content

Verifying a Memory

Verification has three checks:

leaf == hash(memory)
proof -> root
root == onchain commitment

Inputs

InputSource
Content and metadataMemory owner or grant
Inclusion proofGET /memories/:id/proof
Owner public keyMemory owner
Solana RPCVerifier

No toda credential is required after the memory and proof have been obtained.

TypeScript

import { Connection, PublicKey } from "@solana/web3.js";
import { leafHash, bytesToHex, hexToBytes, verifyProof } from "@toda/crypto";
import { readUserCommit } from "@toda/solana";

const memory = {
content: "Deployments run on Fridays.",
metadata: { kind: "preference" },
};

const computed = leafHash(memory);
if (bytesToHex(computed) !== bundle.proof.leaf) {
throw new Error("leaf mismatch");
}

const root = hexToBytes(bundle.batchRoot);
if (!verifyProof(bundle.proof, root)) {
throw new Error("proof mismatch");
}

const connection = new Connection("https://api.devnet.solana.com");
const tip = await readUserCommit(connection, new PublicKey(ownerPubkey));

if (!tip || bytesToHex(tip.merkleRoot) !== bundle.batchRoot) {
throw new Error("commitment mismatch");
}

Do not use bundle.onchainRoot for independent verification. It is returned by the same API as batchRoot.

Recomputing the leaf

import hashlib, json

def canonical(value):
if isinstance(value, list):
return [canonical(v) for v in value]
if isinstance(value, dict):
return {k: canonical(value[k]) for k in sorted(value)}
return value

def leaf_hash(content, metadata=None):
body = json.dumps(
{"content": content, "metadata": canonical(metadata)},
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
return hashlib.sha256(b"toda:leaf:v1" + b"\x00" + body).digest()

The top-level field order is content, then metadata. Nested object keys are sorted. Missing metadata is encoded as null.

Verifying the fold

def verify_proof(leaf, siblings, directions, root):
acc = leaf
for sibling, sibling_on_right in zip(siblings, directions):
pair = acc + sibling if sibling_on_right else sibling + acc
acc = hashlib.sha256(b"toda:node:v1" + pair).digest()
return acc == root

True means the sibling is on the right.

Reading the chain

The commitment account is a 129-byte PDA.

program_id = Pubkey.from_string("5SwaBH3pXzEKhmL6pbqZ1JgeDiGaf6i5oVj8951RX9sm")
pda, _ = Pubkey.find_program_address([b"user", bytes(owner)], program_id)
data = rpc.get_account_info(pda).value.data

owner_key = data[8:40]
authority = data[40:72]
merkle_root = data[72:104]
memory_count = int.from_bytes(data[104:112], "little")
nonce = int.from_bytes(data[112:120], "little")
last_updated = int.from_bytes(data[120:128], "little", signed=True)

Compare merkle_root with the root produced by the proof fold.

Result

A valid proof shows that the exact memory was included in the wallet's committed log no later than the anchor block time.

It does not prove:

  • factual accuracy
  • authorship by the wallet owner
  • that the memory is the latest statement on a subject
  • completeness of the log
  • continued offchain availability

Anchored under an older root

verify() compares batchRoot with the current tip. An older batch root will differ after later leaves are added.

To verify against the current tip, build a proof from the full ordered leaf set and compare the resulting rolling root with the commitment account.

If only the historical proof is available, verify it against batchRoot and confirm txSignature directly on Solana.