README.md
provable
What gno.land can and cannot prove about itself, demonstrated rather than asserted.
Demo realm for p/moul/x/merkle/v0 and
p/moul/x/mmr/v0.
The working half
The realm keeps a small append-only log in a Merkle mountain range and hands out inclusion
proofs against its root. Appending costs O(log n) hashes and never rebuilds. Verification is
a single native crypto/merkle call.
Append("something") → index
Root() → the current root, hex
ProofOf(index) → path, before, after
Verify(root, index, total, entry, path, before, after) → bool
Verify takes the root as an argument rather than reading it from state. That is the
honest signature: the realm is a verifier, not an oracle. Passing Root() checks against the
live log; passing an older root checks against that older log.
VerifyFixed accepts the other proof encoding, the flat Tendermint sibling list, against the
same root. Both work because the log's root is the Tendermint simple-tree root over the
same entries. TestVerifyFixedAcceptsTendermintProof pins that rather than asserting it.
The half that is the point
A proof from this realm says this entry is consistent with the root this realm published. It cannot say this root is the chain's own. Two independent reasons, both verifiable in the monorepo source:
1. Realm state is not merkleized. gno.land/pkg/gnoland/app.go mounts exactly two stores:
1baseApp.MountStoreWithDB(mainKey, iavl.StoreConstructor, cfg.DB) // merkleized
2baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, cfg.DB) // not
and tm2/pkg/store/dbadapter/store.go says it in a comment: "Always returns a zero
commitID, as dbadapter store doesn't merkleize". The VM keeper takes both, and
gnovm/pkg/gnolang/store.go puts realm objects, types and realm metadata in the base
store.
| Data | Store | Provable against the app hash |
|---|---|---|
| Package source | iavl | yes |
| Account balances | iavl | yes |
| Escaped object hashes (cross-realm, refcount > 1) | iavl | yes, the hash only |
| Realm objects, types, realm metadata | base | no |
| This log | base | no |
So you can prove to a light client that a package has a given source, and that an address holds a given balance. You cannot prove that any realm's AVL tree contains any key. Every GRC20 balance and every DAO vote on the chain is, today, unprovable.
2. A realm cannot see a header. chain/runtime exposes ChainID, ChainDomain,
ChainHeight and GetSessionInfo, and nothing else. No app hash, no block hash, no header.
Even handed a valid Tendermint proof, a realm has no trusted root to check it against.
The one crack in the wall: an escaped object, one referenced across realm boundaries, does get its hash written into the IAVL store. Cross-realm objects are partially provable already. Nothing else is.
Why say so out loud
Merkle proofs are the part of a chain that most invites overclaiming. "Verifiable on chain" is true here in a narrow sense and false in the sense a reader assumes. Anything built on Merkle proofs in a gno realm is trust-minimised relative to a committed root, never trustless. A demo that showed only the working half would be the interesting-sounding part of a true story.
Bounds
MaxEntries is 512 and MaxEntryLen is 256 bytes. Render lists the last 10 entries, so the
page stays a fixed size: the chain caps a query at maxGasQuery and a reader cannot raise it,
so an unbounded Render is a permanently unreadable page.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
Dependency graph:

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.