diff --git a/crates/ot-core/src/kos/attack_poc.rs b/crates/ot-core/src/kos/attack_poc.rs index ab08d7b3..21374785 100644 --- a/crates/ot-core/src/kos/attack_poc.rs +++ b/crates/ot-core/src/kos/attack_poc.rs @@ -88,8 +88,12 @@ fn domain_separation_must_prevent_delta_recovery_under_seed_precompensation() { assert_ne!(id_a, id_b); let tweak = id_a ^ id_b; - let receiver_seeds_b: [[Block; 2]; CSP] = - std::array::from_fn(|i| [receiver_seeds_a[i][0] ^ tweak, receiver_seeds_a[i][1] ^ tweak]); + let receiver_seeds_b: [[Block; 2]; CSP] = std::array::from_fn(|i| { + [ + receiver_seeds_a[i][0] ^ tweak, + receiver_seeds_a[i][1] ^ tweak, + ] + }); let sender_seeds_b = sender_seeds_for(delta, &receiver_seeds_b); let a = run_instance(delta, id_a, sender_seeds_a, receiver_seeds_a); @@ -101,7 +105,10 @@ fn domain_separation_must_prevent_delta_recovery_under_seed_precompensation() { let j = (0..COUNT) .find(|&j| a.receiver_choices[j] != b.receiver_choices[j]) .unwrap(); - println!("receiver-only msg XOR = {:?}", a.receiver_msgs[j] ^ b.receiver_msgs[j]); + println!( + "receiver-only msg XOR = {:?}", + a.receiver_msgs[j] ^ b.receiver_msgs[j] + ); println!("true Δ = {:?}", delta); println!("recovered Δ = {:?}", recovered); diff --git a/crates/ot-core/src/kos/receiver.rs b/crates/ot-core/src/kos/receiver.rs index f8a46da5..334489c4 100644 --- a/crates/ot-core/src/kos/receiver.rs +++ b/crates/ot-core/src/kos/receiver.rs @@ -8,7 +8,7 @@ use crate::{ use itybity::{BitLength, FromBitIterator, IntoBitIterator, IntoBits}; use mpz_common::future::{MaybeDone, Sender, new_output}; -use mpz_core::{Block, prg::Prg}; +use mpz_core::{Block, aes::FIXED_KEY_AES, prg::Prg}; use rand::{Rng as _, SeedableRng}; use rand_core::RngCore; @@ -82,10 +82,15 @@ impl Receiver { instance_id, state: state::Extension { // Domain-separate the base-OT-derived PRG seeds by the instance - // id, matching the sender's transform. + // id via a tweakable correlation-robust hash (tccr), matching the + // sender's transform. Must be non-invertible in `seed` — see the + // note in `sender.rs::setup` for the pre-compensation attack a + // linear XOR mix would enable. rngs: seeds .into_iter() - .map(|seeds| seeds.map(|seed| Prg::from_seed(seed ^ instance_id))) + .map(|seeds| { + seeds.map(|seed| Prg::from_seed(FIXED_KEY_AES.tccr(instance_id, seed))) + }) .collect(), msgs: Vec::default(), choices: Vec::default(), diff --git a/crates/ot-core/src/kos/sender.rs b/crates/ot-core/src/kos/sender.rs index 22de556c..38581a13 100644 --- a/crates/ot-core/src/kos/sender.rs +++ b/crates/ot-core/src/kos/sender.rs @@ -8,7 +8,7 @@ use crate::{ use itybity::ToBits; use mpz_common::future::{MaybeDone, Sender as OutputSender, new_output}; -use mpz_core::{Block, prg::Prg}; +use mpz_core::{Block, aes::FIXED_KEY_AES, prg::Prg}; use rand::{Rng as _, SeedableRng, rng}; @@ -93,11 +93,16 @@ impl Sender { instance_id, state: state::Extension { // Domain-separate the base-OT-derived PRG seeds by the instance - // id. Both parties transform the same chosen seed identically, - // so extension correctness is preserved. + // id via a tweakable correlation-robust hash (tccr) keyed by the + // instance id. Both parties transform the same chosen seed + // identically, so extension correctness is preserved. The mix must + // be non-invertible in `seed`: a plain XOR (`seed ^ instance_id`) + // lets a malicious receiver, who controls the base-OT seeds, + // pre-compensate `seed_B = seed_A ^ id_A ^ id_B` to collapse two + // instances onto the same PRG stream and recover `delta`. rngs: seeds .into_iter() - .map(|seed| Prg::from_seed(seed ^ instance_id)) + .map(|seed| Prg::from_seed(FIXED_KEY_AES.tccr(instance_id, seed))) .collect(), keys: Vec::default(), extended: false,