github.com/decred/dcrlnd@v0.7.6/shachain/store.go (about)

     1  package shachain
     2  
     3  import (
     4  	"encoding/binary"
     5  	"io"
     6  
     7  	"github.com/go-errors/errors"
     8  )
     9  
    10  // Store is an interface which serves as an abstraction over data structure
    11  // responsible for efficiently storing and restoring hash secrets by given
    12  // indexes.
    13  //
    14  // Description: The Lightning Network wants a chain of (say 1 million)
    15  // unguessable 256 bit values; we generate them and send them one at a time to
    16  // a remote node.  We don't want the remote node to have to store all the
    17  // values, so it's better if they can derive them once they see them.
    18  type Store interface {
    19  	// LookUp function is used to restore/lookup/fetch the previous secret
    20  	// by its index.
    21  	LookUp(uint64) (*ShaHash, error)
    22  
    23  	// AddNextEntry attempts to store the given hash within its internal
    24  	// storage in an efficient manner.
    25  	//
    26  	// NOTE: The hashes derived from the shachain MUST be inserted in the
    27  	// order they're produced by a shachain.Producer.
    28  	AddNextEntry(*ShaHash) error
    29  
    30  	// Encode writes a binary serialization of the shachain elements
    31  	// currently saved by implementation of shachain.Store to the passed
    32  	// io.Writer.
    33  	Encode(io.Writer) error
    34  }
    35  
    36  // RevocationStore is a concrete implementation of the Store interface. The
    37  // revocation store is able to efficiently store N derived shachain elements in
    38  // a space efficient manner with a space complexity of O(log N). The original
    39  // description of the storage methodology can be found here:
    40  // https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#efficient-per-commitment-secret-storage
    41  type RevocationStore struct {
    42  	// lenBuckets stores the number of currently active buckets.
    43  	lenBuckets uint8
    44  
    45  	// buckets is an array of elements from which we may derive all
    46  	// previous elements, each bucket corresponds to the element with the
    47  	// particular number of trailing zeros.
    48  	buckets [maxHeight]element
    49  
    50  	// index is an available index which will be assigned to the new
    51  	// element.
    52  	index index
    53  }
    54  
    55  // A compile time check to ensure RevocationStore implements the Store
    56  // interface.
    57  var _ Store = (*RevocationStore)(nil)
    58  
    59  // NewRevocationStore creates the new shachain store.
    60  func NewRevocationStore() *RevocationStore {
    61  	return &RevocationStore{
    62  		lenBuckets: 0,
    63  		index:      startIndex,
    64  	}
    65  }
    66  
    67  // NewRevocationStoreFromBytes recreates the initial store state from the given
    68  // binary shachain store representation.
    69  func NewRevocationStoreFromBytes(r io.Reader) (*RevocationStore, error) {
    70  	store := &RevocationStore{}
    71  
    72  	if err := binary.Read(r, binary.BigEndian, &store.lenBuckets); err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	for i := uint8(0); i < store.lenBuckets; i++ {
    77  		var hashIndex index
    78  		err := binary.Read(r, binary.BigEndian, &hashIndex)
    79  		if err != nil {
    80  			return nil, err
    81  		}
    82  
    83  		var nextHash ShaHash
    84  		if _, err := io.ReadFull(r, nextHash[:]); err != nil {
    85  			return nil, err
    86  		}
    87  
    88  		store.buckets[i] = element{
    89  			index: hashIndex,
    90  			hash:  nextHash,
    91  		}
    92  	}
    93  
    94  	if err := binary.Read(r, binary.BigEndian, &store.index); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	return store, nil
    99  }
   100  
   101  // LookUp function is used to restore/lookup/fetch the previous secret by its
   102  // index. If secret which corresponds to given index was not previously placed
   103  // in store we will not able to derive it and function will fail.
   104  //
   105  // NOTE: This function is part of the Store interface.
   106  func (store *RevocationStore) LookUp(v uint64) (*ShaHash, error) {
   107  	ind := newIndex(v)
   108  
   109  	// Trying to derive the index from one of the existing buckets elements.
   110  	for i := uint8(0); i < store.lenBuckets; i++ {
   111  		element, err := store.buckets[i].derive(ind)
   112  		if err != nil {
   113  			continue
   114  		}
   115  
   116  		return &element.hash, nil
   117  	}
   118  
   119  	return nil, errors.Errorf("unable to derive hash #%v", ind)
   120  }
   121  
   122  // AddNextEntry attempts to store the given hash within its internal storage in
   123  // an efficient manner.
   124  //
   125  // NOTE: The hashes derived from the shachain MUST be inserted in the order
   126  // they're produced by a shachain.Producer.
   127  //
   128  // NOTE: This function is part of the Store interface.
   129  func (store *RevocationStore) AddNextEntry(hash *ShaHash) error {
   130  	newElement := &element{
   131  		index: store.index,
   132  		hash:  *hash,
   133  	}
   134  
   135  	bucket := countTrailingZeros(newElement.index)
   136  
   137  	for i := uint8(0); i < bucket; i++ {
   138  		e, err := newElement.derive(store.buckets[i].index)
   139  		if err != nil {
   140  			return err
   141  		}
   142  
   143  		if !e.isEqual(&store.buckets[i]) {
   144  			return errors.New("hash isn't derivable from " +
   145  				"previous ones")
   146  		}
   147  	}
   148  
   149  	store.buckets[bucket] = *newElement
   150  	if bucket+1 > store.lenBuckets {
   151  		store.lenBuckets = bucket + 1
   152  	}
   153  
   154  	store.index--
   155  	return nil
   156  }
   157  
   158  // Encode writes a binary serialization of the shachain elements currently
   159  // saved by implementation of shachain.Store to the passed io.Writer.
   160  //
   161  // NOTE: This function is part of the Store interface.
   162  func (store *RevocationStore) Encode(w io.Writer) error {
   163  	err := binary.Write(w, binary.BigEndian, store.lenBuckets)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	for i := uint8(0); i < store.lenBuckets; i++ {
   169  		element := store.buckets[i]
   170  
   171  		err := binary.Write(w, binary.BigEndian, element.index)
   172  		if err != nil {
   173  			return err
   174  		}
   175  
   176  		if _, err = w.Write(element.hash[:]); err != nil {
   177  			return err
   178  		}
   179  
   180  	}
   181  
   182  	return binary.Write(w, binary.BigEndian, store.index)
   183  }