Verifying a Memory
Verification has three checks:
leaf == hash(memory)
proof -> root
root == onchain commitment
Inputs
| Input | Source |
|---|---|
| Content and metadata | Memory owner or grant |
| Inclusion proof | GET /memories/:id/proof |
| Owner public key | Memory owner |
| Solana RPC | Verifier |
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.