merkle_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c4803a642169c873dcc0ece2b6a63c6322db9ebf9794c36ddaec8dd6d8bfbb5a
4
+ data.tar.gz: '008ea58e5d83dbef7c888e5d06507d001ed0cf1c8cc020a9242f37c0a5f56bc4'
5
+ SHA512:
6
+ metadata.gz: 8c962e9d69320a7822a2f4a52e0836f5b7f813b31174e2719d8a3f8391f44fb3ddfb0a4f0fea81c323c1183740ff8924380ba794156afcb562c38c07c14c8552
7
+ data.tar.gz: 3ff25ab4867c9bc5179e485243489f0ac4feff35b61e8416944d330d9d8520bb9151f05dc09fdc1d24b0a20a2e7b2cd4215417a10fb544744fb9d14476be0e8d
@@ -0,0 +1,7 @@
1
+ # Change log
2
+
3
+ ## [v0.1.0] - 2019-03-16
4
+
5
+ * Initial implementation and release
6
+
7
+ [v0.1.0]: https://github.com/piotrmurach/merkle_tree/compare/v0.1.0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Piotr Murach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,361 @@
1
+ # MerkleTree
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/merkle_tree.svg)][gem]
4
+ [![Build Status](https://secure.travis-ci.org/piotrmurach/merkle_tree.svg?branch=master)][travis]
5
+ [![Build status](https://ci.appveyor.com/api/projects/status/kcbi55cyq2wlfuhc?svg=true)][appveyor]
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ce00667c8785a62cd892/maintainability)][codeclimate]
7
+ [![Coverage Status](https://coveralls.io/repos/piotrmurach/merkle_tree/badge.svg)][coverage]
8
+ [![Inline docs](http://inch-ci.org/github/piotrmurach/merkle_tree.svg?branch=master)][inchpages]
9
+
10
+ [gem]: http://badge.fury.io/rb/merkle_tree
11
+ [travis]: http://travis-ci.org/piotrmurach/merkle_tree
12
+ [appveyor]: https://ci.appveyor.com/project/piotrmurach/merkle-tree
13
+ [codeclimate]: https://codeclimate.com/github/piotrmurach/merkle_tree/maintainability
14
+ [coverage]: https://coveralls.io/r/piotrmurach/merkle_tree
15
+ [inchpages]: http://inch-ci.org/github/piotrmurach/merkle_tree
16
+
17
+ > A binary tree of one-time signatures known as merkle tree. Often used in distributed systems such as Git, Cassandra or Bitcoin for efficiently summarizing sets of data.
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'merkle_tree'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install merkle_tree
34
+
35
+ ## Contents
36
+
37
+ * [1. Usage](#1-usage)
38
+ * [2. API](#2-api)
39
+ * [2.1 root](#21-root)
40
+ * [2.2 leaves](#22-leaves)
41
+ * [2.3 subtree](#23-subtree)
42
+ * [2.4 height](#24-height)
43
+ * [2.5 size](#25-size)
44
+ * [2.6 include?](#26-include)
45
+ * [2.7 add](#27-add)
46
+ * [2.8 update](#28-update)
47
+ * [2.9 to_h](#29-to_h)
48
+ * [2.10 to_s](#210-to_s)
49
+ * [2.11 :digest](#211-digest)
50
+ * [3. See Also](#3-see-also)
51
+
52
+ ## 1. Usage
53
+
54
+ Create a new *MerkleTree* by passing all the messages to be hashed:
55
+
56
+ ```ruby
57
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
58
+ ```
59
+
60
+ Then you can use the tree to verify if a message belongs:
61
+
62
+ ```ruby
63
+ merkle_tree.include?("L3", 2) # => true
64
+ ```
65
+
66
+ Or change the message to a new content:
67
+
68
+ ```ruby
69
+ merkle_tree.update("L3*", 2)
70
+ ```
71
+
72
+ You can also check the tree height:
73
+
74
+ ```ruby
75
+ merkle_tree.height # => 2
76
+ ```
77
+
78
+ And how many nodes it has:
79
+
80
+ ```ruby
81
+ merkle_tree.size # => 7
82
+ ```
83
+
84
+ Finally, you can print the contents of the tree to see all the signatures:
85
+
86
+ ```ruby
87
+ merkle_tree.to_s
88
+ # =>
89
+ # 63442ffc2d48a92c8ba746659331f273748ccede648b27f4eacf00cb0786c439
90
+ # f2b92f33b56466fce14bc2ccf6a92f6edfcd8111446644c20221d6ae831dd67c
91
+ # dffe8596427fc50e8f64654a609af134d45552f18bbecef90b31135a9e7acaa0
92
+ # d76354d8457898445bb69e0dc0dc95fb74cc3cf334f8c1859162a16ad0041f8d
93
+ # 8f75b0c1b3d1c0bb2eda264a43f8fdc5c72c853c95fbf2b01c1d5a3e12c6fe9a
94
+ # 842983de8fb1d277a3fad5c8295c7a14317c458718a10c5a35b23e7f992a5c80
95
+ # 4a5a97c6433c4c062457e9335709d57493e75527809d8a9586c141e591ac9f2c
96
+ ```
97
+
98
+ ## 2. API
99
+
100
+ ### 2.1 root
101
+
102
+ To access the root node of the merkle tree use the `root` method that will return the tree structure with all its children and their signatures.
103
+
104
+ For example, given a tree with 4 messages
105
+
106
+ ```ruby
107
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
108
+ ```
109
+
110
+ A full tree of one-time signatures will be available:
111
+
112
+ ```ruby
113
+ merkle_tree.root
114
+ # =>
115
+ # MerkleTree::Node
116
+ # @value="63442ffc2d48a92c8ba746659331f273748ccede648b27f4eacf00cb0786c439"
117
+ # @left=MerkleTree::Node
118
+ # @value="f2b92f33b56466fce14bc2ccf6a92f6edfcd8111446644c20221d6ae831dd67c"
119
+ # @left=MerkleTree::Leaf
120
+ # @value="dffe8596427fc50e8f64654a609af134d45552f18bbecef90b31135a9e7acaa0"
121
+ # @right=MerkleTree::Leaf
122
+ # @value="d76354d8457898445bb69e0dc0dc95fb74cc3cf334f8c1859162a16ad0041f8d"
123
+ # @right=MerkleTree::Node
124
+ # @value="8f75b0c1b3d1c0bb2eda264a43f8fdc5c72c853c95fbf2b01c1d5a3e12c6fe9a"
125
+ # @left=
126
+ # MerkleTree::Leaf
127
+ # @value="842983de8fb1d277a3fad5c8295c7a14317c458718a10c5a35b23e7f992a5c80"
128
+ # @right=MerkleTree::Leaf
129
+ # @value="4a5a97c6433c4c062457e9335709d57493e75527809d8a9586c141e591ac9f2c"
130
+ ```
131
+
132
+ Since the root is a node you can retrieve it's signature using the `value` call:
133
+
134
+ ```ruby
135
+ merkle_tree.root.value
136
+ # => "63442ffc2d48a92c8ba746659331f273748ccede648b27f4eacf00cb0786c439"
137
+ ```
138
+
139
+ ### 2.2 leaves
140
+
141
+ You can access all the leaves and their one-time signatures using the `leaves` method:
142
+
143
+ ```ruby
144
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
145
+
146
+ merkle_tree.leaves
147
+ # =>
148
+ # [
149
+ # <MerkleTree::Leaf @value="dffe8596...", @left_index=0, @right_index=0, @height=0>,
150
+ # <MerkleTree::Leaf @value="d76354d8...", @left_index=1, @right_index=1, @height=0>,
151
+ # <MerkleTree::Leaf @value="842983de...", @left_index=2, @right_index=2, @height=0>,
152
+ # <MerkleTree::Leaf @value="4a5a97c6...", @left_index=3, @right_index=3, @height=0>
153
+ # ]
154
+ ```
155
+
156
+ ### 2.3 subtree
157
+
158
+ To access a part of Merkle tree use `subtree` with the index of the one-time signature:
159
+
160
+ ```ruby
161
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4", "L5", "L6", "L7", "L8")
162
+
163
+ merkle_tree.subtree(2).to_h
164
+ # =>
165
+ # {
166
+ # value: "63442ffc2d48a92c8ba746659331f273748ccede648b27f4eacf00cb0786c439",
167
+ # left: {
168
+ # value: "f2b92f33b56466fce14bc2ccf6a92f6edfcd8111446644c20221d6ae831dd67c",
169
+ # left: { value: "dffe8596427fc50e8f64654a609af134d45552f18bbecef90b31135a9e7acaa0" },
170
+ # right: { value: "d76354d8457898445bb69e0dc0dc95fb74cc3cf334f8c1859162a16ad0041f8d" }
171
+ # },
172
+ # right: {
173
+ # value: "8f75b0c1b3d1c0bb2eda264a43f8fdc5c72c853c95fbf2b01c1d5a3e12c6fe9a",
174
+ # left: { value: "842983de8fb1d277a3fad5c8295c7a14317c458718a10c5a35b23e7f992a5c80" },
175
+ # right: { value: "4a5a97c6433c4c062457e9335709d57493e75527809d8a9586c141e591ac9f2c" }
176
+ # }
177
+ # }
178
+ ```
179
+
180
+ ### 2.4 height
181
+
182
+ Every leaf in the tree has height 0 and the hash function of two leaves has height 1. So the tree has a total height of `H` if there is `N` leaves so that `N = 2^H`.
183
+
184
+
185
+ ```ruby
186
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4", "L5", "L6", "L7", "L8")
187
+ ```
188
+
189
+ And since `8 = 2^3` then the height:
190
+
191
+ ```ruby
192
+ merkle_tree.height # => 3
193
+ ```
194
+
195
+ ### 2.5 size
196
+
197
+ You can check total number of tree nodes with `size` or `length` calls:
198
+
199
+ ```ruby
200
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4", "L5", "L6", "L7", "L8")
201
+ merkle_tree.size
202
+ # => 15
203
+ ```
204
+
205
+ ### 2.6 include?
206
+
207
+ You can verify that a leaf belongs to merkle tree, namely, there is an authentication path or merkle path from the leaf to the root. This operation is `log2N` where N is number of leaves.
208
+
209
+ Given a tree with 4 messages:
210
+
211
+ ```ruby
212
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
213
+ ```
214
+
215
+ To check if a message `L3` is contained in one of the one-time signatures, use the `include?` or `member?` method passing the message and position index:
216
+
217
+ ```ruby
218
+ merkle_tree.include?("L3", 2) # => true
219
+ ```
220
+
221
+ Conversely, if the message is not part of one-time signature at position indexed:
222
+
223
+ ```ruby
224
+ merkle_tree.include?("invalid", 2) # => false
225
+ ````
226
+
227
+ ### 2.7 add
228
+
229
+ To add new messages to already existing tree use `add` or `<<` method. This action will create a new merkle root and increase tree height.
230
+
231
+ A merkle tree with 4 messages:
232
+
233
+ ```ruby
234
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
235
+ ```
236
+
237
+ Will have the following properties:
238
+
239
+ ```ruby
240
+ merkle_tree.leaves.size
241
+ # => 4
242
+ merkle_tree.height
243
+ # => 2
244
+ merkle_tree.size
245
+ # => 7
246
+ ```
247
+
248
+ To add `L5` and `L6` messages do:
249
+
250
+ ```ruby
251
+ merkle_tree.add("L5", "L6")
252
+ ```
253
+
254
+ This will expand tree to have:
255
+
256
+ ```ruby
257
+ merkle_tree.leaves.size
258
+ # => 6
259
+ merkle_tree.height
260
+ # => 3
261
+ merkle_tree.size
262
+ # => 15
263
+ ```
264
+
265
+ ### 2.8 update
266
+
267
+ You can replace any merkle tree message with a new one using the `update` call, which accepts a new message as a first argument and the index of the message to replace.
268
+
269
+ For example, given the tree:
270
+
271
+ ```ruby
272
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
273
+ ```
274
+
275
+ To update message from `L3` to `L3*` do:
276
+
277
+ ```ruby
278
+ merkle_tree.update("L3*", 2)
279
+ # =>
280
+ # #<MerkleTree::Leaf @value="e9a1dd00f5c5e848f6ca6d8660c5191d76ac5dd8867b7a8b08fb59c5ed2806db" ... >
281
+ ```
282
+
283
+ ### 2.9 to_h
284
+
285
+ You can dump the whole structure of the tree with `to_h` method:
286
+
287
+ ```ruby
288
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
289
+
290
+ merkle_tree.to_h
291
+ # =>
292
+ # root: {
293
+ # value: "63442ffc2d48a92c8ba746659331f273748ccede648b27f4eacf00cb0786c439",
294
+ # left: {
295
+ # value: "f2b92f33b56466fce14bc2ccf6a92f6edfcd8111446644c20221d6ae831dd67c",
296
+ # left: { value: "dffe8596427fc50e8f64654a609af134d45552f18bbecef90b31135a9e7acaa0" },
297
+ # right: { value: "d76354d8457898445bb69e0dc0dc95fb74cc3cf334f8c1859162a16ad0041f8d" }
298
+ # },
299
+ # right: {
300
+ # value: "8f75b0c1b3d1c0bb2eda264a43f8fdc5c72c853c95fbf2b01c1d5a3e12c6fe9a",
301
+ # left: { value: "842983de8fb1d277a3fad5c8295c7a14317c458718a10c5a35b23e7f992a5c80" },
302
+ # right: { value: "4a5a97c6433c4c062457e9335709d57493e75527809d8a9586c141e591ac9f2c" }
303
+ # }
304
+ # }
305
+ ```
306
+
307
+ ### 2.10 to_s
308
+
309
+ ```ruby
310
+ merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
311
+ ```
312
+
313
+ You can print merkle tree out to string:
314
+
315
+ ```ruby
316
+ merkle_tree.to_s
317
+ # =>
318
+ # 63442ffc2d48a92c8ba746659331f273748ccede648b27f4eacf00cb0786c439
319
+ # f2b92f33b56466fce14bc2ccf6a92f6edfcd8111446644c20221d6ae831dd67c
320
+ # dffe8596427fc50e8f64654a609af134d45552f18bbecef90b31135a9e7acaa0
321
+ # d76354d8457898445bb69e0dc0dc95fb74cc3cf334f8c1859162a16ad0041f8d
322
+ # 8f75b0c1b3d1c0bb2eda264a43f8fdc5c72c853c95fbf2b01c1d5a3e12c6fe9a
323
+ # 842983de8fb1d277a3fad5c8295c7a14317c458718a10c5a35b23e7f992a5c80
324
+ # 4a5a97c6433c4c062457e9335709d57493e75527809d8a9586c141e591ac9f2c
325
+ ```
326
+
327
+ ### 2.11 `:digest`
328
+
329
+ By default the `SHA-256` is used to create one-time signatures using Ruby's `openssl` package. You can see different [OpenSSl::Digest](https://ruby-doc.org/stdlib-2.6.1/libdoc/openssl/rdoc/OpenSSL/Digest.html).
330
+
331
+ To provide your own algorithm use the `:digest` keyword and as value a lambda that will produce message hash. For example, to use `SHA-512` message digest algorithm do:
332
+
333
+ ```ruby
334
+ MerkleTree.new("L1",..., digest: -> (val) { OpenSSL::Digest::SHA256.hexdigest(val) })
335
+ ```
336
+
337
+ ## 3. See Also
338
+
339
+ - [splay_tree](https://github.com/piotrmurach/splay_tree) – A self-balancing binary tree with amortised access.
340
+
341
+ ## Development
342
+
343
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
344
+
345
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
346
+
347
+ ## Contributing
348
+
349
+ Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/merkle_tree. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
350
+
351
+ ## License
352
+
353
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
354
+
355
+ ## Code of Conduct
356
+
357
+ Everyone interacting in the MerkleTree project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/merkle_tree/blob/master/CODE_OF_CONDUCT.md).
358
+
359
+ ## Copyright
360
+
361
+ Copyright (c) 2019 Piotr Murach. See [LICENSE.txt](https://github.com/piotrmurach/tty-pie_chart/blob/master/LICENSE.txt) for further details.
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ FileList['tasks/**/*.rake'].each(&method(:import))
4
+
5
+ desc 'Run all specs'
6
+ task ci: %w[ spec ]
7
+
8
+ task default: %w[ spec ]
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "merkle_tree"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'English'
5
+
6
+ require_relative 'merkle_tree/leaf'
7
+ require_relative 'merkle_tree/node'
8
+ require_relative 'merkle_tree/version'
9
+
10
+ # A binary tree of one-time signatures known as merkle tree.
11
+ class MerkleTree
12
+ # The tree root node
13
+ attr_reader :root
14
+
15
+ # All the leaf nodes
16
+ attr_reader :leaves
17
+
18
+ # The number of tree leaves
19
+ #
20
+ # @return [Integer]
21
+ #
22
+ # @api public
23
+ attr_reader :width
24
+
25
+ # The one way hash function
26
+ attr_reader :digest
27
+
28
+ # The default hash function using SHA 256
29
+ #
30
+ # @return [Proc]
31
+ #
32
+ # @api public
33
+ def self.default_digest
34
+ ->(val) { OpenSSL::Digest::SHA256.hexdigest(val) }
35
+ end
36
+
37
+ # Create a new Merkle Tree
38
+ #
39
+ # @example
40
+ # MerkleTree.new("L1", "L2", "L3", "L4")
41
+ #
42
+ # @param [Array[String]] messages
43
+ # the message to digest
44
+ # @param [Proc] :digest
45
+ # the message digest algorithm
46
+ #
47
+ # @api public
48
+ def initialize(*messages, digest: MerkleTree.default_digest)
49
+ @root = Node::EMPTY
50
+ @width = 0
51
+ @digest = digest
52
+ @leaves = to_leaves(*messages)
53
+ @width = @leaves.size
54
+ @root = build(@leaves)
55
+ end
56
+
57
+ # Convert messages to leaf data types
58
+ #
59
+ # @param [Array[String]] messages
60
+ # the message to digest
61
+ #
62
+ # @api private
63
+ def to_leaves(*messages)
64
+ if messages.size.odd?
65
+ messages << messages.last.dup
66
+ end
67
+
68
+ messages.each_with_index.map do |msg, pos|
69
+ Leaf.build(msg, pos, digest: digest)
70
+ end
71
+ end
72
+
73
+ # Check if this tree has any messages
74
+ #
75
+ # @api public
76
+ def empty?
77
+ @root == Node::EMPTY
78
+ end
79
+
80
+ # The tree height
81
+ #
82
+ # @api public
83
+ def height
84
+ @root.height
85
+ end
86
+
87
+ # The number of nodes in this tree
88
+ #
89
+ # @api public
90
+ def size
91
+ @root.size
92
+ end
93
+ alias length size
94
+
95
+ # Create subtree that contains message at index
96
+ #
97
+ # @return [Node]
98
+ # the root node of the subtree
99
+ #
100
+ # @api public
101
+ def subtree(index)
102
+ root.subtree(index)
103
+ end
104
+
105
+ # Calcualte the root value of this tree
106
+ #
107
+ # @param [Leaf] nodes
108
+ # the leaf nodes to build from
109
+ #
110
+ # @api private
111
+ def build(nodes)
112
+ return Node::EMPTY if nodes.empty?
113
+
114
+ if nodes.size == 1
115
+ return nodes[0]
116
+ end
117
+
118
+ parent_nodes = nodes.each_slice(2).map do |left, right|
119
+ right = left if right.nil? # Duplicate odd nodes
120
+ Node.build(left, right, digest: digest)
121
+ end
122
+ build(parent_nodes)
123
+ end
124
+
125
+ # Add new message(s)
126
+ #
127
+ # @example
128
+ # merkle_tree = MerkleTree.new("L1", "L2", "L3", "L4")
129
+ # merkle_tree.add("L5", "L6", "L7", "L8")
130
+ #
131
+ # @param [Array[String]] messages
132
+ # the message to digest
133
+ #
134
+ def add(*messages)
135
+ nodes = to_leaves(*messages)
136
+ nodes.each { |node| @leaves << node }
137
+ @width = @leaves.size
138
+ top_node = Node::EMPTY
139
+
140
+ while root.height != top_node.height
141
+ top_node = build(nodes)
142
+ nodes = [top_node, top_node]
143
+ end
144
+
145
+ @root = Node.build(@root, top_node)
146
+ end
147
+ alias << add
148
+
149
+ # Traverse tree from root to leaf collecting siblings hashes
150
+ #
151
+ # @param [Node] node
152
+ # @param [Integer] index
153
+ # @param [Array] path
154
+ #
155
+ # @return Array
156
+ #
157
+ # @api private
158
+ def traverse(node, index, path)
159
+ return path if node.leaf? || node == Node::EMPTY
160
+
161
+ path << node.sibling(index)
162
+
163
+ traverse(node.subtree(index), index, path)
164
+ end
165
+
166
+ # Create an authentication path required to authenticate leaf with index
167
+ #
168
+ # @param [Integer] index
169
+ #
170
+ # @return [Array[String,String]]
171
+ # an array of direction and hash tuples
172
+ #
173
+ # @api public
174
+ def auth_path(index)
175
+ traverse(root, index, [])
176
+ end
177
+
178
+ # Verifies that an authentication path exists from leaf to root
179
+ #
180
+ # @param [String] message
181
+ # the message to hash
182
+ #
183
+ # @param [Integer] index
184
+ # the index of the message to be authenticated
185
+ #
186
+ # @api public
187
+ def include?(message, index)
188
+ return false if empty?
189
+
190
+ leaf_hash = digest.(message)
191
+
192
+ hash = auth_path(index).reverse_each.reduce(leaf_hash) do |h, (dir, sibling)|
193
+ digest.(dir == :left ? sibling + h : h + sibling)
194
+ end
195
+
196
+ hash == root.value
197
+ end
198
+ alias member? include?
199
+
200
+ # Visit all direct children collecting child nodes
201
+ #
202
+ # @api private
203
+ def visit(node, index, path)
204
+ return path if node.leaf? || node == Node::EMPTY
205
+
206
+ path << node.child(index)
207
+
208
+ visit(node.subtree(index), index, path)
209
+ end
210
+
211
+ # The regeneration path from leaf to root
212
+ #
213
+ # @return [Node]
214
+ #
215
+ # @api public
216
+ def regeneration_path(index)
217
+ visit(@root, index, [@root])
218
+ end
219
+
220
+ # Update a leaf at index position
221
+ #
222
+ # @param [String] message
223
+ # the new message to hash
224
+ # @param [Integer] index
225
+ # the index of the message to be rehashed
226
+ #
227
+ # @return [Leaf]
228
+ # the updated leaf
229
+ #
230
+ # @api public
231
+ def update(message, index)
232
+ return if empty?
233
+
234
+ leaf_hash = digest.(message)
235
+
236
+ regeneration_path(index).reverse_each do |node|
237
+ if node.leaf?
238
+ node.value = leaf_hash
239
+ else
240
+ node.update(digest)
241
+ end
242
+ end.last
243
+ end
244
+
245
+ # Hash representation of this tree
246
+ #
247
+ # @api public
248
+ def to_h
249
+ { root: root.to_h }
250
+ end
251
+
252
+ # String representation of this tree
253
+ #
254
+ # @api public
255
+ def to_s(indent = '')
256
+ root.to_s(indent)
257
+ end
258
+ end # MerkleTree