github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/elkrem/elkrem.go (about) 1 package elkrem 2 3 import ( 4 "fmt" 5 6 "github.com/mit-dci/lit/btcutil/chaincfg/chainhash" 7 ) 8 9 /* elkrem is a simpler alternative to the 64 dimensional sha-chain. 10 it's basically a reverse merkle tree. If we want to provide 2**64 possible 11 hashes, this requires a worst case computation of 63 hashes for the 12 sender, and worst-case storage of 64 hashes for the receiver. 13 14 The operations are left hash L() and right hash R(), which are 15 hash(parent) and hash(parent, 1) respectively. (concatenate one byte) 16 17 Here is a shorter example of a tree with 8 leaves and 15 total nodes. 18 19 The sender first computes the bottom left leaf 0b0000. This is 20 L(L(L(L(root)))). The receiver stores leaf 0. 21 22 Next the sender computes 0b0001. R(L(L(L(root)))). Receiver stores. 23 Next sender computes 0b1000 (8). L(L(L(root))). Receiver stores this, and 24 discards leaves 0b0000 and 0b0001, as they have the parent node 8. 25 26 For total hashes (2**h)-1 requires a tree of height h. 27 28 Sender: 29 as state, must store 1 hash (root) and that's all 30 generate any index, compute at most h hashes. 31 32 Receiver: 33 as state, must store at most h+1 hashes and the index of each hash (h*(h+1)) bits 34 to compute a previous index, compute at most h hashes. 35 */ 36 const maxIndex = uint64(281474976710654) // 2^48 - 2 37 const maxHeight = uint8(47) 38 39 // You can calculate h from i but I can't figure out how without taking 40 // O(i) ops. Feels like there should be a clever O(h) way. 1 byte, whatever. 41 type ElkremNode struct { 42 H uint8 `json:"h"` // height of this node 43 I uint64 `json:"i"` // index (i'th node) 44 Sha *chainhash.Hash `json:"hash"` // hash 45 } 46 type ElkremSender struct { 47 root *chainhash.Hash // root hash of the tree 48 } 49 type ElkremReceiver struct { 50 Nodes []ElkremNode `json:"nodes"` // store of received hashes 51 } 52 53 func LeftSha(in chainhash.Hash) chainhash.Hash { 54 return chainhash.DoubleHashH(append(in.CloneBytes(), 0x00)) // left is sha(sha(in, 0)) 55 } 56 func RightSha(in chainhash.Hash) chainhash.Hash { 57 return chainhash.DoubleHashH(append(in.CloneBytes(), 0x01)) // right is sha(sha(in, 1)) 58 } 59 60 // iterative descent of sub-tree. w = hash number you Want. i = input Index 61 // h = Height of input index. sha = input hash 62 func descend(w, i uint64, h uint8, sha chainhash.Hash) (chainhash.Hash, error) { 63 for w < i { 64 if w <= i-(1<<h) { // left 65 sha = LeftSha(sha) 66 i -= 1 << h // left descent reduces index by 2**h 67 } else { // right 68 sha = RightSha(sha) 69 i-- // right descent reduces index by 1 70 } 71 if h == 0 { // avoid underflowing h 72 break 73 } 74 h-- // either descent reduces height by 1 75 } 76 if w != i { // somehow couldn't / didn't end up where we wanted to go 77 return sha, fmt.Errorf("can't generate index %d from %d", w, i) 78 } 79 return sha, nil 80 } 81 82 // NewElkremSender makes a new elkrem sender from a root hash. 83 func NewElkremSender(r chainhash.Hash) *ElkremSender { 84 var e ElkremSender 85 e.root = &r 86 return &e 87 } 88 89 // NewElkremReceiver makes a new empty elkrem receiver. 90 func NewElkremReceiver() *ElkremReceiver { 91 return &ElkremReceiver{make([]ElkremNode, 0)} 92 } 93 94 // AtIndex skips to the requested index 95 // should never error; remove error..? 96 func (e *ElkremSender) AtIndex(w uint64) (*chainhash.Hash, error) { 97 out, err := descend(w, maxIndex, maxHeight, *e.root) 98 return &out, err 99 } 100 101 // AddNext inserts the next hash in the tree. Returns an error if 102 // the incoming hash doesn't fit. 103 func (e *ElkremReceiver) AddNext(sha *chainhash.Hash) error { 104 // note: careful about atomicity / disk writes here 105 var n ElkremNode 106 n.Sha = sha 107 t := len(e.Nodes) - 1 // top of stack 108 if t >= 0 { // if this is not the first hash (>= because we -1'd) 109 n.I = e.Nodes[t].I + 1 // incoming index is tip of stack index + 1 110 } 111 if t > 0 && e.Nodes[t-1].H == e.Nodes[t].H { // top 2 elements are equal height 112 // next node must be parent; verify and remove children 113 n.H = e.Nodes[t].H + 1 // assign height 114 l := LeftSha(*sha) // calc l child 115 r := RightSha(*sha) // calc r child 116 if !e.Nodes[t-1].Sha.IsEqual(&l) { // test l child 117 return fmt.Errorf("left child doesn't match, expect %s got %s", 118 e.Nodes[t-1].Sha.String(), l.String()) 119 } 120 if !e.Nodes[t].Sha.IsEqual(&r) { // test r child 121 return fmt.Errorf("right child doesn't match, expect %s got %s", 122 e.Nodes[t].Sha.String(), r.String()) 123 } 124 e.Nodes = e.Nodes[:len(e.Nodes)-2] // l and r children OK, remove them 125 } // if that didn't happen, height defaults to 0 126 e.Nodes = append(e.Nodes, n) // append new node to stack 127 return nil 128 } 129 130 // AtIndex returns the w'th hash in the receiver. 131 func (e *ElkremReceiver) AtIndex(w uint64) (*chainhash.Hash, error) { 132 if e == nil || e.Nodes == nil { 133 return nil, fmt.Errorf("nil elkrem receiver") 134 } 135 var out ElkremNode // node we will eventually return 136 for _, n := range e.Nodes { // go through stack 137 if w <= n.I { // found one bigger than or equal to what we want 138 out = n 139 break 140 } 141 } 142 if out.Sha == nil { // didn't find anything 143 return nil, fmt.Errorf("receiver has max %d, less than requested %d", 144 e.Nodes[len(e.Nodes)-1].I, w) 145 } 146 sha, err := descend(w, out.I, out.H, *out.Sha) 147 return &sha, err 148 } 149 150 // UpTo tells you what the receiver can go up to. 151 func (e *ElkremReceiver) UpTo() uint64 { 152 if len(e.Nodes) < 1 { 153 return 0 154 } 155 return e.Nodes[len(e.Nodes)-1].I 156 }