@bcts/spqr 1.0.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +11 -0
- package/dist/index.cjs +4321 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +115 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +115 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +4318 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +4312 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -0
- package/src/authenticator.ts +163 -0
- package/src/chain.ts +522 -0
- package/src/constants.ts +90 -0
- package/src/encoding/gf.ts +190 -0
- package/src/encoding/index.ts +15 -0
- package/src/encoding/polynomial.ts +657 -0
- package/src/error.ts +75 -0
- package/src/incremental-mlkem768.ts +546 -0
- package/src/index.ts +415 -0
- package/src/kdf.ts +34 -0
- package/src/proto/index.ts +1376 -0
- package/src/proto/pq-ratchet-types.ts +195 -0
- package/src/types.ts +81 -0
- package/src/util.ts +61 -0
- package/src/v1/chunked/index.ts +60 -0
- package/src/v1/chunked/message.ts +257 -0
- package/src/v1/chunked/send-ct.ts +352 -0
- package/src/v1/chunked/send-ek.ts +285 -0
- package/src/v1/chunked/serialize.ts +278 -0
- package/src/v1/chunked/states.ts +399 -0
- package/src/v1/index.ts +9 -0
- package/src/v1/unchunked/index.ts +20 -0
- package/src/v1/unchunked/send-ct.ts +231 -0
- package/src/v1/unchunked/send-ek.ts +177 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2025 Signal Messenger, LLC
|
|
3
|
+
* Copyright © 2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
* Top-level public API for the SPQR protocol.
|
|
6
|
+
*
|
|
7
|
+
* Matches Signal's Rust `lib.rs` interface. All state is serialized as
|
|
8
|
+
* opaque protobuf bytes (Uint8Array) so that callers never need to touch
|
|
9
|
+
* internal types.
|
|
10
|
+
*
|
|
11
|
+
* Exported functions:
|
|
12
|
+
* - emptyState() -> empty serialized state (V0)
|
|
13
|
+
* - initialState(p) -> create initial serialized state
|
|
14
|
+
* - send(state, rng) -> produce next message + advance state
|
|
15
|
+
* - recv(state, msg) -> consume incoming message + advance state
|
|
16
|
+
* - currentVersion(s) -> inspect version negotiation status
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
type States,
|
|
21
|
+
initA,
|
|
22
|
+
initB,
|
|
23
|
+
send as chunkedSend,
|
|
24
|
+
recv as chunkedRecv,
|
|
25
|
+
} from "./v1/chunked/index.js";
|
|
26
|
+
import { serializeMessage, deserializeMessage } from "./v1/chunked/message.js";
|
|
27
|
+
import { statesToPb, statesFromPb } from "./v1/chunked/serialize.js";
|
|
28
|
+
import { Chain } from "./chain.js";
|
|
29
|
+
import { encodePqRatchetState, decodePqRatchetState } from "./proto/index.js";
|
|
30
|
+
import type { PbPqRatchetState, PbVersionNegotiation } from "./proto/pq-ratchet-types.js";
|
|
31
|
+
import { SpqrError, SpqrErrorCode } from "./error.js";
|
|
32
|
+
import {
|
|
33
|
+
Version,
|
|
34
|
+
Direction,
|
|
35
|
+
type Params,
|
|
36
|
+
type Secret,
|
|
37
|
+
type Send,
|
|
38
|
+
type Recv,
|
|
39
|
+
type CurrentVersion,
|
|
40
|
+
type SerializedState,
|
|
41
|
+
type SerializedMessage,
|
|
42
|
+
type RandomBytes,
|
|
43
|
+
type ChainParams,
|
|
44
|
+
} from "./types.js";
|
|
45
|
+
|
|
46
|
+
// Re-export public types
|
|
47
|
+
export {
|
|
48
|
+
Version,
|
|
49
|
+
Direction,
|
|
50
|
+
type Params,
|
|
51
|
+
type Secret,
|
|
52
|
+
type Send,
|
|
53
|
+
type Recv,
|
|
54
|
+
type CurrentVersion,
|
|
55
|
+
type SerializedState,
|
|
56
|
+
type SerializedMessage,
|
|
57
|
+
type RandomBytes,
|
|
58
|
+
type ChainParams,
|
|
59
|
+
};
|
|
60
|
+
export { SpqrError, SpqrErrorCode } from "./error.js";
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// emptyState
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Return an empty (V0) serialized state.
|
|
68
|
+
*/
|
|
69
|
+
export function emptyState(): SerializedState {
|
|
70
|
+
return new Uint8Array(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// initialState
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create an initial serialized state from parameters.
|
|
79
|
+
*
|
|
80
|
+
* For V0, returns an empty state. For V1+, initializes the inner V1
|
|
81
|
+
* state machine and version negotiation.
|
|
82
|
+
*/
|
|
83
|
+
export function initialState(params: Params): SerializedState {
|
|
84
|
+
if (params.version === Version.V0) {
|
|
85
|
+
return emptyState();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Initialize the V1 inner state
|
|
89
|
+
const inner = initInner(params.version, params.direction, params.authKey);
|
|
90
|
+
|
|
91
|
+
// Build version negotiation
|
|
92
|
+
const versionNegotiation: PbVersionNegotiation = {
|
|
93
|
+
authKey: Uint8Array.from(params.authKey),
|
|
94
|
+
direction: params.direction,
|
|
95
|
+
minVersion: params.minVersion,
|
|
96
|
+
chainParams: {
|
|
97
|
+
maxJump: params.chainParams.maxJump,
|
|
98
|
+
maxOooKeys: params.chainParams.maxOooKeys,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const pbState: PbPqRatchetState = {
|
|
103
|
+
versionNegotiation,
|
|
104
|
+
chain: undefined,
|
|
105
|
+
v1: inner,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return encodePqRatchetState(pbState);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// send
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Produce the next outgoing message from the current state.
|
|
117
|
+
*
|
|
118
|
+
* Returns the updated state, serialized message, and optional message key.
|
|
119
|
+
*/
|
|
120
|
+
export function send(state: SerializedState, rng: RandomBytes): Send {
|
|
121
|
+
// V0: empty state passthrough
|
|
122
|
+
if (state.length === 0) {
|
|
123
|
+
return { state: new Uint8Array(0), msg: new Uint8Array(0), key: null };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const statePb = decodePqRatchetState(state);
|
|
127
|
+
|
|
128
|
+
if (statePb.v1 === undefined) {
|
|
129
|
+
// No V1 inner => V0
|
|
130
|
+
return { state: new Uint8Array(0), msg: new Uint8Array(0), key: null };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Deserialize runtime states from protobuf
|
|
134
|
+
const runtimeStates = statesFromPb(statePb.v1);
|
|
135
|
+
|
|
136
|
+
// Execute the chunked send
|
|
137
|
+
const sendResult = chunkedSend(runtimeStates, rng);
|
|
138
|
+
|
|
139
|
+
// Get or create chain
|
|
140
|
+
let chain: Chain | undefined;
|
|
141
|
+
if (statePb.chain !== undefined) {
|
|
142
|
+
chain = Chain.fromProto(statePb.chain);
|
|
143
|
+
} else if (statePb.versionNegotiation !== undefined) {
|
|
144
|
+
const vn = statePb.versionNegotiation;
|
|
145
|
+
if ((vn.minVersion as Version) > Version.V0) {
|
|
146
|
+
chain = chainFromVersionNegotiation(vn);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
throw new SpqrError(
|
|
150
|
+
"Chain not available and no version negotiation",
|
|
151
|
+
SpqrErrorCode.ChainNotAvailable,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let index: number;
|
|
156
|
+
let msgKey: Uint8Array;
|
|
157
|
+
let chainPb: typeof statePb.chain;
|
|
158
|
+
|
|
159
|
+
if (chain === undefined) {
|
|
160
|
+
// No chain (min_version === V0, still negotiating)
|
|
161
|
+
if (sendResult.key !== null) {
|
|
162
|
+
// Should not happen in V0 min_version case during negotiation
|
|
163
|
+
throw new SpqrError("Unexpected epoch secret without chain", SpqrErrorCode.ChainNotAvailable);
|
|
164
|
+
}
|
|
165
|
+
index = 0;
|
|
166
|
+
msgKey = new Uint8Array(0);
|
|
167
|
+
chainPb = undefined;
|
|
168
|
+
} else {
|
|
169
|
+
if (sendResult.key !== null) {
|
|
170
|
+
// Epoch secret epoch matches chain expectation:
|
|
171
|
+
// state machine epoch N maps directly to chain addEpoch(N)
|
|
172
|
+
// since chain.currentEpoch starts at 0 and expects N = currentEpoch + 1
|
|
173
|
+
chain.addEpoch(sendResult.key);
|
|
174
|
+
}
|
|
175
|
+
const msgEpoch = sendResult.msg.epoch - 1n;
|
|
176
|
+
const [sendIndex, sendKey] = chain.sendKey(msgEpoch);
|
|
177
|
+
index = sendIndex;
|
|
178
|
+
msgKey = sendKey;
|
|
179
|
+
chainPb = chain.toProto();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Serialize message
|
|
183
|
+
const serializedMsg = serializeMessage(sendResult.msg, index);
|
|
184
|
+
|
|
185
|
+
// Serialize updated state
|
|
186
|
+
const v1Pb = statesToPb(sendResult.state);
|
|
187
|
+
const newStatePb: PbPqRatchetState = {
|
|
188
|
+
versionNegotiation: statePb.versionNegotiation, // preserved on send
|
|
189
|
+
chain: chainPb,
|
|
190
|
+
v1: v1Pb,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
state: encodePqRatchetState(newStatePb),
|
|
195
|
+
msg: serializedMsg,
|
|
196
|
+
key: msgKey.length === 0 ? null : msgKey,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// recv
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Process an incoming message and transition the state.
|
|
206
|
+
*
|
|
207
|
+
* Returns the updated state and optional message key.
|
|
208
|
+
*/
|
|
209
|
+
export function recv(state: SerializedState, msg: SerializedMessage): Recv {
|
|
210
|
+
// V0: empty state passthrough
|
|
211
|
+
if (state.length === 0 && msg.length === 0) {
|
|
212
|
+
return { state: new Uint8Array(0), key: null };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Decode the pre-negotiated state
|
|
216
|
+
const prenegotiatedPb =
|
|
217
|
+
state.length === 0
|
|
218
|
+
? ({ v1: undefined, chain: undefined, versionNegotiation: undefined } as PbPqRatchetState)
|
|
219
|
+
: decodePqRatchetState(state);
|
|
220
|
+
|
|
221
|
+
// Determine message version
|
|
222
|
+
const msgVer = msgVersion(msg);
|
|
223
|
+
if (msgVer === undefined) {
|
|
224
|
+
// Unknown version, ignore the message
|
|
225
|
+
return { state: Uint8Array.from(state), key: null };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const stateVer = stateVersion(prenegotiatedPb);
|
|
229
|
+
|
|
230
|
+
// Version negotiation
|
|
231
|
+
let statePb: PbPqRatchetState;
|
|
232
|
+
if (msgVer >= stateVer) {
|
|
233
|
+
// Equal or greater version -- proceed with current state
|
|
234
|
+
statePb = prenegotiatedPb;
|
|
235
|
+
} else {
|
|
236
|
+
// Message version < state version -- negotiate down
|
|
237
|
+
const vn = prenegotiatedPb.versionNegotiation;
|
|
238
|
+
if (vn === undefined) {
|
|
239
|
+
throw new SpqrError(
|
|
240
|
+
`Version mismatch: state=${stateVer}, msg=${msgVer}, no negotiation available`,
|
|
241
|
+
SpqrErrorCode.VersionMismatch,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (msgVer < (vn.minVersion as Version)) {
|
|
245
|
+
throw new SpqrError(
|
|
246
|
+
`Minimum version not met: min=${vn.minVersion}, msg=${msgVer}`,
|
|
247
|
+
SpqrErrorCode.MinimumVersion,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Negotiate down to the message version
|
|
252
|
+
const inner = initInner(msgVer, vn.direction as Direction, Uint8Array.from(vn.authKey));
|
|
253
|
+
const chainResult = chainFrom(prenegotiatedPb.chain, vn);
|
|
254
|
+
statePb = {
|
|
255
|
+
v1: inner,
|
|
256
|
+
versionNegotiation: undefined, // disallow further negotiation
|
|
257
|
+
chain: chainResult,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Process the message
|
|
262
|
+
if (statePb.v1 === undefined) {
|
|
263
|
+
// V0 state
|
|
264
|
+
return { state: new Uint8Array(0), key: null };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Deserialize the message
|
|
268
|
+
const { msg: sckaMsg, index } = deserializeMessage(msg);
|
|
269
|
+
|
|
270
|
+
// Deserialize runtime states from protobuf
|
|
271
|
+
const runtimeStates = statesFromPb(statePb.v1);
|
|
272
|
+
|
|
273
|
+
// Execute the chunked recv
|
|
274
|
+
const recvResult = chunkedRecv(runtimeStates, sckaMsg);
|
|
275
|
+
|
|
276
|
+
// Chain key derivation
|
|
277
|
+
const msgKeyEpoch = sckaMsg.epoch - 1n;
|
|
278
|
+
const chainObj = chainFromState(statePb.chain, statePb.versionNegotiation);
|
|
279
|
+
|
|
280
|
+
if (recvResult.key !== null) {
|
|
281
|
+
// Epoch secret epoch matches chain expectation directly
|
|
282
|
+
chainObj.addEpoch(recvResult.key);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let msgKey: Uint8Array;
|
|
286
|
+
if (msgKeyEpoch === 0n && index === 0) {
|
|
287
|
+
// First message has no chain key
|
|
288
|
+
msgKey = new Uint8Array(0);
|
|
289
|
+
} else {
|
|
290
|
+
msgKey = chainObj.recvKey(msgKeyEpoch, index);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Serialize the updated state
|
|
294
|
+
const v1Pb = statesToPb(recvResult.state);
|
|
295
|
+
const newStatePb: PbPqRatchetState = {
|
|
296
|
+
versionNegotiation: undefined, // cleared on recv
|
|
297
|
+
chain: chainObj.toProto(),
|
|
298
|
+
v1: v1Pb,
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
state: encodePqRatchetState(newStatePb),
|
|
303
|
+
key: msgKey.length === 0 ? null : msgKey,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// currentVersion
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Inspect the current version negotiation status of a serialized state.
|
|
313
|
+
*/
|
|
314
|
+
export function currentVersion(state: SerializedState): CurrentVersion {
|
|
315
|
+
if (state.length === 0) {
|
|
316
|
+
return { type: "negotiation_complete", version: Version.V0 };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const statePb = decodePqRatchetState(state);
|
|
320
|
+
const version = statePb.v1 !== undefined ? Version.V1 : Version.V0;
|
|
321
|
+
|
|
322
|
+
if (statePb.versionNegotiation !== undefined) {
|
|
323
|
+
return {
|
|
324
|
+
type: "still_negotiating",
|
|
325
|
+
version,
|
|
326
|
+
minVersion: statePb.versionNegotiation.minVersion as Version,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return { type: "negotiation_complete", version };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// Internal helpers
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Initialize the V1 inner state based on version and direction.
|
|
338
|
+
*/
|
|
339
|
+
function initInner(
|
|
340
|
+
version: Version,
|
|
341
|
+
direction: Direction,
|
|
342
|
+
authKey: Uint8Array,
|
|
343
|
+
): PbPqRatchetState["v1"] {
|
|
344
|
+
if (version === Version.V0) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// V1
|
|
349
|
+
let states: States;
|
|
350
|
+
if (direction === Direction.A2B) {
|
|
351
|
+
states = initA(authKey);
|
|
352
|
+
} else {
|
|
353
|
+
states = initB(authKey);
|
|
354
|
+
}
|
|
355
|
+
return statesToPb(states);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Extract version from a serialized message.
|
|
360
|
+
* Empty msg -> V0. msg[0]: 0 -> V0, 1 -> V1, else undefined.
|
|
361
|
+
*/
|
|
362
|
+
function msgVersion(msg: SerializedMessage): Version | undefined {
|
|
363
|
+
if (msg.length === 0) return Version.V0;
|
|
364
|
+
const v = msg[0];
|
|
365
|
+
if (v === 0) return Version.V0;
|
|
366
|
+
if (v === 1) return Version.V1;
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Extract version from the decoded state.
|
|
372
|
+
* No v1 inner -> V0. Has v1 inner -> V1.
|
|
373
|
+
*/
|
|
374
|
+
function stateVersion(state: PbPqRatchetState): Version {
|
|
375
|
+
return state.v1 !== undefined ? Version.V1 : Version.V0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create a Chain from version negotiation parameters.
|
|
380
|
+
*/
|
|
381
|
+
function chainFromVersionNegotiation(vn: PbVersionNegotiation): Chain {
|
|
382
|
+
const chainParams: ChainParams = vn.chainParams ?? {
|
|
383
|
+
maxJump: 25000,
|
|
384
|
+
maxOooKeys: 2000,
|
|
385
|
+
};
|
|
386
|
+
return Chain.create(Uint8Array.from(vn.authKey), vn.direction as Direction, chainParams);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get or create a Chain from the existing chain proto and version negotiation.
|
|
391
|
+
* Prefers existing chain, falls back to creating from version negotiation.
|
|
392
|
+
*/
|
|
393
|
+
function chainFrom(
|
|
394
|
+
chainPb: PbPqRatchetState["chain"],
|
|
395
|
+
vn: PbVersionNegotiation | undefined,
|
|
396
|
+
): PbPqRatchetState["chain"] {
|
|
397
|
+
if (chainPb !== undefined) return chainPb;
|
|
398
|
+
if (vn !== undefined) return chainFromVersionNegotiation(vn).toProto();
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get a Chain object from state, creating from vn if needed.
|
|
404
|
+
*/
|
|
405
|
+
function chainFromState(
|
|
406
|
+
chainPb: PbPqRatchetState["chain"],
|
|
407
|
+
vn: PbVersionNegotiation | undefined,
|
|
408
|
+
): Chain {
|
|
409
|
+
if (chainPb !== undefined) return Chain.fromProto(chainPb);
|
|
410
|
+
if (vn !== undefined) return chainFromVersionNegotiation(vn);
|
|
411
|
+
throw new SpqrError(
|
|
412
|
+
"Chain not available and no version negotiation",
|
|
413
|
+
SpqrErrorCode.ChainNotAvailable,
|
|
414
|
+
);
|
|
415
|
+
}
|
package/src/kdf.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2025 Signal Messenger, LLC
|
|
3
|
+
* Copyright © 2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
* KDF wrappers for SPQR (HKDF-SHA256 and HMAC-SHA256).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
9
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
10
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* HKDF-SHA256 key derivation.
|
|
14
|
+
* @param ikm Input key material
|
|
15
|
+
* @param salt Salt (use ZERO_SALT for empty)
|
|
16
|
+
* @param info Context info string or bytes
|
|
17
|
+
* @param length Output length in bytes
|
|
18
|
+
*/
|
|
19
|
+
export function hkdfSha256(
|
|
20
|
+
ikm: Uint8Array,
|
|
21
|
+
salt: Uint8Array,
|
|
22
|
+
info: Uint8Array | string,
|
|
23
|
+
length: number,
|
|
24
|
+
): Uint8Array {
|
|
25
|
+
const infoBytes = typeof info === "string" ? new TextEncoder().encode(info) : info;
|
|
26
|
+
return hkdf(sha256, ikm, salt, infoBytes, length);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* HMAC-SHA256 computation.
|
|
31
|
+
*/
|
|
32
|
+
export function hmacSha256(key: Uint8Array, data: Uint8Array): Uint8Array {
|
|
33
|
+
return hmac(sha256, key, data);
|
|
34
|
+
}
|