Skip to main content
The verifier is Carry’s closing argument: don’t trust Carry — verify it. Open /verify/<receiptId> with no wallet, and the page independently re-derives every claim a proof makes.

Try it

Open the live app, anchor an answer, and follow the “verify” link.

The three checks

For a given Receipt object id, the verifier reads it from Sui and runs three independent checks:
1

Hash chain intact

Recompute blake2b256(prev_digest ++ digest) from the object’s own fields and confirm it equals the stored chain_digest. A reordered or deleted receipt breaks this.
2

Content binding (Walrus ↔ chain)

Fetch the Walrus blob named by walrus_blob, re-canonicalize and hash it, and confirm it equals the on-chain digest. This proves the proof points to exactly this content.
3

Authorization recomputed

Call is_allowed for every used namespace against the live policy and confirm the result matches the receipt’s all_authorized. This proves the verdict, recomputed now, still holds.

The verification logic

Why it’s robust

The content check re-canonicalizes the fetched blob before hashing — so it doesn’t matter how Walrus serialized the JSON (key order, whitespace). The proof binds the canonical content.
The verifier is entirely read-only: getObject + is_allowed via devInspect + a Walrus aggregator GET. No wallet, no Carry-internal state, no trust in the app required.

What a failure looks like

If someone tampers with the stored blob, the content check goes red. If the on-chain policy has since revoked a namespace the receipt used, the authorization check goes red. If the receipt were reordered in the chain, the chain check goes red. Honest proofs are all-green; tampered ones can’t hide.