github.com/ethersphere/bee/v2@v2.2.0/pkg/bmt/reference/reference.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package reference is a simple nonconcurrent reference implementation of the BMT hash
     6  //
     7  // This implementation does not take advantage of any paralellisms and uses
     8  // far more memory than necessary, but it is easy to see that it is correct.
     9  // It can be used for generating test cases for optimized implementations.
    10  // There is extra check on reference hasher correctness in reference_test.go
    11  package reference
    12  
    13  import (
    14  	"hash"
    15  )
    16  
    17  // RefHasher is the non-optimized easy-to-read reference implementation of BMT.
    18  type RefHasher struct {
    19  	maxDataLength int       // c * hashSize, where c = 2 ^ ceil(log2(count)), where count = ceil(length / hashSize)
    20  	sectionLength int       // 2 * hashSize
    21  	hasher        hash.Hash // base hash func (Keccak256 SHA3)
    22  }
    23  
    24  // NewRefHasher returns a new RefHasher.
    25  func NewRefHasher(h hash.Hash, count int) *RefHasher {
    26  	hashsize := h.Size()
    27  	c := 2
    28  	for ; c < count; c *= 2 {
    29  	}
    30  	return &RefHasher{
    31  		sectionLength: 2 * hashsize,
    32  		maxDataLength: c * hashsize,
    33  		hasher:        h,
    34  	}
    35  }
    36  
    37  // Hash returns the BMT hash of the byte slice.
    38  func (rh *RefHasher) Hash(data []byte) ([]byte, error) {
    39  	// if data is shorter than the base length (maxDataLength), we provide padding with zeros
    40  	d := make([]byte, rh.maxDataLength)
    41  	length := len(data)
    42  	if length > rh.maxDataLength {
    43  		length = rh.maxDataLength
    44  	}
    45  	copy(d, data[:length])
    46  	return rh.hash(d, rh.maxDataLength)
    47  }
    48  
    49  // hash calls itself recursively on both halves of the given slice
    50  // concatenates the results, and returns the hash of that
    51  // if the length of d is 2 * segmentSize then just returns the hash of that section
    52  // data has length maxDataLength = segmentSize * 2^k
    53  func (rh *RefHasher) hash(data []byte, length int) ([]byte, error) {
    54  	var section []byte
    55  	if length == rh.sectionLength {
    56  		// section contains two data segments (d)
    57  		section = data
    58  	} else {
    59  		// section contains hashes of left and right BMT subtree
    60  		// to be calculated by calling hash recursively on left and right half of d
    61  		length /= 2
    62  		left, err := rh.hash(data[:length], length)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  		right, err := rh.hash(data[length:], length)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  		section = append(left, right...)
    71  	}
    72  	rh.hasher.Reset()
    73  	_, err := rh.hasher.Write(section)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	return rh.hasher.Sum(nil), nil
    78  }