group.mx raw
1 package mls
2
3 // MLS Group state machine (RFC 9420 §11, §12).
4 // High-level API: create, join, commit, encrypt, decrypt.
5
6 import "errors"
7
8 type pendingProposal struct {
9 ref proposalRef
10 proposal *proposal
11 sender leafIndex
12 }
13
14 // Group is the high-level MLS group state.
15 type Group struct {
16 tree ratchetTree
17 groupContext groupContext
18
19 interimTranscriptHash []byte
20 pskSecret []byte
21 epochSecret []byte
22 initSecret []byte
23
24 myLeafIndex leafIndex
25 privTree []hpkePrivateKey
26 signaturePriv signaturePrivateKey
27
28 pendingProposals []pendingProposal
29 }
30
31 // Epoch returns the current MLS epoch.
32 func (g *Group) Epoch() uint64 {
33 return g.groupContext.epoch
34 }
35
36 // GroupID returns the MLS group ID.
37 func (g *Group) GroupID() GroupID {
38 return g.groupContext.groupID
39 }
40
41 // Members returns the credential identity bytes of every occupied leaf in
42 // the ratchet tree, in leaf-index order. Blank leaves (removed members) are
43 // skipped. For basic credentials in Smesh's use this is the member's nostr
44 // pubkey. Callers use this for p-tag fan-out on kind 444/445 events.
45 func (g *Group) Members() [][]byte {
46 var out [][]byte
47 n := g.tree.numLeaves()
48 for li := leafIndex(0); li < leafIndex(n); li++ {
49 ln := g.tree.getLeaf(li)
50 if ln == nil {
51 continue
52 }
53 if ln.credential.credentialType != credentialTypeBasic {
54 continue
55 }
56 id := []byte{:len(ln.credential.identity)}
57 copy(id, ln.credential.identity)
58 out = append(out, id)
59 }
60 return out
61 }
62
63 // ExporterSecret derives the exporter secret from the current epoch.
64 func (g *Group) ExporterSecret() ([]byte, error) {
65 return g.groupContext.cipherSuite.deriveSecret(g.epochSecret, secretLabelExporter)
66 }
67
68 // DeriveExporter derives keying material via MLS exporter (RFC 9420 §8).
69 func (g *Group) DeriveExporter(label, context []byte, length uint16) ([]byte, error) {
70 exporterSecret, err := g.ExporterSecret()
71 if err != nil {
72 return nil, err
73 }
74 return deriveExporter(g.groupContext.cipherSuite, exporterSecret, label, context, length)
75 }
76
77 // GroupContextExtensions returns the group context extensions.
78 func (g *Group) GroupContextExtensions() []extension {
79 return g.groupContext.extensions
80 }
81
82 // FindGroupContextExtension returns extension data by type, or nil.
83 func (g *Group) FindGroupContextExtension(t extensionType) []byte {
84 return findExtensionData(g.groupContext.extensions, t)
85 }
86
87 // --- Serialization ---
88
89 // Marshal serializes the full Group state for local persistence.
90 // Output contains sensitive key material — encrypt at rest.
91 func (g *Group) Marshal() ([]byte, error) {
92 var w Writer
93 g.groupContext.marshal(&w)
94 g.tree.marshal(&w)
95
96 w.writeOpaqueVec(g.interimTranscriptHash)
97 w.writeOpaqueVec(g.pskSecret)
98 w.writeOpaqueVec(g.epochSecret)
99 w.writeOpaqueVec(g.initSecret)
100
101 w.addUint32(uint32(g.myLeafIndex))
102 w.writeOpaqueVec([]byte(g.signaturePriv))
103
104 w.writeVector(len(g.privTree), func(w *Writer, i int) {
105 w.writeOpaqueVec([]byte(g.privTree[i]))
106 })
107 return w.bytes()
108 }
109
110 // UnmarshalGroup restores a Group from bytes produced by Marshal.
111 func UnmarshalGroup(raw []byte) (*Group, error) {
112 r := newReader(raw)
113 g := &Group{}
114
115 if err := g.groupContext.unmarshal(&r); err != nil {
116 return nil, err
117 }
118 if err := g.tree.unmarshal(&r); err != nil {
119 return nil, err
120 }
121
122 var ok bool
123 g.interimTranscriptHash, ok = r.readOpaqueVec()
124 if !ok {
125 return nil, errUnexpectedEOF
126 }
127 g.pskSecret, ok = r.readOpaqueVec()
128 if !ok {
129 return nil, errUnexpectedEOF
130 }
131 g.epochSecret, ok = r.readOpaqueVec()
132 if !ok {
133 return nil, errUnexpectedEOF
134 }
135 g.initSecret, ok = r.readOpaqueVec()
136 if !ok {
137 return nil, errUnexpectedEOF
138 }
139
140 v, ok := r.readUint32()
141 if !ok {
142 return nil, errUnexpectedEOF
143 }
144 g.myLeafIndex = leafIndex(v)
145
146 sigPriv, ok := r.readOpaqueVec()
147 if !ok {
148 return nil, errUnexpectedEOF
149 }
150 g.signaturePriv = signaturePrivateKey(sigPriv)
151
152 err := r.readVector(func(r *Reader) error {
153 k, ok := r.readOpaqueVec()
154 if !ok {
155 return errUnexpectedEOF
156 }
157 g.privTree = append(g.privTree, hpkePrivateKey(k))
158 return nil
159 })
160 if err != nil {
161 return nil, err
162 }
163 return g, nil
164 }
165
166 // --- Group creation ---
167
168 // GroupOptions configures group creation.
169 type GroupOptions struct {
170 Extensions []extension
171 }
172
173 // CreateGroup creates a new single-member group at epoch 0.
174 func CreateGroup(groupID GroupID, kpp *KeyPairPackage) (*Group, error) {
175 return CreateGroupWithOptions(groupID, kpp, nil)
176 }
177
178 // CreateGroupWithOptions creates a new group with custom extensions.
179 func CreateGroupWithOptions(groupID GroupID, kpp *KeyPairPackage, opts *GroupOptions) (*Group, error) {
180 cs := kpp.Public.cipherSuite
181
182 tree := ratchetTree([]*node{:1})
183 tree.add(&kpp.Public.leafNode)
184
185 privTree := []hpkePrivateKey{:len(tree)}
186 privTree[0] = kpp.Private.EncryptionKey
187
188 treeHash, err := tree.computeRootTreeHash(cs)
189 if err != nil {
190 return nil, err
191 }
192
193 confirmedTranscriptHash := []byte{:cs.HashSize()}
194
195 epochSecret := cs.randomBytes(cs.ExtractSize())
196
197 var ctxExts []extension
198 if opts != nil {
199 ctxExts = opts.Extensions
200 }
201
202 ctx := groupContext{
203 version: kpp.Public.version,
204 cipherSuite: cs,
205 groupID: groupID,
206 epoch: 0,
207 treeHash: treeHash,
208 confirmedTranscriptHash: confirmedTranscriptHash,
209 extensions: ctxExts,
210 }
211
212 confirmationTag, err := ctx.signConfirmationTag(epochSecret)
213 if err != nil {
214 return nil, err
215 }
216 interimTH, err := nextInterimTranscriptHash(cs, confirmedTranscriptHash, confirmationTag)
217 if err != nil {
218 return nil, err
219 }
220 pskSecret, err := extractPSKSecret(cs, nil, nil)
221 if err != nil {
222 return nil, err
223 }
224 initSecret, err := cs.deriveSecret(epochSecret, secretLabelInit)
225 if err != nil {
226 return nil, err
227 }
228
229 return &Group{
230 tree: tree,
231 privTree: privTree,
232 myLeafIndex: 0,
233 signaturePriv: kpp.Private.SignatureKey,
234 groupContext: ctx,
235 interimTranscriptHash: interimTH,
236 pskSecret: pskSecret,
237 epochSecret: epochSecret,
238 initSecret: initSecret,
239 }, nil
240 }
241
242 // --- Join from Welcome ---
243
244 // GroupFromWelcome creates a group from a Welcome message.
245 func GroupFromWelcome(welcome *Welcome, kpp *KeyPairPackage) (*Group, error) {
246 ref, err := kpp.Public.GenerateRef()
247 if err != nil {
248 return nil, err
249 }
250
251 gs, err := welcome.decryptGroupSecrets(ref, kpp.Private.InitKey)
252 if err != nil {
253 return nil, err
254 }
255
256 if !gs.verifySingleReinitOrBranchPSK() {
257 return nil, errors.New("mls: more than one reinit/branch PSK")
258 }
259 if len(gs.psks) != 0 {
260 return nil, errors.New("mls: group secret PSKs not supported")
261 }
262
263 return groupFromSecrets(welcome, kpp, gs, 0)
264 }
265
266 // GroupFromWelcomeAt is like GroupFromWelcome but with explicit time for lifetime verification.
267 func GroupFromWelcomeAt(welcome *Welcome, kpp *KeyPairPackage, nowUnix int64) (*Group, error) {
268 ref, err := kpp.Public.GenerateRef()
269 if err != nil {
270 return nil, err
271 }
272
273 gs, err := welcome.decryptGroupSecrets(ref, kpp.Private.InitKey)
274 if err != nil {
275 return nil, err
276 }
277
278 if !gs.verifySingleReinitOrBranchPSK() {
279 return nil, errors.New("mls: more than one reinit/branch PSK")
280 }
281 if len(gs.psks) != 0 {
282 return nil, errors.New("mls: group secret PSKs not supported")
283 }
284
285 return groupFromSecrets(welcome, kpp, gs, nowUnix)
286 }
287
288 func groupFromSecrets(welcome *Welcome, kpp *KeyPairPackage, gs *groupSecrets, nowUnix int64) (*Group, error) {
289 cs := welcome.cipherSuite
290
291 pskSecret, err := extractPSKSecret(cs, gs.psks, nil)
292 if err != nil {
293 return nil, err
294 }
295
296 gi, err := welcome.decryptGroupInfo(gs.joinerSecret, pskSecret)
297 if err != nil {
298 return nil, err
299 }
300
301 rawTree := findExtensionData(gi.extensions, extensionTypeRatchetTree)
302 if rawTree == nil {
303 return nil, errors.New("mls: missing ratchet tree")
304 }
305
306 var tree ratchetTree
307 if err := unmarshalRaw(rawTree, &tree); err != nil {
308 return nil, err
309 }
310
311 signerNode := tree.getLeaf(gi.signer)
312 if signerNode == nil {
313 return nil, errors.New("mls: signer node is blank")
314 }
315 if !gi.verifySignature(signerNode.signatureKey) {
316 return nil, errors.New("mls: group info signature verification failed")
317 }
318 if !gi.verifyConfirmationTag(gs.joinerSecret, pskSecret) {
319 return nil, errors.New("mls: confirmation tag verification failed")
320 }
321 if gi.groupContext.cipherSuite != cs {
322 return nil, errors.New("mls: group info cipher suite mismatch")
323 }
324
325 if err := tree.verifyIntegrity(&gi.groupContext, nowUnix); err != nil {
326 return nil, err
327 }
328
329 ctx := gi.groupContext
330
331 epochSecret, err := ctx.extractEpochSecret(gs.joinerSecret, pskSecret)
332 if err != nil {
333 return nil, err
334 }
335 initSecret, err := cs.deriveSecret(epochSecret, secretLabelInit)
336 if err != nil {
337 return nil, err
338 }
339 interimTH, err := nextInterimTranscriptHash(cs, ctx.confirmedTranscriptHash, gi.confirmationTag)
340 if err != nil {
341 return nil, err
342 }
343
344 myLI, ok := tree.findLeaf(&kpp.Public.leafNode)
345 if !ok {
346 return nil, errors.New("mls: cannot find my leaf node in tree")
347 }
348
349 privTree := []hpkePrivateKey{:len(tree)}
350 privTree[int(myLI.nodeIndex())] = kpp.Private.EncryptionKey
351
352 if gs.pathSecret != nil {
353 ancestor := commonAncestor(myLI.nodeIndex(), gi.signer.nodeIndex())
354 if err := processPathSecret(cs, tree, privTree, gs.pathSecret, ancestor); err != nil {
355 return nil, err
356 }
357 }
358
359 return &Group{
360 tree: tree,
361 groupContext: ctx,
362 interimTranscriptHash: interimTH,
363 pskSecret: pskSecret,
364 epochSecret: epochSecret,
365 initSecret: initSecret,
366 myLeafIndex: myLI,
367 privTree: privTree,
368 signaturePriv: kpp.Private.SignatureKey,
369 }, nil
370 }
371
372 func processPathSecret(cs CipherSuite, tree ratchetTree, privTree []hpkePrivateKey, pathSecret []byte, ni nodeIndex) error {
373 nodePriv, err := nodePrivFromPathSecret(cs, pathSecret, tree.get(ni).encryptionKey())
374 if err != nil {
375 return err
376 }
377 privTree[int(ni)] = nodePriv
378
379 for {
380 var ok bool
381 ni, ok = tree.numLeaves().parent(ni)
382 if !ok {
383 break
384 }
385 pathSecret, err = cs.deriveSecret(pathSecret, []byte("path"))
386 if err != nil {
387 return err
388 }
389 nodePriv, err = nodePrivFromPathSecret(cs, pathSecret, tree.get(ni).encryptionKey())
390 if err != nil {
391 return err
392 }
393 privTree[int(ni)] = nodePriv
394 }
395 return nil
396 }
397
398 // --- Message processing ---
399
400 // UnmarshalAndProcessMessage decodes and processes an MLS message.
401 // Returns decrypted application data if applicable.
402 func (g *Group) UnmarshalAndProcessMessage(raw []byte) (plaintext []byte, selfSent bool, err error) {
403 var msg mlsMessage
404 if err := unmarshalRaw(raw, &msg); err != nil {
405 return nil, false, err
406 }
407
408 switch msg.wireFormat {
409 case wireFormatMLSPublicMessage:
410 return nil, false, g.processPublicMessage(msg.publicMessage)
411 case wireFormatMLSPrivateMessage:
412 return g.processPrivateMessage(msg.privateMessage)
413 default:
414 return nil, false, errors.New("mls: unsupported wire format")
415 }
416 }
417
418 func (g *Group) processPublicMessage(pubMsg *publicMessage) error {
419 authContent, err := g.verifyPublicMessage(pubMsg)
420 if err != nil {
421 return err
422 }
423 switch authContent.content.contentType {
424 case contentTypeProposal:
425 return g.processProposal(authContent)
426 case contentTypeCommit:
427 return g.processCommit(authContent, nil, nil, 0)
428 case contentTypeApplication:
429 return errors.New("mls: application content must be encrypted")
430 default:
431 return errors.New("mls: unsupported content type")
432 }
433 }
434
435 func (g *Group) verifyPublicMessage(pubMsg *publicMessage) (*authenticatedContent, error) {
436 if !pubMsg.content.groupID.equal(g.groupContext.groupID) {
437 return nil, errors.New("mls: group ID mismatch")
438 }
439 if pubMsg.content.epoch != g.groupContext.epoch {
440 return nil, errors.New("mls: epoch mismatch")
441 }
442 if pubMsg.content.sender.senderType != senderTypeMember {
443 return nil, errors.New("mls: unsupported sender type")
444 }
445
446 senderLI := pubMsg.content.sender.leafIndex
447 senderNode := g.tree.getLeaf(senderLI)
448 if senderNode == nil {
449 return nil, errors.New("mls: blank sender leaf node")
450 }
451
452 authContent := pubMsg.authenticatedContent()
453 if !authContent.verifySignature(senderNode.signatureKey, &g.groupContext) {
454 return nil, errors.New("mls: public message signature verification failed")
455 }
456
457 membershipKey, err := g.groupContext.cipherSuite.deriveSecret(g.epochSecret, secretLabelMembership)
458 if err != nil {
459 return nil, err
460 }
461 if !pubMsg.verifyMembershipTag(membershipKey, &g.groupContext) {
462 return nil, errors.New("mls: membership tag verification failed")
463 }
464
465 return authContent, nil
466 }
467
468 func (g *Group) processPrivateMessage(privMsg *privateMessage) ([]byte, bool, error) {
469 cs := g.groupContext.cipherSuite
470
471 if !privMsg.groupID.equal(g.groupContext.groupID) {
472 return nil, false, errors.New("mls: group ID mismatch")
473 }
474 if privMsg.epoch != g.groupContext.epoch {
475 return nil, false, errors.New("mls: epoch mismatch")
476 }
477
478 senderDataSecret, err := cs.deriveSecret(g.epochSecret, secretLabelSenderData)
479 if err != nil {
480 return nil, false, err
481 }
482 sd, err := privMsg.decryptSenderData(cs, senderDataSecret)
483 if err != nil {
484 return nil, false, err
485 }
486
487 encSecret, err := cs.deriveSecret(g.epochSecret, secretLabelEncryption)
488 if err != nil {
489 return nil, false, err
490 }
491 secTree, err := deriveSecretTree(cs, g.tree.numLeaves(), encSecret)
492 if err != nil {
493 return nil, false, err
494 }
495
496 label := ratchetLabelFromContentType(privMsg.contentType)
497 secret, err := secTree.deriveRatchetRoot(cs, sd.leafIndex.nodeIndex(), label)
498 if err != nil {
499 return nil, false, err
500 }
501
502 // Ratchet to the right generation
503 for secret.generation != sd.generation {
504 secret, err = secret.deriveNext(cs)
505 if err != nil {
506 return nil, false, err
507 }
508 }
509
510 privContent, err := privMsg.decryptContent(cs, secret, sd.reuseGuard)
511 if err != nil {
512 return nil, false, err
513 }
514
515 signerNode := g.tree.getLeaf(sd.leafIndex)
516 if signerNode == nil {
517 return nil, false, errors.New("mls: signer node is blank")
518 }
519 authContent := privMsg.authenticatedContent(sd, privContent)
520 if !authContent.verifySignature(signerNode.signatureKey, &g.groupContext) {
521 return nil, false, errors.New("mls: private message signature verification failed")
522 }
523
524 selfSent := sd.leafIndex == g.myLeafIndex
525
526 switch authContent.content.contentType {
527 case contentTypeProposal:
528 return nil, false, g.processProposal(authContent)
529 case contentTypeCommit:
530 return nil, false, g.processCommit(authContent, nil, nil, 0)
531 case contentTypeApplication:
532 return authContent.content.applicationData, selfSent, nil
533 default:
534 return nil, false, errors.New("mls: unsupported content type")
535 }
536 }
537
538 func (g *Group) processProposal(authContent *authenticatedContent) error {
539 ref, err := authContent.generateProposalRef(g.groupContext.cipherSuite)
540 if err != nil {
541 return err
542 }
543 g.pendingProposals = append(g.pendingProposals, pendingProposal{
544 ref: ref,
545 proposal: authContent.content.proposal,
546 sender: authContent.content.sender.leafIndex,
547 })
548 return nil
549 }
550
551 func (g *Group) processCommit(authContent *authenticatedContent, pskIDs []preSharedKeyID, psks [][]byte, nowUnix int64) error {
552 cs := g.groupContext.cipherSuite
553 senderLI := authContent.content.sender.leafIndex
554
555 cmt := authContent.content.commit
556 proposals, senders, err := resolveProposals(cmt.proposals, senderLI, g.pendingProposals)
557 if err != nil {
558 return err
559 }
560 if err := verifyProposalList(proposals, senders, senderLI); err != nil {
561 return err
562 }
563 for _, prop := range proposals {
564 if prop.proposalType == proposalTypeAdd {
565 if err := prop.add.keyPackage.verify(&g.groupContext); err != nil {
566 return err
567 }
568 }
569 }
570 if proposalListNeedsPath(proposals) && cmt.path == nil {
571 return errors.New("mls: commit missing required update path")
572 }
573
574 newCtx := g.groupContext
575 newCtx.epoch++
576
577 newTree := g.tree.copy()
578 newTree.apply(proposals, senders)
579
580 newPrivTree := []hpkePrivateKey{:len(newTree)}
581 for i := range g.tree {
582 if i < len(newPrivTree) {
583 newPrivTree[i] = g.privTree[i]
584 }
585 }
586
587 commitSecret := []byte{:cs.ExtractSize()}
588 if cmt.path != nil {
589 if cmt.path.leafNode.leafNodeSource != leafNodeSourceCommit {
590 return errors.New("mls: commit path leaf source must be commit")
591 }
592
593 senderNode := newTree.getLeaf(senderLI)
594 sigKeys, encKeys := newTree.keys()
595 delete(sigKeys, string(senderNode.signatureKey))
596 err := cmt.path.leafNode.verify(&leafNodeVerifyOptions{
597 cipherSuite: cs,
598 groupID: g.groupContext.groupID,
599 leafIndex: senderLI,
600 supportedCreds: newTree.supportedCreds(),
601 signatureKeys: sigKeys,
602 encryptionKeys: encKeys,
603 nowUnix: nowUnix,
604 })
605 if err != nil {
606 return err
607 }
608
609 for _, upNode := range cmt.path.nodes {
610 if encKeys[string(upNode.encryptionKey)] {
611 return errors.New("mls: update path encryption key already in tree")
612 }
613 }
614
615 if err := newTree.mergeUpdatePath(cs, senderLI, cmt.path); err != nil {
616 return err
617 }
618
619 newCtx.treeHash, err = newTree.computeRootTreeHash(cs)
620 if err != nil {
621 return err
622 }
623
624 commitSecret, err = newTree.decryptPathSecrets(cs, &newCtx, senderLI, g.myLeafIndex, cmt.path, newPrivTree)
625 if err != nil {
626 return err
627 }
628 } else {
629 newCtx.treeHash, err = newTree.computeRootTreeHash(cs)
630 if err != nil {
631 return err
632 }
633 }
634
635 newCtx.confirmedTranscriptHash, err = authContent.confirmedTranscriptHashInput().hashValue(cs, g.interimTranscriptHash)
636 if err != nil {
637 return err
638 }
639 newInterimTH, err := nextInterimTranscriptHash(cs, newCtx.confirmedTranscriptHash, authContent.auth.confirmationTag)
640 if err != nil {
641 return err
642 }
643 newJoinerSecret, err := newCtx.extractJoinerSecret(g.initSecret, commitSecret)
644 if err != nil {
645 return err
646 }
647 newPSKSecret, err := extractPSKSecret(cs, pskIDs, psks)
648 if err != nil {
649 return err
650 }
651 newEpochSecret, err := newCtx.extractEpochSecret(newJoinerSecret, newPSKSecret)
652 if err != nil {
653 return err
654 }
655 newInitSecret, err := cs.deriveSecret(newEpochSecret, secretLabelInit)
656 if err != nil {
657 return err
658 }
659
660 g.tree = newTree
661 g.privTree = newPrivTree
662 g.groupContext = newCtx
663 g.interimTranscriptHash = newInterimTH
664 g.pskSecret = newPSKSecret
665 g.epochSecret = newEpochSecret
666 g.initSecret = newInitSecret
667 g.pendingProposals = nil
668 return nil
669 }
670
671 func resolveProposals(propOrRefs []proposalOrRef, senderLI leafIndex, pending []pendingProposal) ([]proposal, []leafIndex, error) {
672 var proposals []proposal
673 var senders []leafIndex
674 for _, por := range propOrRefs {
675 switch por.typ {
676 case proposalOrRefTypeProposal:
677 proposals = append(proposals, *por.proposal)
678 senders = append(senders, senderLI)
679 case proposalOrRefTypeReference:
680 found := false
681 for _, pp := range pending {
682 if pp.ref.equal(por.reference) {
683 found = true
684 proposals = append(proposals, *pp.proposal)
685 senders = append(senders, pp.sender)
686 break
687 }
688 }
689 if !found {
690 return nil, nil, errors.New("mls: proposal reference not found")
691 }
692 }
693 }
694 return proposals, senders, nil
695 }
696
697 // --- Welcome creation ---
698
699 // CreateWelcome creates a Welcome message inviting new members.
700 // Returns the Welcome and the raw commit message for existing members.
701 func (g *Group) CreateWelcome(keyPkgs []KeyPackage) (*Welcome, []byte, error) {
702 cs := g.groupContext.cipherSuite
703
704 proposals := []proposal{:len(keyPkgs)}
705 propOrRefs := []proposalOrRef{:len(keyPkgs)}
706 for i, kp := range keyPkgs {
707 proposals[i] = proposal{
708 proposalType: proposalTypeAdd,
709 add: &add{keyPackage: kp},
710 }
711 propOrRefs[i] = proposalOrRef{
712 typ: proposalOrRefTypeProposal,
713 proposal: &proposals[i],
714 }
715 }
716
717 cmt := commit{proposals: propOrRefs}
718
719 newCtx := g.groupContext
720 newCtx.epoch++
721
722 newTree := g.tree.copy()
723 senders := []leafIndex{:len(proposals)}
724 for i := range senders {
725 senders[i] = g.myLeafIndex
726 }
727 newTree.apply(proposals, senders)
728
729 var err error
730 newCtx.treeHash, err = newTree.computeRootTreeHash(cs)
731 if err != nil {
732 return nil, nil, err
733 }
734
735 commitSecret := []byte{:cs.ExtractSize()}
736
737 pskSecret, err := extractPSKSecret(cs, nil, nil)
738 if err != nil {
739 return nil, nil, err
740 }
741
742 fc := framedContent{
743 groupID: g.groupContext.groupID,
744 epoch: g.groupContext.epoch,
745 sender: sender{
746 senderType: senderTypeMember,
747 leafIndex: g.myLeafIndex,
748 },
749 contentType: contentTypeCommit,
750 commit: &cmt,
751 }
752
753 // Sign as private message (default)
754 privContent, err := signPrivateMessageContent(cs, g.signaturePriv, &fc, &g.groupContext)
755 if err != nil {
756 return nil, nil, err
757 }
758 authContent := privContent.authenticatedContent(&fc)
759 authData := &privContent.auth
760
761 newCtx.confirmedTranscriptHash, err = authContent.confirmedTranscriptHashInput().hashValue(cs, g.interimTranscriptHash)
762 if err != nil {
763 return nil, nil, err
764 }
765
766 joinerSecret, err := newCtx.extractJoinerSecret(g.initSecret, commitSecret)
767 if err != nil {
768 return nil, nil, err
769 }
770 epochSecret, err := newCtx.extractEpochSecret(joinerSecret, pskSecret)
771 if err != nil {
772 return nil, nil, err
773 }
774 confirmationTag, err := newCtx.signConfirmationTag(epochSecret)
775 if err != nil {
776 return nil, nil, err
777 }
778 authData.confirmationTag = confirmationTag
779
780 rawTree, err := marshalRaw(newTree)
781 if err != nil {
782 return nil, nil, err
783 }
784
785 newGI := groupInfo{
786 groupContext: newCtx,
787 confirmationTag: confirmationTag,
788 signer: g.myLeafIndex,
789 extensions: []extension{
790 {
791 extensionType: extensionTypeRatchetTree,
792 extensionData: rawTree,
793 },
794 },
795 }
796 if err := newGI.sign(g.signaturePriv); err != nil {
797 return nil, nil, err
798 }
799
800 encGI, err := newGI.encrypt(joinerSecret, pskSecret)
801 if err != nil {
802 return nil, nil, err
803 }
804
805 gSec := groupSecrets{joinerSecret: joinerSecret}
806 encSecrets := []encryptedGroupSecrets{:len(keyPkgs)}
807 for i, kp := range keyPkgs {
808 ref, err := kp.GenerateRef()
809 if err != nil {
810 return nil, nil, err
811 }
812 encGS, err := gSec.encrypt(cs, kp.initKey, encGI)
813 if err != nil {
814 return nil, nil, err
815 }
816 encSecrets[i] = encryptedGroupSecrets{
817 newMember: ref,
818 encryptedGroupSecrets: *encGS,
819 }
820 }
821
822 rawMsg, err := g.encryptPrivateMessage(&fc, privContent)
823 if err != nil {
824 return nil, nil, err
825 }
826
827 return &Welcome{
828 cipherSuite: cs,
829 secrets: encSecrets,
830 encryptedGroupInfo: encGI,
831 }, rawMsg, nil
832 }
833
834 // --- Application messages ---
835
836 // CreateApplicationMessage encrypts application data for the group.
837 func (g *Group) CreateApplicationMessage(data []byte) ([]byte, error) {
838 cs := g.groupContext.cipherSuite
839
840 fc := framedContent{
841 groupID: g.groupContext.groupID,
842 epoch: g.groupContext.epoch,
843 sender: sender{
844 senderType: senderTypeMember,
845 leafIndex: g.myLeafIndex,
846 },
847 contentType: contentTypeApplication,
848 applicationData: data,
849 }
850 privContent, err := signPrivateMessageContent(cs, g.signaturePriv, &fc, &g.groupContext)
851 if err != nil {
852 return nil, err
853 }
854 return g.encryptPrivateMessage(&fc, privContent)
855 }
856
857 func (g *Group) encryptPrivateMessage(fc *framedContent, privContent *privateMessageContent) ([]byte, error) {
858 cs := g.groupContext.cipherSuite
859
860 sd, err := newSenderData(g.myLeafIndex, 0)
861 if err != nil {
862 return nil, err
863 }
864
865 encSecret, err := cs.deriveSecret(g.epochSecret, secretLabelEncryption)
866 if err != nil {
867 return nil, err
868 }
869 secTree, err := deriveSecretTree(cs, g.tree.numLeaves(), encSecret)
870 if err != nil {
871 return nil, err
872 }
873 label := ratchetLabelFromContentType(fc.contentType)
874 secret, err := secTree.deriveRatchetRoot(cs, g.myLeafIndex.nodeIndex(), label)
875 if err != nil {
876 return nil, err
877 }
878
879 senderDataSecret, err := cs.deriveSecret(g.epochSecret, secretLabelSenderData)
880 if err != nil {
881 return nil, err
882 }
883
884 privMsg, err := encryptPrivateMessage(cs, secret, senderDataSecret, fc, privContent, sd)
885 if err != nil {
886 return nil, err
887 }
888
889 return marshalRaw(&mlsMessage{
890 version: protocolVersionMLS10,
891 wireFormat: wireFormatMLSPrivateMessage,
892 privateMessage: privMsg,
893 })
894 }
895
896 // signPublicMessageMembershipTag signs and wraps a public message.
897 func (g *Group) signPublicMessageMembershipTag(pubMsg *publicMessage) ([]byte, error) {
898 cs := g.groupContext.cipherSuite
899
900 membershipKey, err := cs.deriveSecret(g.epochSecret, secretLabelMembership)
901 if err != nil {
902 return nil, err
903 }
904 if err := pubMsg.signMembershipTag(cs, membershipKey, &g.groupContext); err != nil {
905 return nil, err
906 }
907 return marshalRaw(&mlsMessage{
908 version: protocolVersionMLS10,
909 wireFormat: wireFormatMLSPublicMessage,
910 publicMessage: pubMsg,
911 })
912 }
913
914