melos 0.0.1

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.
@@ -0,0 +1,1019 @@
1
+ require_relative '../vec'
2
+ require_relative 'base'
3
+ require 'securerandom'
4
+
5
+ class Melos::Struct::EncryptContext < Melos::Struct::Base
6
+ attr_reader :label, :context
7
+ STRUCT = [
8
+ [:label, :vec],
9
+ [:context, :vec]
10
+ ]
11
+ end
12
+
13
+ class Melos::Struct::RefHashInput < Melos::Struct::Base
14
+ attr_reader :label, :value
15
+ STRUCT = [
16
+ [:label, :vec],
17
+ [:value, :vec]
18
+ ]
19
+ end
20
+
21
+ # Section 5.3 Credentials
22
+ class Melos::Struct::Certificate < Melos::Struct::Base
23
+ attr_reader :cert_data
24
+ STRUCT = [
25
+ [:cert_data, :vec]
26
+ ]
27
+ end
28
+
29
+ class Melos::Struct::Credential < Melos::Struct::Base
30
+ attr_reader :credential_type, :identity, :certificates
31
+ STRUCT = [
32
+ [:credential_type, :uint16],
33
+ [:identity, :select, ->(ctx){ctx[:credential_type] == Melos::Constants::CredentialType::BASIC}, :vec],
34
+ [:certificates, :select, ->(ctx){ctx[:credential_type] == Melos::Constants::CredentialType::X509}, :classes, Melos::Struct::Certificate]
35
+ ]
36
+ end
37
+
38
+ ## 6.3.2
39
+
40
+ class Melos::Struct::SenderData < Melos::Struct::Base
41
+ attr_reader :leaf_index, :generation, :reuse_guard
42
+ STRUCT = [
43
+ [:leaf_index, :uint32],
44
+ [:generation, :uint32],
45
+ [:reuse_guard, :opaque, 4]
46
+ ]
47
+
48
+ def self.create(leaf_index:, generation:, reuse_guard:)
49
+ new_instance = self.allocate
50
+ new_instance.instance_variable_set(:@leaf_index, leaf_index)
51
+ new_instance.instance_variable_set(:@generation, generation)
52
+ new_instance.instance_variable_set(:@reuse_guard, reuse_guard)
53
+ new_instance
54
+ end
55
+ end
56
+
57
+ ## 7.1
58
+
59
+ class Melos::Struct::ParentNode < Melos::Struct::Base
60
+ attr_reader :encryption_key, :parent_hash, :unmerged_leaves
61
+ STRUCT = [
62
+ [:encryption_key, :vec], # HPKEPublicKey = opaque <V>
63
+ [:parent_hash, :vec],
64
+ [:unmerged_leaves, :vec_of_type, :uint32] # becomes a vec of uint32
65
+ ]
66
+
67
+ def self.create(encryption_key:, parent_hash:, unmerged_leaves:)
68
+ new_instance = self.allocate
69
+ new_instance.instance_variable_set(:@encryption_key, encryption_key)
70
+ new_instance.instance_variable_set(:@parent_hash, parent_hash)
71
+ new_instance.instance_variable_set(:@unmerged_leaves, unmerged_leaves)
72
+ new_instance
73
+ end
74
+ end
75
+
76
+ ## 7.2
77
+
78
+ class Melos::Struct::Capabilities < Melos::Struct::Base
79
+ attr_reader :versions, :cipher_suites, :extensions, :proposals, :credentials
80
+ STRUCT = [
81
+ [:versions, :vec], # vec of ProtocolVersion (uint16)
82
+ [:cipher_suites, :vec], # vec of CipherSuite (uint16)
83
+ [:extensions, :vec], # vec of ExtensionType (uint16)
84
+ [:proposals, :vec], # vec of ProposalTypes (uint16)
85
+ [:credentials, :vec] # vec of CredentialTypes (uint16)
86
+ ]
87
+ end
88
+
89
+ class Melos::Struct::Lifetime < Melos::Struct::Base
90
+ attr_reader :not_before, :not_after
91
+ STRUCT = [
92
+ [:not_before, :uint64],
93
+ [:not_after, :uint64]
94
+ ]
95
+ end
96
+
97
+ class Melos::Struct::Extension < Melos::Struct::Base
98
+ attr_reader :extension_type, :extension_data
99
+ STRUCT = [
100
+ [:extension_type, :uint16], # ExtensionType = uint16
101
+ [:extension_data, :vec]
102
+ ]
103
+ end
104
+
105
+ class Melos::Struct::LeafNode < Melos::Struct::Base
106
+ attr_reader :encryption_key, :signature_key, :credential, :capabilities, :leaf_node_source, :lifetime, :parent_hash, :extensions, :signature
107
+ STRUCT = [
108
+ [:encryption_key, :vec], # HPKEPublicKey = opaque <V>
109
+ [:signature_key, :vec], # SignaturePublicKey = opaque <V>
110
+ [:credential, :class, Melos::Struct::Credential],
111
+ [:capabilities, :class, Melos::Struct::Capabilities],
112
+ [:leaf_node_source, :uint8], # LeafNodeSource = enum of uint8,
113
+ [:lifetime, :select, ->(ctx){ctx[:leaf_node_source] == Melos::Constants::LeafNodeSource::KEY_PACKAGE}, :class, Melos::Struct::Lifetime],
114
+ [:parent_hash, :select, ->(ctx){ctx[:leaf_node_source] == Melos::Constants::LeafNodeSource::COMMIT}, :vec],
115
+ [:extensions, :classes, Melos::Struct::Extension],
116
+ [:signature, :vec]
117
+ ]
118
+
119
+ def leaf_node_tbs(group_id, leaf_index)
120
+ buf = ''
121
+ buf += Melos::Vec.from_string(encryption_key)
122
+ buf += Melos::Vec.from_string(signature_key)
123
+ buf += credential.raw
124
+ buf += capabilities.raw
125
+ buf += [leaf_node_source].pack('C')
126
+ if leaf_node_source == Melos::Constants::LeafNodeSource::KEY_PACKAGE
127
+ buf += lifetime.raw
128
+ elsif leaf_node_source == Melos::Constants::LeafNodeSource::COMMIT
129
+ buf += Melos::Vec.from_string(parent_hash)
130
+ end
131
+ buf += Melos::Vec.from_string(extensions.map(&:raw).join)
132
+ if leaf_node_source == Melos::Constants::LeafNodeSource::UPDATE || leaf_node_source == Melos::Constants::LeafNodeSource::COMMIT
133
+ buf += Melos::Vec.from_string(group_id)
134
+ buf += [leaf_index].pack('L>') # uint32
135
+ end
136
+ buf
137
+ end
138
+
139
+ def self.create(
140
+ encryption_key:, signature_key:, credential:, capabilities:,
141
+ leaf_node_source:, lifetime:, parent_hash:, extensions:, signature:
142
+ )
143
+ new_instance = self.allocate
144
+ new_instance.instance_variable_set(:@encryption_key, encryption_key)
145
+ new_instance.instance_variable_set(:@signature_key, signature_key)
146
+ new_instance.instance_variable_set(:@credential, credential)
147
+ new_instance.instance_variable_set(:@capabilities, capabilities)
148
+ new_instance.instance_variable_set(:@leaf_node_source, leaf_node_source)
149
+ new_instance.instance_variable_set(:@lifetime, lifetime)
150
+ new_instance.instance_variable_set(:@parent_hash, parent_hash)
151
+ new_instance.instance_variable_set(:@extensions, extensions)
152
+ new_instance.instance_variable_set(:@signature, signature)
153
+ new_instance
154
+ end
155
+
156
+ def sign(suite, signature_private_key, group_id, leaf_index)
157
+ @signature = Melos::Crypto.sign_with_label(suite, signature_private_key, "LeafNodeTBS", leaf_node_tbs(group_id, leaf_index))
158
+ end
159
+
160
+ def verify(suite, group_id, leaf_index)
161
+ Melos::Crypto.verify_with_label(suite, signature_key, "LeafNodeTBS", leaf_node_tbs(group_id, leaf_index), signature)
162
+ end
163
+ end
164
+
165
+ ## 7.6
166
+
167
+ class Melos::Struct::HPKECipherText < Melos::Struct::Base
168
+ attr_reader :kem_output, :ciphertext
169
+ STRUCT = [
170
+ [:kem_output, :vec],
171
+ [:ciphertext, :vec]
172
+ ]
173
+
174
+ def self.create(kem_output:, ciphertext:)
175
+ new_instance = self.allocate
176
+ new_instance.instance_variable_set(:@kem_output, kem_output)
177
+ new_instance.instance_variable_set(:@ciphertext, ciphertext)
178
+ new_instance
179
+ end
180
+ end
181
+
182
+ class Melos::Struct::UpdatePathNode < Melos::Struct::Base
183
+ attr_reader :encryption_key, :encrypted_path_secret
184
+ STRUCT = [
185
+ [:encryption_key, :vec], # HPKEPublicKey = opaque <V>
186
+ [:encrypted_path_secret, :classes, Melos::Struct::HPKECipherText]
187
+ ]
188
+
189
+ def self.create(encryption_key:, encrypted_path_secret:)
190
+ new_instance = self.allocate
191
+ new_instance.instance_variable_set(:@encryption_key, encryption_key)
192
+ new_instance.instance_variable_set(:@encrypted_path_secret, encrypted_path_secret)
193
+ new_instance
194
+ end
195
+ end
196
+
197
+ class Melos::Struct::UpdatePath < Melos::Struct::Base
198
+ attr_reader :leaf_node, :nodes
199
+ STRUCT = [
200
+ [:leaf_node, :class, Melos::Struct::LeafNode],
201
+ [:nodes, :classes, Melos::Struct::UpdatePathNode]
202
+ ]
203
+
204
+ def self.create(leaf_node:, nodes:)
205
+ new_instance = self.allocate
206
+ new_instance.instance_variable_set(:@leaf_node, leaf_node)
207
+ new_instance.instance_variable_set(:@nodes, nodes)
208
+ new_instance
209
+ end
210
+ end
211
+
212
+ ## 7.8
213
+
214
+ ## NodeType: uint8 enum
215
+
216
+ class Melos::Struct::LeafNodeHashInput < Melos::Struct::Base
217
+ attr_reader :leaf_index, :leaf_node
218
+ STRUCT = [
219
+ [:leaf_index, :uint32],
220
+ [:leaf_node, :optional, Melos::Struct::LeafNode]
221
+ ]
222
+ end
223
+
224
+ class Melos::Struct::ParentNodeHashInput < Melos::Struct::Base
225
+ attr_reader :parent_node, :left_hash, :right_hash
226
+ STRUCT = [
227
+ [:parent_node, :optional, Melos::Struct::ParentNode],
228
+ [:left_hash, :vec],
229
+ [:right_hash, :vec]
230
+ ]
231
+ end
232
+
233
+ ## 7.9
234
+
235
+ class Melos::Struct::ParentHashInput < Melos::Struct::Base
236
+ attr_reader :encryption_key, :parent_hash, :original_sibling_tree_hash
237
+ STRUCT = [
238
+ [:encryption_key, :vec], # HPKEPublicKey
239
+ [:parent_hash, :vec],
240
+ [:original_sibling_tree_hash, :vec]
241
+ ]
242
+ end
243
+
244
+ class Melos::Struct::TreeHashInput < Melos::Struct::Base
245
+ attr_reader :node_type, :leaf_node, :parent_node
246
+ STRUCT = [
247
+ [:node_type, :uint8],
248
+ [:leaf_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::LEAF}, :class, Melos::Struct::LeafNodeHashInput],
249
+ [:parent_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::PARENT}, :class, Melos::Struct::ParentNodeHashInput],
250
+ ]
251
+ end
252
+
253
+ ## 8
254
+
255
+ class Melos::Struct::KDFLabel < Melos::Struct::Base
256
+ attr_reader :length, :label, :context
257
+ STRUCT = [
258
+ [:length, :uint16],
259
+ [:label, :vec],
260
+ [:context, :vec]
261
+ ]
262
+ end
263
+
264
+ ## 8.1
265
+
266
+ class Melos::Struct::GroupContext < Melos::Struct::Base
267
+ attr_reader :version, :cipher_suite, :group_id, :epoch, :tree_hash, :confirmed_transcript_hash
268
+ attr_accessor :extensions
269
+ STRUCT = [
270
+ [:version, :uint16],
271
+ [:cipher_suite, :uint16],
272
+ [:group_id, :vec],
273
+ [:epoch, :uint64],
274
+ [:tree_hash, :vec],
275
+ [:confirmed_transcript_hash, :vec],
276
+ [:extensions, :classes, Melos::Struct::Extension]
277
+ ]
278
+
279
+ def self.create(cipher_suite:, group_id:, epoch:, tree_hash:, confirmed_transcript_hash:, extensions:)
280
+ new_instance = self.allocate
281
+ new_instance.instance_variable_set(:@version, 1)
282
+ new_instance.instance_variable_set(:@cipher_suite, cipher_suite)
283
+ new_instance.instance_variable_set(:@group_id, group_id)
284
+ new_instance.instance_variable_set(:@epoch, epoch)
285
+ new_instance.instance_variable_set(:@tree_hash, tree_hash)
286
+ new_instance.instance_variable_set(:@confirmed_transcript_hash, confirmed_transcript_hash)
287
+ new_instance.instance_variable_set(:@extensions, extensions)
288
+ new_instance
289
+ end
290
+ end
291
+
292
+ ## 8.4
293
+
294
+ class Melos::Struct::PreSharedKeyID < Melos::Struct::Base
295
+ attr_reader :psktype, :psk_id, :usage, :psk_group_id, :psk_epoch, :psk_nonce
296
+ STRUCT = [
297
+ [:psktype, :uint8],
298
+ [:psk_id, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::EXTERNAL}, :vec], # external
299
+ [:usage, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::RESUMPTION}, :uint8], # resumption
300
+ [:psk_group_id, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::RESUMPTION}, :vec], # resumption
301
+ [:psk_epoch, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::RESUMPTION}, :uint64], # resumption
302
+ [:psk_nonce, :vec]
303
+ ]
304
+
305
+ def self.create_external(psk_id:, psk_nonce:)
306
+ new_instance = self.allocate
307
+ new_instance.instance_variable_set(:@psktype, Melos::Constants::PSKType::EXTERNAL)
308
+ new_instance.instance_variable_set(:@psk_id, psk_id)
309
+ new_instance.instance_variable_set(:@psk_nonce, psk_nonce)
310
+ new_instance
311
+ end
312
+ end
313
+
314
+ class Melos::Struct::PSKLabel < Melos::Struct::Base
315
+ attr_reader :id, :index, :count
316
+ STRUCT = [
317
+ [:id, :class, Melos::Struct::PreSharedKeyID],
318
+ [:index, :uint16],
319
+ [:count, :uint16]
320
+ ]
321
+
322
+ def self.create(id:, index:, count:)
323
+ new_instance = self.allocate
324
+ new_instance.instance_variable_set(:@id, id)
325
+ new_instance.instance_variable_set(:@index, index)
326
+ new_instance.instance_variable_set(:@count, count)
327
+ new_instance
328
+ end
329
+ end
330
+
331
+ ## 10
332
+
333
+ class Melos::Struct::KeyPackage < Melos::Struct::Base
334
+ attr_reader :version, :cipher_suite, :init_key, :leaf_node, :extensions, :signature
335
+ STRUCT = [
336
+ [:version, :uint16],
337
+ [:cipher_suite, :uint16],
338
+ [:init_key, :vec], # HPKEPublicKey
339
+ [:leaf_node, :class, Melos::Struct::LeafNode],
340
+ [:extensions, :classes, Melos::Struct::Extension],
341
+ [:signature, :vec] # SignWithLabel(., "KeyPackageTBS", KeyPackageTBS)
342
+ ]
343
+
344
+ def ref(suite)
345
+ Melos::Crypto.make_keypackage_ref(suite, self.raw)
346
+ end
347
+ end
348
+
349
+ class Melos::Struct::KeyPackageTBS < Melos::Struct::Base
350
+ attr_reader :version, :cipher_suite, :init_key, :leaf_node, :extensions
351
+ STRUCT = [
352
+ [:version, :uint16],
353
+ [:cipher_suite, :uint16],
354
+ [:init_key, :vec], # HPKEPublicKey
355
+ [:leaf_node, :class, Melos::Struct::LeafNode],
356
+ [:extensions, :classes, Melos::Struct::Extension]
357
+ ]
358
+ end
359
+
360
+ ## 11
361
+
362
+ class Melos::Struct::RequiredCapabilities < Melos::Struct::Base
363
+ attr_reader :extension_types, :proposal_types, :credential_types
364
+ STRUCT = [
365
+ [:extension_types, :vec], # vec of uint16
366
+ [:proposal_types, :vec], # vec of uint16
367
+ [:credential_types, :vec] # vec of uint16
368
+ ]
369
+ end
370
+
371
+ ## 12
372
+
373
+ ## 12.1.1 - 12.1.7
374
+
375
+ class Melos::Struct::Add < Melos::Struct::Base
376
+ attr_reader :key_package
377
+ STRUCT = [
378
+ [:key_package, :class, Melos::Struct::KeyPackage]
379
+ ]
380
+ end
381
+
382
+ class Melos::Struct::Update < Melos::Struct::Base
383
+ attr_reader :leaf_node
384
+ STRUCT = [
385
+ [:leaf_node, :class, Melos::Struct::LeafNode]
386
+ ]
387
+ end
388
+
389
+ class Melos::Struct::Remove < Melos::Struct::Base
390
+ attr_reader :removed
391
+ STRUCT = [
392
+ [:removed, :uint32]
393
+ ]
394
+ end
395
+
396
+ class Melos::Struct::PreSharedKey < Melos::Struct::Base
397
+ attr_reader :psk
398
+ STRUCT = [
399
+ [:psk, :class, Melos::Struct::PreSharedKeyID]
400
+ ]
401
+ end
402
+
403
+ class Melos::Struct::ReInit < Melos::Struct::Base
404
+ attr_reader :group_id, :version, :cipher_suite, :extensions
405
+ STRUCT = [
406
+ [:group_id, :vec],
407
+ [:version, :uint16],
408
+ [:cipher_suite, :uint16],
409
+ [:extensions, :classes, Melos::Struct::Extension]
410
+ ]
411
+ end
412
+
413
+ class Melos::Struct::ExternalInit < Melos::Struct::Base
414
+ attr_reader :kem_output
415
+ STRUCT = [
416
+ [:kem_output, :vec]
417
+ ]
418
+ end
419
+
420
+ class Melos::Struct::GroupContextExtensions < Melos::Struct::Base
421
+ attr_reader :extensions
422
+ STRUCT = [
423
+ [:extensions, :classes, Melos::Struct::Extension]
424
+ ]
425
+ end
426
+
427
+ ## 12.1.8.1
428
+
429
+ class Melos::Struct::ExternalSender < Melos::Struct::Base
430
+ attr_reader :signature_key, :credential
431
+ STRUCT = [
432
+ [:signature_key, :vec],
433
+ [:credential, :class, Melos::Struct::Credential]
434
+ ]
435
+ end
436
+
437
+ ## 12.1
438
+
439
+ class Melos::Struct::Proposal < Melos::Struct::Base
440
+ attr_reader :proposal_type, :add, :update, :remove, :psk, :reinit, :external_init, :group_context_extensions
441
+ STRUCT = [
442
+ [:proposal_type, :uint16],
443
+ [:add, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::ADD}, :class, Melos::Struct::Add],
444
+ [:update, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::UPDATE}, :class, Melos::Struct::Update],
445
+ [:remove, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::REMOVE}, :class, Melos::Struct::Remove],
446
+ [:psk, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::PSK}, :class, Melos::Struct::PreSharedKey],
447
+ [:reinit, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::REINIT}, :class, Melos::Struct::ReInit],
448
+ [:external_init, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::EXTERNAL_INIT}, :class, Melos::Struct::ExternalInit],
449
+ [:group_context_extensions, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::GROUP_CONTEXT_EXTENSIONS}, :class, Melos::Struct::GroupContextExtensions],
450
+ ]
451
+
452
+ def proposal_content
453
+ @add || @update || @remove || @psk || @reinit || @external_init || @group_context_extensions
454
+ end
455
+ end
456
+
457
+ ## 12.4
458
+
459
+ class Melos::Struct::ProposalOrRef < Melos::Struct::Base
460
+ attr_reader :type, :proposal, :reference
461
+ STRUCT = [
462
+ [:type, :uint8],
463
+ [:proposal, :select, ->(ctx){ctx[:type] == Melos::Constants::ProposalOrRefType::PROPOSAL}, :class, Melos::Struct::Proposal],
464
+ [:reference, :select, ->(ctx){ctx[:type] == Melos::Constants::ProposalOrRefType::REFERENCE}, :vec] # ProposalRef is a HashReference, which is a :vec
465
+ ]
466
+ end
467
+
468
+ class Melos::Struct::Commit < Melos::Struct::Base
469
+ attr_reader :proposals, :path
470
+ STRUCT = [
471
+ [:proposals, :classes, Melos::Struct::ProposalOrRef],
472
+ [:path, :optional, Melos::Struct::UpdatePath]
473
+ ]
474
+ end
475
+
476
+ ## 12.4.3
477
+
478
+ class Melos::Struct::GroupInfo < Melos::Struct::Base
479
+ attr_reader :group_context, :extensions, :confirmation_tag, :signer, :signature
480
+ STRUCT = [
481
+ [:group_context, :class, Melos::Struct::GroupContext],
482
+ [:extensions, :classes, Melos::Struct::Extension],
483
+ [:confirmation_tag, :vec], # MAC = opaque <V>
484
+ [:signer, :uint32],
485
+ [:signature, :vec]
486
+ ]
487
+
488
+ def group_info_tbs
489
+ Melos::Struct::GroupInfoTBS.create(
490
+ group_context:,
491
+ extensions:,
492
+ confirmation_tag:,
493
+ signer:
494
+ )
495
+ end
496
+
497
+ def sign(suite, signer_private)
498
+ Melos::Crypto.sign_with_label(suite, signer_private_key, "GroupInfoTBS", group_info_tbs.raw)
499
+ end
500
+
501
+ def verify(suite, signer_public_key)
502
+ Melos::Crypto.verify_with_label(suite, signer_public_key, "GroupInfoTBS", group_info_tbs.raw, signature)
503
+ end
504
+ end
505
+
506
+
507
+ class Melos::Struct::GroupInfoTBS < Melos::Struct::Base
508
+ attr_reader :group_context, :extensions, :confirmation_tag, :signer, :signature
509
+ STRUCT = [
510
+ [:group_context, :class, Melos::Struct::GroupContext],
511
+ [:extensions, :classes, Melos::Struct::Extension],
512
+ [:confirmation_tag, :vec], # MAC = opaque <V>
513
+ [:signer, :uint32]
514
+ ]
515
+
516
+ def self.create(group_context:, extensions:, confirmation_tag:, signer:)
517
+ new_instance = self.allocate
518
+ new_instance.instance_variable_set(:@group_context, group_context)
519
+ new_instance.instance_variable_set(:@extensions, extensions)
520
+ new_instance.instance_variable_set(:@confirmation_tag, confirmation_tag)
521
+ new_instance.instance_variable_set(:@signer, signer)
522
+ new_instance
523
+ end
524
+ end
525
+
526
+ # 12.4.3.1
527
+
528
+ class Melos::Struct::PathSecret < Melos::Struct::Base
529
+ attr_reader :path_secret
530
+ STRUCT = [
531
+ [:path_secret, :vec]
532
+ ]
533
+ end
534
+
535
+ class Melos::Struct::GroupSecrets < Melos::Struct::Base
536
+ attr_reader :joiner_secret, :path_secret, :psks
537
+ STRUCT = [
538
+ [:joiner_secret, :vec],
539
+ [:path_secret, :optional, Melos::Struct::PathSecret],
540
+ [:psks, :classes, Melos::Struct::PreSharedKeyID]
541
+ ]
542
+ end
543
+
544
+ class Melos::Struct::EncryptedGroupSecrets < Melos::Struct::Base
545
+ attr_reader :new_member, :encrypted_group_secrets
546
+ STRUCT = [
547
+ [:new_member, :vec], # KeyPackageRef = opaque <V>
548
+ [:encrypted_group_secrets, :class, Melos::Struct::HPKECipherText]
549
+ ]
550
+ end
551
+
552
+ class Melos::Struct::Welcome < Melos::Struct::Base
553
+ attr_reader :cipher_suite, :secrets, :encrypted_group_info
554
+ STRUCT = [
555
+ [:cipher_suite, :uint16],
556
+ [:secrets, :classes, Melos::Struct::EncryptedGroupSecrets],
557
+ [:encrypted_group_info, :vec]
558
+ ]
559
+ end
560
+
561
+ ## 12.4.3.2
562
+
563
+ class Melos::Struct::ExternalPub < Melos::Struct::Base
564
+ attr_reader :external_pub
565
+ STRUCT = [
566
+ [:external_pub, :vec] # HPKEPublicKey
567
+ ]
568
+ end
569
+
570
+ class Melos::Struct::Node < Melos::Struct::Base
571
+ attr_reader :node_type, :leaf_node, :parent_node
572
+ STRUCT = [
573
+ [:node_type, :uint8],
574
+ [:leaf_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::LEAF}, :class, Melos::Struct::LeafNode], # leaf
575
+ [:parent_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::PARENT}, :class, Melos::Struct::ParentNode] # parent
576
+ ]
577
+
578
+ def parent_hash_in_node
579
+ @leaf_node&.parent_hash || @parent_node.parent_hash
580
+ end
581
+
582
+ def public_encryption_key
583
+ @leaf_node&.encryption_key || @parent_node&.encryption_key
584
+ end
585
+
586
+ def self.new_leaf_node(leaf_node)
587
+ instance = self.allocate
588
+ instance.new_leaf_node_impl(leaf_node)
589
+ instance
590
+ end
591
+
592
+ def self.new_parent_node(parent_node)
593
+ instance = self.allocate
594
+ instance.new_parent_node_impl(parent_node)
595
+ instance
596
+ end
597
+
598
+ def new_leaf_node_impl(leaf_node)
599
+ @node_type = Melos::Constants::NodeType::LEAF
600
+ @leaf_node = leaf_node
601
+ end
602
+
603
+ def new_parent_node_impl(parent_node)
604
+ @node_type = Melos::Constants::NodeType::PARENT
605
+ @parent_node = parent_node
606
+ end
607
+ end
608
+
609
+ # Section 6.1
610
+ class Melos::Struct::Sender < Melos::Struct::Base
611
+ attr_reader :sender_type, :leaf_index, :sender_index
612
+ STRUCT = [
613
+ [:sender_type, :uint8],
614
+ [:leaf_index, :select, ->(ctx){ctx[:sender_type] == Melos::Constants::SenderType::MEMBER}, :uint32],
615
+ [:sender_index, :select, ->(ctx){ctx[:sender_type] == Melos::Constants::SenderType::EXTERNAL}, :uint32],
616
+ ]
617
+
618
+ def self.create_member(leaf_index)
619
+ instance = self.allocate
620
+ instance.instance_variable_set(:@sender_type, Melos::Constants::SenderType::MEMBER)
621
+ instance.instance_variable_set(:@leaf_index, leaf_index)
622
+ instance
623
+ end
624
+
625
+ def self.create_external(sender_index)
626
+ instance = self.allocate
627
+ instance.instance_variable_set(:@sender_type, Melos::Constants::SenderType::EXTERNAL)
628
+ instance.instance_variable_set(:@sender_index, sender_index)
629
+ instance
630
+ end
631
+ end
632
+
633
+ class Melos::Struct::FramedContentAuthData < Melos::Struct::Base
634
+ attr_reader :signature, :confirmation_tag, :content_type
635
+
636
+ STRUCT = [
637
+ [:signature, :vec]
638
+ ]
639
+
640
+ # initialize from stream
641
+ def self.new_and_rest_with_content_type(buf, content_type)
642
+ instance = self.allocate
643
+ context, buf = instance.send(:deserialize, buf)
644
+ # custom part based on instance variable
645
+ if content_type == Melos::Constants::ContentType::COMMIT # commit
646
+ # read MAC(opaque <V>) confirmation_tag
647
+ value, buf = Melos::Vec.parse_vec(buf)
648
+ context << [:confirmation_tag, value]
649
+ end
650
+ context << [:content_type, content_type]
651
+ instance.send(:set_instance_vars, context)
652
+ [instance, buf]
653
+ end
654
+
655
+ def raw
656
+ if @content_type == Melos::Constants::ContentType::COMMIT
657
+ Melos::Vec.from_string(@signature) + Melos::Vec.from_string(@confirmation_tag)
658
+ else
659
+ Melos::Vec.from_string(@signature)
660
+ end
661
+ end
662
+
663
+ def self.create(signature:, content_type:, confirmation_tag:)
664
+ instance = self.allocate
665
+ instance.instance_variable_set(:@signature, signature)
666
+ instance.instance_variable_set(:@content_type, content_type)
667
+ if content_type == Melos::Constants::ContentType::COMMIT
668
+ instance.instance_variable_set(:@confirmation_tag, confirmation_tag)
669
+ end
670
+ instance
671
+ end
672
+ end
673
+
674
+ class Melos::Struct::FramedContent < Melos::Struct::Base
675
+ attr_reader :group_id, :epoch, :sender, :authenticated_data, :content_type, :application_data, :proposal, :commit
676
+ STRUCT = [
677
+ [:group_id, :vec],
678
+ [:epoch, :uint64],
679
+ [:sender, :class, Melos::Struct::Sender],
680
+ [:authenticated_data, :vec],
681
+ [:content_type, :uint8],
682
+ [:application_data, :select, ->(context){context[:content_type] == Melos::Constants::ContentType::APPLICATION}, :vec],
683
+ [:proposal, :select, ->(context){context[:content_type] == Melos::Constants::ContentType::PROPOSAL}, :class, Melos::Struct::Proposal],
684
+ [:commit, :select, ->(context){context[:content_type] == Melos::Constants::ContentType::COMMIT}, :class, Melos::Struct::Commit]
685
+ ]
686
+
687
+ def content_tbs(version, wire_format, group_context)
688
+ buf = [version].pack('S>') + [wire_format].pack('S>') + self.raw
689
+ case sender.sender_type
690
+ when Melos::Constants::SenderType::MEMBER, Melos::Constants::SenderType::NEW_MEMBER_COMMIT
691
+ buf += group_context.raw
692
+ when Melos::Constants::SenderType::EXTERNAL, Melos::Constants::SenderType::NEW_MEMBER_PROPOSAL
693
+ # do nothing
694
+ end
695
+ buf
696
+ end
697
+
698
+ def self.create(group_id:, epoch:, sender:, authenticated_data:, content_type:, content:)
699
+ new_instance = self.allocate
700
+ new_instance.instance_variable_set(:@group_id, group_id)
701
+ new_instance.instance_variable_set(:@epoch, epoch)
702
+ new_instance.instance_variable_set(:@sender, sender)
703
+ new_instance.instance_variable_set(:@authenticated_data, authenticated_data)
704
+ new_instance.instance_variable_set(:@content_type, content_type)
705
+ case content_type
706
+ when Melos::Constants::ContentType::APPLICATION
707
+ new_instance.instance_variable_set(:@application_data, content)
708
+ when Melos::Constants::ContentType::PROPOSAL
709
+ new_instance.instance_variable_set(:@proposal, content)
710
+ when Melos::Constants::ContentType::COMMIT
711
+ new_instance.instance_variable_set(:@commit, content)
712
+ end
713
+ new_instance
714
+ end
715
+ end
716
+
717
+ class Melos::Struct::AuthenticatedContent < Melos::Struct::Base
718
+ attr_reader :wire_format, :content, :auth
719
+ STRUCT = [
720
+ [:wire_format, :uint16],
721
+ [:content, :class, Melos::Struct::FramedContent],
722
+ [:auth, :framed_content_auth_data]
723
+ ]
724
+
725
+ def content_tbm
726
+ content.content_tbs(Melos::Constants::Version::MLS10, wire_format) + auth.raw
727
+ end
728
+
729
+ def confirmed_transcript_hash_input
730
+ Melos::Struct::ConfirmedTranscriptHashInput.create(
731
+ wire_format: wire_format,
732
+ content: content,
733
+ signature: auth.signature
734
+ )
735
+ end
736
+
737
+ def verify(suite, signature_public_key, context)
738
+ return false if (wire_format == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE && content.content_type == Melos::Constants::ContentType::APPLICATION)
739
+
740
+ content_tbs = content.content_tbs(Melos::Constants::Version::MLS10, wire_format, context)
741
+
742
+ return Melos::Crypto.verify_with_label(suite, signature_public_key, "FramedContentTBS", content_tbs, auth.signature)
743
+ end
744
+
745
+ def self.create(wire_format:, content:, auth:)
746
+ new_instance = self.allocate
747
+ new_instance.instance_variable_set(:@wire_format, wire_format)
748
+ new_instance.instance_variable_set(:@content, content)
749
+ new_instance.instance_variable_set(:@auth, auth)
750
+ new_instance
751
+ end
752
+
753
+ # populate auth with values
754
+ def sign(suite, signature_private_key, group_context)
755
+ raise ArgumentError.new('Application data cannot be sent as a PublicMessage') if wire_format == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE && content.content_type == Melos::Constants::ContentType::APPLICATION
756
+ content_tbs = content.content_tbs(Melos::Constants::Version::MLS10, wire_format, group_context)
757
+ signature = Melos::Crypto.sign_with_label(suite, signature_private_key, "FramedContentTBS", content_tbs)
758
+ @auth = Melos::Struct::FramedContentAuthData.create(
759
+ signature: signature,
760
+ content_type: content.content_type,
761
+ confirmation_tag: nil
762
+ )
763
+ end
764
+ end
765
+
766
+ ## 6.2
767
+
768
+ class Melos::Struct::PublicMessage < Melos::Struct::Base
769
+ attr_reader :content, :auth, :membership_tag
770
+ STRUCT = [
771
+ [:content, :class, Melos::Struct::FramedContent],
772
+ [:auth, :framed_content_auth_data],
773
+ [:membership_tag, :select, ->(ctx){ctx[:content].sender.sender_type == Melos::Constants::SenderType::MEMBER}, :vec] # MAC is opaque <V>
774
+ ]
775
+
776
+ def self.protect(authenticated_content, suite, membership_key, group_context)
777
+ message = self.allocate
778
+ message.instance_variable_set(:@content, authenticated_content.content)
779
+ message.instance_variable_set(:@auth, authenticated_content.auth)
780
+ if message.content.sender.sender_type == Melos::Constants::SenderType::MEMBER # member
781
+ message.instance_variable_set(:@membership_tag, message.membership_mac(suite, membership_key, group_context))
782
+ end
783
+ message
784
+ end
785
+
786
+ def unprotect(suite, membership_key, group_context)
787
+ ## if sender type is member then membershipMac(suite, membership_key, group_context)
788
+ if (content.sender.sender_type == Melos::Constants::SenderType::MEMBER)
789
+ return nil if membership_tag != membership_mac(suite, membership_key, group_context)
790
+ end
791
+ Melos::Struct::AuthenticatedContent.create(
792
+ wire_format: Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE, # public_message
793
+ content: content,
794
+ auth: auth
795
+ )
796
+ end
797
+
798
+ def membership_mac(suite, membership_key, group_context)
799
+ authenticated_content_tbm = content.content_tbs(
800
+ Melos::Constants::Version::MLS10,
801
+ Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE,
802
+ group_context
803
+ ) + auth.raw
804
+ Melos::Crypto.mac(suite, membership_key, authenticated_content_tbm)
805
+ end
806
+ end
807
+
808
+ ## 6.3
809
+
810
+ class Melos::Struct::PrivateMessage < Melos::Struct::Base
811
+ attr_reader :group_id, :epoch, :content_type, :authenticated_data, :encrypted_sender_data, :ciphertext
812
+ STRUCT = [
813
+ [:group_id, :vec],
814
+ [:epoch, :uint64],
815
+ [:content_type, :uint8],
816
+ [:authenticated_data, :vec],
817
+ [:encrypted_sender_data, :vec],
818
+ [:ciphertext, :vec]
819
+ ]
820
+
821
+ def self.create(group_id:, epoch:, content_type:, authenticated_data:, encrypted_sender_data:, ciphertext:)
822
+ new_instance = self.allocate
823
+ new_instance.instance_variable_set(:@group_id, group_id)
824
+ new_instance.instance_variable_set(:@epoch, epoch)
825
+ new_instance.instance_variable_set(:@content_type, content_type)
826
+ new_instance.instance_variable_set(:@authenticated_data, authenticated_data)
827
+ new_instance.instance_variable_set(:@encrypted_sender_data, encrypted_sender_data)
828
+ new_instance.instance_variable_set(:@ciphertext, ciphertext)
829
+ new_instance
830
+ end
831
+
832
+ def sender_data_aad
833
+ self.class.sender_data_aad_impl(group_id, epoch, content_type)
834
+ end
835
+
836
+ def self.sender_data_aad_impl(gid, ep, ct)
837
+ Melos::Vec.from_string(gid) + [ep].pack('Q>') + [ct].pack('C')
838
+ end
839
+
840
+ def private_content_aad
841
+ self.class.private_content_aad_impl(group_id, epoch, content_type, authenticated_data)
842
+ end
843
+
844
+ def self.private_content_aad_impl(gid, ep, ct, ad)
845
+ Melos::Vec.from_string(gid) + [ep].pack('Q>') + [ct].pack('C') + Melos::Vec.from_string(ad)
846
+ end
847
+
848
+ def self.protect(authenticated_content, suite, secret_tree, sender_data_secret, padding_size)
849
+ leaf_index = authenticated_content.content.sender.leaf_index
850
+ content_type = authenticated_content.content.content_type
851
+ reuse_guard = SecureRandom.random_bytes(4)
852
+ key, nonce, generation = Melos::SecretTree::ratchet_and_get(suite, content_type, secret_tree, leaf_index)
853
+ new_nonce = apply_nonce_reuse_guard(nonce, reuse_guard)
854
+
855
+ private_message_content_plain = serialize_private_message_content(authenticated_content.content, authenticated_content.auth, padding_size)
856
+ aad = private_content_aad_impl(
857
+ authenticated_content.content.group_id,
858
+ authenticated_content.content.epoch,
859
+ authenticated_content.content.content_type,
860
+ authenticated_content.content.authenticated_data)
861
+ private_message_content_ciphertext = Melos::Crypto.aead_encrypt(suite, key, new_nonce, aad, private_message_content_plain)
862
+
863
+ sender_data_plain = Melos::Struct::SenderData.create(
864
+ leaf_index: leaf_index,
865
+ generation: generation,
866
+ reuse_guard: reuse_guard
867
+ )
868
+ sd_aad = sender_data_aad_impl(
869
+ authenticated_content.content.group_id,
870
+ authenticated_content.content.epoch,
871
+ authenticated_content.content.content_type)
872
+ sender_data_key = Melos::Crypto.sender_data_key(suite, sender_data_secret, private_message_content_ciphertext)
873
+ sender_data_nonce = Melos::Crypto.sender_data_nonce(suite, sender_data_secret, private_message_content_ciphertext)
874
+ sender_data_ciphertext = Melos::Crypto.aead_encrypt(suite, sender_data_key, sender_data_nonce, sd_aad, sender_data_plain.raw)
875
+
876
+ create(
877
+ group_id: authenticated_content.content.group_id,
878
+ epoch: authenticated_content.content.epoch,
879
+ content_type: authenticated_content.content.content_type,
880
+ authenticated_data: authenticated_content.content.authenticated_data,
881
+ encrypted_sender_data: sender_data_ciphertext,
882
+ ciphertext: private_message_content_ciphertext
883
+ )
884
+ end
885
+
886
+ def unprotect(suite, secret_tree, sender_data_secret)
887
+ sender_data = decrypt_sender_data(suite, sender_data_secret)
888
+ key, nonce, _ = Melos::SecretTree.ratchet_until_and_get(suite, content_type, secret_tree, sender_data.leaf_index, sender_data.generation)
889
+ new_nonce = self.class.apply_nonce_reuse_guard(nonce, sender_data.reuse_guard)
890
+ pmc, _ = Melos::Struct::PrivateMessageContent.new_and_rest_with_content_type(Melos::Crypto.aead_decrypt(suite, key, new_nonce, private_content_aad, ciphertext), content_type)
891
+
892
+ fc = Melos::Struct::FramedContent.create(
893
+ group_id: group_id,
894
+ epoch: epoch,
895
+ sender: Melos::Struct::Sender.create_member(sender_data.leaf_index),
896
+ authenticated_data: authenticated_data,
897
+ content_type: content_type,
898
+ content: pmc.content
899
+ )
900
+
901
+ Melos::Struct::AuthenticatedContent.create(
902
+ wire_format: Melos::Constants::WireFormat::MLS_PRIVATE_MESSAGE, # private_message
903
+ content: fc,
904
+ auth: pmc.auth
905
+ )
906
+ end
907
+
908
+ private
909
+ def decrypt_sender_data(suite, sender_data_secret)
910
+ sender_data_key = Melos::Crypto.sender_data_key(suite, sender_data_secret, ciphertext)
911
+ sender_data_nonce = Melos::Crypto.sender_data_nonce(suite, sender_data_secret, ciphertext)
912
+ Melos::Struct::SenderData.new(Melos::Crypto.aead_decrypt(suite, sender_data_key, sender_data_nonce, sender_data_aad, encrypted_sender_data))
913
+ end
914
+
915
+ def self.apply_nonce_reuse_guard(nonce, guard)
916
+ guard_arr = guard.unpack('c*')
917
+ nonce_arr = nonce.unpack('c*')
918
+ guard_arr.each_with_index do |char, index|
919
+ nonce_arr[index] = nonce_arr[index] ^ char
920
+ end
921
+ nonce_arr.pack('C*')
922
+ end
923
+
924
+ def self.serialize_private_message_content(framed_content, framed_content_auth_data, padding_size)
925
+ buf = ''
926
+ case framed_content.content_type
927
+ when Melos::Constants::ContentType::APPLICATION
928
+ buf += Melos::Vec.from_string(framed_content.application_data)
929
+ when Melos::Constants::ContentType::PROPOSAL
930
+ buf += framed_content.proposal.raw
931
+ when Melos::Constants::ContentType::COMMIT
932
+ buf += framed_content.commit.raw
933
+ end
934
+ buf += Melos::Vec.from_string(framed_content_auth_data.signature)
935
+ if framed_content.content_type == Melos::Constants::ContentType::COMMIT
936
+ buf += Melos::Vec.from_string(framed_content_auth_data.confirmation_tag)
937
+ end
938
+ buf += Melos::Crypto::Util.zero_vector(padding_size)
939
+ buf
940
+ end
941
+ end
942
+
943
+ class Melos::Struct::PrivateMessageContent < Melos::Struct::Base
944
+ attr_accessor :application_data, :proposal, :commit, :auth, :padding
945
+ # bytes -> struct: decode the content and auth field, rest is padding
946
+ # struct -> bytes: encode content and auth field, add set amount of padding (zero bytes)
947
+
948
+ def self.new_and_rest_with_content_type(buf, content_type)
949
+ instance = self.allocate
950
+ context = []
951
+ # deserialize application_data/proposal/commit
952
+ case content_type
953
+ when Melos::Constants::ContentType::APPLICATION
954
+ value, buf = Melos::Vec.parse_vec(buf)
955
+ context << [:application_data, value]
956
+ when Melos::Constants::ContentType::PROPOSAL
957
+ value, buf = Melos::Struct::Proposal.new_and_rest(buf)
958
+ context << [:proposal, value]
959
+ when Melos::Constants::ContentType::COMMIT
960
+ value, buf = Melos::Struct::Commit.new_and_rest(buf)
961
+ context << [:commit, value]
962
+ end
963
+ fcad, buf = Melos::Struct::FramedContentAuthData.new_and_rest_with_content_type(buf, content_type)
964
+ context << [:auth, fcad]
965
+ # assume rest is padding
966
+ context << [:padding, buf]
967
+ instance.send(:set_instance_vars, context)
968
+ [instance, '']
969
+ end
970
+
971
+ def content
972
+ @application_data || @proposal || @commit
973
+ end
974
+ end
975
+
976
+ ## 8.2
977
+
978
+ class Melos::Struct::ConfirmedTranscriptHashInput < Melos::Struct::Base
979
+ attr_accessor :wire_format, :content, :signature
980
+ STRUCT = [
981
+ [:wire_format, :uint16],
982
+ [:content, :class, Melos::Struct::FramedContent], # with content_type == commit
983
+ [:signature, :vec]
984
+ ]
985
+
986
+ def self.create(wire_format:, content:, signature:)
987
+ new_instance = self.allocate
988
+ new_instance.instance_variable_set(:@wire_format, wire_format)
989
+ new_instance.instance_variable_set(:@content, content)
990
+ new_instance.instance_variable_set(:@signature, signature)
991
+ new_instance
992
+ end
993
+ end
994
+
995
+ class Melos::Struct::InterimTranscriptHashInput < Melos::Struct::Base
996
+ attr_reader :confirmation_tag
997
+ STRUCT = [
998
+ [:confirmation_tag, :vec]
999
+ ]
1000
+ end
1001
+
1002
+ class Melos::Struct::MLSMessage < Melos::Struct::Base
1003
+ attr_accessor :version, :wire_format, :public_message, :private_message, :welcome, :group_info, :key_package
1004
+ STRUCT = [
1005
+ [:version, :uint16], #mls10 = 1
1006
+ [:wire_format, :uint16],
1007
+ [:public_message, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE}, :class, Melos::Struct::PublicMessage],
1008
+ [:private_message, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_PRIVATE_MESSAGE}, :class, Melos::Struct::PrivateMessage],
1009
+ [:welcome, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_WELCOME}, :class, Melos::Struct::Welcome],
1010
+ [:group_info, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_GROUP_INFO}, :class, Melos::Struct::GroupInfo],
1011
+ [:key_package, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_KEY_PACKAGE}, :class, Melos::Struct::KeyPackage]
1012
+ ]
1013
+
1014
+ def verify(suite, signer_public_key, group_context)
1015
+ if wire_format == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE
1016
+ public_message.verify(suite, signer_public_key, version, wire_format, group_context)
1017
+ end
1018
+ end
1019
+ end