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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2bf77bb8efa4e33fe73b3bd1b54e577e02097fadda8420924caf8df0ad9df21
4
- data.tar.gz: a5715d5289504147a0b76f8c3935910df965cae05df2a284136735389f8daa27
3
+ metadata.gz: e99668bb044c1670cee5d94e301f8113321c04ce9ac06b697fcfa8eabf1668f9
4
+ data.tar.gz: 7928cf9abd46c85c505dc770c2cbcce33e82ce98c2f7f772872792366bcd6eaa
5
5
  SHA512:
6
- metadata.gz: 30e1c75109384039d6d87ed9702e7bfbe2848e499285460b67da66dd80668e32590ce409d5deedd64a08ed5cae6f558d40817c73275a271c0b92c6a0f1cab8e0
7
- data.tar.gz: 6231535740549dfdb317e122945464ec92789c24479f164058ebdb06a3bfb2cabd688a0b6c0eca4c48881e9a891f5b72532a7609b4ec27edf132f9986f6ad558
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)
@@ -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
@@ -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
- @sum = sum
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
@@ -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 :empty_tree, :store
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 [Array] merkle proof
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 [Array] proof merkle proof.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MSSMT
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
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.1.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-17 00:00:00.000000000 Z
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