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

LukaJCB / ts-mls / 20887934276

11 Jan 2026 02:12AM UTC coverage: 95.727% (+0.06%) from 95.665%
20887934276

push

github

LukaJCB
Update to vitest v4

409 of 417 branches covered (98.08%)

Branch coverage included in aggregate %.

2369 of 2485 relevant lines covered (95.33%)

80736.06 hits per line

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

96.97
/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 { SenderNonMember } from "./sender.js"
21

22
export interface ProtectProposalPublicResult {
23
  publicMessage: PublicMessage
24
}
25

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

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

52
  const auth = await signFramedContentApplicationOrProposal(signKey, tbs, cs)
45✔
53

54
  const authenticatedContent: AuthenticatedContent = {
45✔
55
    wireformat: "mls_public_message",
56
    content: framedContent,
57
    auth,
58
  }
59

60
  const msg = await protectPublicMessage(membershipKey, groupContext, authenticatedContent, cs)
45✔
61

62
  return { publicMessage: msg }
45✔
63
}
64

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

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

90
  const auth = await signFramedContentApplicationOrProposal(signKey, tbs, cs)
38✔
91

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

98
  return { publicMessage: msg }
38✔
99
}
100

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

109
  if (content.content.sender.senderType == "member") {
166✔
110
    const authenticatedContent: AuthenticatedContentTBM = {
128✔
111
      contentTbs: toTbs(content.content, "mls_public_message", groupContext),
112
      auth: content.auth,
113
    }
114

115
    const tag = await createMembershipTag(membershipKey, authenticatedContent, cs.hash)
128✔
116
    return {
128✔
117
      content: content.content,
118
      auth: content.auth,
119
      senderType: "member",
120
      membershipTag: tag,
121
    }
122
  }
123

124
  return {
38✔
125
    content: content.content,
126
    auth: content.auth,
127
    senderType: content.content.sender.senderType,
128
  }
129
}
130

131
export interface ProtectCommitPublicResult {
132
  publicMessage: PublicMessage
133
}
134

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

145
  if (msg.senderType === "member") {
2,302✔
146
    const authenticatedContent: AuthenticatedContentTBM = {
2,150✔
147
      contentTbs: toTbs(msg.content, "mls_public_message", groupContext),
148
      auth: msg.auth,
149
    }
150

151
    if (!(await verifyMembershipTag(membershipKey, authenticatedContent, msg.membershipTag, cs.hash)))
2,150✔
152
      throw new CryptoVerificationError("Could not verify membership")
×
153
  }
154

155
  const signaturePublicKey =
156
    overrideSignatureKey !== undefined
2,302✔
157
      ? overrideSignatureKey
158
      : findSignaturePublicKey(ratchetTree, groupContext, msg.content)
159

160
  const signatureValid = await verifyFramedContentSignature(
2,302✔
161
    signaturePublicKey,
162
    "mls_public_message",
163
    msg.content,
164
    msg.auth,
165
    groupContext,
166
    cs.signature,
167
  )
168

169
  if (!signatureValid) throw new CryptoVerificationError("Signature invalid")
2,302✔
170

171
  return {
2,302✔
172
    wireformat: "mls_public_message",
173
    content: msg.content,
174
    auth: msg.auth,
175
  }
176
}
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