From c0379efa81cad70c21c922f65a6094da6f10c630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Kr=C3=B3l?= Date: Thu, 23 Jul 2026 11:43:56 +0200 Subject: [PATCH] feat(kos): domain-separate instances with a per-instance salt Add an `instance_id` salt to `kos::Sender`/`Receiver`, mixed into the base-OT-derived setup PRG seeds via a tweakable correlation-robust hash (`FIXED_KEY_AES.tccr(instance_id, seed)`). Two KOS instances that share one global `delta` and the same base OT then produce independent extension transcripts, so per-instance consistency-check leakage can no longer be composed to sample or recover `delta` (demonstrated in #1). The mix is non-invertible in `seed` on purpose: a linear XOR mix (`seed ^ instance_id`) would let a malicious receiver, who controls the base-OT seeds, pre-compensate them to cancel the salt and collapse two instances onto one PRG stream. API-breaking: `Sender::new`/`Receiver::new` gain an `instance_id: Block`. Paired sender and receiver must use the same id; distinct instances reusing one `delta` must use distinct ids. --- crates/ot-core/src/kos.rs | 20 ++++++++++---------- crates/ot-core/src/kos/receiver.rs | 13 ++++++++++--- crates/ot-core/src/kos/sender.rs | 15 ++++++++++++--- crates/ot/src/kos.rs | 4 ++-- crates/ot/src/kos/receiver.rs | 5 +++-- crates/ot/src/kos/sender.rs | 6 ++++-- 6 files changed, 41 insertions(+), 22 deletions(-) diff --git a/crates/ot-core/src/kos.rs b/crates/ot-core/src/kos.rs index 2a9a450cb..0ddbc8f17 100644 --- a/crates/ot-core/src/kos.rs +++ b/crates/ot-core/src/kos.rs @@ -173,8 +173,8 @@ mod tests { ) { let count = 128; - let sender = Sender::new(SenderConfig::default(), delta); - let receiver = Receiver::new(ReceiverConfig::default()); + let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO); + let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO); let mut sender = sender.setup(sender_seeds); let mut receiver = receiver.setup(receiver_seeds); @@ -226,8 +226,8 @@ mod tests { let count = sender_config.batch_size() * 3; - let sender = Sender::new(sender_config, delta); - let receiver = Receiver::new(receiver_config); + let sender = Sender::new(sender_config, delta, Block::ZERO); + let receiver = Receiver::new(receiver_config, Block::ZERO); let mut sender = sender.setup(sender_seeds); let mut receiver = receiver.setup(receiver_seeds); @@ -276,8 +276,8 @@ mod tests { ) { let count = 128; - let sender = Sender::new(SenderConfig::default(), delta); - let receiver = Receiver::new(ReceiverConfig::default()); + let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO); + let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO); let mut sender = sender.setup(sender_seeds); let mut receiver = receiver.setup(receiver_seeds); @@ -308,8 +308,8 @@ mod tests { ) { let count = 128; - let sender = Sender::new(SenderConfig::default(), delta); - let receiver = Receiver::new(ReceiverConfig::default()); + let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO); + let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO); let mut sender = sender.setup(sender_seeds); let mut receiver = receiver.setup(receiver_seeds); @@ -340,8 +340,8 @@ mod tests { ) { let count = 128; - let sender = Sender::new(SenderConfig::default(), delta); - let receiver = Receiver::new(ReceiverConfig::default()); + let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO); + let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO); let mut sender = sender.setup(sender_seeds); let mut receiver = receiver.setup(receiver_seeds); diff --git a/crates/ot-core/src/kos/receiver.rs b/crates/ot-core/src/kos/receiver.rs index 87611fc45..c98730d74 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; @@ -29,6 +29,7 @@ pub struct Receiver { alloc: usize, transfer_id: TransferId, queue: VecDeque, + instance_id: Block, state: T, } @@ -48,7 +49,8 @@ impl Receiver { /// # Arguments /// /// * `config` - The Receiver's configuration - pub fn new(config: ReceiverConfig) -> Self { + /// * `instance_id` - Domain separator; must match the paired sender. + pub fn new(config: ReceiverConfig, instance_id: Block) -> Self { Receiver { config, // We need to extend SSP OTs for the consistency check. @@ -57,6 +59,7 @@ impl Receiver { alloc: SSP, transfer_id: TransferId::default(), queue: VecDeque::default(), + instance_id, state: state::Initialized {}, } } @@ -67,15 +70,19 @@ impl Receiver { /// /// * `seeds` - The receiver's rng seeds pub fn setup(self, seeds: [[Block; 2]; CSP]) -> Receiver { + let instance_id = self.instance_id; Receiver { config: self.config, alloc: self.alloc, transfer_id: self.transfer_id, queue: self.queue, + instance_id, state: state::Extension { rngs: seeds .into_iter() - .map(|seeds| seeds.map(Prg::from_seed)) + .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 234972306..825f5a7d2 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}; @@ -35,6 +35,7 @@ pub struct Sender { queue: VecDeque, transfer_id: TransferId, delta: Block, + instance_id: Block, state: T, } @@ -55,7 +56,9 @@ impl Sender { /// /// * `config` - Sender's configuration. /// * `delta` - Global COT correlation. - pub fn new(config: SenderConfig, delta: Block) -> Self { + /// * `instance_id` - Domain separator; must match the paired receiver and + /// differ across instances that reuse `delta`. + pub fn new(config: SenderConfig, delta: Block, instance_id: Block) -> Self { Sender { config, // We need to extend SSP OTs for the consistency check. @@ -65,6 +68,7 @@ impl Sender { transfer_id: TransferId::default(), queue: VecDeque::default(), delta, + instance_id, state: state::Initialized::default(), } } @@ -75,14 +79,19 @@ impl Sender { /// /// * `seeds` - The rng seeds chosen during base OT pub fn setup(self, seeds: [Block; CSP]) -> Sender { + let instance_id = self.instance_id; Sender { config: self.config, alloc: self.alloc, transfer_id: self.transfer_id, queue: self.queue, delta: self.delta, + instance_id, state: state::Extension { - rngs: seeds.into_iter().map(Prg::from_seed).collect(), + rngs: seeds + .into_iter() + .map(|seed| Prg::from_seed(FIXED_KEY_AES.tccr(instance_id, seed))) + .collect(), keys: Vec::default(), extended: false, unchecked_qs_trans: Vec::default(), diff --git a/crates/ot/src/kos.rs b/crates/ot/src/kos.rs index 1a5acf744..fa7fb0b90 100644 --- a/crates/ot/src/kos.rs +++ b/crates/ot/src/kos.rs @@ -31,8 +31,8 @@ mod tests { let mut rng = StdRng::seed_from_u64(0); let (base_sender, base_receiver) = ideal_ot(); let delta = Block::random(&mut rng); - let sender = Sender::new(SenderConfig::default(), delta, base_receiver); - let receiver = Receiver::new(ReceiverConfig::default(), base_sender); + let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO, base_receiver); + let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO, base_sender); test_rcot(sender, receiver, 128, 1).await; } diff --git a/crates/ot/src/kos/receiver.rs b/crates/ot/src/kos/receiver.rs index c52478dc8..fb9e6e835 100644 --- a/crates/ot/src/kos/receiver.rs +++ b/crates/ot/src/kos/receiver.rs @@ -40,12 +40,13 @@ impl Receiver { /// # Arguments /// /// * `config` - The Receiver's configuration. + /// * `instance_id` - Domain separator; must match the paired sender. /// * `base_ot` - Base OT. - pub fn new(config: ReceiverConfig, base_ot: BaseOT) -> Self { + pub fn new(config: ReceiverConfig, instance_id: Block, base_ot: BaseOT) -> Self { Self { state: State::Initialized { base_ot, - receiver: Core::new(config), + receiver: Core::new(config, instance_id), }, } } diff --git a/crates/ot/src/kos/sender.rs b/crates/ot/src/kos/sender.rs index 01fc9f333..7c448a36c 100644 --- a/crates/ot/src/kos/sender.rs +++ b/crates/ot/src/kos/sender.rs @@ -40,12 +40,14 @@ impl Sender { /// /// * `config` - The Sender's configuration. /// * `delta` - Global COT correlation. + /// * `instance_id` - Domain separator; must match the paired receiver and + /// differ across instances that reuse `delta`. /// * `base_ot` - Base OT. - pub fn new(config: SenderConfig, delta: Block, base_ot: BaseOT) -> Self { + pub fn new(config: SenderConfig, delta: Block, instance_id: Block, base_ot: BaseOT) -> Self { Self { state: State::Initialized { base_ot, - sender: Core::new(config, delta), + sender: Core::new(config, delta, instance_id), }, } }