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

mmr.gno

7.55 Kb · 229 lines
  1// Package mmr implements a Merkle Mountain Range: an append-only log with a
  2// root that updates in O(log n) and inclusion proofs of O(log n) size.
  3//
  4// # Why not a plain Merkle tree
  5//
  6// gno.land/p/moul/x/merkle/v0 commits to a FIXED list. Adding a leaf means
  7// rebuilding from every leaf, which on chain means re-reading the whole set
  8// and re-hashing it, every time. An MMR never rebuilds: an append hashes at
  9// most log2(n) times and touches nothing else.
 10//
 11// That is the shape any on-chain log wants. A wiki committing to its revision
 12// history, a forge committing to its objects, an agent realm committing to the
 13// receipts it issued: all append, none rewrite.
 14//
 15// # The structure
 16//
 17// An MMR is a list of perfect binary trees ("mountains") of strictly
 18// decreasing height, one per set bit of the leaf count. Eleven leaves is
 19// 8 + 2 + 1, so three mountains of height 3, 1 and 0. Appending a leaf pushes
 20// a height-0 mountain and merges equal-height neighbours, exactly like
 21// incrementing a binary counter.
 22//
 23// The root is the peaks "bagged" right to left:
 24//
 25//	root = InnerHash(p0, InnerHash(p1, InnerHash(p2, …)))
 26//
 27// Hashing is gno.land/p/moul/x/merkle/v0's, which is Tendermint's: leaves are
 28// tagged 0x00 and inner nodes 0x01. Leaf and inner preimages therefore cannot
 29// collide, so the second-preimage forgery that works against untagged schemes
 30// does not apply here. See that package for the worked attack.
 31//
 32// # This IS the Tendermint tree
 33//
 34// The root above equals the Tendermint simple-tree root over the same leaves,
 35// at every size and not merely at powers of two. Tendermint splits an n-leaf
 36// tree at the largest power of two below n: that left half is exactly the
 37// first mountain, and the right half recurses through the remaining set bits,
 38// which is exactly the bagging. The two constructions coincide.
 39//
 40// So this package is a drop-in INCREMENTAL BUILDER for Tendermint roots.
 41// Append in O(log n) on chain and hand out a root that any Tendermint verifier
 42// accepts; proofs cross over in both directions, which
 43// TestMerkleProofVerifiesAgainstMMRRoot pins. Use merkle.New when the leaf set
 44// is fixed and you want the simpler proof encoding, use this when the log
 45// grows.
 46//
 47// # Proofs are bound to position and to size
 48//
 49// A Proof carries the leaf index and the leaf count the MMR had when it was
 50// issued. The verifier recomputes the whole peak structure from that count,
 51// which fixes the number of peaks, which mountain holds the leaf, the local
 52// index inside it, and therefore the exact expected length of every component
 53// of the proof. A proof cannot be replayed at another index, against another
 54// size, or padded.
 55//
 56// Because the root changes on every append, a proof is valid against the root
 57// at its own Total and no other. A realm that wants old proofs to keep
 58// verifying must keep the historical roots; Roots grow by one 32-byte hash per
 59// append, which is the price of an auditable log.
 60//
 61// # Storage
 62//
 63// Append stores one node per leaf plus one per merge, so 2n-popcount(n) hashes
 64// for n leaves, under 64 bytes per leaf amortised. That is what on-chain proof
 65// generation costs. A realm that only ever needs to VERIFY proofs, with the
 66// tree living off chain, should store the root alone and use
 67// gno.land/p/moul/x/merkle/v0's Verify instead.
 68//
 69// Live demo: gno.land/r/moul/x/provable/v0
 70package mmr
 71
 72import (
 73	"encoding/hex"
 74	"errors"
 75
 76	"gno.land/p/moul/x/merkle/v0"
 77)
 78
 79// HashSize is the length in bytes of every node hash.
 80const HashSize = merkle.HashSize
 81
 82// MaxPeaks caps the peak count a Proof may claim. A 64-peak MMR would hold
 83// more than 2^64 leaves.
 84const MaxPeaks = 64
 85
 86var (
 87	ErrEmpty      = errors.New("mmr: log is empty")
 88	ErrIndexRange = errors.New("mmr: leaf index out of range")
 89	ErrBadHex     = errors.New("mmr: hash is not valid hex")
 90	ErrBadSize    = errors.New("mmr: hash is not 32 bytes")
 91	ErrTooManyPeaks = errors.New("mmr: more peaks than MaxPeaks")
 92)
 93
 94// peak is one mountain: the position of its root in nodes, and its height.
 95type peak struct {
 96	pos    int
 97	height int
 98}
 99
100// MMR is an append-only Merkle mountain range.
101//
102// The zero value is an empty, ready-to-use log.
103type MMR struct {
104	nodes  [][]byte // every node, in postorder: [left subtree][right subtree][root]
105	peaks  []peak
106	leaves int
107}
108
109// New returns an empty MMR.
110func New() *MMR { return &MMR{} }
111
112// Size returns the number of leaves appended so far.
113func (m *MMR) Size() int { return m.leaves }
114
115// Nodes returns the number of stored hashes, leaves and merges together. Use
116// it to reason about storage growth.
117func (m *MMR) Nodes() int { return len(m.nodes) }
118
119// Append adds a leaf and returns its index. O(log n) hashes, no rebuild.
120func (m *MMR) Append(leaf []byte) int {
121	index := m.leaves
122	m.nodes = append(m.nodes, merkle.LeafHash(leaf))
123	m.peaks = append(m.peaks, peak{pos: len(m.nodes) - 1, height: 0})
124	for len(m.peaks) >= 2 {
125		right := m.peaks[len(m.peaks)-1]
126		left := m.peaks[len(m.peaks)-2]
127		if left.height != right.height {
128			break
129		}
130		m.nodes = append(m.nodes, merkle.InnerHash(m.nodes[left.pos], m.nodes[right.pos]))
131		m.peaks = m.peaks[:len(m.peaks)-2]
132		m.peaks = append(m.peaks, peak{pos: len(m.nodes) - 1, height: left.height + 1})
133	}
134	m.leaves++
135	return index
136}
137
138// Root returns the current root, nil while the log is empty.
139func (m *MMR) Root() []byte {
140	if len(m.peaks) == 0 {
141		return nil
142	}
143	return bag(m.peakHashes())
144}
145
146// RootHex returns Root hex-encoded.
147func (m *MMR) RootHex() string { return hex.EncodeToString(m.Root()) }
148
149// PeakHashes returns the current peak hashes, left to right. This is the whole
150// state a verifier needs, and it is O(log n).
151func (m *MMR) PeakHashes() [][]byte { return m.peakHashes() }
152
153func (m *MMR) peakHashes() [][]byte {
154	out := make([][]byte, len(m.peaks))
155	for i, p := range m.peaks {
156		out[i] = m.nodes[p.pos]
157	}
158	return out
159}
160
161// bag folds peaks right to left: InnerHash(p0, InnerHash(p1, …)).
162func bag(peaks [][]byte) []byte {
163	if len(peaks) == 0 {
164		return nil
165	}
166	acc := peaks[len(peaks)-1]
167	for i := len(peaks) - 2; i >= 0; i-- {
168		acc = merkle.InnerHash(peaks[i], acc)
169	}
170	return acc
171}
172
173// Proof is an inclusion proof for one leaf of a log that held Total leaves.
174//
175// Path holds the sibling hashes inside the leaf's own mountain, leaf first.
176// Before and After hold the other peak hashes, in left-to-right order.
177type Proof struct {
178	Index  int
179	Total  int
180	Path   [][]byte
181	Before [][]byte
182	After  [][]byte
183}
184
185// Proof returns the inclusion proof for the leaf at index, against the current
186// root.
187func (m *MMR) Proof(index int) (Proof, error) {
188	if m.leaves == 0 {
189		return Proof{}, ErrEmpty
190	}
191	if index < 0 || index >= m.leaves {
192		return Proof{}, ErrIndexRange
193	}
194	j, local := locate(m.leaves, index)
195	hashes := m.peakHashes()
196	return Proof{
197		Index:  index,
198		Total:  m.leaves,
199		Path:   m.pathIn(m.peaks[j], local),
200		Before: hashes[:j],
201		After:  hashes[j+1:],
202	}, nil
203}
204
205// pathIn walks down the perfect tree rooted at p, collecting the sibling at
206// each level, and returns them leaf first.
207//
208// The layout is postorder, so for a node at position pos and height h the
209// right child root sits at pos-1 and the left child root at pos-1-(2^h - 1),
210// a perfect subtree of height h-1 holding 2^h - 1 nodes.
211func (m *MMR) pathIn(p peak, local int) [][]byte {
212	var out [][]byte
213	pos, h := p.pos, p.height
214	for h > 0 {
215		rightRoot := pos - 1
216		leftRoot := pos - 1 - ((1 << uint(h)) - 1)
217		half := 1 << uint(h-1)
218		if local < half {
219			out = append([][]byte{m.nodes[rightRoot]}, out...)
220			pos = leftRoot
221		} else {
222			out = append([][]byte{m.nodes[leftRoot]}, out...)
223			pos = rightRoot
224			local -= half
225		}
226		h--
227	}
228	return out
229}