Skip to content

Commit 03854a4

Browse files
committed
attempt at unifying notation
1 parent 4322fb8 commit 03854a4

File tree

2 files changed

+70
-67
lines changed

2 files changed

+70
-67
lines changed

cryprot-ot/docs/mlkem-ot-protocol.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ In libOTe, this corresponds to `randomPK`, where it instead generates `A_hat` an
6969

7070
Output: `(t_hat, rho)`. The `rho` is passed through unchanged.
7171

72-
**`HashToKey(ek) -> (h, ek.rho)`**
72+
**`HashEK(ek) -> (h, ek.rho)`**
7373

74-
HashToKey corresponds to libOTe's `pkHash`. Maps an encapsulation key to another
74+
HashEK corresponds to libOTe's `pkHash`. Maps an encapsulation key to another
7575
encapsulation key. Takes an element of `T_q^k`, hashes it to a 32-byte seed, and uses that seed to sample a new element of `T_q^k`.
7676

7777
Given an encapsulation key `ek = (t_hat, rho)`:
@@ -83,13 +83,13 @@ h = SampleNTTVector(seed, ek.rho) // sample a new NttVector<k> from t
8383

8484
Output: `(h, ek.rho)` where `h` is an `NttVector<k>` in `T_q^k`.
8585

86-
**`RandomEK(seed, rho) -> (t_hat, rho)`**
86+
**`RandomEK(rng, rho) -> (t_hat, rho)`**
8787

88-
Generate a random encapsulation key from the given random 32 byte `seed` and `rho`:
88+
Generate a random encapsulation key. A 32-byte `seed` is sampled from `rng`:
8989

9090
Output: `SampleNTTVector(seed, rho)`
9191

92-
This is identical to `H` except the seed is random rather than derived from a hash.
92+
This is identical to `HashEK` except the seed is random rather than derived from a hash.
9393

9494
## Protocol
9595

@@ -107,13 +107,13 @@ component only. The `rho` component is always the same across all keys in a sing
107107

108108
2. **Sample random key for position `1-b`:**
109109
```
110-
seed = 32 random bytes
111-
r_{1-b} = RandomEK(seed, ek.rho)
110+
r_{1-b} = RandomEK(rng, ek.rho)
112111
```
112+
where `rng` is a cryptographically secure random number generator.
113113

114114
3. **Compute the correlated key for position `b`:**
115115
```
116-
r_b = ek - H(r_{1-b})
116+
r_b = ek - HashEK(r_{1-b})
117117
```
118118

119119
4. **Send to sender:**
@@ -128,7 +128,7 @@ component only. The `rho` component is always the same across all keys in a sing
128128

129129
6. **For each `j in {0, 1}`, reconstruct the encapsulation key:**
130130
```
131-
ek_j = r_j + H(r_{1-j})
131+
ek_j = r_j + HashEK(r_{1-j})
132132
```
133133

134134
7. **Encapsulate to both reconstructed keys:**
@@ -174,8 +174,8 @@ component only. The `rho` component is always the same across all keys in a sing
174174
175175
For the chosen side `b`, the sender reconstructs in step 6:
176176
```
177-
ek_b = r_b + H(r_{1-b})
178-
= (ek - H(r_{1-b})) + H(r_{1-b})
177+
ek_b = r_b + HashEK(r_{1-b})
178+
= (ek - HashEK(r_{1-b})) + HashEK(r_{1-b})
179179
= ek
180180
```
181181
So `ek_b = ek`, the real public key. In step 10, the receiver calls `ML-KEM.Decaps(dk, ct_b)` and
@@ -185,20 +185,20 @@ recovers the same shared secret `ss_b` that the sender computed via `ML-KEM.Enca
185185
186186
For the other side `1-b`, the sender reconstructs in step 6:
187187
```
188-
ek_{1-b} = r_{1-b} + H(r_b)
188+
ek_{1-b} = r_{1-b} + HashEK(r_b)
189189
```
190190
191191
Expanding `r_b` (from step 3):
192192
```
193-
ek_{1-b} = r_{1-b} + H(ek - H(r_{1-b}))
193+
ek_{1-b} = r_{1-b} + HashEK(ek - HashEK(r_{1-b}))
194194
```
195195
196-
This does NOT simplify — `H` is a hash function, so `H(ek - H(r_{1-b}))` does not
197-
cancel with `H(r_{1-b})`. The result `ek_{1-b}` is an unrelated key for which the
196+
This does NOT simplify — `HashEK` is a hash function, so `HashEK(ek - HashEK(r_{1-b}))` does not
197+
cancel with `HashEK(r_{1-b})`. The result `ek_{1-b}` is an unrelated key for which the
198198
receiver does not have a decapsulation key `dk`, so they cannot decapsulate `ct_{1-b}`.
199199
200-
The choice bit `b` is hidden because `r_b = ek - H(r_{1-b})`. Since `ek` is
201-
indistinguishable from uniform under the MLWE assumption, and `H(r_{1-b})` is determined by the
200+
The choice bit `b` is hidden because `r_b = ek - HashEK(r_{1-b})`. Since `ek` is
201+
indistinguishable from uniform under the MLWE assumption, and `HashEK(r_{1-b})` is determined by the
202202
already-public `r_{1-b}`, subtracting it from a uniform value still yields a uniform
203203
value. So both `r_0` and `r_1` appear uniform to the sender — neither reveals which
204204
is the real key.

cryprot-ot/src/mlkem_ot.rs

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ impl std::ops::Add<&NttVector> for &EncapsulationKey {
111111
}
112112
}
113113

114-
/// XOF(rho, j, i) from FIPS 203, Algorithm 2 SHAKE128example.
115-
/// In Algorithm 13 (K-PKE.KeyGen), this is called as XOF(rho, j, i) where
114+
/// XOF(seed, j, i) from FIPS 203, Algorithm 2 SHAKE128example.
115+
/// In Algorithm 13 (K-PKE.KeyGen), this is called as XOF(seed, j, i) where
116116
/// j is the column index (byte 32) and i is the row index (byte 33),
117117
/// using 0-based indexing.
118118
fn xof(seed: &Seed, j: u8, i: u8) -> impl XofReader {
@@ -176,15 +176,15 @@ fn sample_ntt_vector(seed: &Seed) -> NttVector {
176176
}
177177

178178
/// Maps an encapsulation key to an NttVector via SHA3-256.
179-
/// Only the t_hat component is hashed; rho is ignored.
179+
/// Only the t_hat component is used; rho is ignored.
180180
/// Corresponds to libOTe's `pkHash`.
181-
fn hash_to_key(ek: &EncapsulationKey) -> NttVector {
181+
fn hash_ek(ek: &EncapsulationKey) -> NttVector {
182182
let encoded = <NttVector as Encode<U12>>::encode(&ek.t_hat);
183183
let seed: Seed = sha3::Sha3_256::digest(encoded.as_slice()).into();
184184
sample_ntt_vector(&seed)
185185
}
186186

187-
/// RandomEK: generate a random encapsulation key from a random seed.
187+
/// Generate a random encapsulation key using the given randomness and rho.
188188
fn random_ek(rng: &mut StdRng, rho: Seed) -> EncapsulationKey {
189189
let seed: Seed = rng.random();
190190
EncapsulationKey {
@@ -235,9 +235,9 @@ impl ConditionallySelectable for EncapsulationKeyBytes {
235235
}
236236

237237
#[derive(Copy, Clone, Serialize, Deserialize)]
238-
struct CtBytes(#[serde(with = "serde_bytes")] [u8; CIPHERTEXT_LEN]);
238+
struct CiphertextBytes(#[serde(with = "serde_bytes")] [u8; CIPHERTEXT_LEN]);
239239

240-
impl ConditionallySelectable for CtBytes {
240+
impl ConditionallySelectable for CiphertextBytes {
241241
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
242242
Self(<[u8; CIPHERTEXT_LEN]>::conditional_select(
243243
&a.0, &b.0, choice,
@@ -248,15 +248,15 @@ impl ConditionallySelectable for CtBytes {
248248
// Message from receiver to sender: two values (r_0, r_1) per OT.
249249
#[derive(Serialize, Deserialize)]
250250
struct EncapsulationKeysMessage {
251-
eks0: Vec<EncapsulationKeyBytes>,
252-
eks1: Vec<EncapsulationKeyBytes>,
251+
rs_0: Vec<EncapsulationKeyBytes>,
252+
rs_1: Vec<EncapsulationKeyBytes>,
253253
}
254254

255255
// Message from sender to receiver: two ciphertexts per OT.
256256
#[derive(Serialize, Deserialize)]
257257
struct CiphertextsMessage {
258-
cts0: Vec<CtBytes>,
259-
cts1: Vec<CtBytes>,
258+
cts_0: Vec<CiphertextBytes>,
259+
cts_1: Vec<CiphertextBytes>,
260260
}
261261

262262
pub struct MlKemOt {
@@ -300,44 +300,44 @@ impl RotSender for MlKemOt {
300300
recv_stream.next().await.ok_or(Error::ClosedStream)??
301301
};
302302

303-
if receiver_msg.eks0.len() != count || receiver_msg.eks1.len() != count {
303+
if receiver_msg.rs_0.len() != count || receiver_msg.rs_1.len() != count {
304304
return Err(Error::InvalidDataCount {
305305
expected: count,
306-
actual0: receiver_msg.eks0.len(),
307-
actual1: receiver_msg.eks1.len(),
306+
actual0: receiver_msg.rs_0.len(),
307+
actual1: receiver_msg.rs_1.len(),
308308
});
309309
}
310310

311-
let mut cts0 = Vec::with_capacity(count);
312-
let mut cts1 = Vec::with_capacity(count);
313-
for (i, (r0_bytes, r1_bytes)) in receiver_msg
314-
.eks0
311+
let mut cts_0 = Vec::with_capacity(count);
312+
let mut cts_1 = Vec::with_capacity(count);
313+
for (i, (r_0_bytes, r_1_bytes)) in receiver_msg
314+
.rs_0
315315
.iter()
316-
.zip(receiver_msg.eks1.iter())
316+
.zip(receiver_msg.rs_1.iter())
317317
.enumerate()
318318
{
319319
// Step 5: Receive (r_0, r_1) from the receiver (done above).
320-
let r0 = EncapsulationKey::from_bytes(&r0_bytes.0);
321-
let r1 = EncapsulationKey::from_bytes(&r1_bytes.0);
320+
let r_0 = EncapsulationKey::from_bytes(&r_0_bytes.0);
321+
let r_1 = EncapsulationKey::from_bytes(&r_1_bytes.0);
322322

323323
// Step 6: Reconstruct encapsulation keys: ek_j = r_j + H(r_{1-j}).
324-
let ek0 = &r0 + &hash_to_key(&r1);
325-
let ek1 = &r1 + &hash_to_key(&r0);
324+
let ek_0 = &r_0 + &hash_ek(&r_1);
325+
let ek_1 = &r_1 + &hash_ek(&r_0);
326326

327327
// Step 7: Encapsulate to both reconstructed keys.
328-
let (ct0, ss0) = encapsulate(&(&ek0).into(), &mut self.rng);
329-
let (ct1, ss1) = encapsulate(&(&ek1).into(), &mut self.rng);
328+
let (ct_0, ss_0) = encapsulate(&(&ek_0).into(), &mut self.rng);
329+
let (ct_1, ss_1) = encapsulate(&(&ek_1).into(), &mut self.rng);
330330

331331
// Step 8: Derive OT output keys.
332-
let key0 = hash(&ss0, i);
333-
let key1 = hash(&ss1, i);
332+
let key_0 = derive_ot_key(&ss_0, i);
333+
let key_1 = derive_ot_key(&ss_1, i);
334334

335-
cts0.push(ct0);
336-
cts1.push(ct1);
337-
ots[i] = [key0, key1];
335+
cts_0.push(ct_0);
336+
cts_1.push(ct_1);
337+
ots[i] = [key_0, key_1];
338338
}
339339

340-
let sender_msg = CiphertextsMessage { cts0, cts1 };
340+
let sender_msg = CiphertextsMessage { cts_0, cts_1 };
341341
{
342342
let mut send_stream = send.as_stream();
343343
send_stream.send(sender_msg).await?;
@@ -363,8 +363,8 @@ impl RotReceiver for MlKemOt {
363363
let (mut send, mut recv) = self.conn.byte_stream().await?;
364364

365365
let mut decap_keys: Vec<DecapsulationKey<MlKemParams>> = Vec::with_capacity(count);
366-
let mut eks0 = Vec::with_capacity(count);
367-
let mut eks1 = Vec::with_capacity(count);
366+
let mut rs_0 = Vec::with_capacity(count);
367+
let mut rs_1 = Vec::with_capacity(count);
368368

369369
for choice in choices.iter() {
370370
// Step 1: Generate real keypair.
@@ -380,22 +380,22 @@ impl RotReceiver for MlKemOt {
380380
let r_1_b = random_ek(&mut self.rng, ek.rho);
381381

382382
// Step 3: Compute correlated key: r_b = ek - H(r_{1-b}).
383-
let r_b = &ek - &hash_to_key(&r_1_b);
383+
let r_b = &ek - &hash_ek(&r_1_b);
384384
let r_b_bytes: EncapsulationKeyBytes = (&r_b).into();
385385
let r_1_b_bytes: EncapsulationKeyBytes = (&r_1_b).into();
386386

387387
// Step 4: Select (r_0, r_1) based on choice bit (constant-time).
388388
// If b=0: r_0 = real, r_1 = random.
389389
// If b=1: r_0 = random, r_1 = real.
390-
let ek0 = EncapsulationKeyBytes::conditional_select(&r_b_bytes, &r_1_b_bytes, *choice);
391-
let ek1 = EncapsulationKeyBytes::conditional_select(&r_1_b_bytes, &r_b_bytes, *choice);
390+
let ek_0 = EncapsulationKeyBytes::conditional_select(&r_b_bytes, &r_1_b_bytes, *choice);
391+
let ek_1 = EncapsulationKeyBytes::conditional_select(&r_1_b_bytes, &r_b_bytes, *choice);
392392

393393
decap_keys.push(dk);
394-
eks0.push(ek0);
395-
eks1.push(ek1);
394+
rs_0.push(ek_0);
395+
rs_1.push(ek_1);
396396
}
397397

398-
let receiver_msg = EncapsulationKeysMessage { eks0, eks1 };
398+
let receiver_msg = EncapsulationKeysMessage { rs_0, rs_1 };
399399
{
400400
let mut send_stream = send.as_stream();
401401
send_stream.send(receiver_msg).await?;
@@ -406,30 +406,30 @@ impl RotReceiver for MlKemOt {
406406
recv_stream.next().await.ok_or(Error::ClosedStream)??
407407
};
408408

409-
if sender_msg.cts0.len() != count || sender_msg.cts1.len() != count {
409+
if sender_msg.cts_0.len() != count || sender_msg.cts_1.len() != count {
410410
return Err(Error::InvalidDataCount {
411411
expected: count,
412-
actual0: sender_msg.cts0.len(),
413-
actual1: sender_msg.cts1.len(),
412+
actual0: sender_msg.cts_0.len(),
413+
actual1: sender_msg.cts_1.len(),
414414
});
415415
}
416416

417417
// Step 10-11: Decapsulate the chosen ciphertext and derive OT key.
418-
for (i, ((dk, choice), (ct0, ct1))) in decap_keys
418+
for (i, ((dk, choice), (ct_0, ct_1))) in decap_keys
419419
.iter()
420420
.zip(choices.iter())
421-
.zip(sender_msg.cts0.iter().zip(sender_msg.cts1.iter()))
421+
.zip(sender_msg.cts_0.iter().zip(sender_msg.cts_1.iter()))
422422
.enumerate()
423423
{
424-
let ct_bytes = CtBytes::conditional_select(ct0, ct1, *choice).0;
424+
let ct_bytes = CiphertextBytes::conditional_select(ct_0, ct_1, *choice).0;
425425
let chosen_ct: MlKemCiphertext<MlKem> = ct_bytes
426426
.as_slice()
427427
.try_into()
428428
.expect("incorrect ciphertext size");
429-
let shared_key = dk
429+
let shared_secret = dk
430430
.decapsulate(&chosen_ct)
431431
.map_err(|_| Error::Decapsulation)?;
432-
let shared_key = hash(&shared_key, i);
432+
let shared_key = derive_ot_key(&shared_secret, i);
433433
ots[i] = shared_key;
434434
}
435435

@@ -438,20 +438,23 @@ impl RotReceiver for MlKemOt {
438438
}
439439

440440
// Encapsulates to the given key, returning the ciphertext and the shared key.
441-
fn encapsulate(ek: &EncapsulationKeyBytes, rng: &mut StdRng) -> (CtBytes, SharedKey<MlKem>) {
441+
fn encapsulate(
442+
ek: &EncapsulationKeyBytes,
443+
rng: &mut StdRng,
444+
) -> (CiphertextBytes, SharedKey<MlKem>) {
442445
let parsed_ek = MlKemEncapsulationKey::<MlKemParams>::from_bytes((&ek.0).into());
443446
let (ct, k): (MlKemCiphertext<MlKem>, SharedKey<MlKem>) = parsed_ek
444447
.encapsulate(&mut RngCompat(rng))
445448
.expect("encapsulation should not fail");
446449
(
447-
CtBytes(ct.as_slice().try_into().expect("incorrect ciphertext size")),
450+
CiphertextBytes(ct.as_slice().try_into().expect("incorrect ciphertext size")),
448451
k,
449452
)
450453
}
451454

452455
// Derive an OT key from the ML-KEM shared key using a random oracle XOF,
453456
// extracting a Block-sized (128-bit) output.
454-
fn hash(key: &SharedKey<MlKem>, tweak: usize) -> Block {
457+
fn derive_ot_key(key: &SharedKey<MlKem>, tweak: usize) -> Block {
455458
let mut ro = RandomOracle::new();
456459
ro.update(HASH_DOMAIN_SEPARATOR);
457460
ro.update(key.as_slice());

0 commit comments

Comments
 (0)