Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

README.md

4.32 Kb · 94 lines

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:

gno.land/r/moul/x/provable/v0 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.