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  }