• 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

91.46
/src/leafNode.ts
1
import { Capabilities, capabilitiesEncoder, decodeCapabilities } from "./capabilities.js"
2
import { decodeUint32, uint32Encoder } from "./codec/number.js"
3
import { Decoder, mapDecoders, flatMapDecoder, mapDecoderOption, mapDecoder } from "./codec/tlsDecoder.js"
4
import { BufferEncoder, contramapBufferEncoders, encode, Encoder } from "./codec/tlsEncoder.js"
5
import { varLenDataEncoder, decodeVarLenData, varLenTypeEncoder, decodeVarLenType } from "./codec/variableLength.js"
6
import { credentialEncoder, decodeCredential, Credential } from "./credential.js"
7
import { Signature, signWithLabel, verifyWithLabel } from "./crypto/signature.js"
8
import { Extension, extensionEncoder, decodeExtension } from "./extension.js"
9
import { leafNodeSourceEncoder, decodeLeafNodeSource } from "./leafNodeSource.js"
10
import { Lifetime, lifetimeEncoder, decodeLifetime } from "./lifetime.js"
11

12
export interface LeafNodeData {
13
  hpkePublicKey: Uint8Array
14
  signaturePublicKey: Uint8Array
15
  credential: Credential
16
  capabilities: Capabilities
17
}
18

19
export const leafNodeDataEncoder: BufferEncoder<LeafNodeData> = contramapBufferEncoders(
3✔
20
  [varLenDataEncoder, varLenDataEncoder, credentialEncoder, capabilitiesEncoder],
21
  (data) => [data.hpkePublicKey, data.signaturePublicKey, data.credential, data.capabilities] as const,
62,568✔
22
)
23

24
export const encodeLeafNodeData: Encoder<LeafNodeData> = encode(leafNodeDataEncoder)
3✔
25

26
export const decodeLeafNodeData: Decoder<LeafNodeData> = mapDecoders(
3✔
27
  [decodeVarLenData, decodeVarLenData, decodeCredential, decodeCapabilities],
28
  (hpkePublicKey, signaturePublicKey, credential, capabilities) => ({
4,933✔
29
    hpkePublicKey,
30
    signaturePublicKey,
31
    credential,
32
    capabilities,
33
  }),
34
)
35

36
export type LeafNodeInfoOmitted = LeafNodeInfoKeyPackage | LeafNodeInfoUpdateOmitted | LeafNodeInfoCommitOmitted
37

38
export interface LeafNodeInfoUpdateOmitted {
39
  leafNodeSource: "update"
40
  extensions: Extension[]
41
}
42
export interface LeafNodeInfoCommitOmitted {
43
  leafNodeSource: "commit"
44
  parentHash: Uint8Array
45
  extensions: Extension[]
46
}
47

48
export interface LeafNodeInfoKeyPackage {
49
  leafNodeSource: "key_package"
50
  lifetime: Lifetime
51
  extensions: Extension[]
52
}
53

54
export const leafNodeInfoKeyPackageEncoder: BufferEncoder<LeafNodeInfoKeyPackage> = contramapBufferEncoders(
3✔
55
  [leafNodeSourceEncoder, lifetimeEncoder, varLenTypeEncoder(extensionEncoder)],
56
  (info) => ["key_package", info.lifetime, info.extensions] as const,
57,489✔
57
)
58

59
export const encodeLeafNodeInfoKeyPackage: Encoder<LeafNodeInfoKeyPackage> = encode(leafNodeInfoKeyPackageEncoder)
3✔
60

61
export const leafNodeInfoUpdateOmittedEncoder: BufferEncoder<LeafNodeInfoUpdateOmitted> = contramapBufferEncoders(
3✔
62
  [leafNodeSourceEncoder, varLenTypeEncoder(extensionEncoder)],
63
  (i) => [i.leafNodeSource, i.extensions] as const,
401✔
64
)
65

66
export const encodeLeafNodeInfoUpdateOmitted: Encoder<LeafNodeInfoUpdate> = encode(leafNodeInfoUpdateOmittedEncoder)
3✔
67

68
export const leafNodeInfoCommitOmittedEncoder: BufferEncoder<LeafNodeInfoCommitOmitted> = contramapBufferEncoders(
3✔
69
  [leafNodeSourceEncoder, varLenDataEncoder, varLenTypeEncoder(extensionEncoder)],
70
  (info) => [info.leafNodeSource, info.parentHash, info.extensions] as const,
68,765✔
71
)
72

73
export const encodeLeafNodeInfoCommitOmitted: Encoder<LeafNodeInfoCommitOmitted> = encode(
3✔
74
  leafNodeInfoCommitOmittedEncoder,
75
)
76

77
export const leafNodeInfoOmittedEncoder: BufferEncoder<LeafNodeInfoOmitted> = (info) => {
3✔
78
  switch (info.leafNodeSource) {
130,206✔
79
    case "key_package":
80
      return leafNodeInfoKeyPackageEncoder(info)
65,042✔
81
    case "update":
82
      return leafNodeInfoUpdateOmittedEncoder(info)
387✔
83
    case "commit":
84
      return leafNodeInfoCommitOmittedEncoder(info)
64,777✔
85
  }
86
}
87

88
export const encodeLeafNodeInfoOmitted: Encoder<LeafNodeInfoOmitted> = encode(leafNodeInfoOmittedEncoder)
3✔
89

90
export const decodeLeafNodeInfoKeyPackage: Decoder<LeafNodeInfoKeyPackage> = mapDecoders(
3✔
91
  [decodeLifetime, decodeVarLenType(decodeExtension)],
92
  (lifetime, extensions) => ({
5,510✔
93
    leafNodeSource: "key_package",
94
    lifetime,
95
    extensions,
96
  }),
97
)
98

99
export const decodeLeafNodeInfoUpdateOmitted: Decoder<LeafNodeInfoUpdateOmitted> = mapDecoder(
3✔
100
  decodeVarLenType(decodeExtension),
101
  (extensions) => ({
316✔
102
    leafNodeSource: "update",
103
    extensions,
104
  }),
105
)
106

107
export const decodeLeafNodeInfoCommitOmitted: Decoder<LeafNodeInfoCommitOmitted> = mapDecoders(
3✔
108
  [decodeVarLenData, decodeVarLenType(decodeExtension)],
109
  (parentHash, extensions) => ({
2,006✔
110
    leafNodeSource: "commit",
111
    parentHash,
112
    extensions,
113
  }),
114
)
115

116
export const decodeLeafNodeInfoOmitted: Decoder<LeafNodeInfoOmitted> = flatMapDecoder(
3✔
117
  decodeLeafNodeSource,
118
  (leafNodeSource): Decoder<LeafNodeInfoOmitted> => {
119
    switch (leafNodeSource) {
11,119✔
120
      case "key_package":
121
        return decodeLeafNodeInfoKeyPackage
6,249✔
122
      case "update":
123
        return decodeLeafNodeInfoUpdateOmitted
316✔
124
      case "commit":
125
        return decodeLeafNodeInfoCommitOmitted
4,554✔
126
    }
127
  },
128
)
129

130
export type LeafNodeInfo = LeafNodeInfoKeyPackage | LeafNodeInfoUpdate | LeafNodeInfoCommit
131

132
export interface LeafNodeInfoKeyPackage {
133
  leafNodeSource: "key_package"
134
  lifetime: Lifetime
135
  extensions: Extension[]
136
}
137

138
export type LeafNodeInfoUpdate = LeafNodeInfoUpdateOmitted & {
139
  groupId: Uint8Array
140
  leafIndex: number
141
}
142
export type LeafNodeInfoCommit = LeafNodeInfoCommitOmitted & {
143
  groupId: Uint8Array
144
  leafIndex: number
145
}
146

147
export const leafNodeInfoUpdateEncoder: BufferEncoder<LeafNodeInfoUpdate> = contramapBufferEncoders(
3✔
148
  [leafNodeInfoUpdateOmittedEncoder, varLenDataEncoder, uint32Encoder],
149
  (i) => [i, i.groupId, i.leafIndex] as const,
14✔
150
)
151

152
export const encodeLeafNodeInfoUpdate: Encoder<LeafNodeInfoUpdate> = encode(leafNodeInfoUpdateEncoder)
3✔
153

154
export const leafNodeInfoCommitEncoder: BufferEncoder<LeafNodeInfoCommit> = contramapBufferEncoders(
3✔
155
  [leafNodeInfoCommitOmittedEncoder, varLenDataEncoder, uint32Encoder],
156
  (info) => [info, info.groupId, info.leafIndex] as const,
4,588✔
157
)
158

159
export const encodeLeafNodeInfoCommit: Encoder<LeafNodeInfoCommit> = encode(leafNodeInfoCommitEncoder)
3✔
160

161
export const leafNodeInfoEncoder: BufferEncoder<LeafNodeInfo> = (info) => {
3✔
162
  switch (info.leafNodeSource) {
14,223✔
163
    case "key_package":
164
      return leafNodeInfoKeyPackageEncoder(info)
9,619✔
165
    case "update":
166
      return leafNodeInfoUpdateEncoder(info)
14✔
167
    case "commit":
168
      return leafNodeInfoCommitEncoder(info)
4,590✔
169
  }
170
}
171

172
export const encodeLeafNodeInfo: Encoder<LeafNodeInfo> = encode(leafNodeInfoEncoder)
3✔
173

174
export const decodeLeafNodeInfoUpdate: Decoder<LeafNodeInfoUpdate> = mapDecoders(
3✔
175
  [decodeLeafNodeInfoUpdateOmitted, decodeVarLenData, decodeUint32],
176
  (ln, groupId, leafIndex) => ({
×
177
    ...ln,
178
    groupId,
179
    leafIndex,
180
  }),
181
)
182

183
export const decodeLeafNodeInfoCommit: Decoder<LeafNodeInfoCommit> = mapDecoders(
3✔
184
  [decodeLeafNodeInfoCommitOmitted, decodeVarLenData, decodeUint32],
185
  (ln, groupId, leafIndex) => ({
×
186
    ...ln,
187
    groupId,
188
    leafIndex,
189
  }),
190
)
191

192
export const decodeLeafNodeInfo: Decoder<LeafNodeInfo> = flatMapDecoder(
3✔
193
  decodeLeafNodeSource,
194
  (leafNodeSource): Decoder<LeafNodeInfo> => {
195
    switch (leafNodeSource) {
×
196
      case "key_package":
197
        return decodeLeafNodeInfoKeyPackage
×
198
      case "update":
199
        return decodeLeafNodeInfoUpdate
×
200
      case "commit":
201
        return decodeLeafNodeInfoCommit
×
202
    }
203
  },
204
)
205

206
export type LeafNodeTBS = LeafNodeData & LeafNodeInfo
207

208
export type LeafNodeTBSCommit = LeafNodeData & LeafNodeInfoCommit
209

210
export type LeafNodeTBSKeyPackage = LeafNodeData & LeafNodeInfoKeyPackage
211

212
export const leafNodeTBSEncoder: BufferEncoder<LeafNodeTBS> = contramapBufferEncoders(
3✔
213
  [leafNodeDataEncoder, leafNodeInfoEncoder],
214
  (tbs) => [tbs, tbs] as const,
2,492✔
215
)
216

217
export const encodeLeafNodeTBS: Encoder<LeafNodeTBS> = encode(leafNodeTBSEncoder)
3✔
218

219
export type LeafNode = LeafNodeData & LeafNodeInfoOmitted & { signature: Uint8Array }
220

221
export const leafNodeEncoder: BufferEncoder<LeafNode> = contramapBufferEncoders(
3✔
222
  [leafNodeDataEncoder, leafNodeInfoOmittedEncoder, varLenDataEncoder],
223
  (leafNode) => [leafNode, leafNode, leafNode.signature] as const,
104,288✔
224
)
225

226
export const encodeLeafNode: Encoder<LeafNode> = encode(leafNodeEncoder)
3✔
227

228
export const decodeLeafNode: Decoder<LeafNode> = mapDecoders(
3✔
229
  [decodeLeafNodeData, decodeLeafNodeInfoOmitted, decodeVarLenData],
230
  (data, info, signature) => ({
3,804✔
231
    ...data,
232
    ...info,
233
    signature,
234
  }),
235
)
236

237
export type LeafNodeKeyPackage = LeafNode & { leafNodeSource: "key_package" }
238

239
export const decodeLeafNodeKeyPackage: Decoder<LeafNodeKeyPackage> = mapDecoderOption(decodeLeafNode, (ln) =>
3✔
240
  ln.leafNodeSource === "key_package" ? ln : undefined,
3,094✔
241
)
242

243
export type LeafNodeCommit = LeafNode & { leafNodeSource: "commit" }
244

245
export const decodeLeafNodeCommit: Decoder<LeafNodeCommit> = mapDecoderOption(decodeLeafNode, (ln) =>
3✔
246
  ln.leafNodeSource === "commit" ? ln : undefined,
2,987✔
247
)
248

249
export type LeafNodeUpdate = LeafNode & { leafNodeSource: "update" }
250

251
export const decodeLeafNodeUpdate: Decoder<LeafNodeUpdate> = mapDecoderOption(decodeLeafNode, (ln) =>
3✔
252
  ln.leafNodeSource === "update" ? ln : undefined,
315✔
253
)
254

255
function toTbs(leafNode: LeafNode, groupId: Uint8Array, leafIndex: number): LeafNodeTBS {
256
  switch (leafNode.leafNodeSource) {
3,584✔
257
    case "key_package":
258
      return { ...leafNode, leafNodeSource: leafNode.leafNodeSource }
119✔
259
    case "update":
260
      return { ...leafNode, leafNodeSource: leafNode.leafNodeSource, groupId, leafIndex }
14✔
261
    case "commit":
262
      return { ...leafNode, leafNodeSource: leafNode.leafNodeSource, groupId, leafIndex }
3,451✔
263
  }
264
}
265

266
export async function signLeafNodeCommit(
267
  tbs: LeafNodeTBSCommit,
268
  signaturePrivateKey: Uint8Array,
269
  sig: Signature,
270
): Promise<LeafNodeCommit> {
271
  return {
1,139✔
272
    ...tbs,
273
    signature: await signWithLabel(signaturePrivateKey, "LeafNodeTBS", encode(leafNodeTBSEncoder)(tbs), sig),
274
  }
275
}
276

277
export async function signLeafNodeKeyPackage(
278
  tbs: LeafNodeTBSKeyPackage,
279
  signaturePrivateKey: Uint8Array,
280
  sig: Signature,
281
): Promise<LeafNodeKeyPackage> {
282
  return {
1,206✔
283
    ...tbs,
284
    signature: await signWithLabel(signaturePrivateKey, "LeafNodeTBS", encode(leafNodeTBSEncoder)(tbs), sig),
285
  }
286
}
287

288
export function verifyLeafNodeSignature(
289
  leaf: LeafNode,
290
  groupId: Uint8Array,
291
  leafIndex: number,
292
  sig: Signature,
293
): Promise<boolean> {
294
  return verifyWithLabel(
3,584✔
295
    leaf.signaturePublicKey,
296
    "LeafNodeTBS",
297
    encode(leafNodeTBSEncoder)(toTbs(leaf, groupId, leafIndex)),
298
    leaf.signature,
299
    sig,
300
  )
301
}
302

303
export function verifyLeafNodeSignatureKeyPackage(leaf: LeafNodeKeyPackage, sig: Signature): Promise<boolean> {
304
  return verifyWithLabel(leaf.signaturePublicKey, "LeafNodeTBS", encode(leafNodeTBSEncoder)(leaf), leaf.signature, sig)
5,935✔
305
}
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