hash-merkle-tree 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +66 -0
- data/lib/algorithm.rb +95 -0
- data/lib/hash_tree.rb +48 -0
- data/lib/version.rb +3 -0
- metadata +62 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8e8fac845ce12a9f3566c344b5f6582ddd7c000cf924400287fa29fdcbde8553
|
|
4
|
+
data.tar.gz: 9e872429d89cfe67a2c3540d9a56499d0e15dcfc0529b8792112ff20e6b69eca
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4df0bfb1416ebeb82b258e8acae15571f688e99c7253d5a0cecf0cdfaab428ad9d6c0000379c51257f2fca8161f18018ce927f9b3d1238aa5521dd1ab211a14f
|
|
7
|
+
data.tar.gz: 027ee8861d0501d3696dc7baf8d4011b14721eac444001f0d703bbbcbe1e0101f324a71046f3582ee815ffe4b739d701fe8d39c637eba99ef512fe9fcb543575
|
data/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
This repo contains an implementation of "Merkle Hash Trees" (MHT).
|
|
2
|
+
In cryptography and computer science, a hash tree or Merkle Hash Tree
|
|
3
|
+
is a tree in which every "leaf" (node) is labelled with the cryptographic hash of
|
|
4
|
+
a data block, and every node that is not a leaf (called a branch, inner node, or inode)
|
|
5
|
+
is labelled with the cryptographic hash of the labels of its child nodes.
|
|
6
|
+
Specifically, it implements the variant described in
|
|
7
|
+
[RFC6962](http://tools.ietf.org/html/rfc6962)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Basic Usage
|
|
11
|
+
|
|
12
|
+
Assume that you have such an object, named
|
|
13
|
+
`data`, and we'll look at how to use the MHT.
|
|
14
|
+
|
|
15
|
+
data = %w[a b c d e f g h i j k l m n o p q r s t u v w x y z]
|
|
16
|
+
|
|
17
|
+
We'll create a new MHT:
|
|
18
|
+
|
|
19
|
+
tree = Merkle::HashTree.new(data, Digest::SHA256)
|
|
20
|
+
|
|
21
|
+
The `Merkle::HashTree` constructor takes exactly two arguments: an object that
|
|
22
|
+
implements the data access interface we'll talk about later, and a class (or
|
|
23
|
+
object) which implements the same `digest` method signature as the core
|
|
24
|
+
`Digest::Base` class. Typically, this will simply be a `Digest` subclass,
|
|
25
|
+
such as `Digest::MD5`, `Digest::SHA1`, or (as in the example above)
|
|
26
|
+
`Digest::SHA256`. This second argument is the way that the MHT calculates
|
|
27
|
+
hashes in the tree -- it simply calls the `#digest` method on whatever you
|
|
28
|
+
pass in as the second argument, passing in a string and expecting raw octets
|
|
29
|
+
out the other end.
|
|
30
|
+
|
|
31
|
+
Once we have our MHT object, we can start to do things with it.
|
|
32
|
+
For example, we can get the hash of the "head" of the tree:
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
tree.head_build # => "<some long string of octets>"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
We can also ask for a "consistency proof", we need to specify an index(n) of the last
|
|
39
|
+
element of sub_tree (as a result sub_tree will be `data[0...n]`):
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
consistency_proof = tree.consistency_proof_build(10) # => ["<hash>", "<hash>", ... ]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
You can test it checking sum with hash head of the tree. Or use:
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
tree.consistency_proof_build_head(consistency_proof) # => "<some long string of octets>"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
There are also such things as "audit proofs", which you get by specifying a single leaf index:
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
audit_proof = tree.audit_proof_build(10) # => [ { value: "<hash>", position: "left" }, { value: "<hash>", position: "right" }, ... ]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
In this example, the audit proof will return a list of hashes
|
|
58
|
+
that demonstrate that leaf `10` is in the tree and hasn't been removed or altered.
|
|
59
|
+
You can test it checking sum with hash head of the tree. Or use:
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
tree.audit_proof_build_head(audit_proof, data[10]) # => "<some long string of octets>"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
data/lib/algorithm.rb
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Merkle
|
|
2
|
+
module Algorithm
|
|
3
|
+
class << self
|
|
4
|
+
def merkle_tree(array, digest, &block)
|
|
5
|
+
# Define it here since we have to add &block and digest params to every method call (DRY..)
|
|
6
|
+
merkle_tree_method = -> (arg) { merkle_tree(arg, digest, &block) }
|
|
7
|
+
|
|
8
|
+
case array.size
|
|
9
|
+
when 1
|
|
10
|
+
return array.first
|
|
11
|
+
when 2
|
|
12
|
+
# This yeild for define audit_proof method. To build audit_proof_elements
|
|
13
|
+
# we have to know pair of <searching> elements, so case array.size = 2 is what actually need.
|
|
14
|
+
# Basicly we just need to handle this case and we will have a new audit_proof algorithm!
|
|
15
|
+
yield(array) if block_given?
|
|
16
|
+
|
|
17
|
+
return node_hash(digest, array.first, array.last)
|
|
18
|
+
else
|
|
19
|
+
cons = array.size.even? ? 2 : 1
|
|
20
|
+
|
|
21
|
+
merkle_tree_method.call(
|
|
22
|
+
[
|
|
23
|
+
merkle_tree_method.call(array[0...(array.size - cons)]),
|
|
24
|
+
merkle_tree_method.call(array[(array.size - cons)..])
|
|
25
|
+
]
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def audit_proof(array, index, digest = nil)
|
|
31
|
+
audit_proof_path = Array.new
|
|
32
|
+
element = array[index]
|
|
33
|
+
|
|
34
|
+
# This block is inside case with two elements.
|
|
35
|
+
# We have to handle pairs only with our element and add neibour of our element to the path
|
|
36
|
+
# Then we have to update our element
|
|
37
|
+
merkle_tree(array, digest) do |pair_of_elements|
|
|
38
|
+
if pair_of_elements.include?(element)
|
|
39
|
+
audit_proof_path << begin
|
|
40
|
+
if pair_of_elements.first == element
|
|
41
|
+
{
|
|
42
|
+
position: 'right',
|
|
43
|
+
value: pair_of_elements.last
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
{
|
|
47
|
+
position: 'left',
|
|
48
|
+
value: pair_of_elements.first
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
element = node_hash(digest, pair_of_elements.first, pair_of_elements.last)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
audit_proof_path
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Culculate a head of tree using audit_proof path
|
|
61
|
+
def consistency_proof(array, index, digest = nil)
|
|
62
|
+
sub_array = array[0..index]
|
|
63
|
+
|
|
64
|
+
audit_proof(sub_array, index, digest).map{ |e| e[:value] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Culculate a head of tree using audit_proof path
|
|
68
|
+
# By this method we can verify is the element a part of the tree or not depends on returned head
|
|
69
|
+
def audit_proof_build_head(audit_proof_path, element, digest = nil)
|
|
70
|
+
audit_proof_path.inject(element) do |element, node|
|
|
71
|
+
case node[:position]
|
|
72
|
+
when 'right'
|
|
73
|
+
node_hash(digest, element, node[:value])
|
|
74
|
+
when 'left'
|
|
75
|
+
node_hash(digest, node[:value], element)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Culculate a head of sub-tree using consistency_proof path
|
|
81
|
+
# By this method we can verify is the tree includes sub-tree
|
|
82
|
+
def consistency_proof_build_head(consistency_proof_path, digest = nil)
|
|
83
|
+
consistency_proof_path.inject('') do |result, consistency_path_element|
|
|
84
|
+
node_hash(digest, consistency_path_element, result)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private def node_hash(digest, hash1, hash2)
|
|
89
|
+
summary_hash = hash1 + hash2
|
|
90
|
+
|
|
91
|
+
digest ? digest.digest("\x01" + summary_hash) : summary_hash
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/hash_tree.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'algorithm'
|
|
4
|
+
|
|
5
|
+
module Merkle
|
|
6
|
+
class HashTree
|
|
7
|
+
ALGORITHM_CLASS = Merkle::Algorithm
|
|
8
|
+
|
|
9
|
+
def initialize(incoming_data, digest = nil)
|
|
10
|
+
@algorithm = ALGORITHM_CLASS
|
|
11
|
+
@digest = digest
|
|
12
|
+
@incoming_data = incoming_data
|
|
13
|
+
@hashed_data = incoming_data_to_hashes
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :hashed_data, :incoming_data, :digest, :algorithm, :head, :audit_proof,
|
|
17
|
+
:consistency_proof, :consistency_proof_head, :audit_proof_head
|
|
18
|
+
|
|
19
|
+
def head_build
|
|
20
|
+
@head =
|
|
21
|
+
algorithm.merkle_tree(hashed_data, digest)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def audit_proof_build(index)
|
|
25
|
+
@audit_proof =
|
|
26
|
+
algorithm.audit_proof(hashed_data, index, digest)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def audit_proof_build_head(audit_proof_path, element)
|
|
30
|
+
@audit_proof_head =
|
|
31
|
+
algorithm.audit_proof_build_head(audit_proof_path, element, digest)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def consistency_proof_build(index)
|
|
35
|
+
@consistency_proof =
|
|
36
|
+
algorithm.consistency_proof(hashed_data, index, digest)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def consistency_proof_build_head(consistency_proof_path)
|
|
40
|
+
@consistency_proof_head =
|
|
41
|
+
algorithm.consistency_proof_build_head(consistency_proof_path, digest)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private def incoming_data_to_hashes
|
|
45
|
+
digest ? incoming_data.map! { |el| digest.digest("\u0001#{el}") } : incoming_data
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hash-merkle-tree
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- tavrelkate
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2022-07-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rspec
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
description: Implementation of Hash Merkle Tree, audit and consistensy proofs
|
|
28
|
+
email:
|
|
29
|
+
- tavrelkate@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files:
|
|
33
|
+
- README.md
|
|
34
|
+
files:
|
|
35
|
+
- README.md
|
|
36
|
+
- lib/algorithm.rb
|
|
37
|
+
- lib/hash_tree.rb
|
|
38
|
+
- lib/version.rb
|
|
39
|
+
homepage: https://github.com/tavrelkate/hash-merkle-tree
|
|
40
|
+
licenses:
|
|
41
|
+
- MIT
|
|
42
|
+
metadata: {}
|
|
43
|
+
post_install_message:
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 2.5.0
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.2.15
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: Implementation of Hash Merkle Tree, audit and consistensy proofs
|
|
62
|
+
test_files: []
|