Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **Breaking:** `kos::Sender::new` and `kos::Receiver::new` take an
`InstanceId`. KOS15 is analysed as one extension per global `delta`; every
instance sharing a `delta` must now name a distinct id, and the two parties of
one instance must name the same one. Instances are separated by PRG stream, so
`InstanceId::SOLO` reproduces the previous derivation byte for byte and a
`delta` driven by a single instance stays wire compatible.
- **Breaking:** `kos::Receiver` no longer implements `Default`. A receiver
cannot be built without stating its `InstanceId`, and defaulting that id is
the mistake the id exists to prevent. Use `kos::Receiver::new`.
6 changes: 4 additions & 2 deletions crates/ot-core/benches/ot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ fn kos(c: &mut Criterion) {
.unwrap();

b.iter(|| {
let sender = kos::Sender::new(kos::SenderConfig::default(), delta);
let receiver = kos::Receiver::new(kos::ReceiverConfig::default());
let sender =
kos::Sender::new(kos::SenderConfig::default(), delta, kos::InstanceId::SOLO);
let receiver =
kos::Receiver::new(kos::ReceiverConfig::default(), kos::InstanceId::SOLO);

let mut sender = sender.setup(sender_seeds);
let mut receiver = receiver.setup(receiver_seeds);
Expand Down
24 changes: 14 additions & 10 deletions crates/ot-core/src/kos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

mod config;
mod error;
mod instance;
#[cfg(test)]
mod instance_tests;
pub mod msgs;
mod receiver;
mod sender;
Expand All @@ -17,6 +20,7 @@ pub use config::{
SenderConfigBuilder, SenderConfigBuilderError,
};
pub use error::{ReceiverError, SenderError};
pub use instance::{InstanceId, InstanceIds};
use mpz_core::Block;
pub use receiver::{Receiver, state as receiver_state};
pub use sender::{Sender, state as sender_state};
Expand Down Expand Up @@ -173,8 +177,8 @@ mod tests {
) {
let count = 128;

let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO);
let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO);
let sender = Sender::new(SenderConfig::default(), delta, InstanceId::SOLO);
let receiver = Receiver::new(ReceiverConfig::default(), InstanceId::SOLO);

let mut sender = sender.setup(sender_seeds);
let mut receiver = receiver.setup(receiver_seeds);
Expand Down Expand Up @@ -226,8 +230,8 @@ mod tests {

let count = sender_config.batch_size() * 3;

let sender = Sender::new(sender_config, delta, Block::ZERO);
let receiver = Receiver::new(receiver_config, Block::ZERO);
let sender = Sender::new(sender_config, delta, InstanceId::SOLO);
let receiver = Receiver::new(receiver_config, InstanceId::SOLO);

let mut sender = sender.setup(sender_seeds);
let mut receiver = receiver.setup(receiver_seeds);
Expand Down Expand Up @@ -276,8 +280,8 @@ mod tests {
) {
let count = 128;

let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO);
let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO);
let sender = Sender::new(SenderConfig::default(), delta, InstanceId::SOLO);
let receiver = Receiver::new(ReceiverConfig::default(), InstanceId::SOLO);

let mut sender = sender.setup(sender_seeds);
let mut receiver = receiver.setup(receiver_seeds);
Expand Down Expand Up @@ -308,8 +312,8 @@ mod tests {
) {
let count = 128;

let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO);
let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO);
let sender = Sender::new(SenderConfig::default(), delta, InstanceId::SOLO);
let receiver = Receiver::new(ReceiverConfig::default(), InstanceId::SOLO);

let mut sender = sender.setup(sender_seeds);
let mut receiver = receiver.setup(receiver_seeds);
Expand Down Expand Up @@ -340,8 +344,8 @@ mod tests {
) {
let count = 128;

let sender = Sender::new(SenderConfig::default(), delta, Block::ZERO);
let receiver = Receiver::new(ReceiverConfig::default(), Block::ZERO);
let sender = Sender::new(SenderConfig::default(), delta, InstanceId::SOLO);
let receiver = Receiver::new(ReceiverConfig::default(), InstanceId::SOLO);

let mut sender = sender.setup(sender_seeds);
let mut receiver = receiver.setup(receiver_seeds);
Expand Down
98 changes: 98 additions & 0 deletions crates/ot-core/src/kos/instance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Domain separation for KOS instances that share one global `delta`.

use std::fmt;

/// A domain separator for one KOS extension instance.
///
/// KOS15 is analysed as a *single* extension per global correlation `delta`.
/// Running several instances under one `delta` steps outside that analysis: if
/// two instances ever derive the same extension columns, the sender's keys
/// become related by `delta` at every column where the receiver's choice bits
/// differ, and the instances are no longer independent.
///
/// Every instance sharing a `delta` must therefore carry a distinct
/// `InstanceId`, and the two parties of one instance must carry the *same* id.
/// Ids are matched by value, not by construction order.
///
/// [`InstanceId::SOLO`] is the id for a `delta` used by exactly one instance.
/// It reproduces stock KOS byte for byte, so a solo instance stays wire
/// compatible with an implementation that predates this type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct InstanceId(u64);

impl InstanceId {
/// The id for a `delta` driven by exactly one instance.
///
/// Deliberately not [`Default`]: sharing a `delta` between instances that
/// both defaulted is precisely the mistake this type exists to prevent.
pub const SOLO: Self = Self(0);

/// Creates an id.
///
/// # Arguments
///
/// * `id` - Distinct across every instance sharing one `delta`, and equal
/// between the sender and receiver of the same instance.
pub const fn new(id: u64) -> Self {
Self(id)
}

/// Returns the underlying value.
pub const fn to_u64(self) -> u64 {
self.0
}
}

impl fmt::Display for InstanceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

/// Hands out distinct [`InstanceId`]s for one global `delta`.
///
/// Both parties allocate from their own `InstanceIds` and must request ids in
/// the same order, so that instance *n* on one side faces instance *n* on the
/// other. Where a fixed wiring is clearer, name the ids with
/// [`InstanceId::new`] instead.
#[derive(Debug, Clone, Default)]
pub struct InstanceIds {
next: u64,
}

impl InstanceIds {
/// Creates an allocator, starting at [`InstanceId::SOLO`].
pub const fn new() -> Self {
Self { next: 0 }
}

/// Returns an id that no earlier call returned.
///
/// # Panics
///
/// If more than `u64::MAX` ids are requested, rather than wrapping onto an
/// id already in use.
pub fn allocate(&mut self) -> InstanceId {
let id = self.next;
self.next = self
.next
.checked_add(1)
.expect("KOS instance ids exhausted for this delta");
InstanceId(id)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn allocator_never_repeats() {
let mut ids = InstanceIds::new();
let issued: Vec<_> = (0..8).map(|_| ids.allocate()).collect();
assert_eq!(issued[0], InstanceId::SOLO);
for (i, id) in issued.iter().enumerate() {
assert!(!issued[..i].contains(id), "id {id} was issued twice");
}
}
}
Loading