@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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2025 Signal Messenger, LLC
|
|
3
|
+
* Copyright © 2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
* Unchunked send_ct state machine for SPQR V1.
|
|
6
|
+
*
|
|
7
|
+
* Ported from Signal's spqr crate: v1/unchunked/send_ct.rs
|
|
8
|
+
*
|
|
9
|
+
* True incremental ML-KEM-768 implementation (Phase 9):
|
|
10
|
+
*
|
|
11
|
+
* - sendCt1(rng) performs encaps1 using only the header, producing
|
|
12
|
+
* a REAL ct1 and shared secret. The epoch secret is derived here.
|
|
13
|
+
*
|
|
14
|
+
* - recvEk(ek) validates the encapsulation key against the header
|
|
15
|
+
* and stores it for encaps2.
|
|
16
|
+
*
|
|
17
|
+
* - sendCt2() calls encaps2(ek, es) to produce ct2, then MACs ct1||ct2.
|
|
18
|
+
*
|
|
19
|
+
* State transitions:
|
|
20
|
+
* NoHeaderReceived --recvHeader(epoch, hdr, mac)--> HeaderReceived
|
|
21
|
+
* HeaderReceived --sendCt1(rng)----------------> Ct1Sent + REAL ct1 + EpochSecret
|
|
22
|
+
* Ct1Sent --recvEk(ek)------------------> Ct1SentEkReceived
|
|
23
|
+
* Ct1SentEkReceived --sendCt2()-------------------> Ct2Sent + (ct2, mac)
|
|
24
|
+
* Ct2Sent (terminal -- caller creates next KeysUnsampled)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { type Authenticator } from "../../authenticator.js";
|
|
28
|
+
import { hkdfSha256 } from "../../kdf.js";
|
|
29
|
+
import { concat, bigintToBE8 } from "../../util.js";
|
|
30
|
+
import { ZERO_SALT, LABEL_SCKA_KEY } from "../../constants.js";
|
|
31
|
+
import { encaps1, encaps2, ekMatchesHeader } from "../../incremental-mlkem768.js";
|
|
32
|
+
import { SpqrError, SpqrErrorCode } from "../../error.js";
|
|
33
|
+
import type { Epoch, EpochSecret, RandomBytes } from "../../types.js";
|
|
34
|
+
|
|
35
|
+
// Pre-encode the SCKA label
|
|
36
|
+
const SCKA_KEY_LABEL = new TextEncoder().encode(LABEL_SCKA_KEY);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Derive the epoch secret from the KEM shared secret.
|
|
40
|
+
*
|
|
41
|
+
* HKDF-SHA256(ikm=sharedSecret, salt=ZERO_SALT,
|
|
42
|
+
* info=LABEL_SCKA_KEY || epoch_be8, length=32)
|
|
43
|
+
*
|
|
44
|
+
* Matches Rust: info = [b"Signal_PQCKA_V1_MLKEM768:SCKA Key", epoch.to_be_bytes()].concat()
|
|
45
|
+
*/
|
|
46
|
+
function deriveEpochSecret(epoch: Epoch, sharedSecret: Uint8Array): EpochSecret {
|
|
47
|
+
const info = concat(SCKA_KEY_LABEL, bigintToBE8(epoch));
|
|
48
|
+
const secret = hkdfSha256(sharedSecret, ZERO_SALT, info, 32);
|
|
49
|
+
return { epoch, secret };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Result of Ct1SentEkReceived.sendCt2 */
|
|
53
|
+
export interface SendCt2Result {
|
|
54
|
+
/** Next state (terminal for this epoch's send_ct) */
|
|
55
|
+
state: Ct2Sent;
|
|
56
|
+
/** The 128-byte second ciphertext fragment */
|
|
57
|
+
ct2: Uint8Array;
|
|
58
|
+
/** The HMAC-SHA256 MAC over the full ciphertext (ct1 || ct2) */
|
|
59
|
+
mac: Uint8Array;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- State: NoHeaderReceived ----
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Waiting to receive the header from the send_ek peer.
|
|
66
|
+
*/
|
|
67
|
+
export class NoHeaderReceived {
|
|
68
|
+
constructor(
|
|
69
|
+
public readonly epoch: Epoch,
|
|
70
|
+
public readonly auth: Authenticator,
|
|
71
|
+
) {}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Receive the header and verify its MAC.
|
|
75
|
+
*
|
|
76
|
+
* @param epoch - The epoch for this exchange (must match current epoch)
|
|
77
|
+
* @param hdr - The 64-byte public key header
|
|
78
|
+
* @param mac - The 32-byte HMAC-SHA256 MAC over the header
|
|
79
|
+
* @returns Next state
|
|
80
|
+
* @throws {AuthenticatorError} If the header MAC is invalid
|
|
81
|
+
*/
|
|
82
|
+
recvHeader(epoch: Epoch, hdr: Uint8Array, mac: Uint8Array): HeaderReceived {
|
|
83
|
+
// Verify header MAC
|
|
84
|
+
this.auth.verifyHdr(epoch, hdr, mac);
|
|
85
|
+
|
|
86
|
+
return new HeaderReceived(this.epoch, this.auth, hdr);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---- State: HeaderReceived ----
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The header has been received and verified. Ready to produce ct1.
|
|
94
|
+
*
|
|
95
|
+
* In the true incremental ML-KEM approach, sendCt1 performs encaps1
|
|
96
|
+
* using only the header (rho + H(ek)), producing REAL ct1 and shared
|
|
97
|
+
* secret. The epoch secret is derived here.
|
|
98
|
+
*/
|
|
99
|
+
export class HeaderReceived {
|
|
100
|
+
constructor(
|
|
101
|
+
public readonly epoch: Epoch,
|
|
102
|
+
public readonly auth: Authenticator,
|
|
103
|
+
public readonly hdr: Uint8Array,
|
|
104
|
+
) {}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate encapsulation randomness and produce REAL ct1.
|
|
108
|
+
*
|
|
109
|
+
* Performs encaps1 using the header to produce:
|
|
110
|
+
* - Real ct1 (960 bytes)
|
|
111
|
+
* - Real shared secret -> epoch secret
|
|
112
|
+
* - Encapsulation state for later encaps2
|
|
113
|
+
*
|
|
114
|
+
* The authenticator is updated with the derived epoch secret.
|
|
115
|
+
*
|
|
116
|
+
* @param rng - Random byte generator
|
|
117
|
+
* @returns [nextState, real_ct1, epochSecret]
|
|
118
|
+
*/
|
|
119
|
+
sendCt1(rng: RandomBytes): [Ct1Sent, Uint8Array, EpochSecret] {
|
|
120
|
+
const { ct1, es, sharedSecret } = encaps1(this.hdr, rng);
|
|
121
|
+
|
|
122
|
+
// Derive epoch secret (with epoch in HKDF info, matching Rust)
|
|
123
|
+
const epochSecret = deriveEpochSecret(this.epoch, sharedSecret);
|
|
124
|
+
|
|
125
|
+
// Update authenticator with the HKDF-derived secret at current epoch
|
|
126
|
+
const auth = this.auth.clone();
|
|
127
|
+
auth.update(this.epoch, epochSecret.secret);
|
|
128
|
+
|
|
129
|
+
const nextState = new Ct1Sent(this.epoch, auth, this.hdr, es, ct1);
|
|
130
|
+
|
|
131
|
+
return [nextState, ct1, epochSecret];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---- State: Ct1Sent ----
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Real ct1 has been produced. Waiting for the encapsulation key.
|
|
139
|
+
*
|
|
140
|
+
* Stores hdr, es(2080), and ct1(960) for the encaps2 phase.
|
|
141
|
+
*/
|
|
142
|
+
export class Ct1Sent {
|
|
143
|
+
constructor(
|
|
144
|
+
public readonly epoch: Epoch,
|
|
145
|
+
public readonly auth: Authenticator,
|
|
146
|
+
public readonly hdr: Uint8Array,
|
|
147
|
+
public readonly es: Uint8Array,
|
|
148
|
+
public readonly ct1: Uint8Array,
|
|
149
|
+
) {}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Receive the encapsulation key and validate it against the header.
|
|
153
|
+
*
|
|
154
|
+
* In the true incremental approach, this simply validates and stores
|
|
155
|
+
* the ek for later use in sendCt2. No encapsulation happens here.
|
|
156
|
+
*
|
|
157
|
+
* @param ek - The 1152-byte encapsulation key from the send_ek peer
|
|
158
|
+
* @returns Next state
|
|
159
|
+
* @throws {SpqrError} If the ek does not match the header
|
|
160
|
+
*/
|
|
161
|
+
recvEk(ek: Uint8Array): Ct1SentEkReceived {
|
|
162
|
+
if (!ekMatchesHeader(ek, this.hdr)) {
|
|
163
|
+
throw new SpqrError(
|
|
164
|
+
"Encapsulation key does not match header",
|
|
165
|
+
SpqrErrorCode.ErroneousDataReceived,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return new Ct1SentEkReceived(this.epoch, this.auth, this.es, ek, this.ct1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---- State: Ct1SentEkReceived ----
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The encapsulation key has been received and validated.
|
|
177
|
+
* Ready to send ct2.
|
|
178
|
+
*
|
|
179
|
+
* Stores es(2080), ek(1152), and ct1(960) for encaps2 + MAC.
|
|
180
|
+
*/
|
|
181
|
+
export class Ct1SentEkReceived {
|
|
182
|
+
constructor(
|
|
183
|
+
public readonly epoch: Epoch,
|
|
184
|
+
public readonly auth: Authenticator,
|
|
185
|
+
public readonly es: Uint8Array,
|
|
186
|
+
public readonly ek: Uint8Array,
|
|
187
|
+
public readonly ct1: Uint8Array,
|
|
188
|
+
) {}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Produce ct2 by calling encaps2, then MAC over ct1 || ct2.
|
|
192
|
+
*
|
|
193
|
+
* @returns Result with next state, ct2, and MAC
|
|
194
|
+
*/
|
|
195
|
+
sendCt2(): SendCt2Result {
|
|
196
|
+
// encaps2 produces ct2 only
|
|
197
|
+
const ct2 = encaps2(this.ek, this.es);
|
|
198
|
+
|
|
199
|
+
// MAC over the full ciphertext: ct1 || ct2
|
|
200
|
+
const fullCt = concat(this.ct1, ct2);
|
|
201
|
+
const mac = this.auth.macCt(this.epoch, fullCt);
|
|
202
|
+
|
|
203
|
+
const state = new Ct2Sent(this.epoch, this.auth);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
state,
|
|
207
|
+
ct2,
|
|
208
|
+
mac,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---- State: Ct2Sent ----
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Terminal state for this epoch's send_ct exchange.
|
|
217
|
+
*
|
|
218
|
+
* The caller is responsible for creating the next epoch's
|
|
219
|
+
* send_ek::KeysUnsampled state from the epoch and auth.
|
|
220
|
+
*/
|
|
221
|
+
export class Ct2Sent {
|
|
222
|
+
constructor(
|
|
223
|
+
public readonly epoch: Epoch,
|
|
224
|
+
public readonly auth: Authenticator,
|
|
225
|
+
) {}
|
|
226
|
+
|
|
227
|
+
/** The next epoch for the send_ek side */
|
|
228
|
+
get nextEpoch(): Epoch {
|
|
229
|
+
return this.epoch + 1n;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2025 Signal Messenger, LLC
|
|
3
|
+
* Copyright © 2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
* Unchunked send_ek state machine for SPQR V1.
|
|
6
|
+
*
|
|
7
|
+
* Ported from Signal's spqr crate: v1/unchunked/send_ek.rs
|
|
8
|
+
*
|
|
9
|
+
* The send_ek side generates an ML-KEM-768 keypair, sends the public key
|
|
10
|
+
* header (hdr) and encapsulation key (ek), then receives ct1 and ct2 from
|
|
11
|
+
* the send_ct peer. On receiving ct2, it performs decapsulation to derive
|
|
12
|
+
* the shared epoch secret.
|
|
13
|
+
*
|
|
14
|
+
* State transitions:
|
|
15
|
+
* KeysUnsampled --sendHeader(rng)--> HeaderSent + (hdr, mac)
|
|
16
|
+
* HeaderSent --sendEk()---------> EkSent + ek
|
|
17
|
+
* EkSent --recvCt1(ct1)-----> EkSentCt1Received
|
|
18
|
+
* EkSentCt1Received --recvCt2(ct2, mac)--> RecvCt2Result (epoch, auth, epochSecret)
|
|
19
|
+
*
|
|
20
|
+
* The caller is responsible for constructing the next-epoch send_ct state
|
|
21
|
+
* (NoHeaderReceived) from the RecvCt2Result, avoiding circular imports.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { type Authenticator } from "../../authenticator.js";
|
|
25
|
+
import { hkdfSha256 } from "../../kdf.js";
|
|
26
|
+
import { concat, bigintToBE8 } from "../../util.js";
|
|
27
|
+
import { ZERO_SALT, LABEL_SCKA_KEY } from "../../constants.js";
|
|
28
|
+
import { generate, decaps } from "../../incremental-mlkem768.js";
|
|
29
|
+
import type { Epoch, EpochSecret, RandomBytes } from "../../types.js";
|
|
30
|
+
|
|
31
|
+
// Pre-encode the SCKA label
|
|
32
|
+
const SCKA_KEY_LABEL = new TextEncoder().encode(LABEL_SCKA_KEY);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Derive the epoch secret from the KEM shared secret.
|
|
36
|
+
*
|
|
37
|
+
* HKDF-SHA256(ikm=sharedSecret, salt=ZERO_SALT,
|
|
38
|
+
* info=LABEL_SCKA_KEY || epoch_be8, length=32)
|
|
39
|
+
*
|
|
40
|
+
* Matches Rust: info = [b"Signal_PQCKA_V1_MLKEM768:SCKA Key", epoch.to_be_bytes()].concat()
|
|
41
|
+
*/
|
|
42
|
+
function deriveEpochSecret(epoch: Epoch, sharedSecret: Uint8Array): EpochSecret {
|
|
43
|
+
const info = concat(SCKA_KEY_LABEL, bigintToBE8(epoch));
|
|
44
|
+
const secret = hkdfSha256(sharedSecret, ZERO_SALT, info, 32);
|
|
45
|
+
return { epoch, secret };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Result of EkSentCt1Received.recvCt2 */
|
|
49
|
+
export interface RecvCt2Result {
|
|
50
|
+
/** The next epoch (current + 1) */
|
|
51
|
+
nextEpoch: Epoch;
|
|
52
|
+
/** The updated authenticator for the next epoch */
|
|
53
|
+
auth: Authenticator;
|
|
54
|
+
/** The derived epoch secret for chain advancement */
|
|
55
|
+
epochSecret: EpochSecret;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---- State: KeysUnsampled ----
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initial send_ek state. No keypair has been generated yet.
|
|
62
|
+
*/
|
|
63
|
+
export class KeysUnsampled {
|
|
64
|
+
constructor(
|
|
65
|
+
public readonly epoch: Epoch,
|
|
66
|
+
public readonly auth: Authenticator,
|
|
67
|
+
) {}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate an ML-KEM-768 keypair and produce the header + MAC.
|
|
71
|
+
*
|
|
72
|
+
* @param rng - Random byte generator
|
|
73
|
+
* @returns [nextState, hdr, hdrMac]
|
|
74
|
+
*/
|
|
75
|
+
sendHeader(rng: RandomBytes): [HeaderSent, Uint8Array, Uint8Array] {
|
|
76
|
+
const keys = generate(rng);
|
|
77
|
+
const mac = this.auth.macHdr(this.epoch, keys.hdr);
|
|
78
|
+
|
|
79
|
+
const nextState = new HeaderSent(this.epoch, this.auth, keys.ek, keys.dk);
|
|
80
|
+
|
|
81
|
+
return [nextState, keys.hdr, mac];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---- State: HeaderSent ----
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The header has been sent to the peer. Ready to send the encapsulation key.
|
|
89
|
+
*/
|
|
90
|
+
export class HeaderSent {
|
|
91
|
+
constructor(
|
|
92
|
+
public readonly epoch: Epoch,
|
|
93
|
+
public readonly auth: Authenticator,
|
|
94
|
+
public readonly ek: Uint8Array,
|
|
95
|
+
public readonly dk: Uint8Array,
|
|
96
|
+
) {}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Produce the encapsulation key to send to the peer.
|
|
100
|
+
*
|
|
101
|
+
* @returns [nextState, ek]
|
|
102
|
+
*/
|
|
103
|
+
sendEk(): [EkSent, Uint8Array] {
|
|
104
|
+
const nextState = new EkSent(this.epoch, this.auth, this.dk);
|
|
105
|
+
return [nextState, this.ek];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---- State: EkSent ----
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Both the header and encapsulation key have been sent.
|
|
113
|
+
* Waiting to receive ct1 from the send_ct peer.
|
|
114
|
+
*/
|
|
115
|
+
export class EkSent {
|
|
116
|
+
constructor(
|
|
117
|
+
public readonly epoch: Epoch,
|
|
118
|
+
public readonly auth: Authenticator,
|
|
119
|
+
public readonly dk: Uint8Array,
|
|
120
|
+
) {}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Receive the first ciphertext fragment from the peer.
|
|
124
|
+
*
|
|
125
|
+
* @param ct1 - The 960-byte first ciphertext fragment
|
|
126
|
+
* @returns Next state
|
|
127
|
+
*/
|
|
128
|
+
recvCt1(ct1: Uint8Array): EkSentCt1Received {
|
|
129
|
+
return new EkSentCt1Received(this.epoch, this.auth, this.dk, ct1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---- State: EkSentCt1Received ----
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* ct1 has been received. Waiting for ct2 to complete decapsulation.
|
|
137
|
+
*/
|
|
138
|
+
export class EkSentCt1Received {
|
|
139
|
+
constructor(
|
|
140
|
+
public readonly epoch: Epoch,
|
|
141
|
+
public readonly auth: Authenticator,
|
|
142
|
+
public readonly dk: Uint8Array,
|
|
143
|
+
public readonly ct1: Uint8Array,
|
|
144
|
+
) {}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Receive ct2 and MAC, perform decapsulation, verify the MAC,
|
|
148
|
+
* and derive the epoch secret.
|
|
149
|
+
*
|
|
150
|
+
* The caller constructs the next send_ct::NoHeaderReceived state from
|
|
151
|
+
* the returned nextEpoch and auth.
|
|
152
|
+
*
|
|
153
|
+
* @param ct2 - The 128-byte second ciphertext fragment
|
|
154
|
+
* @param mac - The 32-byte HMAC-SHA256 MAC over the full ciphertext
|
|
155
|
+
* @returns Result containing next epoch, updated auth, and epoch secret
|
|
156
|
+
* @throws {AuthenticatorError} If the ciphertext MAC is invalid
|
|
157
|
+
*/
|
|
158
|
+
recvCt2(ct2: Uint8Array, mac: Uint8Array): RecvCt2Result {
|
|
159
|
+
// Decapsulate to recover shared secret
|
|
160
|
+
const sharedSecret = decaps(this.dk, this.ct1, ct2);
|
|
161
|
+
|
|
162
|
+
// Derive epoch secret (with epoch in HKDF info, matching Rust)
|
|
163
|
+
const epochSecret = deriveEpochSecret(this.epoch, sharedSecret);
|
|
164
|
+
|
|
165
|
+
// Update authenticator with the HKDF-derived secret at current epoch
|
|
166
|
+
const auth = this.auth.clone();
|
|
167
|
+
auth.update(this.epoch, epochSecret.secret);
|
|
168
|
+
|
|
169
|
+
// Verify the ciphertext MAC: MAC over ct1 || ct2
|
|
170
|
+
const fullCt = concat(this.ct1, ct2);
|
|
171
|
+
auth.verifyCt(this.epoch, fullCt, mac);
|
|
172
|
+
|
|
173
|
+
const nextEpoch = this.epoch + 1n;
|
|
174
|
+
|
|
175
|
+
return { nextEpoch, auth, epochSecret };
|
|
176
|
+
}
|
|
177
|
+
}
|