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

provable.gno

5.48 Kb · 170 lines
  1// Package provable is an honest demonstration of what gno.land can and cannot
  2// prove about itself.
  3//
  4// It keeps a small append-only log in a Merkle mountain range and hands out
  5// inclusion proofs against the log's root. That part works, and it is the
  6// useful half: a realm can commit to a set and let anyone verify membership
  7// cheaply, with one native call.
  8//
  9// The other half is the point of the realm. A proof here says "this entry is
 10// consistent with the root this realm published". It cannot say "this root is
 11// the chain's own", because:
 12//
 13//  1. gno.land mounts two stores (gno.land/pkg/gnoland/app.go). The IAVL one
 14//     is merkleized and holds package source and account balances. The other
 15//     is a plain dbadapter whose Commit is documented as
 16//     "Always returns a zero commitID, as dbadapter store doesn't merkleize",
 17//     and THAT is where every realm's objects live. So no realm's state,
 18//     including this log, contributes anything to the app hash. There is no
 19//     state proof to produce.
 20//  2. chain/runtime exposes ChainID, ChainDomain, ChainHeight and
 21//     GetSessionInfo, and nothing else. No app hash, no block hash, no header.
 22//     So even given a Tendermint proof, a realm has no trusted root to check
 23//     it against.
 24//
 25// The one crack in the wall: an ESCAPED object, one referenced across realm
 26// boundaries, does get its hash written into the IAVL store
 27// (gnovm/pkg/gnolang/store.go). Cross-realm objects are therefore partially
 28// provable already. Nothing else is.
 29//
 30// Demo of gno.land/p/moul/x/merkle/v0 and gno.land/p/moul/x/mmr/v0.
 31package provable
 32
 33import (
 34	"strconv"
 35	"strings"
 36
 37	"gno.land/p/moul/x/merkle/v0"
 38	"gno.land/p/moul/x/mmr/v0"
 39)
 40
 41const (
 42	// MaxEntries bounds the log. Unbounded append from an untrusted caller is
 43	// a storage-growth hazard even when the caller pays the deposit, and an
 44	// unbounded Render is a permanently unreadable page: the chain caps a
 45	// query at maxGasQuery, and a reader cannot raise it.
 46	MaxEntries = 512
 47
 48	// MaxEntryLen bounds one entry.
 49	MaxEntryLen = 256
 50
 51	// renderEntries is how many of the most recent entries Render lists, so
 52	// the page stays a fixed size no matter how full the log is.
 53	renderEntries = 10
 54)
 55
 56var (
 57	log     = mmr.New()
 58	entries []string
 59)
 60
 61func init() { seed() }
 62
 63// Append adds an entry to the log and returns its index. The root moves, so
 64// every previously issued proof stops verifying against the new root: that is
 65// the nature of an append-only commitment, not a bug.
 66func Append(cur realm, entry string) int { return appendEntry(entry) }
 67
 68func appendEntry(entry string) int {
 69	entry = strings.TrimSpace(entry)
 70	if entry == "" {
 71		panic("provable: empty entry")
 72	}
 73	if len(entry) > MaxEntryLen {
 74		panic("provable: entry longer than " + strconv.Itoa(MaxEntryLen) + " bytes")
 75	}
 76	if len(entries) >= MaxEntries {
 77		panic("provable: log is full at " + strconv.Itoa(MaxEntries) + " entries")
 78	}
 79	entries = append(entries, entry)
 80	return log.Append([]byte(entry))
 81}
 82
 83// Size returns the number of entries.
 84func Size() int { return log.Size() }
 85
 86// Root returns the current log root, hex-encoded. It is also the Tendermint
 87// simple-tree root over the same entries, so any Tendermint verifier accepts
 88// it.
 89func Root() string { return log.RootHex() }
 90
 91// Entry returns the entry at index.
 92func Entry(index int) string {
 93	mustRange(index)
 94	return entries[index]
 95}
 96
 97// ProofOf returns the inclusion proof for index, as the three comma-separated
 98// hex lists Verify expects.
 99func ProofOf(index int) (path, before, after string) {
100	mustRange(index)
101	p, err := log.Proof(index)
102	if err != nil {
103		panic("provable: " + err.Error())
104	}
105	return p.Hex()
106}
107
108// Verify checks a proof against a root the caller supplies, which is the
109// honest signature: the realm is a verifier, not an oracle. Pass Root() to
110// check against the live log.
111func Verify(root string, index, total int, entry, path, before, after string) bool {
112	rb, err := hexHash(root)
113	if err != nil {
114		return false
115	}
116	p, err := mmr.ParseProof(index, total, path, before, after)
117	if err != nil {
118		return false
119	}
120	return mmr.Verify(rb, []byte(entry), p)
121}
122
123// VerifyFixed checks a Tendermint simple-tree proof, the fixed-leaf-set
124// encoding, against a root the caller supplies. Same tree as the log, a
125// different proof shape: siblings are one flat list, leaf first.
126//
127// Both encodings verify against the same root, because the Tendermint tree and
128// a right-bagged mountain range are the same structure.
129func VerifyFixed(root string, index, total int, leaf, siblings string) bool {
130	rb, err := hexHash(root)
131	if err != nil {
132		return false
133	}
134	p, err := merkle.ParseProof(index, total, siblings)
135	if err != nil {
136		return false
137	}
138	return p.Verify(rb, []byte(leaf))
139}
140
141func mustRange(index int) {
142	if index < 0 || index >= len(entries) {
143		panic("provable: index out of range")
144	}
145}
146
147func hexHash(s string) ([]byte, error) {
148	p, err := merkle.ParseProof(0, 1, strings.TrimSpace(s))
149	if err != nil {
150		return nil, err
151	}
152	if len(p.Siblings) != 1 {
153		return nil, merkle.ErrBadHashSize
154	}
155	return p.Siblings[0], nil
156}
157
158// seed returns the realm to its deployed state: an empty log plus the three
159// demo entries, so a fresh deployment renders something and so the example
160// tests have a fixed starting point.
161//
162// Realm globals persist across a whole test binary and examples run after
163// every Test, so a pinned Render must call this first.
164func seed() {
165	log = mmr.New()
166	entries = nil
167	appendEntry("genesis of this log")
168	appendEntry("a second commitment")
169	appendEntry("a third commitment")
170}