class MerkleTree

Constants

MAJOR
MINOR
PATCH
VERSION

Attributes

leaves[R]
root[R]

Public Class Methods

banner() click to toggle source
calc_hash( data ) click to toggle source
# File lib/merkletree.rb, line 163
def self.calc_hash( data )
  sha = Digest::SHA256.new
  sha.update( data )
  sha.hexdigest
end
compute_root( *args ) click to toggle source

shortcut/convenience - compute root hash w/o building tree nodes

# File lib/merkletree.rb, line 128
def self.compute_root( *args )
  if args.size == 1 && args[0].is_a?( Array )
     hashes = args[0]   ## "unwrap" array in array
  else
     hashes = args      ## use "auto-wrapped" splat array
  end

  ## todo/fix: handle hashes.size == 0 case
  ##   - throw exception - why? why not?
  ##   -  return empty node with hash '0' - why? why not?

  if hashes.size == 1
    hashes[0]
  else
    ## while there's more than one hash in the list, keep looping...
    while hashes.size > 1
      # if number of hashes is odd e.g. 3,5,7,etc., duplicate last hash in list
      hashes << hashes[-1]   if hashes.size % 2 != 0

      ## loop through hashes two at a time
      hashes = hashes.each_slice(2).map do |left,right|
         ## join both hashes slice[0]+slice[1] together
         hash = calc_hash( left + right )
      end
    end

    ## debug output
    ## puts "current merkle hashes (#{hashes.size}):"
    ## pp hashes
    ### finally we end up with a single hash
    hashes[0]
  end
end
compute_root_for( *args ) click to toggle source
# File lib/merkletree.rb, line 64
def self.compute_root_for( *args )
  if args.size == 1 && args[0].is_a?( Array )
     transactions = args[0]   ## "unwrap" array in array
  else
     transactions = args      ## use "auto-wrapped" splat array
  end

  ## for now use to_s for calculation hash
  hashes = transactions.map { |tx| calc_hash( tx.to_s ) }
  self.compute_root( hashes )
end
for( *args ) click to toggle source

convenience helpers

# File lib/merkletree.rb, line 53
def self.for( *args )
   if args.size == 1 && args[0].is_a?( Array )
      transactions = args[0]   ## "unwrap" array in array
   else
      transactions = args      ## use "auto-wrapped" splat array
   end
   ## for now use to_s for calculation hash
   hashes = transactions.map { |tx| calc_hash( tx.to_s ) }
   self.new( hashes )
end
new( *args ) click to toggle source
# File lib/merkletree.rb, line 81
def initialize( *args )
  if args.size == 1 && args[0].is_a?( Array )
     hashes = args[0]   ## "unwrap" array in array
  else
     hashes = args      ## use "auto-wrapped" splat array
  end

  @hashes = hashes
  @root   = build_tree
end
root() click to toggle source
# File lib/merkletree/version.rb, line 18
def self.root
  File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
end
version() click to toggle source
# File lib/merkletree/version.rb, line 10
def self.version
  VERSION
end

Public Instance Methods

build_tree() click to toggle source
# File lib/merkletree.rb, line 93
def build_tree
  level = @leaves = @hashes.map { |hash| Node.new( hash, nil, nil ) }

  ## todo/fix: handle hashes.size == 0 case
  ##   - throw exception - why? why not?
  ##   -  return empty node with hash '0' - why? why not?

  if @hashes.size == 1
    level[0]
  else
    ## while there's more than one hash in the layer, keep looping...
    while level.size > 1
      ## loop through hashes two at a time
      level = level.each_slice(2).map do |left, right|
        ## note: handle special case
        # if number of nodes is odd e.g. 3,5,7,etc.
        #   last right node is nil  --  duplicate node value for hash
        ##   todo/check - duplicate just hash? or add right node ref too - why? why not?
        right = left   if right.nil?

        Node.new( MerkleTree.calc_hash( left.value + right.value ), left, right)
      end
      ## debug output
      ## puts "current merkle hash level (#{level.size} nodes):"
      ## pp level
    end
    ### finally we end up with a single hash
    level[0]
  end
end