github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/merkler/stacked.go (about)

     1  // Copyright 2020 Insolar Network Ltd.
     2  // All rights reserved.
     3  // This material is licensed under the Insolar License version 1.0,
     4  // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md.
     5  
     6  package merkler
     7  
     8  import (
     9  	"github.com/insolar/vanilla/args"
    10  	"github.com/insolar/vanilla/cryptkit"
    11  	"github.com/insolar/vanilla/longbits"
    12  )
    13  
    14  var _ cryptkit.SequenceDigester = &StackedCalculator{}
    15  
    16  type TraceFunc func(v longbits.FoldableReader, isLeaf bool)
    17  
    18  // Creates a sequence digester that does merkle tree calculation.
    19  // When (unbalancedStub) is empty, then the resulting merkle-tree will be closed without stubbing, otherwise with per-level stubbing with (unbalancedStub) on the right side.
    20  // When (traceFn) is not nil, then StackedCalculator will call it each time when a leaf or node is added.
    21  //
    22  // NB! For every AddNext() call the (traceFn) is always called twice - first time with (leafX, true) value, and then (nodeX, false), where nodeX can be nil.
    23  // And during FinishSequence() - the (traceFn) can be called N < 2*log(leafCount) times with (nodeX, false), where nodeX can not be nil.
    24  //
    25  func NewStackedCalculator(digester cryptkit.PairDigester, unbalancedStub cryptkit.Digest, traceFn TraceFunc) StackedCalculator {
    26  	if digester == nil {
    27  		panic("illegal value")
    28  	}
    29  	return StackedCalculator{digester: digester, unbalancedStub: unbalancedStub, traceFn: traceFn}
    30  }
    31  
    32  // A calculator to do streaming calculation of Merkle-tree by using provided PairDigester.
    33  // The resulting merkle will have same depth for all branches except for the rightmost branch.
    34  //
    35  // When unbalancedStub == nil, then FinishSequence() will create the rightmost branch by recursively
    36  // applying the same rule - all sub-branches will have same depth except for the rightmost sub-branch.
    37  //
    38  // When unbalancedStub != nil, then FinishSequence() will create a perfect tree by using unbalancedStub
    39  // once per level when a value for the rightmost sub-branch is missing.
    40  //
    41  // When AddNext() was never called then FinishSequence() will return a non-nil unbalancedStub otherwise will panic.
    42  //
    43  // Complexity (n - a number of added hashes):
    44  //  - AddNext() is O(1), it does only upto 2 calls to PairDigester.DigestPair()
    45  //  - FinishSequence() is O(log n), it does k*log(n) calls to PairDigester.DigestPair() where k ~ 1 when unbalancedStub == nil and k ~ 2 otherwise
    46  //  - ForkSequence() is O(log n), but only copies memory
    47  //  - Memory is O(log n)
    48  //
    49  type StackedCalculator struct {
    50  	digester       cryptkit.PairDigester
    51  	unbalancedStub cryptkit.Digest
    52  	prevAdded      longbits.FoldableReader
    53  	traceFn        func(v longbits.FoldableReader, isLeaf bool)
    54  	count          uint
    55  	treeLevels     []treeLevel
    56  	finished       bool
    57  }
    58  
    59  type treeLevel struct {
    60  	digest0 cryptkit.Digest
    61  	digest1 cryptkit.Digest
    62  }
    63  
    64  func (p *StackedCalculator) GetDigestMethod() cryptkit.DigestMethod {
    65  	return p.digester.GetDigestMethod() + "/merkle"
    66  }
    67  
    68  func (p *StackedCalculator) GetDigestSize() int {
    69  	return p.digester.GetDigestSize()
    70  }
    71  
    72  func (p *StackedCalculator) digest(v0, v1 longbits.FoldableReader) cryptkit.Digest {
    73  	d := p.digester.DigestPair(v0, v1)
    74  	if p.traceFn != nil {
    75  		p.traceFn(d, false)
    76  	}
    77  	return d
    78  }
    79  
    80  func (p *StackedCalculator) AddNext(addDigest longbits.FoldableReader) {
    81  
    82  	if p.finished || p.digester == nil {
    83  		panic("illegal state")
    84  	}
    85  
    86  	if p.traceFn != nil {
    87  		p.traceFn(addDigest, true)
    88  	}
    89  
    90  	/*
    91  		Here we use position of grey-code transition bit to choose the level that requires pair-hashing
    92  		Tree level is counted from a leaf to the root, leaf level is considered as -1
    93  	*/
    94  
    95  	p.count++
    96  	pairPosition := args.GreyIncBit(p.count) // level + 1
    97  
    98  	var bottomDigest cryptkit.Digest
    99  
   100  	if pairPosition == 0 {
   101  		// Level -1 (leafs) is special - it only stores a previous value
   102  		bottomDigest = p.digest(p.prevAdded, addDigest)
   103  		p.prevAdded = nil
   104  	} else {
   105  		if p.prevAdded != nil {
   106  			panic("illegal state")
   107  		}
   108  		p.prevAdded = addDigest
   109  
   110  		if int(pairPosition) > len(p.treeLevels) {
   111  			if p.traceFn != nil {
   112  				p.traceFn(nil, false)
   113  			}
   114  			return
   115  		}
   116  		pairLevel := &p.treeLevels[pairPosition-1]
   117  		bottomDigest = p.digest(pairLevel.digest0, pairLevel.digest1)
   118  		pairLevel.digest0, pairLevel.digest1 = cryptkit.Digest{}, cryptkit.Digest{}
   119  	}
   120  
   121  	if int(pairPosition) == len(p.treeLevels) {
   122  		p.treeLevels = append(p.treeLevels, treeLevel{digest0: bottomDigest})
   123  		return
   124  	}
   125  
   126  	var d *cryptkit.Digest
   127  	if p.count&(uint(2)<<pairPosition) != 0 {
   128  		d = &p.treeLevels[pairPosition].digest0
   129  	} else {
   130  		d = &p.treeLevels[pairPosition].digest1
   131  	}
   132  	if !d.IsEmpty() {
   133  		panic("illegal state")
   134  	}
   135  	*d = bottomDigest
   136  }
   137  
   138  func (p *StackedCalculator) Count() int {
   139  	return int(p.count)
   140  }
   141  
   142  func (p *StackedCalculator) FinishSequence() cryptkit.Digest {
   143  
   144  	if p.finished || p.digester == nil {
   145  		panic("illegal state")
   146  	}
   147  	p.finished = true
   148  
   149  	hasStub := !p.unbalancedStub.IsEmpty()
   150  	if p.count == 0 {
   151  		if hasStub {
   152  			return p.unbalancedStub
   153  		}
   154  		panic("illegal state - empty")
   155  	}
   156  
   157  	var bottomDigest cryptkit.Digest
   158  	if p.prevAdded != nil {
   159  		if hasStub {
   160  			bottomDigest = p.digest(p.prevAdded, p.unbalancedStub)
   161  		} else {
   162  			bottomDigest = cryptkit.NewDigest(p.prevAdded, p.digester.GetDigestMethod())
   163  		}
   164  	}
   165  
   166  	// TODO refactor/simplify
   167  	for i := 0; i < len(p.treeLevels); i++ { // DONT USE range as treeLevels can be appended inside the loop!
   168  		curLevel := &p.treeLevels[i]
   169  
   170  		switch {
   171  		case !curLevel.digest1.IsEmpty():
   172  			// both are present
   173  			if curLevel.digest0.IsEmpty() {
   174  				panic("illegal state")
   175  			}
   176  			levelDigest := p.digest(curLevel.digest0, curLevel.digest1)
   177  
   178  			if bottomDigest.IsEmpty() {
   179  				bottomDigest = levelDigest
   180  				continue
   181  			}
   182  
   183  			if i+1 == len(p.treeLevels) {
   184  				p.treeLevels = append(p.treeLevels, treeLevel{digest0: levelDigest})
   185  			} else {
   186  				nextLevel := &p.treeLevels[i+1]
   187  				switch {
   188  				case nextLevel.digest0.IsEmpty():
   189  					if !nextLevel.digest1.IsEmpty() {
   190  						panic("illegal state")
   191  					}
   192  					nextLevel.digest0 = levelDigest
   193  				case nextLevel.digest1.IsEmpty():
   194  					nextLevel.digest1 = levelDigest
   195  				default:
   196  					panic("illegal state")
   197  				}
   198  			}
   199  			if hasStub {
   200  				bottomDigest = p.digest(bottomDigest, p.unbalancedStub)
   201  			}
   202  			// or leave as is
   203  		case !curLevel.digest0.IsEmpty():
   204  			switch {
   205  			case !bottomDigest.IsEmpty():
   206  				bottomDigest = p.digest(curLevel.digest0, bottomDigest)
   207  			case hasStub && i < len(p.treeLevels)-1: // never stub the last level
   208  				bottomDigest = p.digest(curLevel.digest0, p.unbalancedStub)
   209  			default:
   210  				bottomDigest = curLevel.digest0
   211  			}
   212  		case !bottomDigest.IsEmpty() && hasStub:
   213  			bottomDigest = p.digest(bottomDigest, p.unbalancedStub)
   214  		}
   215  	}
   216  
   217  	// return p.treeLevels[len(p.treeLevels)-1].digest0
   218  	return bottomDigest
   219  }