mssmt 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|