melos 0.0.2 → 0.1.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 +63 -11
- data/docs/validation.md +168 -0
- data/lib/melos/capabilities.rb +15 -0
- data/lib/melos/client.rb +85 -0
- data/lib/melos/crypto.rb +84 -15
- data/lib/melos/epoch_secrets.rb +113 -0
- data/lib/melos/group/validation.rb +237 -0
- data/lib/melos/group.rb +594 -0
- data/lib/melos/key_package_bundle.rb +59 -0
- data/lib/melos/private_tree.rb +51 -0
- data/lib/melos/ratchet_tree.rb +407 -0
- data/lib/melos/secret_tree.rb +72 -89
- data/lib/melos/struct/base.rb +46 -20
- data/lib/melos/struct/structs.rb +162 -87
- data/lib/melos/tree.rb +4 -120
- data/lib/melos/vec.rb +20 -35
- data/lib/melos/version.rb +1 -1
- data/lib/melos.rb +9 -2
- metadata +13 -6
- data/lib/melos/key_schedule.rb +0 -62
- data/lib/melos/struct/ratchet_tree.rb +0 -324
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
## Ratchet Tree Extension (12.4.3.3)
|
|
2
|
-
require_relative 'base'
|
|
3
|
-
require_relative 'structs'
|
|
4
|
-
require_relative '../vec'
|
|
5
|
-
require_relative '../tree'
|
|
6
|
-
require_relative '../crypto'
|
|
7
|
-
|
|
8
|
-
module Melos::Struct::RatchetTree
|
|
9
|
-
def self.parse(stream)
|
|
10
|
-
stream = StringIO.new(stream) if stream.is_a?(String)
|
|
11
|
-
new(stream)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.new(stream)
|
|
15
|
-
array = []
|
|
16
|
-
data = Melos::Vec.parse_stringio(stream)
|
|
17
|
-
data_stream = StringIO.new(data)
|
|
18
|
-
while !data_stream.eof?
|
|
19
|
-
presence = data_stream.read(1).unpack1('C')
|
|
20
|
-
case presence
|
|
21
|
-
when 0
|
|
22
|
-
array << nil
|
|
23
|
-
when 1
|
|
24
|
-
node = Melos::Struct::Node.new(data_stream)
|
|
25
|
-
array << node
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
array
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def self.raw(array)
|
|
32
|
-
buf = ''
|
|
33
|
-
array.each do |optional_node|
|
|
34
|
-
if optional_node.nil?
|
|
35
|
-
buf += [0].pack('C')
|
|
36
|
-
else
|
|
37
|
-
buf += [1].pack('C')
|
|
38
|
-
buf += optional_node.raw
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
Melos::Vec.from_string(buf)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def self.tree_hash(tree, node_index, suite)
|
|
46
|
-
node = tree[node_index]
|
|
47
|
-
if Melos::Tree.leaf?(node_index)
|
|
48
|
-
# is a leaf node
|
|
49
|
-
leaf_index = node_index / 2
|
|
50
|
-
leaf_node_hash_input = [leaf_index].pack('L>')
|
|
51
|
-
if node.nil?
|
|
52
|
-
leaf_node_hash_input += [0].pack('C')
|
|
53
|
-
else
|
|
54
|
-
leaf_node_hash_input += [1].pack('C') + node.leaf_node.raw
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
tree_hash_input = [1].pack('C') + leaf_node_hash_input
|
|
58
|
-
else
|
|
59
|
-
# is a parent node, so calculate using ParentNodeHashInput
|
|
60
|
-
parent_node_hash_input = ''
|
|
61
|
-
if node.nil?
|
|
62
|
-
parent_node_hash_input += [0].pack('C')
|
|
63
|
-
else
|
|
64
|
-
parent_node_hash_input += [1].pack('C') + node.parent_node.raw
|
|
65
|
-
end
|
|
66
|
-
parent_node_hash_input += Melos::Vec.from_string(tree_hash(tree, Melos::Tree.left(node_index), suite))
|
|
67
|
-
parent_node_hash_input += Melos::Vec.from_string(tree_hash(tree, Melos::Tree.right(node_index), suite))
|
|
68
|
-
|
|
69
|
-
tree_hash_input = [2].pack('C') + parent_node_hash_input
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# The RFC omits the actual definition of calculating a tree hash...
|
|
73
|
-
# it could totally be a ExpandWithLabel-ish thing...
|
|
74
|
-
Melos::Crypto.hash(suite, tree_hash_input)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def self.tree_hash_except(tree, node_index, unmerged_leaves, suite)
|
|
78
|
-
new_tree = tree.dup
|
|
79
|
-
unmerged_leaves.each do |leaf_index|
|
|
80
|
-
node_index_to_remove = leaf_index * 2
|
|
81
|
-
new_tree[node_index_to_remove] = nil
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
tree_hash(new_tree, node_index, suite)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def self.calculate_parent_hash(tree, node_index, sibling, suite)
|
|
88
|
-
parent_node = tree[node_index].parent_node
|
|
89
|
-
sibling_hash = tree_hash_except(tree, sibling, parent_node.unmerged_leaves, suite)
|
|
90
|
-
Melos::Crypto.parent_hash(suite, parent_node.encryption_key, parent_node.parent_hash, sibling_hash)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def self.verify_parent_hash_at(tree, node_index, suite)
|
|
94
|
-
node = tree[node_index]
|
|
95
|
-
if Melos::Tree.leaf?(node_index)
|
|
96
|
-
false # maybe an ArgumentError, because there is no verifying a ParentHash on a leaf node
|
|
97
|
-
else
|
|
98
|
-
if node.nil?
|
|
99
|
-
true
|
|
100
|
-
else
|
|
101
|
-
left_index = Melos::Tree.left(node_index)
|
|
102
|
-
right_index = Melos::Tree.right(node_index)
|
|
103
|
-
|
|
104
|
-
# either the node at node_index is Parent-Hash Valid wrt someone in left tree or someone in right tree
|
|
105
|
-
has_parent_hash(tree, left_index, calculate_parent_hash(tree, node_index, right_index, suite)) || has_parent_hash(tree, right_index, calculate_parent_hash(tree, node_index, left_index, suite))
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def self.has_parent_hash(tree, child_index, parent_hash_value)
|
|
111
|
-
resolutions = Melos::Tree.resolution(tree, child_index)
|
|
112
|
-
resolutions.each do |node_index|
|
|
113
|
-
if tree[node_index]&.parent_hash_in_node == parent_hash_value
|
|
114
|
-
# if any of the resolution of specified child has matching parent_hash_value then parent is Parent-Hash Valid wrt that child
|
|
115
|
-
return true
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
return false
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def self.verify_parent_hash_of_tree(tree, suite)
|
|
122
|
-
parent_indexes = (1..((tree.count - 1) / 2)).map { _1 * 2 - 1} # this makes node_indexes of odd numbers
|
|
123
|
-
parent_indexes_from_bottom_to_top = parent_indexes.sort_by { Melos::Tree.level(_1) } # this sorts node_indexes based on level
|
|
124
|
-
parent_indexes_from_bottom_to_top.all? { verify_parent_hash_at(tree, _1, suite) } # this makes it so that nodes are evaluated from lower level to higher level
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def self.add_leaf_node(tree, node_to_insert)
|
|
128
|
-
inserted = false
|
|
129
|
-
inserted_node_index = 0
|
|
130
|
-
# if there is a blank in tree, insert there
|
|
131
|
-
tree.each_with_index do |node, node_index|
|
|
132
|
-
if Melos::Tree.leaf?(node_index)
|
|
133
|
-
if tree[node_index].nil?
|
|
134
|
-
tree[node_index] = node_to_insert
|
|
135
|
-
inserted = true
|
|
136
|
-
inserted_node_index = node_index
|
|
137
|
-
break
|
|
138
|
-
end
|
|
139
|
-
else
|
|
140
|
-
# do nothing to a parent
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
# if not, extend tree
|
|
144
|
-
if !inserted
|
|
145
|
-
tree << nil
|
|
146
|
-
tree << node_to_insert
|
|
147
|
-
inserted_node_index = tree.count - 1
|
|
148
|
-
end
|
|
149
|
-
# then update unmerged list up till root
|
|
150
|
-
inserted_leaf_index = inserted_node_index / 2
|
|
151
|
-
current_node_index = inserted_node_index
|
|
152
|
-
while(current_node_index != Melos::Tree.root(tree.count))
|
|
153
|
-
if tree[current_node_index] && tree[current_node_index].node_type == 0x02
|
|
154
|
-
tree[current_node_index].parent_node.unmerged_leaves << inserted_leaf_index
|
|
155
|
-
end
|
|
156
|
-
current_node_index = Melos::Tree.parent(current_node_index, tree.count)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
return inserted_leaf_index
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def self.update_leaf_node(tree, node_to_update, leaf_index_of_sender)
|
|
163
|
-
node_index = leaf_index_of_sender * 2
|
|
164
|
-
tree[node_index] = node_to_update
|
|
165
|
-
# blank the intermediate nodes along the path from sender's leaf to root
|
|
166
|
-
current_node_index = node_index
|
|
167
|
-
while(current_node_index != Melos::Tree.root(tree.count))
|
|
168
|
-
if tree[current_node_index] && tree[current_node_index].node_type == 0x02
|
|
169
|
-
tree[current_node_index] = nil
|
|
170
|
-
end
|
|
171
|
-
current_node_index = Melos::Tree.parent(current_node_index, tree.count)
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def self.remove_leaf_node(tree, leaf_index_to_remove)
|
|
176
|
-
node_index = leaf_index_to_remove * 2
|
|
177
|
-
tree[node_index] = nil
|
|
178
|
-
# blank the intermediate nodes along the path from sender's leaf to root
|
|
179
|
-
current_node_index = node_index
|
|
180
|
-
while(current_node_index != Melos::Tree.root(tree.count))
|
|
181
|
-
if tree[current_node_index] && tree[current_node_index].node_type == 0x02
|
|
182
|
-
tree[current_node_index] = nil
|
|
183
|
-
end
|
|
184
|
-
current_node_index = Melos::Tree.parent(current_node_index, tree.count)
|
|
185
|
-
end
|
|
186
|
-
# then truncate tree
|
|
187
|
-
Melos::Tree.truncate!(tree)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def self.root_tree_hash(suite, tree)
|
|
191
|
-
root_index = Melos::Tree.root(Melos::Tree.n_leaves(tree))
|
|
192
|
-
tree_hash(tree, root_index, suite)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def self.merge_update_path(suite, ratchet_tree, leaf_index, update_path)
|
|
196
|
-
node_index_of_leaf = leaf_index * 2
|
|
197
|
-
filtered_direct_path = Melos::Tree.filtered_direct_path(ratchet_tree, node_index_of_leaf)
|
|
198
|
-
nodes_from_update_path = update_path.nodes
|
|
199
|
-
|
|
200
|
-
parent_hashes = calculate_parent_hashes(suite, ratchet_tree, leaf_index, update_path.nodes)
|
|
201
|
-
# update parent nodes on path
|
|
202
|
-
filtered_direct_path.each_with_index do |node_index, path_index|
|
|
203
|
-
parent_node = Melos::Struct::ParentNode.create(
|
|
204
|
-
encryption_key: nodes_from_update_path[path_index].encryption_key,
|
|
205
|
-
parent_hash: parent_hashes[path_index + 1],
|
|
206
|
-
unmerged_leaves: []
|
|
207
|
-
)
|
|
208
|
-
node = Melos::Struct::Node.new_parent_node(parent_node)
|
|
209
|
-
ratchet_tree[node_index] = node
|
|
210
|
-
end
|
|
211
|
-
# update leaf
|
|
212
|
-
node = Melos::Struct::Node.new_leaf_node(update_path.leaf_node)
|
|
213
|
-
ratchet_tree[node_index_of_leaf] = node
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def self.calculate_parent_hashes(suite, ratchet_tree, leaf_index_from, update_path_nodes)
|
|
217
|
-
hashes = []
|
|
218
|
-
filtered_direct_path = Melos::Tree.filtered_direct_path(ratchet_tree, leaf_index_from * 2)
|
|
219
|
-
# count down from root, calculate parent hash
|
|
220
|
-
calculated_parent_hash = ""
|
|
221
|
-
# node_index = Melos::Tree.root(Melos::Tree.n_leaves(ratchet_tree))
|
|
222
|
-
# puts "fdp count: #{filtered_direct_path.count}"
|
|
223
|
-
# puts "update path count: #{nodes_from_update_path.count}"
|
|
224
|
-
hashes[filtered_direct_path.count] = ''
|
|
225
|
-
(filtered_direct_path.count - 1).downto(0) do |path_index|
|
|
226
|
-
node_index = filtered_direct_path[path_index]
|
|
227
|
-
leaf_node_index = leaf_index_from * 2
|
|
228
|
-
sibling_node_index = Melos::Tree.sibling_from_leaf(leaf_node_index, node_index, Melos::Tree.n_leaves(ratchet_tree))
|
|
229
|
-
encryption_key = update_path_nodes[path_index].encryption_key
|
|
230
|
-
sibling_hash = Melos::Struct::RatchetTree.tree_hash(ratchet_tree, sibling_node_index, suite)
|
|
231
|
-
calculated_parent_hash = Melos::Crypto.parent_hash(suite, encryption_key, calculated_parent_hash, sibling_hash)
|
|
232
|
-
hashes[path_index] = calculated_parent_hash
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
hashes
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def self.decrypt_path_secret(suite, ratchet_tree, encryption_priv_tree, update_path, sender_leaf_index, receiver_leaf_index, group_context, leaves_to_remove = [])
|
|
239
|
-
receiver_node_index = receiver_leaf_index * 2
|
|
240
|
-
|
|
241
|
-
filtered_direct_path = Melos::Tree.filtered_direct_path(ratchet_tree, sender_leaf_index * 2)
|
|
242
|
-
# puts "filtered direct path: #{filtered_direct_path}"
|
|
243
|
-
raise ArgumentError.new('malformed update path') unless filtered_direct_path.count == update_path.nodes.count
|
|
244
|
-
overlap_node = Melos::Tree.overlap_with_filtered_direct_path(receiver_node_index, filtered_direct_path, Melos::Tree.n_leaves(ratchet_tree))
|
|
245
|
-
# puts "overlap node: #{overlap_node}"
|
|
246
|
-
overlap_index = filtered_direct_path.find_index { _1 == overlap_node }
|
|
247
|
-
overlap_node_index = filtered_direct_path[overlap_index]
|
|
248
|
-
# puts "overlap index: #{overlap_index}"
|
|
249
|
-
copath_node_index = Melos::Tree.copath_nodes_of_filtered_direct_path(ratchet_tree, sender_leaf_index * 2)[overlap_index]
|
|
250
|
-
# puts "copath node: #{copath_node_index}"
|
|
251
|
-
resolution_of_copath_node = Melos::Tree.resolution(ratchet_tree, copath_node_index)
|
|
252
|
-
resolution_of_copath_node = resolution_of_copath_node - leaves_to_remove.map{ _1 * 2 } # leaf index -> node index
|
|
253
|
-
# puts "resolution: #{resolution_of_copath_node}"
|
|
254
|
-
|
|
255
|
-
priv_key = nil
|
|
256
|
-
priv_index = nil
|
|
257
|
-
resolution_of_copath_node.each_with_index do |res, idx|
|
|
258
|
-
if encryption_priv_tree[res]
|
|
259
|
-
priv_key = encryption_priv_tree[res]
|
|
260
|
-
priv_index = idx
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
if priv_key.nil?
|
|
265
|
-
raise ArgumentError.new("priv key not found in tree (sender leaf index: #{sender_leaf_index})")
|
|
266
|
-
end
|
|
267
|
-
target_update_path_node = update_path.nodes[overlap_index]
|
|
268
|
-
target_encrypted_path_secret = target_update_path_node.encrypted_path_secret[priv_index]
|
|
269
|
-
raise ArgumentError.new('# of resolution of copath node does not match with # of encrypted path secrets') unless target_update_path_node.encrypted_path_secret.count == resolution_of_copath_node.count
|
|
270
|
-
|
|
271
|
-
# puts "priv_key: #{to_hex priv_key}"
|
|
272
|
-
path_secret = Melos::Crypto.decrypt_with_label(suite, priv_key, "UpdatePathNode", group_context.raw, target_encrypted_path_secret.kem_output, target_encrypted_path_secret.ciphertext)
|
|
273
|
-
|
|
274
|
-
# place private keys based on path secret
|
|
275
|
-
update_encryption_priv_tree(suite, ratchet_tree, encryption_priv_tree, overlap_node_index, path_secret)
|
|
276
|
-
|
|
277
|
-
path_secret
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def self.update_encryption_priv_tree(suite, ratchet_tree, encryption_priv_tree, start, path_secret)
|
|
281
|
-
filtered_direct_path = Melos::Tree.filtered_direct_path(ratchet_tree, start)
|
|
282
|
-
secret = path_secret
|
|
283
|
-
filtered_direct_path = [start] + filtered_direct_path
|
|
284
|
-
|
|
285
|
-
filtered_direct_path.each do |n_i|
|
|
286
|
-
node_secret = Melos::Crypto.derive_secret(suite, secret, "node")
|
|
287
|
-
encryption_priv, encryption_pub = Melos::Crypto.derive_key_pair(suite, node_secret)
|
|
288
|
-
encryption_priv_tree[n_i] = encryption_priv
|
|
289
|
-
# puts "n_i #{n_i}: ps: #{to_hex secret} -> ns: #{to_hex node_secret} -> pub: #{to_hex encryption_pub}"
|
|
290
|
-
# p Melos::Crypto.encapsulation_key_pair_corresponds?(suite, encryption_priv, ratchet_tree[n_i].public_encryption_key)
|
|
291
|
-
secret = Melos::Crypto.derive_secret(suite, secret, "path")
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
def self.calculate_commit_secret(suite, ratchet_tree, update_path, sender_leaf_index, receiver_leaf_index, path_secret)
|
|
296
|
-
receiver_node_index = receiver_leaf_index * 2
|
|
297
|
-
|
|
298
|
-
filtered_direct_path = Melos::Tree.filtered_direct_path(ratchet_tree, sender_leaf_index * 2)
|
|
299
|
-
raise ArgumentError.new('malformed update path') unless filtered_direct_path.count == update_path.nodes.count
|
|
300
|
-
overlap_node = Melos::Tree.overlap_with_filtered_direct_path(receiver_node_index, filtered_direct_path, Melos::Tree.n_leaves(ratchet_tree))
|
|
301
|
-
overlap_index = filtered_direct_path.find_index { _1 == overlap_node}
|
|
302
|
-
|
|
303
|
-
path_secret_n = path_secret
|
|
304
|
-
index = overlap_index
|
|
305
|
-
while filtered_direct_path[index] != Melos::Tree.root(Melos::Tree.n_leaves(ratchet_tree))
|
|
306
|
-
path_secret_n = Melos::Crypto.derive_secret(suite, path_secret_n, "path")
|
|
307
|
-
index += 1
|
|
308
|
-
end
|
|
309
|
-
Melos::Crypto.derive_secret(suite, path_secret_n, "path") # commit secret is node's path_secret +1
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
# just a test function
|
|
313
|
-
def self.dump_tree(tree)
|
|
314
|
-
tree.each_with_index do |node, index|
|
|
315
|
-
if node.nil?
|
|
316
|
-
puts "#{index}, nil"
|
|
317
|
-
elsif node.parent_node
|
|
318
|
-
puts "#{index}, PN (#{Melos::Util.to_hex(node.public_encryption_key)[0, 8]}) - #{node.parent_node.unmerged_leaves}"
|
|
319
|
-
else
|
|
320
|
-
puts "#{index}, LN (#{Melos::Util.to_hex(node.public_encryption_key)[0, 8]})"
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|