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

     1  package shachain
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"errors"
     6  )
     7  
     8  // element represents the entity which contains the hash and index
     9  // corresponding to it. An element is the output of the shachain PRF. By
    10  // comparing two indexes we're able to mutate the hash in such way to derive
    11  // another element.
    12  type element struct {
    13  	index index
    14  	hash  ShaHash
    15  }
    16  
    17  // newElementFromStr creates new element from the given hash string.
    18  func newElementFromStr(s string, index index) (*element, error) {
    19  	hash, err := NewHashFromStr(s)
    20  	if err != nil {
    21  		return nil, err
    22  	}
    23  
    24  	return &element{
    25  		index: index,
    26  		hash:  *hash,
    27  	}, nil
    28  }
    29  
    30  // derive computes one shachain element from another by applying a series of
    31  // bit flips and hashing operations based on the starting and ending index.
    32  func (e *element) derive(toIndex index) (*element, error) {
    33  	fromIndex := e.index
    34  
    35  	positions, err := fromIndex.deriveBitTransformations(toIndex)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	buf := e.hash
    41  	for _, position := range positions {
    42  		// Flip the bit and then hash the current state.
    43  		byteNumber := position / 8
    44  		bitNumber := position % 8
    45  
    46  		buf[byteNumber] ^= (1 << bitNumber)
    47  
    48  		buf = sha256.Sum256(buf[:])
    49  	}
    50  
    51  	return &element{
    52  		index: toIndex,
    53  		hash:  buf,
    54  	}, nil
    55  }
    56  
    57  // isEqual returns true if two elements are identical and false otherwise.
    58  func (e *element) isEqual(e2 *element) bool {
    59  	return (e.index == e2.index) && e.hash == e2.hash
    60  }
    61  
    62  const (
    63  	// maxHeight is used to determine the maximum allowable index and the
    64  	// length of the array required to order to derive all previous hashes
    65  	// by index. The entries of this array as also known as buckets.
    66  	maxHeight uint8 = 48
    67  
    68  	// rootIndex is an index which corresponds to the root hash.
    69  	rootIndex index = 0
    70  )
    71  
    72  // startIndex is the index of first element in the shachain PRF.
    73  var startIndex index = (1 << maxHeight) - 1
    74  
    75  // index is a number which identifies the hash number and serves as a way to
    76  // determine the hashing operation required  to derive one hash from another.
    77  // index is initialized with the startIndex and decreases down to zero with
    78  // successive derivations.
    79  type index uint64
    80  
    81  // newIndex is used to create index instance. The inner operations with index
    82  // implies that index decreasing from some max number to zero, but for
    83  // simplicity and backward compatibility with previous logic it was transformed
    84  // to work in opposite way.
    85  func newIndex(v uint64) index {
    86  	return startIndex - index(v)
    87  }
    88  
    89  // deriveBitTransformations function checks that the 'to' index is derivable
    90  // from the 'from' index by checking the indexes are prefixes of another. The
    91  // bit positions where the zeroes should be changed to ones in order for the
    92  // indexes to become the same are returned. This set of bits is needed in order
    93  // to derive one hash from another.
    94  //
    95  // NOTE: The index 'to' is derivable from index 'from' iff index 'from' lies
    96  // left and above index 'to' on graph below, for example:
    97  // 1. 7(0b111) -> 7
    98  // 2. 6(0b110) -> 6,7
    99  // 3. 5(0b101) -> 5
   100  // 4. 4(0b100) -> 4,5,6,7
   101  // 5. 3(0b011) -> 3
   102  // 6. 2(0b010) -> 2, 3
   103  // 7. 1(0b001) -> 1
   104  //
   105  //	  ^ bucket number
   106  //	  |
   107  //	3 |   x
   108  //	  |   |
   109  //	2 |   |               x
   110  //	  |   |               |
   111  //	1 |   |       x       |       x
   112  //	  |   |       |       |       |
   113  //	0 |   |   x   |   x   |   x   |   x
   114  //	  |   |   |   |   |   |   |   |   |
   115  //	  +---|---|---|---|---|---|---|---|---> index
   116  //	      0   1   2   3   4   5   6   7
   117  func (from index) deriveBitTransformations(to index) ([]uint8, error) {
   118  	var positions []uint8
   119  
   120  	if from == to {
   121  		return positions, nil
   122  	}
   123  
   124  	//	+ --------------- +
   125  	// 	| №  | from | to  |
   126  	//	+ -- + ---- + --- +
   127  	//	| 48 |	 1  |  1  |
   128  	//	| 47 |	 0  |  0  | [48-5] - same part of 'from' and 'to'
   129  	//	| 46 |   0  |  0  |	    indexes which also is called prefix.
   130  	//		....
   131  	//	|  5 |	 1  |  1  |
   132  	//	|  4 |	 0  |  1  | <--- position after which indexes becomes
   133  	//	|  3 |   0  |  0  |	 different, after this position
   134  	//	|  2 |   0  |  1  |	 bits in 'from' index all should be
   135  	//	|  1 |   0  |  0  |	 zeros or such indexes considered to be
   136  	//	|  0 |   0  |  1  |	 not derivable.
   137  	//	+ -- + ---- + --- +
   138  	zeros := countTrailingZeros(from)
   139  	if uint64(from) != getPrefix(to, zeros) {
   140  		return nil, errors.New("prefixes are different - indexes " +
   141  			"aren't derivable")
   142  	}
   143  
   144  	// The remaining part of 'to' index represents the positions which we
   145  	// will use then in order to derive one element from another.
   146  	for position := zeros - 1; ; position-- {
   147  		if getBit(to, position) == 1 {
   148  			positions = append(positions, position)
   149  		}
   150  
   151  		if position == 0 {
   152  			break
   153  		}
   154  	}
   155  
   156  	return positions, nil
   157  }