mssmt 0.1.0 → 0.3.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 +4 -4
- data/README.md +2 -2
- data/lib/mssmt/branch_node.rb +9 -0
- data/lib/mssmt/computed_node.rb +22 -0
- data/lib/mssmt/leaf_node.rb +7 -1
- data/lib/mssmt/proof.rb +125 -0
- data/lib/mssmt/tree.rb +29 -26
- data/lib/mssmt/version.rb +1 -1
- data/lib/mssmt.rb +3 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e99668bb044c1670cee5d94e301f8113321c04ce9ac06b697fcfa8eabf1668f9
|
4
|
+
data.tar.gz: 7928cf9abd46c85c505dc770c2cbcce33e82ce98c2f7f772872792366bcd6eaa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b0b1648a73caed3a29c764c688b8077dea1beaa65da3772429470fc1ca1eab352b11128538e0e3b4daa2e8c578452360a562a08bd69bebe3bebadd228070f74
|
7
|
+
data.tar.gz: 653ad3b9270105b25a9dfea42414918cc0640cdc9359a7c9c01c712ee503f6689d81ee74796510099c3827fae53209fd2a7325392a36541f64b39447482d6e30
|
data/README.md
CHANGED
@@ -36,9 +36,9 @@ tree.insert(key, leaf)
|
|
36
36
|
# Get root node
|
37
37
|
root_node = tree.root_node
|
38
38
|
# Root hash
|
39
|
-
root_hash = tree.root_hash
|
39
|
+
root_hash = tree.root_hash.unpack1('H*')
|
40
40
|
# or
|
41
|
-
root_node.node_hash
|
41
|
+
root_node.node_hash.unpack1('H*')
|
42
42
|
|
43
43
|
# Get leaf node in tree
|
44
44
|
leaf = tree.get(key)
|
data/lib/mssmt/branch_node.rb
CHANGED
@@ -16,10 +16,19 @@ module MSSMT
|
|
16
16
|
@left = left
|
17
17
|
@right = right
|
18
18
|
@sum = left.sum + right.sum
|
19
|
+
warn("sum:#{@sum} cause overflow.") if @sum > 0xffffffffffffffff # TODO
|
20
|
+
@sum = (@sum & 0xffffffffffffffff)
|
19
21
|
@node_hash =
|
20
22
|
Digest::SHA256.digest(
|
21
23
|
"#{left.node_hash}#{right.node_hash}#{[@sum].pack("Q>")}"
|
22
24
|
)
|
23
25
|
end
|
26
|
+
|
27
|
+
# Check whether same branch|computed node or not.
|
28
|
+
# @return [Boolean]
|
29
|
+
def ==(other)
|
30
|
+
return false unless [BranchNode, ComputedNode].include?(other.class)
|
31
|
+
node_hash == other.node_hash && sum == other.sum
|
32
|
+
end
|
24
33
|
end
|
25
34
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MSSMT
|
4
|
+
# Node within a MS-SMT that has already had its node_hash and sum computed, i.e., its preimage is not available.
|
5
|
+
class ComputedNode
|
6
|
+
attr_reader :node_hash, :sum
|
7
|
+
|
8
|
+
# Constructor
|
9
|
+
# @param [String] node_hash node hash with binary fomat.
|
10
|
+
# @param [Integer] sum
|
11
|
+
def initialize(node_hash, sum)
|
12
|
+
@node_hash = node_hash
|
13
|
+
warn("sum: #{sum} cause overflow.") if sum > 0xffffffffffffffff # TODO
|
14
|
+
@sum = sum
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
return false unless [BranchNode, ComputedNode].include?(other.class)
|
19
|
+
node_hash == other.node_hash && sum == other.sum
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/mssmt/leaf_node.rb
CHANGED
@@ -10,7 +10,8 @@ module MSSMT
|
|
10
10
|
# @param [Integer] sum integer value associated with the value
|
11
11
|
def initialize(value, sum)
|
12
12
|
@value = value
|
13
|
-
|
13
|
+
warn("sum: #{sum} cause overflow.") if sum > 0xffffffffffffffff # TODO
|
14
|
+
@sum = sum & 0xffffffffffffffff
|
14
15
|
end
|
15
16
|
|
16
17
|
# Generate empty leaf node.
|
@@ -30,5 +31,10 @@ module MSSMT
|
|
30
31
|
def empty?
|
31
32
|
(value.nil? or value.empty?) && sum.zero?
|
32
33
|
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
return false unless other.is_a?(LeafNode)
|
37
|
+
node_hash == other.node_hash
|
38
|
+
end
|
33
39
|
end
|
34
40
|
end
|
data/lib/mssmt/proof.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module MSSMT
|
3
|
+
# Merkle proof for MS-SMT.
|
4
|
+
class Proof
|
5
|
+
attr_reader :nodes
|
6
|
+
|
7
|
+
# Constructor
|
8
|
+
# @param [Array(MSSMT::BranchNode|MSSMT::LeafNode)] nodes Siblings that should be hashed with the leaf and
|
9
|
+
# its parents to arrive at the root of the MS-SMT.
|
10
|
+
def initialize(nodes)
|
11
|
+
@nodes = nodes
|
12
|
+
end
|
13
|
+
|
14
|
+
# Compresses a merkle proof by replacing its empty nodes with a bit vector.
|
15
|
+
# @return [MSSMT::CompressedProof]
|
16
|
+
def compress
|
17
|
+
bits = Array.new(nodes.length, false)
|
18
|
+
compact_nodes = []
|
19
|
+
nodes.each.each_with_index do |node, i|
|
20
|
+
if node.node_hash == Tree.empty_tree[Tree::MAX_LEVEL - i].node_hash
|
21
|
+
bits[i] = true
|
22
|
+
else
|
23
|
+
compact_nodes << node
|
24
|
+
end
|
25
|
+
end
|
26
|
+
CompressedProof.new(compact_nodes, bits)
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
return false unless other.is_a?(Proof)
|
31
|
+
nodes == other.nodes
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Compressed MS-SMT merkle proof.
|
36
|
+
# Since merkle proofs for a MS-SMT are always constant size (255 nodes),
|
37
|
+
# we replace its empty nodes by a bit vector.
|
38
|
+
class CompressedProof < Proof
|
39
|
+
attr_reader :bits
|
40
|
+
|
41
|
+
# Constructor
|
42
|
+
# @param [Array(MSSMT::BranchNode|MSSMT::LeafNode)] nodes Siblings that should be hashed with the leaf and
|
43
|
+
# its parents to arrive at the root of the MS-SMT.
|
44
|
+
# @param [Array] bits +bits+ determines whether a sibling node within a proof is part of the empty tree.
|
45
|
+
# This allows us to efficiently compress proofs by not including any pre-computed nodes.
|
46
|
+
def initialize(nodes, bits)
|
47
|
+
super(nodes)
|
48
|
+
@bits = bits
|
49
|
+
end
|
50
|
+
|
51
|
+
# Decode compressed proof.
|
52
|
+
# @param [String] compressed proof with binary fomat.
|
53
|
+
# @return [MSSMT::CompressedProof]
|
54
|
+
def self.decode(data)
|
55
|
+
buf = data.is_a?(StringIO) ? data : StringIO.new(data)
|
56
|
+
nodes_len = buf.read(2).unpack1("n")
|
57
|
+
nodes =
|
58
|
+
nodes_len.times.map do
|
59
|
+
ComputedNode.new(buf.read(32), buf.read(8).unpack1("Q>"))
|
60
|
+
end
|
61
|
+
bytes = buf.read(MSSMT::Tree::MAX_LEVEL / 8)
|
62
|
+
bits = unpack_bits(bytes.unpack("C*"))
|
63
|
+
CompressedProof.new(nodes, bits)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Decompress a compressed merkle proof by replacing its bit vector with the empty nodes it represents.
|
67
|
+
# @return [MSSMT::Proof]
|
68
|
+
def decompress
|
69
|
+
count = 0
|
70
|
+
full_nodes = []
|
71
|
+
bits.each.with_index do |b, i|
|
72
|
+
if b
|
73
|
+
full_nodes << Tree.empty_tree[Tree::MAX_LEVEL - i]
|
74
|
+
else
|
75
|
+
full_nodes << nodes[count]
|
76
|
+
count += 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
Proof.new(full_nodes)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Encode the compressed proof.
|
83
|
+
# @return [String] encoded string.
|
84
|
+
def encode
|
85
|
+
buf = [nodes.length].pack("n")
|
86
|
+
nodes.each do |node|
|
87
|
+
buf << node.node_hash
|
88
|
+
buf << [node.sum].pack("Q>")
|
89
|
+
end
|
90
|
+
buf << pack_bits.pack("C*")
|
91
|
+
buf
|
92
|
+
end
|
93
|
+
|
94
|
+
def ==(other)
|
95
|
+
return false unless other.is_a?(CompressedProof)
|
96
|
+
bits == other.bits && nodes == other.nodes
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def pack_bits
|
102
|
+
bytes = Array.new((bits.length + 8 - 1) / 8, 0)
|
103
|
+
bits.each_with_index do |b, i|
|
104
|
+
next unless b
|
105
|
+
byte_index = i / 8
|
106
|
+
bit_index = i % 8
|
107
|
+
bytes[byte_index] |= (1 << bit_index)
|
108
|
+
end
|
109
|
+
bytes
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.unpack_bits(bytes)
|
113
|
+
bit_len = bytes.length * 8
|
114
|
+
bits = Array.new(bit_len, false)
|
115
|
+
bit_len.times do |i|
|
116
|
+
byte_index = i / 8
|
117
|
+
bit_index = i % 8
|
118
|
+
bits[i] = ((bytes[byte_index] >> bit_index) & 1) == 1
|
119
|
+
end
|
120
|
+
bits
|
121
|
+
end
|
122
|
+
|
123
|
+
private_class_method :unpack_bits
|
124
|
+
end
|
125
|
+
end
|
data/lib/mssmt/tree.rb
CHANGED
@@ -10,10 +10,29 @@ module MSSMT
|
|
10
10
|
# Index of the last bit for MS-SMT keys
|
11
11
|
LAST_BIT_INDEX = MAX_LEVEL - 1
|
12
12
|
|
13
|
-
attr_reader :
|
13
|
+
attr_reader :store
|
14
|
+
|
15
|
+
def self.empty_tree
|
16
|
+
@empty_tree ||= build_empty_tree
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate empty tree.
|
20
|
+
# @return [Array]
|
21
|
+
def self.build_empty_tree
|
22
|
+
tree = Array.new(MSSMT::Tree::MAX_LEVEL + 1)
|
23
|
+
tree[MSSMT::Tree::MAX_LEVEL] = MSSMT::LeafNode.empty_leaf
|
24
|
+
MSSMT::Tree::MAX_LEVEL.times do |i|
|
25
|
+
branch =
|
26
|
+
MSSMT::BranchNode.new(
|
27
|
+
tree[MSSMT::Tree::MAX_LEVEL - i],
|
28
|
+
tree[MSSMT::Tree::MAX_LEVEL - i]
|
29
|
+
)
|
30
|
+
tree[MSSMT::Tree::MAX_LEVEL - (i + 1)] = branch
|
31
|
+
end
|
32
|
+
tree.freeze
|
33
|
+
end
|
14
34
|
|
15
35
|
def initialize(store: MSSMT::Store::DefaultStore.new)
|
16
|
-
@empty_tree = build_empty_tree
|
17
36
|
@store = store
|
18
37
|
end
|
19
38
|
|
@@ -26,7 +45,7 @@ module MSSMT
|
|
26
45
|
# Get root node in tree.
|
27
46
|
# @return [MSSMT::BranchNode]
|
28
47
|
def root_node
|
29
|
-
store.root.nil? ? empty_tree[0] : store.root
|
48
|
+
store.root.nil? ? Tree.empty_tree[0] : store.root
|
30
49
|
end
|
31
50
|
|
32
51
|
# Insert a leaf node at the given key within the MS-SMT.
|
@@ -49,10 +68,10 @@ module MSSMT
|
|
49
68
|
root =
|
50
69
|
walk_up(key, leaf, siblings) do |i, _, _, parent|
|
51
70
|
prev_parent = prev_parents[MSSMT::Tree::MAX_LEVEL - 1 - i]
|
52
|
-
unless prev_parent == empty_tree[i].node_hash
|
71
|
+
unless prev_parent == Tree.empty_tree[i].node_hash
|
53
72
|
store.delete_branch(prev_parent)
|
54
73
|
end
|
55
|
-
unless parent.node_hash == empty_tree[i].node_hash
|
74
|
+
unless parent.node_hash == Tree.empty_tree[i].node_hash
|
56
75
|
store.insert_branch(parent)
|
57
76
|
end
|
58
77
|
end
|
@@ -76,19 +95,19 @@ module MSSMT
|
|
76
95
|
|
77
96
|
# Generate a merkle proof for the leaf node found at the given +key+.
|
78
97
|
# @param [String] key key with hex format.
|
79
|
-
# @return [
|
98
|
+
# @return [MSSMT::Proof] merkle proof
|
80
99
|
def merkle_proof(key)
|
81
100
|
proof = Array.new(MAX_LEVEL)
|
82
101
|
walk_down(key) { |i, _, sibling, _| proof[MAX_LEVEL - 1 - i] = sibling }
|
83
|
-
proof
|
102
|
+
MSSMT::Proof.new(proof)
|
84
103
|
end
|
85
104
|
|
86
105
|
# Verify whether a merkle proof for the leaf found at the given key is valid.
|
87
106
|
# @param [String] key key with hex format.
|
88
107
|
# @param [MSSMT::LeafNode] leaf leaf node.
|
89
|
-
# @param [
|
108
|
+
# @param [MSSMT::Proof] proof merkle proof.
|
90
109
|
def valid_merkle_proof?(key, leaf, proof)
|
91
|
-
root_hash == walk_up(key, leaf, proof).node_hash
|
110
|
+
root_hash == walk_up(key, leaf, proof.nodes).node_hash
|
92
111
|
end
|
93
112
|
|
94
113
|
private
|
@@ -110,7 +129,7 @@ module MSSMT
|
|
110
129
|
end
|
111
130
|
|
112
131
|
def get_node(height, key)
|
113
|
-
return empty_tree[height] if empty_tree[height].node_hash == key
|
132
|
+
return Tree.empty_tree[height] if Tree.empty_tree[height].node_hash == key
|
114
133
|
store.branches[key] || store.leaves[key]
|
115
134
|
end
|
116
135
|
|
@@ -152,21 +171,5 @@ module MSSMT
|
|
152
171
|
value = [key].pack("H*")[idx / 8].ord
|
153
172
|
(value >> (idx % 8)) & 1
|
154
173
|
end
|
155
|
-
|
156
|
-
# Generate empty tree.
|
157
|
-
# @return [Array]
|
158
|
-
def build_empty_tree
|
159
|
-
tree = Array.new(MSSMT::Tree::MAX_LEVEL + 1)
|
160
|
-
tree[MSSMT::Tree::MAX_LEVEL] = MSSMT::LeafNode.empty_leaf
|
161
|
-
MSSMT::Tree::MAX_LEVEL.times do |i|
|
162
|
-
branch =
|
163
|
-
MSSMT::BranchNode.new(
|
164
|
-
tree[MSSMT::Tree::MAX_LEVEL - i],
|
165
|
-
tree[MSSMT::Tree::MAX_LEVEL - i]
|
166
|
-
)
|
167
|
-
tree[MSSMT::Tree::MAX_LEVEL - (i + 1)] = branch
|
168
|
-
end
|
169
|
-
tree
|
170
|
-
end
|
171
174
|
end
|
172
175
|
end
|
data/lib/mssmt/version.rb
CHANGED
data/lib/mssmt.rb
CHANGED
@@ -11,5 +11,8 @@ module MSSMT
|
|
11
11
|
autoload :Store, "mssmt/store"
|
12
12
|
autoload :LeafNode, "mssmt/leaf_node"
|
13
13
|
autoload :BranchNode, "mssmt/branch_node"
|
14
|
+
autoload :ComputedNode, "mssmt/computed_node"
|
14
15
|
autoload :Tree, "mssmt/tree"
|
16
|
+
autoload :Proof, "mssmt/proof"
|
17
|
+
autoload :CompressedProof, "mssmt/proof"
|
15
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mssmt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Ruby implementation of Merkle Sum Sparse Merkle Tree
|
14
14
|
email:
|
@@ -32,7 +32,9 @@ files:
|
|
32
32
|
- bin/setup
|
33
33
|
- lib/mssmt.rb
|
34
34
|
- lib/mssmt/branch_node.rb
|
35
|
+
- lib/mssmt/computed_node.rb
|
35
36
|
- lib/mssmt/leaf_node.rb
|
37
|
+
- lib/mssmt/proof.rb
|
36
38
|
- lib/mssmt/store.rb
|
37
39
|
- lib/mssmt/store/default_store.rb
|
38
40
|
- lib/mssmt/tree.rb
|