From ff541d974089d60ceebb7cfa779519f19553d951 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Tue, 8 Aug 2023 16:45:24 -0700 Subject: [PATCH 01/16] VDAF-06+ ping-pong topology Implements the ping-pong topology introduced in VDAF-06, though the implementation here is based on the revised ping-pong interface in a yet-unpublished VDAF version. We add a `topology` module, on the premise that we might someday add new topologies and their implementations there, and `topology::ping_pong`. This also adds an implementation of a dummy VDAF, brought over from Janus ([1]). `vdaf::dummy` is only compiled if the `test-util` Cargo feature is enabled. The dummy VDAF implements the `vdaf::{Vdaf, Aggregator, Client, Collector}` and provides associated types for output shares, prepare shares, etc., but it doesn't do anything except return success, making it useful for testing higher-level constructions like ping-pong. Finally, we replace the derived `std::fmt::Debug` implementations on a few `prio3` and `poplar1` associated types so that they redact fields that are either sensitive secrets or just too big to be worth printing when debugging. This is so that we can provide `Debug` impls on new types in `topology::ping_pong` without pulling in crate `derivative`, which would require us to do 9,000+ lines of audits. [1]: https://github.com/divviup/janus --- Cargo.toml | 3 +- src/lib.rs | 1 + src/topology/mod.rs | 7 + src/topology/ping_pong.rs | 682 ++++++++++++++++++++++++++++++++++++++ src/vdaf.rs | 10 +- src/vdaf/dummy.rs | 327 ++++++++++++++++++ src/vdaf/poplar1.rs | 10 +- src/vdaf/prio3.rs | 18 +- supply-chain/config.toml | 5 + 9 files changed, 1059 insertions(+), 4 deletions(-) create mode 100644 src/topology/mod.rs create mode 100644 src/topology/ping_pong.rs create mode 100644 src/vdaf/dummy.rs diff --git a/Cargo.toml b/Cargo.toml index 0020ce2ef..771bd714a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ itertools = "0.11.0" modinverse = "0.1.0" num-bigint = "0.4.4" once_cell = "1.18.0" -prio = { path = ".", features = ["crypto-dependencies"] } +prio = { path = ".", features = ["crypto-dependencies", "test-util"] } rand = "0.8" serde_json = "1.0" statrs = "0.16.0" @@ -58,6 +58,7 @@ experimental = ["bitvec", "fiat-crypto", "fixed", "num-bigint", "num-rational", multithreaded = ["rayon"] prio2 = ["crypto-dependencies", "hmac", "sha2"] crypto-dependencies = ["aes", "ctr"] +test-util = ["dep:rand"] [workspace] members = [".", "binaries"] diff --git a/src/lib.rs b/src/lib.rs index a7ac4ddbb..c9d4e22c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,4 +30,5 @@ mod fp; pub mod idpf; mod polynomial; mod prng; +pub mod topology; pub mod vdaf; diff --git a/src/topology/mod.rs b/src/topology/mod.rs new file mode 100644 index 000000000..8bf73d45f --- /dev/null +++ b/src/topology/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Implementations of the aggregator communication topologies specified in [VDAF]. +//! +//! [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.7 + +pub mod ping_pong; diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs new file mode 100644 index 000000000..7c50e8ace --- /dev/null +++ b/src/topology/ping_pong.rs @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Implements the Ping-Pong Topology described in [VDAF]. This topology assumes there are exactly +//! two aggregators, designated "Leader" and "Helper". Note that while this implementation is +//! compatible with VDAF-06, it actually implements the ping-pong wrappers specified in VDAF-07 +//! (forthcoming) since those line up better with the VDAF implementation in this crate. +//! +//! [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 + +use crate::{ + codec::{decode_u32_items, encode_u32_items, CodecError, Decode, Encode, ParameterizedDecode}, + vdaf::{Aggregator, PrepareTransition, VdafError}, +}; +use std::fmt::Debug; + +/// Errors emitted by this module. +#[derive(Debug, thiserror::Error)] +pub enum PingPongError { + /// Error running prepare_init + #[error("vdaf.prepare_init: {0}")] + VdafPrepareInit(VdafError), + + /// Error running prepare_preprocess + #[error("vdaf.prepare_preprocess {0}")] + VdafPreparePreprocess(VdafError), + + /// Error running prepare_step + #[error("vdaf.prepare_step {0}")] + VdafPrepareStep(VdafError), + + /// Error decoding a prepare share + #[error("decode prep share {0}")] + CodecPrepShare(CodecError), + + /// Error decoding a prepare message + #[error("decode prep message {0}")] + CodecPrepMessage(CodecError), + + /// State machine mismatch between peer and host. + #[error("state mismatch: host state {0} peer state {0}")] + StateMismatch(&'static str, &'static str), + + /// Internal error + #[error("internal error: {0}")] + InternalError(&'static str), +} + +/// Distinguished aggregator roles. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Role { + /// The Leader aggregator. + Leader, + /// The Helper aggregator. + Helper, +} + +/// Corresponds to `struct Message` in [VDAF's Ping-Pong Topology][VDAF]. All of the fields of the +/// variants are opaque byte buffers. This is because the ping-pong routines take responsibility for +/// decoding preparation shares and messages, which usually requires having the preparation state. +/// +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +#[derive(Clone, PartialEq, Eq)] +pub enum Message { + /// Corresponds to MessageType.initialize. + Initialize { + /// The leader's initial preparation share. + prep_share: Vec, + }, + /// Corresponds to MessageType.continue. + Continue { + /// The current round's preparation message. + prep_msg: Vec, + /// The next round's preparation share. + prep_share: Vec, + }, + /// Corresponds to MessageType.finish. + Finish { + /// The current round's preparation message. + prep_msg: Vec, + }, +} + +impl Message { + fn state_name(&self) -> &'static str { + match self { + Self::Initialize { .. } => "Initialize", + Self::Continue { .. } => "Continue", + Self::Finish { .. } => "Finish", + } + } +} + +impl Debug for Message { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Message::{}", self.state_name()) + } +} + +impl Encode for Message { + fn encode(&self, bytes: &mut Vec) { + // The encoding includes an implicit discriminator byte, called MessageType in the VDAF + // spec. + match self { + Self::Initialize { prep_share } => { + 0u8.encode(bytes); + encode_u32_items(bytes, &(), prep_share); + } + Self::Continue { + prep_msg, + prep_share, + } => { + 1u8.encode(bytes); + encode_u32_items(bytes, &(), prep_msg); + encode_u32_items(bytes, &(), prep_share); + } + Self::Finish { prep_msg } => { + 2u8.encode(bytes); + encode_u32_items(bytes, &(), prep_msg); + } + } + } + + fn encoded_len(&self) -> Option { + match self { + Self::Initialize { prep_share } => Some(1 + 4 + prep_share.len()), + Self::Continue { + prep_msg, + prep_share, + } => Some(1 + 4 + prep_msg.len() + 4 + prep_share.len()), + Self::Finish { prep_msg } => Some(1 + 4 + prep_msg.len()), + } + } +} + +impl Decode for Message { + fn decode(bytes: &mut std::io::Cursor<&[u8]>) -> Result { + let message_type = u8::decode(bytes)?; + Ok(match message_type { + 0 => { + let prep_share = decode_u32_items(&(), bytes)?; + Self::Initialize { prep_share } + } + 1 => { + let prep_msg = decode_u32_items(&(), bytes)?; + let prep_share = decode_u32_items(&(), bytes)?; + Self::Continue { + prep_msg, + prep_share, + } + } + 2 => { + let prep_msg = decode_u32_items(&(), bytes)?; + Self::Finish { prep_msg } + } + _ => return Err(CodecError::UnexpectedValue), + }) + } +} + +/// Corresponds to the `State` enumeration implicitly defined in [VDAF's Ping-Pong Topology][VDAF]. +/// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in +/// code, and the `Rejected` state is represented as `std::result::Result::Error`, so this enum does +/// not include those variants. +/// +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum State { + /// Preparation of the report will continue with the enclosed state. + Continued(PrepState), + /// Preparation of the report is finished and has yielded the enclosed output share. + Finished(OutputShare), +} + +impl Encode for State { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::Continued(prep_state) => { + 0u8.encode(bytes); + prep_state.encode(bytes); + } + Self::Finished(output_share) => { + 1u8.encode(bytes); + output_share.encode(bytes); + } + } + } + + fn encoded_len(&self) -> Option { + Some( + 1 + match self { + Self::Continued(prep_state) => prep_state.encoded_len()?, + Self::Finished(output_share) => output_share.encoded_len()?, + }, + ) + } +} + +/// Decoding parameter for [`State`]. +pub struct StateDecodingParam<'a, PrepStateDecode, OutputShareDecode> { + /// The decoding parameter for the preparation state. + pub prep_state: &'a PrepStateDecode, + /// The decoding parameter for the output share. + pub output_share: &'a OutputShareDecode, +} + +impl< + 'a, + PrepStateDecode, + OutputShareDecode, + PrepState: ParameterizedDecode, + OutputShare: ParameterizedDecode, + > ParameterizedDecode> + for State +{ + fn decode_with_param( + decoding_param: &StateDecodingParam, + bytes: &mut std::io::Cursor<&[u8]>, + ) -> Result { + let variant = u8::decode(bytes)?; + match variant { + 0 => Ok(Self::Continued(PrepState::decode_with_param( + decoding_param.prep_state, + bytes, + )?)), + 1 => Ok(Self::Finished(OutputShare::decode_with_param( + decoding_param.output_share, + bytes, + )?)), + _ => Err(CodecError::UnexpectedValue), + } + } +} + +/// Values returned by [`PingPongTopology::continued`]. +/// +/// Corresponds to the `State` enumeration implicitly defined in [VDAF's Ping-Pong Topology][VDAF], +/// but combined with the components of [`Message`] so that no impossible states may be represented. +/// For example, it's impossible to be in the `Continued` state but to send an outgoing `Message` of +/// type `Finished`. +/// +/// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in +/// code, and the `Rejected` state is represented as `std::result::Result::Error`, so this enum does +/// not include those variants. +/// +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum StateAndMessage { + /// The operation resulted in a new state and a message to transmit to the peer. + WithMessage { + /// The [`State`] to which we just advanced. + state: State, + /// The [`Message`] which should now be transmitted to the peer. + message: Message, + }, + /// The operation caused the host to finish preparation of the input share, yielding an output + /// share and no message for the peer. + FinishedNoMessage { + /// The output share which may now be accumulated. + output_share: OutputShare, + }, +} + +/// Extension trait on [`crate::vdaf::Aggregator`] which adds the [VDAF Ping-Pong Topology][VDAF]. +/// +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +pub trait PingPongTopology: + Aggregator +{ + /// Specialization of [`State`] for this VDAF. + type State; + /// Specialization of [`StateAndMessage`] for this VDAF. + type StateAndMessage; + + /// Initialize leader state using the leader's input share. Corresponds to + /// `ping_pong_leader_init` in the forthcoming `draft-irtf-cfrg-vdaf-07`. + /// + /// If successful, the returned [`State`] (which will always be `State::Continued`) should be + /// stored by the leader for use in the next round, and the returned [`Message`] (which will + /// always be `Message::Initialize`) should be transmitted to the helper. + fn leader_initialize( + &self, + verify_key: &[u8; VERIFY_KEY_SIZE], + agg_param: &Self::AggregationParam, + nonce: &[u8; NONCE_SIZE], + public_share: &Self::PublicShare, + input_share: &Self::InputShare, + ) -> Result<(Self::State, Message), PingPongError>; + + /// Initialize helper state using the helper's input share and the leader's first prepare share. + /// Corresponds to `ping_pong_helper_init` in the forthcoming `draft-irtf-cfrg-vdaf-07`. + /// + /// If successful, the returned [`State`] will either be `State::Continued` and should be stored + /// by the helper for use in the next round or `State::Finished`, in which case preparation is + /// finished and the output share may be accumulated by the helper. + /// + /// Regardless of state, the returned [`Message`] should be transmitted to the leader. + /// + /// # Errors + /// + /// `inbound` must be `Message::Initialize` or the function will fail. + fn helper_initialize( + &self, + verify_key: &[u8; VERIFY_KEY_SIZE], + agg_param: &Self::AggregationParam, + nonce: &[u8; NONCE_SIZE], + public_share: &Self::PublicShare, + input_share: &Self::InputShare, + inbound: &Message, + ) -> Result<(Self::State, Message), PingPongError>; + + /// Continue preparation based on the host's current state and an incoming [`Message`] from the + /// peer. `role` is the host's [`Role`]. Corresponds to `ping_pong_contnued` in the forthcoming + /// `draft-irtf-cfrg-vdaf-07`. + /// + /// If successful, the returned [`StateAndMessage`] will either be: + /// + /// - `StateAndMessage::WithMessage { state, message }`: the `state` will be either + /// `State::Continued` and should be stored by the host for use in the next round or + /// `State::Finished`, in which case preparation is finished and the output share may be + /// accumulated. Regardless of state, `message` should be transmitted to the peer. + /// - `StateAndMessage::FinishedNoMessage`: preparation is finished and the output share may be + /// accumulated. No message needs to be send to the peer. + /// + /// # Errors + /// + /// `host_state` must be `State::Continued` or the function will fail. + /// + /// `inbound` must not be `Message::Initialize` or the function will fail. + fn continued( + &self, + role: Role, + host_state: Self::State, + inbound: &Message, + ) -> Result, PingPongError>; +} + +/// Private interfaces for VDAF Ping-Pong topology. +trait PingPongTopologyPrivate: + PingPongTopology +{ + /// Corresponds to `ping_pong_transition` in the forthcoming `draft-irtf-cfrg-vdaf-07`. + fn transition( + &self, + prep_shares: [Self::PrepareShare; 2], + host_prep_state: Self::PrepareState, + ) -> Result<(Self::State, Message), PingPongError>; +} + +impl + PingPongTopology for A +where + A: Aggregator, +{ + type State = State; + type StateAndMessage = StateAndMessage; + + fn leader_initialize( + &self, + verify_key: &[u8; VERIFY_KEY_SIZE], + agg_param: &Self::AggregationParam, + nonce: &[u8; NONCE_SIZE], + public_share: &Self::PublicShare, + input_share: &Self::InputShare, + ) -> Result<(Self::State, Message), PingPongError> { + self.prepare_init( + verify_key, + /* Leader */ 0, + agg_param, + nonce, + public_share, + input_share, + ) + .map(|(prep_state, prep_share)| { + ( + State::Continued(prep_state), + Message::Initialize { + prep_share: prep_share.get_encoded(), + }, + ) + }) + .map_err(PingPongError::VdafPrepareInit) + } + + fn helper_initialize( + &self, + verify_key: &[u8; VERIFY_KEY_SIZE], + agg_param: &Self::AggregationParam, + nonce: &[u8; NONCE_SIZE], + public_share: &Self::PublicShare, + input_share: &Self::InputShare, + inbound: &Message, + ) -> Result<(Self::State, Message), PingPongError> { + let (prep_state, prep_share) = self + .prepare_init( + verify_key, + /* Helper */ 1, + agg_param, + nonce, + public_share, + input_share, + ) + .map_err(PingPongError::VdafPrepareInit)?; + + let inbound_prep_share = if let Message::Initialize { prep_share } = inbound { + Self::PrepareShare::get_decoded_with_param(&prep_state, prep_share) + .map_err(PingPongError::CodecPrepShare)? + } else { + return Err(PingPongError::StateMismatch( + "initialize", + inbound.state_name(), + )); + }; + + self.transition([inbound_prep_share, prep_share], prep_state) + } + + fn continued( + &self, + role: Role, + host_state: Self::State, + inbound: &Message, + ) -> Result { + let host_prep_state = if let State::Continued(state) = host_state { + state + } else { + return Err(PingPongError::StateMismatch("finished", "continue")); + }; + + let (prep_msg, next_peer_prep_share) = match inbound { + Message::Initialize { .. } => { + return Err(PingPongError::StateMismatch( + "continue", + inbound.state_name(), + )); + } + Message::Continue { + prep_msg, + prep_share, + } => (prep_msg, Some(prep_share)), + Message::Finish { prep_msg } => (prep_msg, None), + }; + + let prep_msg = Self::PrepareMessage::get_decoded_with_param(&host_prep_state, prep_msg) + .map_err(PingPongError::CodecPrepMessage)?; + let host_prep_transition = self + .prepare_step(host_prep_state, prep_msg) + .map_err(PingPongError::VdafPrepareStep)?; + + match (host_prep_transition, next_peer_prep_share) { + ( + PrepareTransition::Continue(next_prep_state, next_host_prep_share), + Some(next_peer_prep_share), + ) => { + let next_peer_prep_share = Self::PrepareShare::get_decoded_with_param( + &next_prep_state, + next_peer_prep_share, + ) + .map_err(PingPongError::CodecPrepShare)?; + let mut prep_shares = [next_host_prep_share, next_peer_prep_share]; + if role == Role::Helper { + prep_shares.reverse(); + } + self.transition(prep_shares, next_prep_state) + .map(|(state, message)| StateAndMessage::WithMessage { state, message }) + } + (PrepareTransition::Finish(output_share), None) => { + Ok(StateAndMessage::FinishedNoMessage { output_share }) + } + (transition, _) => { + return Err(PingPongError::StateMismatch( + inbound.state_name(), + match transition { + PrepareTransition::Continue(_, _) => "continue", + PrepareTransition::Finish(_) => "finished", + }, + )) + } + } + } +} + +impl + PingPongTopologyPrivate for A +where + A: Aggregator, +{ + fn transition( + &self, + prep_shares: [Self::PrepareShare; 2], + host_prep_state: Self::PrepareState, + ) -> Result<(Self::State, Message), PingPongError> { + let prep_message = self + .prepare_preprocess(prep_shares) + .map_err(PingPongError::VdafPreparePreprocess)?; + + self.prepare_step(host_prep_state, prep_message.clone()) + .map(|transition| match transition { + PrepareTransition::Continue(prep_state, prep_share) => ( + State::Continued(prep_state), + Message::Continue { + prep_msg: prep_message.get_encoded(), + prep_share: prep_share.get_encoded(), + }, + ), + PrepareTransition::Finish(output_share) => ( + State::Finished(output_share), + Message::Finish { + prep_msg: prep_message.get_encoded(), + }, + ), + }) + .map_err(PingPongError::VdafPrepareStep) + } +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use super::*; + + use crate::vdaf::dummy; + + #[test] + fn ping_pong_one_round() { + let verify_key = []; + let aggregation_param = dummy::AggregationParam(0); + let nonce = [0; 16]; + #[allow(clippy::let_unit_value)] + let public_share = (); + let input_share = dummy::InputShare(0); + + let leader = dummy::Vdaf::new(1); + let helper = dummy::Vdaf::new(1); + + // Leader inits into round 0 + let (leader_state, leader_message) = leader + .leader_initialize( + &verify_key, + &aggregation_param, + &nonce, + &public_share, + &input_share, + ) + .unwrap(); + + // Helper inits into round 1 + let (helper_state, helper_message) = helper + .helper_initialize( + &verify_key, + &aggregation_param, + &nonce, + &public_share, + &input_share, + &leader_message, + ) + .unwrap(); + + // 1 round VDAF: helper should finish immediately. + assert_matches!(helper_state, State::Finished(_)); + + let leader_state = leader + .continued(Role::Leader, leader_state, &helper_message) + .unwrap(); + // 1 round VDAF: leader should finish when it gets helper message and emit no message. + assert_matches!(leader_state, StateAndMessage::FinishedNoMessage { .. }); + } + + #[test] + fn ping_pong_two_rounds() { + let verify_key = []; + let aggregation_param = dummy::AggregationParam(0); + let nonce = [0; 16]; + #[allow(clippy::let_unit_value)] + let public_share = (); + let input_share = dummy::InputShare(0); + + let leader = dummy::Vdaf::new(2); + let helper = dummy::Vdaf::new(2); + + // Leader inits into round 0 + let (leader_state, leader_message) = leader + .leader_initialize( + &verify_key, + &aggregation_param, + &nonce, + &public_share, + &input_share, + ) + .unwrap(); + + // Helper inits into round 1 + let (helper_state, helper_message) = helper + .helper_initialize( + &verify_key, + &aggregation_param, + &nonce, + &public_share, + &input_share, + &leader_message, + ) + .unwrap(); + + // 2 round VDAF, round 1: helper should continue. + assert_matches!(helper_state, State::Continued(_)); + + let leader_state = leader + .continued(Role::Leader, leader_state, &helper_message) + .unwrap(); + // 2 round VDAF, round 1: leader should finish and emit a finish message. + let leader_message = assert_matches!( + leader_state, StateAndMessage::WithMessage { state: State::Finished(_), message } => message + ); + + let helper_state = helper + .continued(Role::Helper, helper_state, &leader_message) + .unwrap(); + // 2 round vdaf, round 1: helper should finish and emit no message. + assert_matches!(helper_state, StateAndMessage::FinishedNoMessage { .. }); + } + + #[test] + fn ping_pong_three_rounds() { + let verify_key = []; + let aggregation_param = dummy::AggregationParam(0); + let nonce = [0; 16]; + #[allow(clippy::let_unit_value)] + let public_share = (); + let input_share = dummy::InputShare(0); + + let leader = dummy::Vdaf::new(3); + let helper = dummy::Vdaf::new(3); + + // Leader inits into round 0 + let (leader_state, leader_message) = leader + .leader_initialize( + &verify_key, + &aggregation_param, + &nonce, + &public_share, + &input_share, + ) + .unwrap(); + + // Helper inits into round 1 + let (helper_state, helper_message) = helper + .helper_initialize( + &verify_key, + &aggregation_param, + &nonce, + &public_share, + &input_share, + &leader_message, + ) + .unwrap(); + + // 3 round VDAF, round 1: helper should continue. + assert_matches!(helper_state, State::Continued(_)); + + let leader_state = leader + .continued(Role::Leader, leader_state, &helper_message) + .unwrap(); + // 3 round VDAF, round 1: leader should continue and emit a continue message. + let (leader_state, leader_message) = assert_matches!( + leader_state, StateAndMessage::WithMessage { ref message, state } => (state, message) + ); + + let helper_state = helper + .continued(Role::Helper, helper_state, leader_message) + .unwrap(); + // 3 round vdaf, round 2: helper should finish and emit a finish message. + let helper_message = assert_matches!( + helper_state, StateAndMessage::WithMessage { message, state: State::Finished(_) } => message + ); + + let leader_state = leader + .continued(Role::Leader, leader_state, &helper_message) + .unwrap(); + // 3 round VDAF, round 2: leader should finish and emit no message. + assert_matches!(leader_state, StateAndMessage::FinishedNoMessage { .. }); + } +} diff --git a/src/vdaf.rs b/src/vdaf.rs index d7c1499f8..5ed4ba324 100644 --- a/src/vdaf.rs +++ b/src/vdaf.rs @@ -393,7 +393,7 @@ pub trait Aggregatable: Clone + Debug + From { } /// An output share comprised of a vector of field elements. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct OutputShare(Vec); impl PartialEq for OutputShare { @@ -432,6 +432,12 @@ impl Encode for OutputShare { } } +impl Debug for OutputShare { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("OutputShare").finish() + } +} + /// An aggregate share comprised of a vector of field elements. /// /// This is suitable for VDAFs where both output shares and aggregate shares are vectors of field @@ -729,6 +735,8 @@ mod tests { } } +#[cfg(feature = "test-util")] +pub mod dummy; #[cfg(all(feature = "crypto-dependencies", feature = "experimental"))] #[cfg_attr( docsrs, diff --git a/src/vdaf/dummy.rs b/src/vdaf/dummy.rs new file mode 100644 index 000000000..9d2c22b7e --- /dev/null +++ b/src/vdaf/dummy.rs @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Implementation of a dummy VDAF which conforms to the specification in [draft-irtf-cfrg-vdaf-06] +//! but does nothing. Useful for testing. +//! +//! [draft-irtf-cfrg-vdaf-06]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/06/ + +use crate::{ + codec::{CodecError, Decode, Encode}, + vdaf::{self, Aggregatable, PrepareTransition, VdafError}, +}; +use rand::random; +use std::{fmt::Debug, io::Cursor, sync::Arc}; + +type ArcPrepInitFn = + Arc Result<(), VdafError> + 'static + Send + Sync>; +type ArcPrepStepFn = Arc< + dyn Fn(&PrepareState) -> Result, VdafError> + + 'static + + Send + + Sync, +>; + +/// Dummy VDAF that does nothing. +#[derive(Clone)] +pub struct Vdaf { + rounds: u32, + prep_init_fn: ArcPrepInitFn, + prep_step_fn: ArcPrepStepFn, +} + +impl Debug for Vdaf { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Vdaf") + .field("prep_init_result", &"[redacted]") + .field("prep_step_result", &"[redacted]") + .finish() + } +} + +impl Vdaf { + /// The length of the verify key parameter for fake VDAF instantiations. + pub const VERIFY_KEY_LEN: usize = 0; + + /// Construct a new instance of the dummy VDAF. + pub fn new(rounds: u32) -> Self { + Self { + rounds, + prep_init_fn: Arc::new(|_| -> Result<(), VdafError> { Ok(()) }), + prep_step_fn: Arc::new( + |state| -> Result, VdafError> { + let new_round = state.current_round + 1; + if new_round == state.max_rounds { + Ok(PrepareTransition::Finish(OutputShare(state.input_share))) + } else { + Ok(PrepareTransition::Continue( + PrepareState { + current_round: new_round, + ..*state + }, + (), + )) + } + }, + ), + } + } + + /// Provide an alternate implementation of + /// [`vdaf::Aggregator::prepare_init`]. + pub fn with_prep_init_fn Result<(), VdafError>>( + mut self, + f: F, + ) -> Self + where + F: 'static + Send + Sync, + { + self.prep_init_fn = Arc::new(f); + self + } + + /// Provide an alternate implementation of + /// [`vdaf::Aggregator::prepare_step`]. + pub fn with_prep_step_fn< + F: Fn(&PrepareState) -> Result, VdafError>, + >( + mut self, + f: F, + ) -> Self + where + F: 'static + Send + Sync, + { + self.prep_step_fn = Arc::new(f); + self + } +} + +impl Default for Vdaf { + fn default() -> Self { + Self::new(1) + } +} + +impl vdaf::Vdaf for Vdaf { + const ID: u32 = 0xFFFF0000; + + type Measurement = u8; + type AggregateResult = u8; + type AggregationParam = AggregationParam; + type PublicShare = (); + type InputShare = InputShare; + type OutputShare = OutputShare; + type AggregateShare = AggregateShare; + + fn num_aggregators(&self) -> usize { + 2 + } +} + +impl vdaf::Aggregator<0, 16> for Vdaf { + type PrepareState = PrepareState; + type PrepareShare = (); + type PrepareMessage = (); + + fn prepare_init( + &self, + _verify_key: &[u8; 0], + _: usize, + aggregation_param: &Self::AggregationParam, + _nonce: &[u8; 16], + _: &Self::PublicShare, + input_share: &Self::InputShare, + ) -> Result<(Self::PrepareState, Self::PrepareShare), VdafError> { + (self.prep_init_fn)(aggregation_param)?; + Ok(( + PrepareState { + input_share: input_share.0, + current_round: 0, + max_rounds: self.rounds, + }, + (), + )) + } + + fn prepare_preprocess>( + &self, + _: M, + ) -> Result { + Ok(()) + } + + fn prepare_step( + &self, + state: Self::PrepareState, + _: Self::PrepareMessage, + ) -> Result, VdafError> { + (self.prep_step_fn)(&state) + } + + fn aggregate>( + &self, + _: &Self::AggregationParam, + output_shares: M, + ) -> Result { + let mut aggregate_share = AggregateShare(0); + for output_share in output_shares { + aggregate_share.accumulate(&output_share)?; + } + Ok(aggregate_share) + } +} + +impl vdaf::Client<16> for Vdaf { + fn shard( + &self, + measurement: &Self::Measurement, + _nonce: &[u8; 16], + ) -> Result<(Self::PublicShare, Vec), VdafError> { + let first_input_share = random(); + let (second_input_share, _) = measurement.overflowing_sub(first_input_share); + Ok(( + (), + Vec::from([ + InputShare(first_input_share), + InputShare(second_input_share), + ]), + )) + } +} + +/// A dummy input share. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct InputShare(pub u8); + +impl Encode for InputShare { + fn encode(&self, bytes: &mut Vec) { + self.0.encode(bytes) + } + + fn encoded_len(&self) -> Option { + self.0.encoded_len() + } +} + +impl Decode for InputShare { + fn decode(bytes: &mut Cursor<&[u8]>) -> Result { + Ok(Self(u8::decode(bytes)?)) + } +} + +/// Dummy aggregation parameter. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AggregationParam(pub u8); + +impl Encode for AggregationParam { + fn encode(&self, bytes: &mut Vec) { + self.0.encode(bytes) + } + + fn encoded_len(&self) -> Option { + self.0.encoded_len() + } +} + +impl Decode for AggregationParam { + fn decode(bytes: &mut Cursor<&[u8]>) -> Result { + Ok(Self(u8::decode(bytes)?)) + } +} + +/// Dummy output share. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OutputShare(pub u8); + +impl Decode for OutputShare { + fn decode(bytes: &mut Cursor<&[u8]>) -> Result { + Ok(Self(u8::decode(bytes)?)) + } +} + +impl Encode for OutputShare { + fn encode(&self, bytes: &mut Vec) { + self.0.encode(bytes); + } + + fn encoded_len(&self) -> Option { + self.0.encoded_len() + } +} + +/// Dummy prepare state. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct PrepareState { + input_share: u8, + current_round: u32, + max_rounds: u32, +} + +impl Encode for PrepareState { + fn encode(&self, bytes: &mut Vec) { + self.input_share.encode(bytes); + self.current_round.encode(bytes); + self.max_rounds.encode(bytes); + } + + fn encoded_len(&self) -> Option { + Some( + self.input_share.encoded_len()? + + self.current_round.encoded_len()? + + self.max_rounds.encoded_len()?, + ) + } +} + +impl Decode for PrepareState { + fn decode(bytes: &mut Cursor<&[u8]>) -> Result { + let input_share = u8::decode(bytes)?; + let current_round = u32::decode(bytes)?; + let max_rounds = u32::decode(bytes)?; + Ok(Self { + input_share, + current_round, + max_rounds, + }) + } +} + +/// Dummy aggregate share. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct AggregateShare(pub u64); + +impl Aggregatable for AggregateShare { + type OutputShare = OutputShare; + + fn merge(&mut self, other: &Self) -> Result<(), VdafError> { + self.0 += other.0; + Ok(()) + } + + fn accumulate(&mut self, out_share: &Self::OutputShare) -> Result<(), VdafError> { + self.0 += u64::from(out_share.0); + Ok(()) + } +} + +impl From for AggregateShare { + fn from(out_share: OutputShare) -> Self { + Self(u64::from(out_share.0)) + } +} + +impl Decode for AggregateShare { + fn decode(bytes: &mut Cursor<&[u8]>) -> Result { + let val = u64::decode(bytes)?; + Ok(Self(val)) + } +} + +impl Encode for AggregateShare { + fn encode(&self, bytes: &mut Vec) { + self.0.encode(bytes) + } + + fn encoded_len(&self) -> Option { + self.0.encoded_len() + } +} diff --git a/src/vdaf/poplar1.rs b/src/vdaf/poplar1.rs index 727e6f739..2f202ade0 100644 --- a/src/vdaf/poplar1.rs +++ b/src/vdaf/poplar1.rs @@ -312,7 +312,7 @@ impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1 { sketch: SketchState, output_share: Vec, @@ -329,6 +329,14 @@ impl Eq for PrepareState {} impl ConstantTimeEq for PrepareState { fn ct_eq(&self, other: &Self) -> Choice { self.sketch.ct_eq(&other.sketch) & self.output_share.ct_eq(&other.output_share) +} + +impl Debug for PrepareState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PrepareState") + .field("sketch", &"[redacted]") + .field("output_share", &"[redacted]") + .finish() } } diff --git a/src/vdaf/prio3.rs b/src/vdaf/prio3.rs index 5dd224380..3463f9eb5 100644 --- a/src/vdaf/prio3.rs +++ b/src/vdaf/prio3.rs @@ -942,7 +942,7 @@ where } /// State of each [`Aggregator`] during the Preparation phase. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Prio3PrepareState { measurement_share: Share, joint_rand_seed: Option>, @@ -970,6 +970,22 @@ impl ConstantTimeEq for Prio3PrepareS self.joint_rand_seed.as_ref(), other.joint_rand_seed.as_ref(), ) & self.measurement_share.ct_eq(&other.measurement_share) +} + +impl Debug for Prio3PrepareState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Prio3PrepareState") + .field("measurement_share", &"[redacted]") + .field( + "joint_rand_seed", + match self.joint_rand_seed { + Some(_) => &"Some([redacted])", + None => &"None", + }, + ) + .field("agg_id", &self.agg_id) + .field("verifier_len", &self.verifier_len) + .finish() } } diff --git a/supply-chain/config.toml b/supply-chain/config.toml index b97b527f5..1478c2b1c 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -187,6 +187,11 @@ criteria = "safe-to-deploy" version = "0.8.5" criteria = "safe-to-deploy" +[[exemptions.rand_chacha]] +version = "0.3.1" +criteria = "safe-to-deploy" +notes = "This dependency is only used if feature test-util is enabled, which should not be the case for production deployments." + [[exemptions.rand_distr]] version = "0.4.3" criteria = "safe-to-run" From b33f07d7d4a574697d2819e91c02459f3e9cb13e Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Mon, 21 Aug 2023 14:07:29 -0700 Subject: [PATCH 02/16] some notes and swap a parameter to better align with spec text --- src/topology/ping_pong.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index 7c50e8ace..ef5bd6232 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -326,6 +326,14 @@ pub trait PingPongTopology { /// Corresponds to `ping_pong_transition` in the forthcoming `draft-irtf-cfrg-vdaf-07`. + /// `prep_shares` must be ordered so that the leader's prepare share is first. + /// + /// # Notes + /// + /// The specification of this function in [VDAF] takes the aggregation parameter. This version + /// does not, because [`vdaf::Vdaf::prepare_preprocess`] does not take the aggregation + /// parameter. This may change in the future if/when [#670][issue] is addressed. + /// + /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 + /// [issue]: https://github.com/divviup/libprio-rs/issues/670 fn transition( &self, prep_shares: [Self::PrepareShare; 2], @@ -456,8 +474,8 @@ where next_peer_prep_share, ) .map_err(PingPongError::CodecPrepShare)?; - let mut prep_shares = [next_host_prep_share, next_peer_prep_share]; - if role == Role::Helper { + let mut prep_shares = [next_peer_prep_share, next_host_prep_share]; + if role == Role::Leader { prep_shares.reverse(); } self.transition(prep_shares, next_prep_state) From d6d3ab104186f1a61e0c2dfc8d6c29872634ad2c Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Wed, 23 Aug 2023 15:57:02 -0700 Subject: [PATCH 03/16] refactor `ping_pong_transition` --- src/topology/ping_pong.rs | 352 +++++++++++++++++++++++--------------- src/vdaf.rs | 9 +- 2 files changed, 217 insertions(+), 144 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index ef5bd6232..9ee61be56 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -157,80 +157,155 @@ impl Decode for Message { } } -/// Corresponds to the `State` enumeration implicitly defined in [VDAF's Ping-Pong Topology][VDAF]. -/// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in -/// code, and the `Rejected` state is represented as `std::result::Result::Error`, so this enum does -/// not include those variants. +/// A transition in the pong-pong topology. This represents the `ping_pong_transition` function +/// defined in [VDAF]. +/// +/// # Discussion +/// +/// The obvious implementation would of `ping_pong_transition` would be a method on trait +/// [`PingPongTopology`] that returns `(State, Message)`, and then `StateAndMessage::WithMessage` +/// would contain those values. But then DAP implementations would have to store relatively large +/// VDAF prepare shares between rounds of input preparation. +/// +/// Instead, this structure stores just the previous round's prepare state and the current round's +/// preprocessed prepare message. Their encoding is much smaller than the `(State, Message)` tuple, +/// which can always be recomputed with [`Self::evaluate`]. /// /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum State { - /// Preparation of the report will continue with the enclosed state. - Continued(PrepState), - /// Preparation of the report is finished and has yielded the enclosed output share. - Finished(OutputShare), +#[derive(Clone, Debug, Eq)] +pub struct Transition< + const VERIFY_KEY_SIZE: usize, + const NONCE_SIZE: usize, + A: Aggregator, +> { + previous_prepare_state: A::PrepareState, + current_prepare_message: A::PrepareMessage, } -impl Encode for State { - fn encode(&self, bytes: &mut Vec) { - match self { - Self::Continued(prep_state) => { - 0u8.encode(bytes); - prep_state.encode(bytes); - } - Self::Finished(output_share) => { - 1u8.encode(bytes); - output_share.encode(bytes); - } +impl< + const VERIFY_KEY_SIZE: usize, + const NONCE_SIZE: usize, + A: Aggregator, + > Transition +{ + /// Evaluate this transition to obtain a new [`State`] and a [`Message`] which should be + /// transmitted to the peer. + #[allow(clippy::type_complexity)] + pub fn evaluate( + &self, + vdaf: &A, + ) -> Result<(State, Message), PingPongError> { + vdaf.prepare_step( + self.previous_prepare_state.clone(), + self.current_prepare_message.clone(), + ) + .map(|transition| match transition { + PrepareTransition::Continue(prep_state, prep_share) => ( + State::Continued(prep_state), + Message::Continue { + prep_msg: self.current_prepare_message.get_encoded(), + prep_share: prep_share.get_encoded(), + }, + ), + PrepareTransition::Finish(output_share) => ( + State::Finished(output_share), + Message::Finish { + prep_msg: self.current_prepare_message.get_encoded(), + }, + ), + }) + .map_err(PingPongError::VdafPrepareStep) + } +} + +impl< + const VERIFY_KEY_SIZE: usize, + const NONCE_SIZE: usize, + A: Aggregator, + > PartialEq for Transition +{ + fn eq(&self, other: &Self) -> bool { + self.previous_prepare_state == other.previous_prepare_state + && self.current_prepare_message == other.current_prepare_message + } +} + +impl< + const VERIFY_KEY_SIZE: usize, + const NONCE_SIZE: usize, + A: Aggregator, + > Default for Transition +where + A::PrepareState: Default, + A::PrepareMessage: Default, +{ + fn default() -> Self { + Self { + previous_prepare_state: A::PrepareState::default(), + current_prepare_message: A::PrepareMessage::default(), } } +} + +impl Encode + for Transition +where + A: Aggregator, + A::PrepareState: Encode, +{ + fn encode(&self, bytes: &mut Vec) { + self.previous_prepare_state.encode(bytes); + self.current_prepare_message.encode(bytes); + } fn encoded_len(&self) -> Option { Some( - 1 + match self { - Self::Continued(prep_state) => prep_state.encoded_len()?, - Self::Finished(output_share) => output_share.encoded_len()?, - }, + self.previous_prepare_state.encoded_len()? + + self.current_prepare_message.encoded_len()?, ) } } -/// Decoding parameter for [`State`]. -pub struct StateDecodingParam<'a, PrepStateDecode, OutputShareDecode> { - /// The decoding parameter for the preparation state. - pub prep_state: &'a PrepStateDecode, - /// The decoding parameter for the output share. - pub output_share: &'a OutputShareDecode, -} - -impl< - 'a, - PrepStateDecode, - OutputShareDecode, - PrepState: ParameterizedDecode, - OutputShare: ParameterizedDecode, - > ParameterizedDecode> - for State +impl + ParameterizedDecode for Transition +where + A: Aggregator, + A::PrepareState: ParameterizedDecode + PartialEq, + A::PrepareMessage: PartialEq, { fn decode_with_param( - decoding_param: &StateDecodingParam, + decoding_param: &PrepareStateDecode, bytes: &mut std::io::Cursor<&[u8]>, ) -> Result { - let variant = u8::decode(bytes)?; - match variant { - 0 => Ok(Self::Continued(PrepState::decode_with_param( - decoding_param.prep_state, - bytes, - )?)), - 1 => Ok(Self::Finished(OutputShare::decode_with_param( - decoding_param.output_share, - bytes, - )?)), - _ => Err(CodecError::UnexpectedValue), - } + let previous_prepare_state = A::PrepareState::decode_with_param(decoding_param, bytes)?; + let current_prepare_message = + A::PrepareMessage::decode_with_param(&previous_prepare_state, bytes)?; + + Ok(Self { + previous_prepare_state, + current_prepare_message, + }) } } +/// Corresponds to the `State` enumeration implicitly defined in [VDAF's Ping-Pong Topology][VDAF]. +/// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in +/// code, and the `Rejected` state is represented as `std::result::Result::Error`, so this enum does +/// not include those variants. +/// +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum State< + const VERIFY_KEY_SIZE: usize, + const NONCE_SIZE: usize, + A: Aggregator, +> { + /// Preparation of the report will continue with the enclosed state. + Continued(A::PrepareState), + /// Preparation of the report is finished and has yielded the enclosed output share. + Finished(A::OutputShare), +} + /// Values returned by [`PingPongTopology::continued`]. /// /// Corresponds to the `State` enumeration implicitly defined in [VDAF's Ping-Pong Topology][VDAF], @@ -239,24 +314,27 @@ impl< /// type `Finished`. /// /// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in -/// code, and the `Rejected` state is represented as `std::result::Result::Error`, so this enum does +/// code, and the `Rejected` state is represented as [`std::result::Result::Error`], so this enum does /// not include those variants. /// /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum StateAndMessage { +#[derive(Clone, Debug)] +pub enum StateAndMessage< + const VERIFY_KEY_SIZE: usize, + const NONCE_SIZE: usize, + A: Aggregator, +> { /// The operation resulted in a new state and a message to transmit to the peer. WithMessage { - /// The [`State`] to which we just advanced. - state: State, - /// The [`Message`] which should now be transmitted to the peer. - message: Message, + /// The transition that will be executed. Call `Transition::evaluate` to obtain the next + /// [`State`] and a [`Message`] to transmit to the peer. + transition: Transition, }, /// The operation caused the host to finish preparation of the input share, yielding an output /// share and no message for the peer. FinishedNoMessage { /// The output share which may now be accumulated. - output_share: OutputShare, + output_share: A::OutputShare, }, } @@ -274,9 +352,10 @@ pub trait PingPongTopology Result<(Self::State, Message), PingPongError>; + ) -> Result, PingPongError>; /// Continue preparation based on the host's current state and an incoming [`Message`] from the /// peer. `role` is the host's [`Role`]. Corresponds to `ping_pong_contnued` in the forthcoming @@ -314,12 +398,19 @@ pub trait PingPongTopology Result, PingPongError>; -} - -/// Private interfaces for VDAF Ping-Pong topology. -trait PingPongTopologyPrivate: - PingPongTopology -{ - /// Corresponds to `ping_pong_transition` in the forthcoming `draft-irtf-cfrg-vdaf-07`. - /// `prep_shares` must be ordered so that the leader's prepare share is first. - /// - /// # Notes - /// - /// The specification of this function in [VDAF] takes the aggregation parameter. This version - /// does not, because [`vdaf::Vdaf::prepare_preprocess`] does not take the aggregation - /// parameter. This may change in the future if/when [#670][issue] is addressed. - /// - /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 - /// [issue]: https://github.com/divviup/libprio-rs/issues/670 - fn transition( - &self, - prep_shares: [Self::PrepareShare; 2], - host_prep_state: Self::PrepareState, - ) -> Result<(Self::State, Message), PingPongError>; + ) -> Result, PingPongError>; } impl @@ -369,8 +438,8 @@ impl where A: Aggregator, { - type State = State; - type StateAndMessage = StateAndMessage; + type State = State; + type StateAndMessage = StateAndMessage; fn leader_initialize( &self, @@ -407,7 +476,7 @@ where public_share: &Self::PublicShare, input_share: &Self::InputShare, inbound: &Message, - ) -> Result<(Self::State, Message), PingPongError> { + ) -> Result, PingPongError> { let (prep_state, prep_share) = self .prepare_init( verify_key, @@ -429,7 +498,14 @@ where )); }; - self.transition([inbound_prep_share, prep_share], prep_state) + let current_prepare_message = self + .prepare_preprocess([inbound_prep_share, prep_share]) + .map_err(PingPongError::VdafPreparePreprocess)?; + + Ok(Transition { + previous_prepare_state: prep_state, + current_prepare_message, + }) } fn continued( @@ -478,8 +554,16 @@ where if role == Role::Leader { prep_shares.reverse(); } - self.transition(prep_shares, next_prep_state) - .map(|(state, message)| StateAndMessage::WithMessage { state, message }) + let current_prepare_message = self + .prepare_preprocess(prep_shares) + .map_err(PingPongError::VdafPreparePreprocess)?; + + Ok(StateAndMessage::WithMessage { + transition: Transition { + previous_prepare_state: next_prep_state, + current_prepare_message, + }, + }) } (PrepareTransition::Finish(output_share), None) => { Ok(StateAndMessage::FinishedNoMessage { output_share }) @@ -497,40 +581,6 @@ where } } -impl - PingPongTopologyPrivate for A -where - A: Aggregator, -{ - fn transition( - &self, - prep_shares: [Self::PrepareShare; 2], - host_prep_state: Self::PrepareState, - ) -> Result<(Self::State, Message), PingPongError> { - let prep_message = self - .prepare_preprocess(prep_shares) - .map_err(PingPongError::VdafPreparePreprocess)?; - - self.prepare_step(host_prep_state, prep_message.clone()) - .map(|transition| match transition { - PrepareTransition::Continue(prep_state, prep_share) => ( - State::Continued(prep_state), - Message::Continue { - prep_msg: prep_message.get_encoded(), - prep_share: prep_share.get_encoded(), - }, - ), - PrepareTransition::Finish(output_share) => ( - State::Finished(output_share), - Message::Finish { - prep_msg: prep_message.get_encoded(), - }, - ), - }) - .map_err(PingPongError::VdafPrepareStep) - } -} - #[cfg(test)] mod tests { use assert_matches::assert_matches; @@ -572,6 +622,8 @@ mod tests { &input_share, &leader_message, ) + .unwrap() + .evaluate(&helper) .unwrap(); // 1 round VDAF: helper should finish immediately. @@ -617,6 +669,8 @@ mod tests { &input_share, &leader_message, ) + .unwrap() + .evaluate(&helper) .unwrap(); // 2 round VDAF, round 1: helper should continue. @@ -627,7 +681,11 @@ mod tests { .unwrap(); // 2 round VDAF, round 1: leader should finish and emit a finish message. let leader_message = assert_matches!( - leader_state, StateAndMessage::WithMessage { state: State::Finished(_), message } => message + leader_state, StateAndMessage::WithMessage { transition } => { + let (state, message) = transition.evaluate(&leader).unwrap(); + assert_matches!(state, State::Finished(_)); + message + } ); let helper_state = helper @@ -670,6 +728,8 @@ mod tests { &input_share, &leader_message, ) + .unwrap() + .evaluate(&helper) .unwrap(); // 3 round VDAF, round 1: helper should continue. @@ -680,15 +740,23 @@ mod tests { .unwrap(); // 3 round VDAF, round 1: leader should continue and emit a continue message. let (leader_state, leader_message) = assert_matches!( - leader_state, StateAndMessage::WithMessage { ref message, state } => (state, message) + leader_state, StateAndMessage::WithMessage { transition } => { + let (state, message) = transition.evaluate(&leader).unwrap(); + assert_matches!(state, State::Continued(_)); + (state, message) + } ); let helper_state = helper - .continued(Role::Helper, helper_state, leader_message) + .continued(Role::Helper, helper_state, &leader_message) .unwrap(); // 3 round vdaf, round 2: helper should finish and emit a finish message. let helper_message = assert_matches!( - helper_state, StateAndMessage::WithMessage { message, state: State::Finished(_) } => message + helper_state, StateAndMessage::WithMessage { transition } => { + let (state, message) = transition.evaluate(&helper).unwrap(); + assert_matches!(state, State::Finished(_)); + message + } ); let leader_state = leader diff --git a/src/vdaf.rs b/src/vdaf.rs index 5ed4ba324..1a6c5f031 100644 --- a/src/vdaf.rs +++ b/src/vdaf.rs @@ -222,7 +222,7 @@ pub trait Client: Vdaf { /// The Aggregator's role in the execution of a VDAF. pub trait Aggregator: Vdaf { /// State of the Aggregator during the Prepare process. - type PrepareState: Clone + Debug; + type PrepareState: Clone + Debug + PartialEq + Eq; /// The type of messages sent by each aggregator at each round of the Prepare Process. /// @@ -235,7 +235,12 @@ pub trait Aggregator: Vda /// /// Decoding takes a [`Self::PrepareState`] as a parameter; this [`Self::PrepareState`] may be /// associated with any aggregator involved in the execution of the VDAF. - type PrepareMessage: Clone + Debug + ParameterizedDecode + Encode; + type PrepareMessage: Clone + + Debug + + PartialEq + + Eq + + ParameterizedDecode + + Encode; /// Begins the Prepare process with the other Aggregators. The [`Self::PrepareState`] returned /// is passed to [`Self::prepare_next`] to get this aggregator's first-round prepare message. From 65a30b00c8c2213004703c3f27898ac9143319c6 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Wed, 23 Aug 2023 16:11:33 -0700 Subject: [PATCH 04/16] review feedback --- src/topology/ping_pong.rs | 65 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index 9ee61be56..f5b2ac6e7 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -92,7 +92,7 @@ impl Message { impl Debug for Message { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Message::{}", self.state_name()) + f.debug_tuple(self.state_name()).finish() } } @@ -163,7 +163,7 @@ impl Decode for Message { /// # Discussion /// /// The obvious implementation would of `ping_pong_transition` would be a method on trait -/// [`PingPongTopology`] that returns `(State, Message)`, and then `StateAndMessage::WithMessage` +/// [`PingPongTopology`] that returns `(State, Message)`, and then `ContinuedValue::WithMessage` /// would contain those values. But then DAP implementations would have to store relatively large /// VDAF prepare shares between rounds of input preparation. /// @@ -195,6 +195,8 @@ impl< &self, vdaf: &A, ) -> Result<(State, Message), PingPongError> { + let prep_msg = self.current_prepare_message.get_encoded(); + vdaf.prepare_step( self.previous_prepare_state.clone(), self.current_prepare_message.clone(), @@ -203,16 +205,13 @@ impl< PrepareTransition::Continue(prep_state, prep_share) => ( State::Continued(prep_state), Message::Continue { - prep_msg: self.current_prepare_message.get_encoded(), + prep_msg, prep_share: prep_share.get_encoded(), }, ), - PrepareTransition::Finish(output_share) => ( - State::Finished(output_share), - Message::Finish { - prep_msg: self.current_prepare_message.get_encoded(), - }, - ), + PrepareTransition::Finish(output_share) => { + (State::Finished(output_share), Message::Finish { prep_msg }) + } }) .map_err(PingPongError::VdafPrepareStep) } @@ -314,12 +313,12 @@ pub enum State< /// type `Finished`. /// /// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in -/// code, and the `Rejected` state is represented as [`std::result::Result::Error`], so this enum does -/// not include those variants. +/// code, and the `Rejected` state is represented as [`std::result::Result::Error`], so this enum +/// does not include those variants. /// /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 #[derive(Clone, Debug)] -pub enum StateAndMessage< +pub enum ContinuedValue< const VERIFY_KEY_SIZE: usize, const NONCE_SIZE: usize, A: Aggregator, @@ -346,8 +345,8 @@ pub trait PingPongTopology Result, PingPongError>; + ) -> Result, PingPongError>; } impl @@ -439,7 +438,7 @@ where A: Aggregator, { type State = State; - type StateAndMessage = StateAndMessage; + type ContinuedValue = ContinuedValue; fn leader_initialize( &self, @@ -513,7 +512,7 @@ where role: Role, host_state: Self::State, inbound: &Message, - ) -> Result { + ) -> Result { let host_prep_state = if let State::Continued(state) = host_state { state } else { @@ -558,7 +557,7 @@ where .prepare_preprocess(prep_shares) .map_err(PingPongError::VdafPreparePreprocess)?; - Ok(StateAndMessage::WithMessage { + Ok(ContinuedValue::WithMessage { transition: Transition { previous_prepare_state: next_prep_state, current_prepare_message, @@ -566,7 +565,7 @@ where }) } (PrepareTransition::Finish(output_share), None) => { - Ok(StateAndMessage::FinishedNoMessage { output_share }) + Ok(ContinuedValue::FinishedNoMessage { output_share }) } (transition, _) => { return Err(PingPongError::StateMismatch( @@ -633,7 +632,7 @@ mod tests { .continued(Role::Leader, leader_state, &helper_message) .unwrap(); // 1 round VDAF: leader should finish when it gets helper message and emit no message. - assert_matches!(leader_state, StateAndMessage::FinishedNoMessage { .. }); + assert_matches!(leader_state, ContinuedValue::FinishedNoMessage { .. }); } #[test] @@ -681,7 +680,7 @@ mod tests { .unwrap(); // 2 round VDAF, round 1: leader should finish and emit a finish message. let leader_message = assert_matches!( - leader_state, StateAndMessage::WithMessage { transition } => { + leader_state, ContinuedValue::WithMessage { transition } => { let (state, message) = transition.evaluate(&leader).unwrap(); assert_matches!(state, State::Finished(_)); message @@ -692,7 +691,7 @@ mod tests { .continued(Role::Helper, helper_state, &leader_message) .unwrap(); // 2 round vdaf, round 1: helper should finish and emit no message. - assert_matches!(helper_state, StateAndMessage::FinishedNoMessage { .. }); + assert_matches!(helper_state, ContinuedValue::FinishedNoMessage { .. }); } #[test] @@ -740,7 +739,7 @@ mod tests { .unwrap(); // 3 round VDAF, round 1: leader should continue and emit a continue message. let (leader_state, leader_message) = assert_matches!( - leader_state, StateAndMessage::WithMessage { transition } => { + leader_state, ContinuedValue::WithMessage { transition } => { let (state, message) = transition.evaluate(&leader).unwrap(); assert_matches!(state, State::Continued(_)); (state, message) @@ -752,7 +751,7 @@ mod tests { .unwrap(); // 3 round vdaf, round 2: helper should finish and emit a finish message. let helper_message = assert_matches!( - helper_state, StateAndMessage::WithMessage { transition } => { + helper_state, ContinuedValue::WithMessage { transition } => { let (state, message) = transition.evaluate(&helper).unwrap(); assert_matches!(state, State::Finished(_)); message @@ -763,6 +762,6 @@ mod tests { .continued(Role::Leader, leader_state, &helper_message) .unwrap(); // 3 round VDAF, round 2: leader should finish and emit no message. - assert_matches!(leader_state, StateAndMessage::FinishedNoMessage { .. }); + assert_matches!(leader_state, ContinuedValue::FinishedNoMessage { .. }); } } From aecc44738a52c8cfcc07d53e76d202f036b0e31d Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Wed, 23 Aug 2023 16:20:04 -0700 Subject: [PATCH 05/16] fix docs --- src/topology/ping_pong.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index f5b2ac6e7..ca922077d 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -289,7 +289,7 @@ where /// Corresponds to the `State` enumeration implicitly defined in [VDAF's Ping-Pong Topology][VDAF]. /// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in -/// code, and the `Rejected` state is represented as `std::result::Result::Error`, so this enum does +/// code, and the `Rejected` state is represented as `std::result::Result::Err`, so this enum does /// not include those variants. /// /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 @@ -306,17 +306,6 @@ pub enum State< } /// Values returned by [`PingPongTopology::continued`]. -/// -/// Corresponds to the `State` enumeration implicitly defined in [VDAF's Ping-Pong Topology][VDAF], -/// but combined with the components of [`Message`] so that no impossible states may be represented. -/// For example, it's impossible to be in the `Continued` state but to send an outgoing `Message` of -/// type `Finished`. -/// -/// VDAF describes `Start` and `Rejected` states, but the `Start` state is never instantiated in -/// code, and the `Rejected` state is represented as [`std::result::Result::Error`], so this enum -/// does not include those variants. -/// -/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 #[derive(Clone, Debug)] pub enum ContinuedValue< const VERIFY_KEY_SIZE: usize, @@ -420,9 +409,11 @@ pub trait PingPongTopology Date: Thu, 24 Aug 2023 09:25:26 -0700 Subject: [PATCH 06/16] review feedback add tests for encode/decode messages move max rounds into the dummy vdaf prep closure --- src/topology/ping_pong.rs | 108 +++++++++++++++++++++++++++++++++++++- src/vdaf/dummy.rs | 24 +++------ 2 files changed, 112 insertions(+), 20 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index ca922077d..a31770c76 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -573,11 +573,11 @@ where #[cfg(test)] mod tests { - use assert_matches::assert_matches; + use std::io::Cursor; use super::*; - use crate::vdaf::dummy; + use assert_matches::assert_matches; #[test] fn ping_pong_one_round() { @@ -755,4 +755,108 @@ mod tests { // 3 round VDAF, round 2: leader should finish and emit no message. assert_matches!(leader_state, ContinuedValue::FinishedNoMessage { .. }); } + + #[test] + fn roundtrip_message() { + let messages = [ + ( + Message::Initialize { + prep_share: Vec::from("prepare share"), + }, + concat!( + "00", // enum discriminant + concat!( + // prep_share + "0000000d", // length + "70726570617265207368617265", // contents + ), + ), + ), + ( + Message::Continue { + prep_msg: Vec::from("prepare message"), + prep_share: Vec::from("prepare share"), + }, + concat!( + "01", // enum discriminant + concat!( + // prep_msg + "0000000f", // length + "70726570617265206d657373616765", // contents + ), + concat!( + // prep_share + "0000000d", // length + "70726570617265207368617265", // contents + ), + ), + ), + ( + Message::Finish { + prep_msg: Vec::from("prepare message"), + }, + concat!( + "02", // enum discriminant + concat!( + // prep_msg + "0000000f", // length + "70726570617265206d657373616765", // contents + ), + ), + ), + ]; + + for (message, expected_hex) in messages { + let mut encoded_val = Vec::new(); + message.encode(&mut encoded_val); + let got_hex = hex::encode(&encoded_val); + assert_eq!( + &got_hex, expected_hex, + "Couldn't roundtrip (encoded value differs): {message:?}", + ); + let decoded_val = Message::decode(&mut Cursor::new(&encoded_val)).unwrap(); + assert_eq!( + decoded_val, message, + "Couldn't roundtrip (decoded value differs): {message:?}" + ); + assert_eq!( + encoded_val.len(), + message.encoded_len().expect("No encoded length hint"), + "Encoded length hint is incorrect: {message:?}" + ) + } + } + + #[test] + fn roundtrip_transition() { + // VDAF implementations have tests for encoding/decoding their respective PrepareShare and + // PrepareMessage types, so we test here using the dummy VDAF. + let transition = Transition::<0, 16, dummy::Vdaf> { + previous_prepare_state: dummy::PrepareState::default(), + current_prepare_message: (), + }; + + let encoded = transition.get_encoded(); + let hex_encoded = hex::encode(&encoded); + + assert_eq!( + hex_encoded, + concat!( + concat!( + // previous_prepare_state + "00", // input_share + "00000000", // current_round + ), + // current_prepare_message (0 length encoding) + ) + ); + + let decoded = Transition::get_decoded_with_param(&(), &encoded).unwrap(); + assert_eq!(transition, decoded); + + assert_eq!( + encoded.len(), + transition.encoded_len().expect("No encoded length hint"), + ); + } } diff --git a/src/vdaf/dummy.rs b/src/vdaf/dummy.rs index 9d2c22b7e..cd6c9a21a 100644 --- a/src/vdaf/dummy.rs +++ b/src/vdaf/dummy.rs @@ -24,7 +24,6 @@ type ArcPrepStepFn = Arc< /// Dummy VDAF that does nothing. #[derive(Clone)] pub struct Vdaf { - rounds: u32, prep_init_fn: ArcPrepInitFn, prep_step_fn: ArcPrepStepFn, } @@ -45,12 +44,11 @@ impl Vdaf { /// Construct a new instance of the dummy VDAF. pub fn new(rounds: u32) -> Self { Self { - rounds, prep_init_fn: Arc::new(|_| -> Result<(), VdafError> { Ok(()) }), prep_step_fn: Arc::new( - |state| -> Result, VdafError> { + move |state| -> Result, VdafError> { let new_round = state.current_round + 1; - if new_round == state.max_rounds { + if new_round == rounds { Ok(PrepareTransition::Finish(OutputShare(state.input_share))) } else { Ok(PrepareTransition::Continue( @@ -66,8 +64,7 @@ impl Vdaf { } } - /// Provide an alternate implementation of - /// [`vdaf::Aggregator::prepare_init`]. + /// Provide an alternate implementation of [`vdaf::Aggregator::prepare_init`]. pub fn with_prep_init_fn Result<(), VdafError>>( mut self, f: F, @@ -79,8 +76,7 @@ impl Vdaf { self } - /// Provide an alternate implementation of - /// [`vdaf::Aggregator::prepare_step`]. + /// Provide an alternate implementation of [`vdaf::Aggregator::prepare_step`]. pub fn with_prep_step_fn< F: Fn(&PrepareState) -> Result, VdafError>, >( @@ -136,7 +132,6 @@ impl vdaf::Aggregator<0, 16> for Vdaf { PrepareState { input_share: input_share.0, current_round: 0, - max_rounds: self.rounds, }, (), )) @@ -253,22 +248,16 @@ impl Encode for OutputShare { pub struct PrepareState { input_share: u8, current_round: u32, - max_rounds: u32, } impl Encode for PrepareState { fn encode(&self, bytes: &mut Vec) { self.input_share.encode(bytes); self.current_round.encode(bytes); - self.max_rounds.encode(bytes); } fn encoded_len(&self) -> Option { - Some( - self.input_share.encoded_len()? - + self.current_round.encoded_len()? - + self.max_rounds.encoded_len()?, - ) + Some(self.input_share.encoded_len()? + self.current_round.encoded_len()?) } } @@ -276,11 +265,10 @@ impl Decode for PrepareState { fn decode(bytes: &mut Cursor<&[u8]>) -> Result { let input_share = u8::decode(bytes)?; let current_round = u32::decode(bytes)?; - let max_rounds = u32::decode(bytes)?; + Ok(Self { input_share, current_round, - max_rounds, }) } } From ed9d3a590ce4c5b90004653d6e6ef9770024a10c Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Thu, 24 Aug 2023 11:42:25 -0700 Subject: [PATCH 07/16] more review feedback --- src/topology/ping_pong.rs | 7 +++++-- src/vdaf/dummy.rs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index a31770c76..aeb866b6c 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -336,6 +336,8 @@ pub trait PingPongTopology Result, PingPongError>; + ) -> Result; } impl @@ -430,6 +432,7 @@ where { type State = State; type ContinuedValue = ContinuedValue; + type Transition = Transition; fn leader_initialize( &self, @@ -466,7 +469,7 @@ where public_share: &Self::PublicShare, input_share: &Self::InputShare, inbound: &Message, - ) -> Result, PingPongError> { + ) -> Result { let (prep_state, prep_share) = self .prepare_init( verify_key, diff --git a/src/vdaf/dummy.rs b/src/vdaf/dummy.rs index cd6c9a21a..3e51413f6 100644 --- a/src/vdaf/dummy.rs +++ b/src/vdaf/dummy.rs @@ -31,8 +31,8 @@ pub struct Vdaf { impl Debug for Vdaf { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Vdaf") - .field("prep_init_result", &"[redacted]") - .field("prep_step_result", &"[redacted]") + .field("prep_init_fn", &"[redacted]") + .field("prep_step_fn", &"[redacted]") .finish() } } From 9f0e010405c2db6c66fda2bcc66ba33507d492c8 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Tue, 29 Aug 2023 15:43:27 -0700 Subject: [PATCH 08/16] cjpatton review feedback - update various doccomments - remove Role enum and provide distinct leader_continued, helper_continued - rename various structs to `PingPongWhatever` for consistency with Prio3, Poplar1 modules - rename leader_initialize, helper_initialize to leader_initialized, helper_initialized to align with continued --- src/topology/ping_pong.rs | 358 ++++++++++++++++++++++++-------------- 1 file changed, 228 insertions(+), 130 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index aeb866b6c..1db6c9647 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -1,11 +1,15 @@ // SPDX-License-Identifier: MPL-2.0 //! Implements the Ping-Pong Topology described in [VDAF]. This topology assumes there are exactly -//! two aggregators, designated "Leader" and "Helper". Note that while this implementation is -//! compatible with VDAF-06, it actually implements the ping-pong wrappers specified in VDAF-07 -//! (forthcoming) since those line up better with the VDAF implementation in this crate. +//! two aggregators, designated "Leader" and "Helper". This topology is required for implementing +//! the [Distributed Aggregation Protocol][DAP]. +//! +//! Note that while this implementation is compatible with VDAF-06, it actually implements the +//! ping-pong wrappers specified in VDAF-07 (forthcoming) since those line up better with the VDAF +//! implementation in this crate. //! //! [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +//! [DAP]: https://datatracker.ietf.org/doc/html/draft-ietf-ppm-dap use crate::{ codec::{decode_u32_items, encode_u32_items, CodecError, Decode, Encode, ParameterizedDecode}, @@ -45,22 +49,13 @@ pub enum PingPongError { InternalError(&'static str), } -/// Distinguished aggregator roles. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Role { - /// The Leader aggregator. - Leader, - /// The Helper aggregator. - Helper, -} - /// Corresponds to `struct Message` in [VDAF's Ping-Pong Topology][VDAF]. All of the fields of the /// variants are opaque byte buffers. This is because the ping-pong routines take responsibility for /// decoding preparation shares and messages, which usually requires having the preparation state. /// /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 #[derive(Clone, PartialEq, Eq)] -pub enum Message { +pub enum PingPongMessage { /// Corresponds to MessageType.initialize. Initialize { /// The leader's initial preparation share. @@ -80,7 +75,7 @@ pub enum Message { }, } -impl Message { +impl PingPongMessage { fn state_name(&self) -> &'static str { match self { Self::Initialize { .. } => "Initialize", @@ -90,13 +85,19 @@ impl Message { } } -impl Debug for Message { +impl Debug for PingPongMessage { + // We want `PingPongMessage` to implement `Debug`, but we don't want that impl to print out + // prepare shares or messages, because (1) their contents are sensitive and (2) their contents + // are long and not intelligible to humans. For both reasons they generally shouldn't get + // logged. Normally, we'd use the `derivative` crate to customize a derived `Debug`, but that + // crate has not been audited (in the `cargo vet` sense) so we can't use it here unless we audit + // 8,000+ lines of proc macros. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple(self.state_name()).finish() } } -impl Encode for Message { +impl Encode for PingPongMessage { fn encode(&self, bytes: &mut Vec) { // The encoding includes an implicit discriminator byte, called MessageType in the VDAF // spec. @@ -132,7 +133,7 @@ impl Encode for Message { } } -impl Decode for Message { +impl Decode for PingPongMessage { fn decode(bytes: &mut std::io::Cursor<&[u8]>) -> Result { let message_type = u8::decode(bytes)?; Ok(match message_type { @@ -162,7 +163,7 @@ impl Decode for Message { /// /// # Discussion /// -/// The obvious implementation would of `ping_pong_transition` would be a method on trait +/// The obvious implementation of `ping_pong_transition` would be a method on trait /// [`PingPongTopology`] that returns `(State, Message)`, and then `ContinuedValue::WithMessage` /// would contain those values. But then DAP implementations would have to store relatively large /// VDAF prepare shares between rounds of input preparation. @@ -173,7 +174,7 @@ impl Decode for Message { /// /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 #[derive(Clone, Debug, Eq)] -pub struct Transition< +pub struct PingPongTransition< const VERIFY_KEY_SIZE: usize, const NONCE_SIZE: usize, A: Aggregator, @@ -186,15 +187,21 @@ impl< const VERIFY_KEY_SIZE: usize, const NONCE_SIZE: usize, A: Aggregator, - > Transition + > PingPongTransition { - /// Evaluate this transition to obtain a new [`State`] and a [`Message`] which should be - /// transmitted to the peer. + /// Evaluate this transition to obtain a new [`PingPongState`] and a [`PingPongMessage`] which + /// should be transmitted to the peer. #[allow(clippy::type_complexity)] pub fn evaluate( &self, vdaf: &A, - ) -> Result<(State, Message), PingPongError> { + ) -> Result< + ( + PingPongState, + PingPongMessage, + ), + PingPongError, + > { let prep_msg = self.current_prepare_message.get_encoded(); vdaf.prepare_step( @@ -203,15 +210,16 @@ impl< ) .map(|transition| match transition { PrepareTransition::Continue(prep_state, prep_share) => ( - State::Continued(prep_state), - Message::Continue { + PingPongState::Continued(prep_state), + PingPongMessage::Continue { prep_msg, prep_share: prep_share.get_encoded(), }, ), - PrepareTransition::Finish(output_share) => { - (State::Finished(output_share), Message::Finish { prep_msg }) - } + PrepareTransition::Finish(output_share) => ( + PingPongState::Finished(output_share), + PingPongMessage::Finish { prep_msg }, + ), }) .map_err(PingPongError::VdafPrepareStep) } @@ -221,7 +229,7 @@ impl< const VERIFY_KEY_SIZE: usize, const NONCE_SIZE: usize, A: Aggregator, - > PartialEq for Transition + > PartialEq for PingPongTransition { fn eq(&self, other: &Self) -> bool { self.previous_prepare_state == other.previous_prepare_state @@ -233,7 +241,7 @@ impl< const VERIFY_KEY_SIZE: usize, const NONCE_SIZE: usize, A: Aggregator, - > Default for Transition + > Default for PingPongTransition where A::PrepareState: Default, A::PrepareMessage: Default, @@ -247,7 +255,7 @@ where } impl Encode - for Transition + for PingPongTransition where A: Aggregator, A::PrepareState: Encode, @@ -266,7 +274,7 @@ where } impl - ParameterizedDecode for Transition + ParameterizedDecode for PingPongTransition where A: Aggregator, A::PrepareState: ParameterizedDecode + PartialEq, @@ -294,7 +302,7 @@ where /// /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 #[derive(Clone, Debug, PartialEq, Eq)] -pub enum State< +pub enum PingPongState< const VERIFY_KEY_SIZE: usize, const NONCE_SIZE: usize, A: Aggregator, @@ -305,18 +313,20 @@ pub enum State< Finished(A::OutputShare), } -/// Values returned by [`PingPongTopology::continued`]. +/// Values returned by [`PingPongTopology::leader_continued`] or +/// [`PingPongTopology::helper_continued`]. #[derive(Clone, Debug)] -pub enum ContinuedValue< +pub enum PingPongContinuedValue< const VERIFY_KEY_SIZE: usize, const NONCE_SIZE: usize, A: Aggregator, > { /// The operation resulted in a new state and a message to transmit to the peer. WithMessage { - /// The transition that will be executed. Call `Transition::evaluate` to obtain the next - /// [`State`] and a [`Message`] to transmit to the peer. - transition: Transition, + /// The transition that will be executed. Call `PingPongTransition::evaluate` to obtain the + /// next + /// [`PingPongState`] and a [`PingPongMessage`] to transmit to the peer. + transition: PingPongTransition, }, /// The operation caused the host to finish preparation of the input share, yielding an output /// share and no message for the peer. @@ -332,81 +342,85 @@ pub enum ContinuedValue< pub trait PingPongTopology: Aggregator { - /// Specialization of [`State`] for this VDAF. + /// Specialization of [`PingPongState`] for this VDAF. type State; - /// Specialization of [`ContinuedValue`] for this VDAF. + /// Specialization of [`PingPongContinuedValue`] for this VDAF. type ContinuedValue; - /// Specializaton of [`Transition`] for this VDAF. + /// Specializaton of [`PingPongTransition`] for this VDAF. type Transition; /// Initialize leader state using the leader's input share. Corresponds to - /// `ping_pong_leader_init` in the forthcoming `draft-irtf-cfrg-vdaf-07`. + /// `ping_pong_leader_init` in [VDAF]. + /// + /// If successful, the returned [`PingPongMessage`] (which will always be + /// `PingPongMessage::Initialize`) should be transmitted to the helper. The returned + /// [`PingPongState`] (which will always be `PingPongState::Continued`) should be used by the + /// leader along with the next [`PingPongMessage`] received from the helper as input to + /// [`Self::leader_continued`] to advance to the next round. /// - /// If successful, the returned [`Message`] (which will always be `Message::Initialize`) should - /// be transmitted to the helper. The returned [`State`] (which will always be - /// `State::Continued`) should be used by the leader along with the next [`Message`] received - /// from the helper as input to [`Self::continued`] to advance to the next round. - fn leader_initialize( + /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 + fn leader_initialized( &self, verify_key: &[u8; VERIFY_KEY_SIZE], agg_param: &Self::AggregationParam, nonce: &[u8; NONCE_SIZE], public_share: &Self::PublicShare, input_share: &Self::InputShare, - ) -> Result<(Self::State, Message), PingPongError>; + ) -> Result<(Self::State, PingPongMessage), PingPongError>; /// Initialize helper state using the helper's input share and the leader's first prepare share. /// Corresponds to `ping_pong_helper_init` in the forthcoming `draft-irtf-cfrg-vdaf-07`. /// - /// If successful, the returned [`Transition`] should be evaluated, yielding a [`Message`], - /// which should be transmitted to the leader, and a [`State`]. + /// If successful, the returned [`PingPongTransition`] should be evaluated, yielding a + /// [`PingPongMessage`], which should be transmitted to the leader, and a [`PingPongState`]. /// - /// If the state is `State::Continued`, then it should be used by the helper along with the next - /// `Message` received from the leader as input to [`Self::continued`] to advance to the next - /// round. The helper may store the `Transition` between rounds of preparation instead of the - /// `State` and `Message`. + /// If the state is `PingPongState::Continued`, then it should be used by the helper along with + /// the next `PingPongMessage` received from the leader as input to [`Self::helper_continued`] + /// to advance to the next round. The helper may store the `PingPongTransition` between rounds + /// of preparation instead of the `PingPongState` and `PingPongMessage`. /// - /// If the state is `State::Finished`, then preparation is finished and the output share may be - /// accumulated. + /// If the state is `PingPongState::Finished`, then preparation is finished and the output share + /// may be accumulated. /// /// # Errors /// - /// `inbound` must be `Message::Initialize` or the function will fail. - fn helper_initialize( + /// `inbound` must be `PingPongMessage::Initialize` or the function will fail. + fn helper_initialized( &self, verify_key: &[u8; VERIFY_KEY_SIZE], agg_param: &Self::AggregationParam, nonce: &[u8; NONCE_SIZE], public_share: &Self::PublicShare, input_share: &Self::InputShare, - inbound: &Message, - ) -> Result, PingPongError>; + inbound: &PingPongMessage, + ) -> Result, PingPongError>; - /// Continue preparation based on the host's current state and an incoming [`Message`] from the - /// peer. `role` is the host's [`Role`]. Corresponds to `ping_pong_contnued` in the forthcoming - /// `draft-irtf-cfrg-vdaf-07`. + /// Continue preparation based on the leader's current state and an incoming [`PingPongMessage`] + /// from the helper. Corresponds to `ping_pong_leader_continued` in [VDAF]. /// - /// If successful, the returned [`ContinuedValue`] will either be: + /// If successful, the returned [`PingPongContinuedValue`] will either be: /// - /// - `ContinuedValue::WithMessage { transition }`: `transition` should be evaluated, yielding a - /// [`Message`], which should be transmitted to the peer, and a [`State`]. + /// - `PingPongContinuedValue::WithMessage { transition }`: `transition` should be evaluated, + /// yielding a [`PingPongMessage`], which should be transmitted to the helper, and a + /// [`PingPongState`]. /// - /// If the state is `State::Continued`, then it should be used by this aggregator along with - /// the next `Message` received from the peer as input to [`Self::continued`] to advance to - /// the next round. The aggregator may store the `Transition` between rounds of preparation - /// instead of the `State` and `Message`. + /// If the state is `PingPongState::Continued`, then it should be used by the leader along + /// with the next `PingPongMessage` received from the helper as input to + /// [`Self::leader_continued`] to advance to the next round. The leader may store the + /// `PingPongTransition` between rounds of preparation instead of of the `PingPongState` and + /// `PingPongMessage`. /// - /// If the state is `State::Finished`, then preparation is finished and the output share may - /// be accumulated. + /// If the state is `PingPongState::Finished`, then preparation is finished and the output + /// share may be accumulated. /// - /// - `ContinuedValue::FinishedNoMessage`: preparation is finished and the output share may be - /// accumulated. No message needs to be sent to the peer. + /// - `PingPongContinuedValue::FinishedNoMessage`: preparation is finished and the output share + /// may be accumulated. No message needs to be sent to the helper. /// /// # Errors /// - /// `host_state` must be `State::Continued` or the function will fail. + /// `leader_state` must be `PingPongState::Continued` or the function will fail. /// - /// `inbound` must not be `Message::Initialize` or the function will fail. + /// `inbound` must not be `PingPongMessage::Initialize` or the function will fail. /// /// # Notes /// @@ -417,11 +431,64 @@ pub trait PingPongTopology Result; + + /// PingPongContinue preparation based on the helper's current state and an incoming + /// [`PingPongMessage`] from the leader. Corresponds to `ping_pong_helper_contnued` in [VDAF]. + /// + /// If successful, the returned [`PingPongContinuedValue`] will either be: + /// + /// - `PingPongContinuedValue::WithMessage { transition }`: `transition` should be evaluated, + /// yielding a [`PingPongMessage`], which should be transmitted to the leader, and a + /// [`PingPongState`]. + /// + /// If the state is `PingPongState::Continued`, then it should be used by the helper along + /// with the next `PingPongMessage` received from the leader as input to + /// [`Self::helper_continued`] to advance to the next round. The helper may store the + /// `PingPongTransition` between rounds of preparation instead of the `PingPongState` and + /// `PingPongMessage`. + /// + /// If the state is `PingPongState::Finished`, then preparation is finished and the output + /// share may be accumulated. + /// + /// - `PingPongContinuedValue::FinishedNoMessage`: preparation is finished and the output share + /// may be accumulated. No message needs to be sent to the leader. + /// + /// # Errors + /// + /// `helper_state` must be `PingPongState::Continued` or the function will fail. + /// + /// `inbound` must not be `PingPongMessage::Initialize` or the function will fail. + /// + /// # Notes + /// + /// The specification of this function in [VDAF] takes the aggregation parameter. This version + /// does not, because [`crate::vdaf::Aggregator::prepare_preprocess`] does not take the + /// aggregation parameter. This may change in the future if/when [#670][issue] is addressed. + /// + /// + /// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 + /// [issue]: https://github.com/divviup/libprio-rs/issues/670 + fn helper_continued( + &self, + helper_state: Self::State, + inbound: &PingPongMessage, + ) -> Result; +} + +/// Private interfaces for implementing ping-pong +trait PingPongTopologyPrivate: + PingPongTopology +{ fn continued( &self, - role: Role, - host_state: Self::State, - inbound: &Message, + is_leader: bool, + helper_state: Self::State, + inbound: &PingPongMessage, ) -> Result; } @@ -430,18 +497,18 @@ impl where A: Aggregator, { - type State = State; - type ContinuedValue = ContinuedValue; - type Transition = Transition; + type State = PingPongState; + type ContinuedValue = PingPongContinuedValue; + type Transition = PingPongTransition; - fn leader_initialize( + fn leader_initialized( &self, verify_key: &[u8; VERIFY_KEY_SIZE], agg_param: &Self::AggregationParam, nonce: &[u8; NONCE_SIZE], public_share: &Self::PublicShare, input_share: &Self::InputShare, - ) -> Result<(Self::State, Message), PingPongError> { + ) -> Result<(Self::State, PingPongMessage), PingPongError> { self.prepare_init( verify_key, /* Leader */ 0, @@ -452,8 +519,8 @@ where ) .map(|(prep_state, prep_share)| { ( - State::Continued(prep_state), - Message::Initialize { + PingPongState::Continued(prep_state), + PingPongMessage::Initialize { prep_share: prep_share.get_encoded(), }, ) @@ -461,14 +528,14 @@ where .map_err(PingPongError::VdafPrepareInit) } - fn helper_initialize( + fn helper_initialized( &self, verify_key: &[u8; VERIFY_KEY_SIZE], agg_param: &Self::AggregationParam, nonce: &[u8; NONCE_SIZE], public_share: &Self::PublicShare, input_share: &Self::InputShare, - inbound: &Message, + inbound: &PingPongMessage, ) -> Result { let (prep_state, prep_share) = self .prepare_init( @@ -481,7 +548,7 @@ where ) .map_err(PingPongError::VdafPrepareInit)?; - let inbound_prep_share = if let Message::Initialize { prep_share } = inbound { + let inbound_prep_share = if let PingPongMessage::Initialize { prep_share } = inbound { Self::PrepareShare::get_decoded_with_param(&prep_state, prep_share) .map_err(PingPongError::CodecPrepShare)? } else { @@ -495,36 +562,58 @@ where .prepare_preprocess([inbound_prep_share, prep_share]) .map_err(PingPongError::VdafPreparePreprocess)?; - Ok(Transition { + Ok(PingPongTransition { previous_prepare_state: prep_state, current_prepare_message, }) } + fn leader_continued( + &self, + leader_state: Self::State, + inbound: &PingPongMessage, + ) -> Result { + self.continued(true, leader_state, inbound) + } + + fn helper_continued( + &self, + helper_state: Self::State, + inbound: &PingPongMessage, + ) -> Result { + self.continued(false, helper_state, inbound) + } +} + +impl + PingPongTopologyPrivate for A +where + A: Aggregator, +{ fn continued( &self, - role: Role, + is_leader: bool, host_state: Self::State, - inbound: &Message, + inbound: &PingPongMessage, ) -> Result { - let host_prep_state = if let State::Continued(state) = host_state { + let host_prep_state = if let PingPongState::Continued(state) = host_state { state } else { return Err(PingPongError::StateMismatch("finished", "continue")); }; let (prep_msg, next_peer_prep_share) = match inbound { - Message::Initialize { .. } => { + PingPongMessage::Initialize { .. } => { return Err(PingPongError::StateMismatch( "continue", inbound.state_name(), )); } - Message::Continue { + PingPongMessage::Continue { prep_msg, prep_share, } => (prep_msg, Some(prep_share)), - Message::Finish { prep_msg } => (prep_msg, None), + PingPongMessage::Finish { prep_msg } => (prep_msg, None), }; let prep_msg = Self::PrepareMessage::get_decoded_with_param(&host_prep_state, prep_msg) @@ -544,22 +633,22 @@ where ) .map_err(PingPongError::CodecPrepShare)?; let mut prep_shares = [next_peer_prep_share, next_host_prep_share]; - if role == Role::Leader { + if is_leader { prep_shares.reverse(); } let current_prepare_message = self .prepare_preprocess(prep_shares) .map_err(PingPongError::VdafPreparePreprocess)?; - Ok(ContinuedValue::WithMessage { - transition: Transition { + Ok(PingPongContinuedValue::WithMessage { + transition: PingPongTransition { previous_prepare_state: next_prep_state, current_prepare_message, }, }) } (PrepareTransition::Finish(output_share), None) => { - Ok(ContinuedValue::FinishedNoMessage { output_share }) + Ok(PingPongContinuedValue::FinishedNoMessage { output_share }) } (transition, _) => { return Err(PingPongError::StateMismatch( @@ -596,7 +685,7 @@ mod tests { // Leader inits into round 0 let (leader_state, leader_message) = leader - .leader_initialize( + .leader_initialized( &verify_key, &aggregation_param, &nonce, @@ -607,7 +696,7 @@ mod tests { // Helper inits into round 1 let (helper_state, helper_message) = helper - .helper_initialize( + .helper_initialized( &verify_key, &aggregation_param, &nonce, @@ -620,13 +709,16 @@ mod tests { .unwrap(); // 1 round VDAF: helper should finish immediately. - assert_matches!(helper_state, State::Finished(_)); + assert_matches!(helper_state, PingPongState::Finished(_)); let leader_state = leader - .continued(Role::Leader, leader_state, &helper_message) + .leader_continued(leader_state, &helper_message) .unwrap(); // 1 round VDAF: leader should finish when it gets helper message and emit no message. - assert_matches!(leader_state, ContinuedValue::FinishedNoMessage { .. }); + assert_matches!( + leader_state, + PingPongContinuedValue::FinishedNoMessage { .. } + ); } #[test] @@ -643,7 +735,7 @@ mod tests { // Leader inits into round 0 let (leader_state, leader_message) = leader - .leader_initialize( + .leader_initialized( &verify_key, &aggregation_param, &nonce, @@ -654,7 +746,7 @@ mod tests { // Helper inits into round 1 let (helper_state, helper_message) = helper - .helper_initialize( + .helper_initialized( &verify_key, &aggregation_param, &nonce, @@ -667,25 +759,28 @@ mod tests { .unwrap(); // 2 round VDAF, round 1: helper should continue. - assert_matches!(helper_state, State::Continued(_)); + assert_matches!(helper_state, PingPongState::Continued(_)); let leader_state = leader - .continued(Role::Leader, leader_state, &helper_message) + .leader_continued(leader_state, &helper_message) .unwrap(); // 2 round VDAF, round 1: leader should finish and emit a finish message. let leader_message = assert_matches!( - leader_state, ContinuedValue::WithMessage { transition } => { + leader_state, PingPongContinuedValue::WithMessage { transition } => { let (state, message) = transition.evaluate(&leader).unwrap(); - assert_matches!(state, State::Finished(_)); + assert_matches!(state, PingPongState::Finished(_)); message } ); let helper_state = helper - .continued(Role::Helper, helper_state, &leader_message) + .helper_continued(helper_state, &leader_message) .unwrap(); // 2 round vdaf, round 1: helper should finish and emit no message. - assert_matches!(helper_state, ContinuedValue::FinishedNoMessage { .. }); + assert_matches!( + helper_state, + PingPongContinuedValue::FinishedNoMessage { .. } + ); } #[test] @@ -702,7 +797,7 @@ mod tests { // Leader inits into round 0 let (leader_state, leader_message) = leader - .leader_initialize( + .leader_initialized( &verify_key, &aggregation_param, &nonce, @@ -713,7 +808,7 @@ mod tests { // Helper inits into round 1 let (helper_state, helper_message) = helper - .helper_initialize( + .helper_initialized( &verify_key, &aggregation_param, &nonce, @@ -726,44 +821,47 @@ mod tests { .unwrap(); // 3 round VDAF, round 1: helper should continue. - assert_matches!(helper_state, State::Continued(_)); + assert_matches!(helper_state, PingPongState::Continued(_)); let leader_state = leader - .continued(Role::Leader, leader_state, &helper_message) + .leader_continued(leader_state, &helper_message) .unwrap(); // 3 round VDAF, round 1: leader should continue and emit a continue message. let (leader_state, leader_message) = assert_matches!( - leader_state, ContinuedValue::WithMessage { transition } => { + leader_state, PingPongContinuedValue::WithMessage { transition } => { let (state, message) = transition.evaluate(&leader).unwrap(); - assert_matches!(state, State::Continued(_)); + assert_matches!(state, PingPongState::Continued(_)); (state, message) } ); let helper_state = helper - .continued(Role::Helper, helper_state, &leader_message) + .helper_continued(helper_state, &leader_message) .unwrap(); // 3 round vdaf, round 2: helper should finish and emit a finish message. let helper_message = assert_matches!( - helper_state, ContinuedValue::WithMessage { transition } => { + helper_state, PingPongContinuedValue::WithMessage { transition } => { let (state, message) = transition.evaluate(&helper).unwrap(); - assert_matches!(state, State::Finished(_)); + assert_matches!(state, PingPongState::Finished(_)); message } ); let leader_state = leader - .continued(Role::Leader, leader_state, &helper_message) + .leader_continued(leader_state, &helper_message) .unwrap(); // 3 round VDAF, round 2: leader should finish and emit no message. - assert_matches!(leader_state, ContinuedValue::FinishedNoMessage { .. }); + assert_matches!( + leader_state, + PingPongContinuedValue::FinishedNoMessage { .. } + ); } #[test] fn roundtrip_message() { let messages = [ ( - Message::Initialize { + PingPongMessage::Initialize { prep_share: Vec::from("prepare share"), }, concat!( @@ -776,7 +874,7 @@ mod tests { ), ), ( - Message::Continue { + PingPongMessage::Continue { prep_msg: Vec::from("prepare message"), prep_share: Vec::from("prepare share"), }, @@ -795,7 +893,7 @@ mod tests { ), ), ( - Message::Finish { + PingPongMessage::Finish { prep_msg: Vec::from("prepare message"), }, concat!( @@ -817,7 +915,7 @@ mod tests { &got_hex, expected_hex, "Couldn't roundtrip (encoded value differs): {message:?}", ); - let decoded_val = Message::decode(&mut Cursor::new(&encoded_val)).unwrap(); + let decoded_val = PingPongMessage::decode(&mut Cursor::new(&encoded_val)).unwrap(); assert_eq!( decoded_val, message, "Couldn't roundtrip (decoded value differs): {message:?}" @@ -834,7 +932,7 @@ mod tests { fn roundtrip_transition() { // VDAF implementations have tests for encoding/decoding their respective PrepareShare and // PrepareMessage types, so we test here using the dummy VDAF. - let transition = Transition::<0, 16, dummy::Vdaf> { + let transition = PingPongTransition::<0, 16, dummy::Vdaf> { previous_prepare_state: dummy::PrepareState::default(), current_prepare_message: (), }; @@ -854,7 +952,7 @@ mod tests { ) ); - let decoded = Transition::get_decoded_with_param(&(), &encoded).unwrap(); + let decoded = PingPongTransition::get_decoded_with_param(&(), &encoded).unwrap(); assert_eq!(transition, decoded); assert_eq!( From cd4f8ce1298937d9fed4dfc8ce757a8723c347c8 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Wed, 30 Aug 2023 14:51:22 -0700 Subject: [PATCH 09/16] remove impl Default for PingPongTransition --- src/topology/ping_pong.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index 1db6c9647..99d255314 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -237,23 +237,6 @@ impl< } } -impl< - const VERIFY_KEY_SIZE: usize, - const NONCE_SIZE: usize, - A: Aggregator, - > Default for PingPongTransition -where - A::PrepareState: Default, - A::PrepareMessage: Default, -{ - fn default() -> Self { - Self { - previous_prepare_state: A::PrepareState::default(), - current_prepare_message: A::PrepareMessage::default(), - } - } -} - impl Encode for PingPongTransition where From 67c7a2e7652e567419a0335beb345459e448f67a Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Wed, 30 Aug 2023 15:09:35 -0700 Subject: [PATCH 10/16] David Cook review feedback --- src/topology/ping_pong.rs | 53 ++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index 99d255314..2c490e6b7 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -40,9 +40,23 @@ pub enum PingPongError { #[error("decode prep message {0}")] CodecPrepMessage(CodecError), - /// State machine mismatch between peer and host. - #[error("state mismatch: host state {0} peer state {0}")] - StateMismatch(&'static str, &'static str), + /// Host is in an unexpected state + #[error("host state mismatch: in {found} expected {expected}")] + HostStateMismatch { + /// The state the host is in. + found: &'static str, + /// The state the host expected to be in. + expected: &'static str, + }, + + /// Message from peer indicates it is in an unexpected state + #[error("peer message state mismatch: message is {found} expected {expected}")] + PeerMessageStateMismatch { + /// The state in the message from the peer. + found: &'static str, + /// The state the message from the peer was expected to be in. + expected: &'static str, + }, /// Internal error #[error("internal error: {0}")] @@ -76,7 +90,7 @@ pub enum PingPongMessage { } impl PingPongMessage { - fn state_name(&self) -> &'static str { + fn variant(&self) -> &'static str { match self { Self::Initialize { .. } => "Initialize", Self::Continue { .. } => "Continue", @@ -93,7 +107,7 @@ impl Debug for PingPongMessage { // crate has not been audited (in the `cargo vet` sense) so we can't use it here unless we audit // 8,000+ lines of proc macros. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple(self.state_name()).finish() + f.debug_tuple(self.variant()).finish() } } @@ -535,10 +549,10 @@ where Self::PrepareShare::get_decoded_with_param(&prep_state, prep_share) .map_err(PingPongError::CodecPrepShare)? } else { - return Err(PingPongError::StateMismatch( - "initialize", - inbound.state_name(), - )); + return Err(PingPongError::PeerMessageStateMismatch { + found: inbound.variant(), + expected: "initialize", + }); }; let current_prepare_message = self @@ -582,15 +596,18 @@ where let host_prep_state = if let PingPongState::Continued(state) = host_state { state } else { - return Err(PingPongError::StateMismatch("finished", "continue")); + return Err(PingPongError::HostStateMismatch { + found: "finished", + expected: "continued", + }); }; let (prep_msg, next_peer_prep_share) = match inbound { PingPongMessage::Initialize { .. } => { - return Err(PingPongError::StateMismatch( - "continue", - inbound.state_name(), - )); + return Err(PingPongError::PeerMessageStateMismatch { + found: inbound.variant(), + expected: "continued", + }); } PingPongMessage::Continue { prep_msg, @@ -634,13 +651,13 @@ where Ok(PingPongContinuedValue::FinishedNoMessage { output_share }) } (transition, _) => { - return Err(PingPongError::StateMismatch( - inbound.state_name(), - match transition { + return Err(PingPongError::HostStateMismatch { + found: match transition { PrepareTransition::Continue(_, _) => "continue", PrepareTransition::Finish(_) => "finished", }, - )) + expected: inbound.variant(), + }) } } } From 2fdcfbc266347564a7fb998d5623a43103ecb12f Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Wed, 30 Aug 2023 15:10:50 -0700 Subject: [PATCH 11/16] remove obsolete rand_chacha exemption --- supply-chain/config.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 1478c2b1c..b97b527f5 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -187,11 +187,6 @@ criteria = "safe-to-deploy" version = "0.8.5" criteria = "safe-to-deploy" -[[exemptions.rand_chacha]] -version = "0.3.1" -criteria = "safe-to-deploy" -notes = "This dependency is only used if feature test-util is enabled, which should not be the case for production deployments." - [[exemptions.rand_distr]] version = "0.4.3" criteria = "safe-to-run" From 8caf770e5c8640e6ba48b9bb0e3e38edb9be2de6 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Fri, 1 Sep 2023 11:24:48 -0700 Subject: [PATCH 12/16] review feedback --- src/topology/mod.rs | 2 +- src/topology/ping_pong.rs | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/topology/mod.rs b/src/topology/mod.rs index 8bf73d45f..fdce6d722 100644 --- a/src/topology/mod.rs +++ b/src/topology/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -//! Implementations of the aggregator communication topologies specified in [VDAF]. +//! Implementations of some aggregator communication topologies specified in [VDAF]. //! //! [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.7 diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index 2c490e6b7..459b153d5 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -50,11 +50,11 @@ pub enum PingPongError { }, /// Message from peer indicates it is in an unexpected state - #[error("peer message state mismatch: message is {found} expected {expected}")] - PeerMessageStateMismatch { + #[error("peer message mismatch: message is {found} expected {expected}")] + PeerMessageMismatch { /// The state in the message from the peer. found: &'static str, - /// The state the message from the peer was expected to be in. + /// The message expected from the peer. expected: &'static str, }, @@ -549,7 +549,7 @@ where Self::PrepareShare::get_decoded_with_param(&prep_state, prep_share) .map_err(PingPongError::CodecPrepShare)? } else { - return Err(PingPongError::PeerMessageStateMismatch { + return Err(PingPongError::PeerMessageMismatch { found: inbound.variant(), expected: "initialize", }); @@ -604,7 +604,7 @@ where let (prep_msg, next_peer_prep_share) = match inbound { PingPongMessage::Initialize { .. } => { - return Err(PingPongError::PeerMessageStateMismatch { + return Err(PingPongError::PeerMessageMismatch { found: inbound.variant(), expected: "continued", }); @@ -650,13 +650,16 @@ where (PrepareTransition::Finish(output_share), None) => { Ok(PingPongContinuedValue::FinishedNoMessage { output_share }) } - (transition, _) => { - return Err(PingPongError::HostStateMismatch { - found: match transition { - PrepareTransition::Continue(_, _) => "continue", - PrepareTransition::Finish(_) => "finished", - }, - expected: inbound.variant(), + (PrepareTransition::Continue(_, _), None) => { + return Err(PingPongError::PeerMessageMismatch { + found: inbound.variant(), + expected: "continue", + }) + } + (PrepareTransition::Finish(_), Some(_)) => { + return Err(PingPongError::PeerMessageMismatch { + found: inbound.variant(), + expected: "finish", }) } } From 0d6d6d78f64176af2292a6c8721bcc8fd1751fbe Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Fri, 1 Sep 2023 11:29:22 -0700 Subject: [PATCH 13/16] continued -> continue --- src/topology/ping_pong.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index 459b153d5..882f0c28f 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -598,7 +598,7 @@ where } else { return Err(PingPongError::HostStateMismatch { found: "finished", - expected: "continued", + expected: "continue", }); }; @@ -606,7 +606,7 @@ where PingPongMessage::Initialize { .. } => { return Err(PingPongError::PeerMessageMismatch { found: inbound.variant(), - expected: "continued", + expected: "continue", }); } PingPongMessage::Continue { From 71120818d579e7aa1aa4310d0180271a3e6b4c20 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Mon, 11 Sep 2023 10:08:07 -0700 Subject: [PATCH 14/16] rebase cleanup --- Cargo.toml | 2 +- src/vdaf/poplar1.rs | 1 + src/vdaf/prio3.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 771bd714a..8d1dc95df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ experimental = ["bitvec", "fiat-crypto", "fixed", "num-bigint", "num-rational", multithreaded = ["rayon"] prio2 = ["crypto-dependencies", "hmac", "sha2"] crypto-dependencies = ["aes", "ctr"] -test-util = ["dep:rand"] +test-util = ["rand"] [workspace] members = [".", "binaries"] diff --git a/src/vdaf/poplar1.rs b/src/vdaf/poplar1.rs index 2f202ade0..57868fb71 100644 --- a/src/vdaf/poplar1.rs +++ b/src/vdaf/poplar1.rs @@ -329,6 +329,7 @@ impl Eq for PrepareState {} impl ConstantTimeEq for PrepareState { fn ct_eq(&self, other: &Self) -> Choice { self.sketch.ct_eq(&other.sketch) & self.output_share.ct_eq(&other.output_share) + } } impl Debug for PrepareState { diff --git a/src/vdaf/prio3.rs b/src/vdaf/prio3.rs index 3463f9eb5..9c57231a0 100644 --- a/src/vdaf/prio3.rs +++ b/src/vdaf/prio3.rs @@ -970,6 +970,7 @@ impl ConstantTimeEq for Prio3PrepareS self.joint_rand_seed.as_ref(), other.joint_rand_seed.as_ref(), ) & self.measurement_share.ct_eq(&other.measurement_share) + } } impl Debug for Prio3PrepareState { From a2076f917635c7085b566de61a0d4711bbd71bc7 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Tue, 12 Sep 2023 18:55:54 -0700 Subject: [PATCH 15/16] update VDAF references and rebase Rebasing means a couple methods need an agg param argument, since `Vdaf::prepare_shares_to_prepare_message` needs agg param. --- src/topology/ping_pong.rs | 62 ++++++++++++++++++++------------------- src/vdaf/dummy.rs | 5 ++-- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index 882f0c28f..efb3f2605 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -4,11 +4,7 @@ //! two aggregators, designated "Leader" and "Helper". This topology is required for implementing //! the [Distributed Aggregation Protocol][DAP]. //! -//! Note that while this implementation is compatible with VDAF-06, it actually implements the -//! ping-pong wrappers specified in VDAF-07 (forthcoming) since those line up better with the VDAF -//! implementation in this crate. -//! -//! [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +//! [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-07#section-5.8 //! [DAP]: https://datatracker.ietf.org/doc/html/draft-ietf-ppm-dap use crate::{ @@ -24,9 +20,9 @@ pub enum PingPongError { #[error("vdaf.prepare_init: {0}")] VdafPrepareInit(VdafError), - /// Error running prepare_preprocess - #[error("vdaf.prepare_preprocess {0}")] - VdafPreparePreprocess(VdafError), + /// Error running prepare_shares_to_prepare_message + #[error("vdaf.prepare_shares_to_prepare_message {0}")] + VdafPrepareSharesToPrepareMessage(VdafError), /// Error running prepare_step #[error("vdaf.prepare_step {0}")] @@ -67,7 +63,7 @@ pub enum PingPongError { /// variants are opaque byte buffers. This is because the ping-pong routines take responsibility for /// decoding preparation shares and messages, which usually requires having the preparation state. /// -/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-07#section-5.8 #[derive(Clone, PartialEq, Eq)] pub enum PingPongMessage { /// Corresponds to MessageType.initialize. @@ -186,7 +182,7 @@ impl Decode for PingPongMessage { /// preprocessed prepare message. Their encoding is much smaller than the `(State, Message)` tuple, /// which can always be recomputed with [`Self::evaluate`]. /// -/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-07#section-5.8 #[derive(Clone, Debug, Eq)] pub struct PingPongTransition< const VERIFY_KEY_SIZE: usize, @@ -218,7 +214,7 @@ impl< > { let prep_msg = self.current_prepare_message.get_encoded(); - vdaf.prepare_step( + vdaf.prepare_next( self.previous_prepare_state.clone(), self.current_prepare_message.clone(), ) @@ -297,7 +293,7 @@ where /// code, and the `Rejected` state is represented as `std::result::Result::Err`, so this enum does /// not include those variants. /// -/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-07#section-5.8 #[derive(Clone, Debug, PartialEq, Eq)] pub enum PingPongState< const VERIFY_KEY_SIZE: usize, @@ -335,7 +331,7 @@ pub enum PingPongContinuedValue< /// Extension trait on [`crate::vdaf::Aggregator`] which adds the [VDAF Ping-Pong Topology][VDAF]. /// -/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-06#section-5.8 +/// [VDAF]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vdaf-07#section-5.8 pub trait PingPongTopology: Aggregator { @@ -355,7 +351,7 @@ pub trait PingPongTopology Result; @@ -468,11 +465,12 @@ pub trait PingPongTopology Result; } @@ -484,7 +482,8 @@ trait PingPongTopologyPrivate Result; } @@ -556,8 +555,8 @@ where }; let current_prepare_message = self - .prepare_preprocess([inbound_prep_share, prep_share]) - .map_err(PingPongError::VdafPreparePreprocess)?; + .prepare_shares_to_prepare_message(agg_param, [inbound_prep_share, prep_share]) + .map_err(PingPongError::VdafPrepareSharesToPrepareMessage)?; Ok(PingPongTransition { previous_prepare_state: prep_state, @@ -568,17 +567,19 @@ where fn leader_continued( &self, leader_state: Self::State, + agg_param: &Self::AggregationParam, inbound: &PingPongMessage, ) -> Result { - self.continued(true, leader_state, inbound) + self.continued(true, leader_state, agg_param, inbound) } fn helper_continued( &self, helper_state: Self::State, + agg_param: &Self::AggregationParam, inbound: &PingPongMessage, ) -> Result { - self.continued(false, helper_state, inbound) + self.continued(false, helper_state, agg_param, inbound) } } @@ -591,6 +592,7 @@ where &self, is_leader: bool, host_state: Self::State, + agg_param: &Self::AggregationParam, inbound: &PingPongMessage, ) -> Result { let host_prep_state = if let PingPongState::Continued(state) = host_state { @@ -619,7 +621,7 @@ where let prep_msg = Self::PrepareMessage::get_decoded_with_param(&host_prep_state, prep_msg) .map_err(PingPongError::CodecPrepMessage)?; let host_prep_transition = self - .prepare_step(host_prep_state, prep_msg) + .prepare_next(host_prep_state, prep_msg) .map_err(PingPongError::VdafPrepareStep)?; match (host_prep_transition, next_peer_prep_share) { @@ -637,8 +639,8 @@ where prep_shares.reverse(); } let current_prepare_message = self - .prepare_preprocess(prep_shares) - .map_err(PingPongError::VdafPreparePreprocess)?; + .prepare_shares_to_prepare_message(agg_param, prep_shares) + .map_err(PingPongError::VdafPrepareSharesToPrepareMessage)?; Ok(PingPongContinuedValue::WithMessage { transition: PingPongTransition { @@ -715,7 +717,7 @@ mod tests { assert_matches!(helper_state, PingPongState::Finished(_)); let leader_state = leader - .leader_continued(leader_state, &helper_message) + .leader_continued(leader_state, &aggregation_param, &helper_message) .unwrap(); // 1 round VDAF: leader should finish when it gets helper message and emit no message. assert_matches!( @@ -765,7 +767,7 @@ mod tests { assert_matches!(helper_state, PingPongState::Continued(_)); let leader_state = leader - .leader_continued(leader_state, &helper_message) + .leader_continued(leader_state, &aggregation_param, &helper_message) .unwrap(); // 2 round VDAF, round 1: leader should finish and emit a finish message. let leader_message = assert_matches!( @@ -777,7 +779,7 @@ mod tests { ); let helper_state = helper - .helper_continued(helper_state, &leader_message) + .helper_continued(helper_state, &aggregation_param, &leader_message) .unwrap(); // 2 round vdaf, round 1: helper should finish and emit no message. assert_matches!( @@ -827,7 +829,7 @@ mod tests { assert_matches!(helper_state, PingPongState::Continued(_)); let leader_state = leader - .leader_continued(leader_state, &helper_message) + .leader_continued(leader_state, &aggregation_param, &helper_message) .unwrap(); // 3 round VDAF, round 1: leader should continue and emit a continue message. let (leader_state, leader_message) = assert_matches!( @@ -839,7 +841,7 @@ mod tests { ); let helper_state = helper - .helper_continued(helper_state, &leader_message) + .helper_continued(helper_state, &aggregation_param, &leader_message) .unwrap(); // 3 round vdaf, round 2: helper should finish and emit a finish message. let helper_message = assert_matches!( @@ -851,7 +853,7 @@ mod tests { ); let leader_state = leader - .leader_continued(leader_state, &helper_message) + .leader_continued(leader_state, &aggregation_param, &helper_message) .unwrap(); // 3 round VDAF, round 2: leader should finish and emit no message. assert_matches!( diff --git a/src/vdaf/dummy.rs b/src/vdaf/dummy.rs index 3e51413f6..507e7916b 100644 --- a/src/vdaf/dummy.rs +++ b/src/vdaf/dummy.rs @@ -137,14 +137,15 @@ impl vdaf::Aggregator<0, 16> for Vdaf { )) } - fn prepare_preprocess>( + fn prepare_shares_to_prepare_message>( &self, + _: &Self::AggregationParam, _: M, ) -> Result { Ok(()) } - fn prepare_step( + fn prepare_next( &self, state: Self::PrepareState, _: Self::PrepareMessage, From 5d87dcb282d5078e3bcd27e44248d2302657d740 Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Wed, 13 Sep 2023 13:38:55 -0700 Subject: [PATCH 16/16] rename error --- src/topology/ping_pong.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/topology/ping_pong.rs b/src/topology/ping_pong.rs index efb3f2605..c55d4f638 100644 --- a/src/topology/ping_pong.rs +++ b/src/topology/ping_pong.rs @@ -24,9 +24,9 @@ pub enum PingPongError { #[error("vdaf.prepare_shares_to_prepare_message {0}")] VdafPrepareSharesToPrepareMessage(VdafError), - /// Error running prepare_step - #[error("vdaf.prepare_step {0}")] - VdafPrepareStep(VdafError), + /// Error running prepare_next + #[error("vdaf.prepare_next {0}")] + VdafPrepareNext(VdafError), /// Error decoding a prepare share #[error("decode prep share {0}")] @@ -231,7 +231,7 @@ impl< PingPongMessage::Finish { prep_msg }, ), }) - .map_err(PingPongError::VdafPrepareStep) + .map_err(PingPongError::VdafPrepareNext) } } @@ -622,7 +622,7 @@ where .map_err(PingPongError::CodecPrepMessage)?; let host_prep_transition = self .prepare_next(host_prep_state, prep_msg) - .map_err(PingPongError::VdafPrepareStep)?; + .map_err(PingPongError::VdafPrepareNext)?; match (host_prep_transition, next_peer_prep_share) { (