• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LukaJCB / ts-mls / 20944663222

13 Jan 2026 04:25AM UTC coverage: 95.199% (-0.5%) from 95.727%
20944663222

Pull #200

github

web-flow
Merge f4c44cc2f into 6f65b753e
Pull Request #200: Use CiphersuiteId instead of CiphersuiteName for internal values

412 of 421 branches covered (97.86%)

Branch coverage included in aggregate %.

193 of 208 new or added lines in 35 files covered. (92.79%)

6 existing lines in 3 files now uncovered.

2364 of 2495 relevant lines covered (94.75%)

73683.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.29
/src/messageProtectionPublic.ts
1
import {
2
  AuthenticatedContent,
3
  AuthenticatedContentProposalOrCommit,
4
  AuthenticatedContentTBM,
5
  createMembershipTag,
6
  verifyMembershipTag,
7
} from "./authenticatedContent.js"
8
import { CiphersuiteImpl } from "./crypto/ciphersuite.js"
9
import {
10
  FramedContent,
11
  signFramedContentApplicationOrProposal,
12
  toTbs,
13
  verifyFramedContentSignature,
14
} from "./framedContent.js"
15
import { GroupContext } from "./groupContext.js"
16
import { CryptoVerificationError, UsageError } from "./mlsError.js"
17
import { Proposal } from "./proposal.js"
18
import { ExternalPublicMessage, findSignaturePublicKey, PublicMessage } from "./publicMessage.js"
19
import { RatchetTree } from "./ratchetTree.js"
20
import { senderTypes, SenderNonMember } from "./sender.js"
21
import { contentTypes } from "./contentType.js"
22
import { wireformats } from "./wireformat.js"
23

24
export interface ProtectProposalPublicResult {
25
  publicMessage: PublicMessage
26
}
27

28
export async function protectProposalPublic(
29
  signKey: Uint8Array,
30
  membershipKey: Uint8Array,
31
  groupContext: GroupContext,
32
  authenticatedData: Uint8Array,
33
  proposal: Proposal,
34
  leafIndex: number,
35
  cs: CiphersuiteImpl,
36
): Promise<ProtectProposalPublicResult> {
37
  const framedContent: FramedContent = {
45✔
38
    groupId: groupContext.groupId,
39
    epoch: groupContext.epoch,
40
    sender: { senderType: senderTypes.member, leafIndex },
41
    contentType: contentTypes.proposal,
42
    authenticatedData,
43
    proposal,
44
  }
45

46
  const tbs = {
45✔
47
    protocolVersion: groupContext.version,
48
    wireformat: wireformats.mls_public_message,
49
    content: framedContent,
50
    senderType: senderTypes.member,
51
    context: groupContext,
52
  } as const
53

54
  const auth = await signFramedContentApplicationOrProposal(signKey, tbs, cs)
45✔
55

56
  const authenticatedContent: AuthenticatedContent = {
45✔
57
    wireformat: wireformats.mls_public_message,
58
    content: framedContent,
59
    auth,
60
  }
61

62
  const msg = await protectPublicMessage(membershipKey, groupContext, authenticatedContent, cs)
45✔
63

64
  return { publicMessage: msg }
45✔
65
}
66

67
export async function protectExternalProposalPublic(
68
  signKey: Uint8Array,
69
  groupContext: GroupContext,
70
  authenticatedData: Uint8Array,
71
  proposal: Proposal,
72
  sender: SenderNonMember,
73
  cs: CiphersuiteImpl,
74
): Promise<ProtectProposalPublicResult> {
75
  const framedContent: FramedContent = {
38✔
76
    groupId: groupContext.groupId,
77
    epoch: groupContext.epoch,
78
    sender,
79
    contentType: contentTypes.proposal,
80
    authenticatedData,
81
    proposal,
82
  }
83

84
  const tbs = {
38✔
85
    protocolVersion: groupContext.version,
86
    wireformat: wireformats.mls_public_message,
87
    content: framedContent,
88
    senderType: sender.senderType,
89
    context: groupContext,
90
  } as const
91

92
  const auth = await signFramedContentApplicationOrProposal(signKey, tbs, cs)
38✔
93

94
  const msg: ExternalPublicMessage = {
38✔
95
    content: framedContent,
96
    auth,
97
    senderType: sender.senderType,
98
  }
99

100
  return { publicMessage: msg }
38✔
101
}
102

103
export async function protectPublicMessage(
104
  membershipKey: Uint8Array,
105
  groupContext: GroupContext,
106
  content: AuthenticatedContent,
107
  cs: CiphersuiteImpl,
108
): Promise<PublicMessage> {
109
  if (content.content.contentType === contentTypes.application)
173✔
110
    throw new UsageError("Can't make an application message public")
7✔
111

112
  if (content.content.sender.senderType === senderTypes.member) {
166✔
113
    const authenticatedContent: AuthenticatedContentTBM = {
128✔
114
      contentTbs: toTbs(content.content, wireformats.mls_public_message, groupContext),
115
      auth: content.auth,
116
    }
117

118
    const tag = await createMembershipTag(membershipKey, authenticatedContent, cs.hash)
128✔
119
    return {
128✔
120
      content: content.content,
121
      auth: content.auth,
122
      senderType: senderTypes.member,
123
      membershipTag: tag,
124
    }
125
  }
126

127
  return {
38✔
128
    content: content.content,
129
    auth: content.auth,
130
    senderType: content.content.sender.senderType,
131
  }
132
}
133

134
export interface ProtectCommitPublicResult {
135
  publicMessage: PublicMessage
136
}
137

138
export async function unprotectPublicMessage(
139
  membershipKey: Uint8Array,
140
  groupContext: GroupContext,
141
  ratchetTree: RatchetTree,
142
  msg: PublicMessage,
143
  cs: CiphersuiteImpl,
144
  overrideSignatureKey?: Uint8Array,
145
): Promise<AuthenticatedContentProposalOrCommit> {
146
  if (msg.content.contentType === contentTypes.application)
2,302✔
NEW
147
    throw new UsageError("Can't make an application message public")
×
148

149
  if (msg.senderType === senderTypes.member) {
2,302✔
150
    const authenticatedContent: AuthenticatedContentTBM = {
2,150✔
151
      contentTbs: toTbs(msg.content, wireformats.mls_public_message, groupContext),
152
      auth: msg.auth,
153
    }
154

155
    if (!(await verifyMembershipTag(membershipKey, authenticatedContent, msg.membershipTag, cs.hash)))
2,150✔
156
      throw new CryptoVerificationError("Could not verify membership")
×
157
  }
158

159
  const signaturePublicKey =
160
    overrideSignatureKey !== undefined
2,302✔
161
      ? overrideSignatureKey
162
      : findSignaturePublicKey(ratchetTree, groupContext, msg.content)
163

164
  const signatureValid = await verifyFramedContentSignature(
2,302✔
165
    signaturePublicKey,
166
    wireformats.mls_public_message,
167
    msg.content,
168
    msg.auth,
169
    groupContext,
170
    cs.signature,
171
  )
172

173
  if (!signatureValid) throw new CryptoVerificationError("Signature invalid")
2,302✔
174

175
  return {
2,302✔
176
    wireformat: wireformats.mls_public_message,
177
    content: msg.content,
178
    auth: msg.auth,
179
  }
180
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc