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.
data/lib/melos/tree.rb CHANGED
@@ -1,83 +1,7 @@
1
+ # Tree math over node indexes in the array representation of a left-balanced binary tree (RFC 9420 Appendix C).
2
+ # Functions that depend on the tree's contents live on Melos::RatchetTree.
1
3
  class Melos::Tree
2
- attr_accessor :array, :leaf_count
3
- # attr_reader @array
4
-
5
- def initialize
6
- @array = []
7
- @leaf_count = 0
8
- end
9
-
10
- def self.empty_tree(n_leaves)
11
- instance = self.allocate
12
- instance.leaf_count = n_leaves
13
- instance.array = Array.new(node_width(n_leaves))
14
- instance
15
- end
16
-
17
- def add(val)
18
- raise ArgumentError.new('Cannot add nil element') if val.nil?
19
- if @leaf_count == 0
20
- # initialize tree with one node
21
- @array = [val]
22
- @leaf_count = 1
23
- else
24
- # find leftmost empty leaf
25
- extend_tree = true
26
- for k in 0 .. (@leaf_count - 1) do
27
- if @array[k * 2].nil?
28
- @array[k * 2] = val
29
- extend_tree = false
30
- break
31
- end
32
- end
33
- if extend_tree
34
- # if tree is full, extend
35
- @leaf_count = @leaf_count * 2
36
- for k in 0 .. @leaf_count - 2
37
- @array[@leaf_count + k] = nil
38
- end
39
- @array[@leaf_count] = val
40
- end
41
- end
42
- @array
43
- end
44
-
45
- def remove_leaf(leaf_idx)
46
- raise ArgumentError.new('Cannot remove from empty tree') if @leaf_count == 0
47
- remove_node(leaf_idx * 2)
48
- # then if rigbt half of the tree is empty, truncate tree
49
- # q: do we recursively shrink tree?
50
- right_tree_empty = true
51
- for i in 0 .. (@leaf_count / 2) - 1 do
52
- if !@array[@leaf_count + 2 * i].nil?
53
- right_tree_empty = false
54
- break
55
- end
56
- end
57
- if right_tree_empty
58
- @array = @array.first(@leaf_count - 1)
59
- @leaf_count = @leaf_count / 2
60
- end
61
- @array
62
- end
63
-
64
- def remove_node(node_idx)
65
- @array[node_idx] = nil
66
- end
67
-
68
- def root
69
- self.class.root(@leaf_count)
70
- end
71
-
72
- def leaf_at(leaf_index)
73
- @array[leaf_index * 2]
74
- end
75
-
76
4
  class << self
77
- def n_leaves(tree)
78
- (tree.size + 1) / 2
79
- end
80
-
81
5
  def log2(x)
82
6
  if x == 0
83
7
  return 0
@@ -184,7 +108,7 @@ class Melos::Tree
184
108
  raise ArgumentError.new('Failed to find common ancestor')
185
109
  end
186
110
 
187
- dxy.min { level(_1) }
111
+ dxy.min_by { level(_1) }
188
112
  end
189
113
 
190
114
  def overlap_with_filtered_direct_path(x, filtered_direct_path, n)
@@ -195,53 +119,13 @@ class Melos::Tree
195
119
  raise ArgumentError.new('Failed to find overlap')
196
120
  end
197
121
 
198
- dxf.min { level(_1) }
122
+ dxf.min_by { level(_1) }
199
123
  end
200
124
 
201
125
  def leaf?(node_index)
202
126
  node_index % 2 == 0
203
127
  end
204
128
 
205
- def truncate!(tree)
206
- root = root(n_leaves(tree))
207
- if tree[(root + 1)..]&.all?(nil)
208
- tree.slice!(root..) # keep left half of tree
209
- truncate!(tree) # then attempt to truncate again
210
- end
211
- # right half of tree has an element, so finish
212
- end
213
-
214
- def filtered_direct_path(tree, node_index)
215
- n_l = n_leaves(tree)
216
- direct_path(node_index, n_l).reject { resolution(tree, sibling_from_leaf(node_index, _1, n_l)) == [] }
217
- end
218
-
219
- def copath_nodes_of_filtered_direct_path(tree, node_index)
220
- filtered_direct_path(tree, node_index).map do |a|
221
- sibling_from_leaf(node_index, a, n_leaves(tree))
222
- end
223
- end
224
-
225
- def resolution(tree, node_index)
226
- node = tree[node_index]
227
- if node.nil?
228
- if Melos::Tree.leaf?(node_index)
229
- # The resolution of a blank leaf node is the empty list.
230
- []
231
- else
232
- # The resolution of a blank intermediate node is the result of concatenating the resolution of its left child with the resolution of its right child, in that order.
233
- resolution(tree, Melos::Tree.left(node_index)) + resolution(tree, Melos::Tree.right(node_index))
234
- end
235
- else
236
- # The resolution of a non-blank node comprises the node itself, followed by its list of unmerged leaves, if any.
237
- if node.parent_node
238
- [node_index] + node.parent_node.unmerged_leaves.map { _1 * 2} # convert leaf index to node index
239
- else
240
- [node_index]
241
- end
242
- end
243
- end
244
-
245
129
  # def common_ancestor_direct(x, y)
246
130
  # lx = level(x) + 1
247
131
  # ly = level(y) + 1
data/lib/melos/vec.rb CHANGED
@@ -2,6 +2,7 @@ module Melos::Vec
2
2
  extend self
3
3
 
4
4
  def read_varint(data)
5
+ raise ArgumentError.new('variable-length vector is truncated') if data.nil? || data.empty?
5
6
  byte = 0
6
7
  v = data[byte].ord
7
8
  prefix = v >> 6
@@ -10,6 +11,7 @@ module Melos::Vec
10
11
  v = v & 0x3f
11
12
  (length - 1).times do
12
13
  byte += 1
14
+ raise ArgumentError.new('variable-length vector is truncated') if data[byte].nil?
13
15
  v = (v << 8) + data[byte].ord
14
16
  end
15
17
 
@@ -36,54 +38,37 @@ module Melos::Vec
36
38
  header.pack('C*')
37
39
  end
38
40
 
39
- def from_string(str) # = to_vec
41
+ # Adds variable length header to string, thus making it a variable length vector
42
+ def string_to_vec(str)
40
43
  write_varint(str.bytesize) + str
41
44
  end
42
45
 
43
- def parse_vec(vec_as_string)
44
- prefix = vec_as_string[0].ord >> 6
45
- length = read_varint(vec_as_string)
46
- case prefix
47
- when 0
48
- str = vec_as_string.byteslice(1, length)
49
- rest = vec_as_string.byteslice((1 + length)..)
50
- when 1
51
- str = vec_as_string.byteslice(2, length)
52
- rest = vec_as_string.byteslice((2 + length)..)
53
- when 2
54
- str = vec_as_string[4, length]
55
- rest = vec_as_string[(4 + length)..]
56
- else
57
- raise ArgumentError.new('invalid header')
58
- end
59
-
60
- [str, rest]
61
- end
62
-
63
- def parse_stringio(vec_as_stringio)
64
- prefix = prefix_from_stringio(vec_as_stringio)
65
- length = length_from_stringio(vec_as_stringio)
66
- case prefix
67
- when 0..2
68
- vec_as_stringio.pos = (vec_as_stringio.pos + (2 ** prefix))
69
- str = vec_as_stringio.read(length)
70
- else
71
- raise ArgumentError.new('invalid header')
72
- end
73
-
46
+ # sig { params(stream: StringIO).returns(String) }
47
+ def parse_stream(stream)
48
+ prefix = prefix_from_stream(stream)
49
+ raise ArgumentError.new('invalid header') unless (0..2).include?(prefix) # 2.1.2: prefix 3 is invalid
50
+ header_length = 2 ** prefix
51
+ length = length_from_stream(stream)
52
+ # 2.1.2: the length MUST be encoded in the minimum number of bytes; a longer encoding is rejected
53
+ raise ArgumentError.new('variable-length header is not minimal') unless write_varint(length).bytesize == header_length
54
+ stream.pos += header_length
55
+ str = stream.read(length) || ''
56
+ raise ArgumentError.new('variable-length vector is truncated') if str.bytesize < length
74
57
  str
75
58
  end
76
59
 
77
60
  private
78
61
 
79
- def prefix_from_stringio(stringio)
62
+ def prefix_from_stream(stringio)
80
63
  pos = stringio.pos
81
- prefix_ = stringio.getbyte >> 6
64
+ first = stringio.getbyte
65
+ raise ArgumentError.new('variable-length vector is truncated') if first.nil?
66
+ prefix_ = first >> 6
82
67
  stringio.seek(pos)
83
68
  prefix_
84
69
  end
85
70
 
86
- def length_from_stringio(stringio)
71
+ def length_from_stream(stringio)
87
72
  pos = stringio.pos
88
73
  buf = stringio.read(4)
89
74
  stringio.seek(pos)
data/lib/melos/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Melos
4
- VERSION = "0.0.2"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/melos.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'stringio'
2
+
1
3
  module Melos
2
4
  MLS_VERSION = 0x01 # mls10
3
5
  end
@@ -7,9 +9,14 @@ require_relative 'melos/vec'
7
9
  require_relative 'melos/tree'
8
10
  require_relative 'melos/util'
9
11
  require_relative 'melos/constants'
10
- require_relative 'melos/key_schedule'
12
+ require_relative 'melos/epoch_secrets'
11
13
  require_relative 'melos/psk'
14
+ require_relative 'melos/group'
12
15
  require_relative 'melos/secret_tree'
16
+ require_relative 'melos/capabilities'
17
+ require_relative 'melos/key_package_bundle'
18
+ require_relative 'melos/client'
13
19
  require_relative 'melos/struct/base'
14
20
  require_relative 'melos/struct/structs'
15
- require_relative 'melos/struct/ratchet_tree'
21
+ require_relative 'melos/ratchet_tree'
22
+ require_relative 'melos/private_tree'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: melos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Kajiwara
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.3.1
18
+ version: '1.0'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.3.1
25
+ version: '1.0'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: minitest
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -53,14 +53,21 @@ files:
53
53
  - LICENSE.txt
54
54
  - README.md
55
55
  - Rakefile
56
+ - docs/validation.md
56
57
  - lib/melos.rb
58
+ - lib/melos/capabilities.rb
59
+ - lib/melos/client.rb
57
60
  - lib/melos/constants.rb
58
61
  - lib/melos/crypto.rb
59
- - lib/melos/key_schedule.rb
62
+ - lib/melos/epoch_secrets.rb
63
+ - lib/melos/group.rb
64
+ - lib/melos/group/validation.rb
65
+ - lib/melos/key_package_bundle.rb
66
+ - lib/melos/private_tree.rb
60
67
  - lib/melos/psk.rb
68
+ - lib/melos/ratchet_tree.rb
61
69
  - lib/melos/secret_tree.rb
62
70
  - lib/melos/struct/base.rb
63
- - lib/melos/struct/ratchet_tree.rb
64
71
  - lib/melos/struct/structs.rb
65
72
  - lib/melos/tree.rb
66
73
  - lib/melos/util.rb
@@ -103,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
110
  - !ruby/object:Gem::Version
104
111
  version: '0'
105
112
  requirements: []
106
- rubygems_version: 3.7.1
113
+ rubygems_version: 4.0.20
107
114
  specification_version: 4
108
115
  summary: Messaging Layer Security Protocol (RFC 9420) on Ruby
109
116
  test_files: []
@@ -1,62 +0,0 @@
1
- require_relative 'crypto'
2
-
3
- module Melos::KeySchedule
4
- extend self
5
-
6
- def joiner_secret(suite, init_secret, commit_secret, group_context)
7
- Melos::Crypto.expand_with_label(
8
- suite,
9
- Melos::Crypto.kdf_extract(suite, init_secret, commit_secret),
10
- "joiner",
11
- group_context.raw,
12
- suite.kdf.n_h
13
- )
14
- end
15
-
16
- def welcome_secret(suite, joiner_secret, psk_secret)
17
- Melos::Crypto.derive_secret(
18
- suite,
19
- Melos::Crypto.kdf_extract(suite, joiner_secret, psk_secret), # sometimes written as `member_secret`
20
- "welcome"
21
- )
22
- end
23
-
24
- def welcome_key_and_nonce(suite, joiner_secret, psk_secret)
25
- ws = welcome_secret(suite, joiner_secret, psk_secret)
26
- [
27
- Melos::Crypto.expand_with_label(suite, ws, "key", "", suite.hpke.n_k),
28
- Melos::Crypto.expand_with_label(suite, ws, "nonce", "", suite.hpke.n_n)
29
- ]
30
- end
31
-
32
- def epoch_secret(suite, joiner_secret, psk_secret, group_context)
33
- Melos::Crypto.expand_with_label(
34
- suite,
35
- Melos::Crypto.kdf_extract(suite, joiner_secret, psk_secret),
36
- "epoch",
37
- group_context.raw,
38
- suite.kdf.n_h
39
- )
40
- end
41
-
42
- # epoch-derived secrets, from Table 4 in Section 8
43
- # will be defined as something like:
44
- # Melos::KeySchedule.sender_data_secret(suite, epoch_secret)
45
- [
46
- ['sender data', 'sender_data_secret'],
47
- ['encryption', 'encryption_secret'],
48
- ['exporter', 'exporter_secret'],
49
- ['external', 'external_secret'],
50
- ['confirm', 'confirmation_key'],
51
- ['membership', 'membership_key'],
52
- ['resumption', 'resumption_psk'],
53
- ['authentication', 'epoch_authenticator'],
54
- ['init', 'init_secret'] # this is not part of the table but is defined in the same way
55
- ].each do |tuple|
56
- label = tuple[0]
57
- name = tuple[1]
58
- define_method(name.to_sym, ->(suite, epoch_secret){
59
- Melos::Crypto.derive_secret(suite, epoch_secret, label)
60
- })
61
- end
62
- end