From db781771de542cdec75326c4d7af5e988f542add Mon Sep 17 00:00:00 2001 From: Tim Geoghegan Date: Tue, 8 Aug 2023 16:45:24 -0700 Subject: [PATCH] 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 | 4 +- 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 | 11 +- src/vdaf/prio3.rs | 19 +- supply-chain/config.toml | 11 +- supply-chain/imports.lock | 6 - 10 files changed, 1066 insertions(+), 12 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 ee7899f91..e9d29ddb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ fiat-crypto = { version = "0.1.20", optional = true } fixed = { version = "1.23", optional = true } getrandom = { version = "0.2.10", features = ["std"] } hmac = { version = "0.12.1", optional = true } +rand = { version = "0.8.3", optional = true } rand_core = "0.6.4" rayon = { version = "1.7.0", optional = true } serde = { version = "1.0", features = ["derive"] } @@ -41,7 +42,7 @@ itertools = "0.11.0" modinverse = "0.1.0" num-bigint = "0.4.3" once_cell = "1.18.0" -prio = { path = ".", features = ["crypto-dependencies"] } +prio = { path = ".", features = ["crypto-dependencies", "test-util"] } rand = "0.8" serde_json = "1.0" statest = "0.2.2" @@ -54,6 +55,7 @@ experimental = ["bitvec", "fiat-crypto", "fixed"] multithreaded = ["rayon"] prio2 = ["crypto-dependencies", "hmac", "sha2"] crypto-dependencies = ["aes", "ctr", "cmac"] +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 9bee82e56..31568b27d 100644 --- a/src/vdaf.rs +++ b/src/vdaf.rs @@ -310,7 +310,7 @@ pub trait Aggregatable: Clone + Debug + From { } /// An output share comprised of a vector of field elements. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct OutputShare(Vec); impl AsRef<[F]> for OutputShare { @@ -335,6 +335,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 @@ -552,6 +558,8 @@ where assert_eq!(encoded, bytes); } +#[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 88f20d6e5..8c80a2677 100644 --- a/src/vdaf/poplar1.rs +++ b/src/vdaf/poplar1.rs @@ -252,12 +252,21 @@ impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1 { sketch: SketchState, output_share: Vec, } +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() + } +} + impl Encode for PrepareState { fn encode(&self, bytes: &mut Vec) { self.sketch.encode(bytes); diff --git a/src/vdaf/prio3.rs b/src/vdaf/prio3.rs index 17e149570..c5c508b97 100644 --- a/src/vdaf/prio3.rs +++ b/src/vdaf/prio3.rs @@ -833,7 +833,7 @@ where } /// State of each [`Aggregator`](crate::vdaf::Aggregator) during the Preparation phase. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub struct Prio3PrepareState { measurement_share: Share, joint_rand_seed: Option>, @@ -841,6 +841,23 @@ pub struct Prio3PrepareState { verifier_len: usize, } +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() + } +} + impl Encode for Prio3PrepareState { diff --git a/supply-chain/config.toml b/supply-chain/config.toml index f54827024..01e88679c 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -189,7 +189,8 @@ criteria = "safe-to-run" [[exemptions.ppv-lite86]] version = "0.2.16" -criteria = "safe-to-run" +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.proc-macro-error]] version = "1.0.4" @@ -205,7 +206,13 @@ criteria = "safe-to-run" [[exemptions.rand]] version = "0.8.5" -criteria = "safe-to-run" +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_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.2.2" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 937078fed..3f2d992c9 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -561,12 +561,6 @@ criteria = "safe-to-run" version = "0.3.0" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.rand_chacha]] -who = "Android Legacy" -criteria = "safe-to-run" -version = "0.3.1" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - [[audits.google.audits.rand_core]] who = "Android Legacy" criteria = "safe-to-run"