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