// SPDX-License-Identifier: MPL-2.0 //! Verifiable Distributed Aggregation Functions (VDAFs) as described in //! [[draft-irtf-cfrg-vdaf-03]]. //! //! [draft-irtf-cfrg-vdaf-03]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/03/ use crate::codec::{CodecError, Decode, Encode, ParameterizedDecode}; use crate::field::{FieldElement, FieldError}; use crate::flp::FlpError; use crate::prng::PrngError; use crate::vdaf::prg::Seed; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt::Debug; use std::io::Cursor; /// A component of the domain-separation tag, used to bind the VDAF operations to the document /// version. This will be revised with each draft with breaking changes. const VERSION: &[u8] = b"vdaf-03"; /// Length of the domain-separation tag, including document version and algorithm ID. const DST_LEN: usize = VERSION.len() + 4; /// Errors emitted by this module. #[derive(Debug, thiserror::Error)] pub enum VdafError { /// An error occurred. #[error("vdaf error: {0}")] Uncategorized(String), /// Field error. #[error("field error: {0}")] Field(#[from] FieldError), /// An error occured while parsing a message. #[error("io error: {0}")] IoError(#[from] std::io::Error), /// FLP error. #[error("flp error: {0}")] Flp(#[from] FlpError), /// PRNG error. #[error("prng error: {0}")] Prng(#[from] PrngError), /// failure when calling getrandom(). #[error("getrandom: {0}")] GetRandom(#[from] getrandom::Error), } /// An additive share of a vector of field elements. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Share { /// An uncompressed share, typically sent to the leader. Leader(Vec), /// A compressed share, typically sent to the helper. Helper(Seed), } impl Share { /// Truncate the Leader's share to the given length. If this is the Helper's share, then this /// method clones the input without modifying it. #[cfg(feature = "prio2")] pub(crate) fn truncated(&self, len: usize) -> Self { match self { Self::Leader(ref data) => Self::Leader(data[..len].to_vec()), Self::Helper(ref seed) => Self::Helper(seed.clone()), } } } /// Parameters needed to decode a [`Share`] #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ShareDecodingParameter { Leader(usize), Helper, } impl ParameterizedDecode> for Share { fn decode_with_param( decoding_parameter: &ShareDecodingParameter, bytes: &mut Cursor<&[u8]>, ) -> Result { match decoding_parameter { ShareDecodingParameter::Leader(share_length) => { let mut data = Vec::with_capacity(*share_length); for _ in 0..*share_length { data.push(F::decode(bytes)?) } Ok(Self::Leader(data)) } ShareDecodingParameter::Helper => { let seed = Seed::decode(bytes)?; Ok(Self::Helper(seed)) } } } } impl Encode for Share { fn encode(&self, bytes: &mut Vec) { match self { Share::Leader(share_data) => { for x in share_data { x.encode(bytes); } } Share::Helper(share_seed) => { share_seed.encode(bytes); } } } } /// The base trait for VDAF schemes. This trait is inherited by traits [`Client`], [`Aggregator`], /// and [`Collector`], which define the roles of the various parties involved in the execution of /// the VDAF. // TODO(brandon): once GATs are stabilized [https://github.com/rust-lang/rust/issues/44265], // state the "&AggregateShare must implement Into>" constraint in terms of a where clause // on the associated type instead of a where clause on the trait. pub trait Vdaf: Clone + Debug where for<'a> &'a Self::AggregateShare: Into>, { /// Algorithm identifier for this VDAF. const ID: u32; /// The type of Client measurement to be aggregated. type Measurement: Clone + Debug; /// The aggregate result of the VDAF execution. type AggregateResult: Clone + Debug; /// The aggregation parameter, used by the Aggregators to map their input shares to output /// shares. type AggregationParam: Clone + Debug + Decode + Encode; /// A public share sent by a Client. type PublicShare: Clone + Debug + for<'a> ParameterizedDecode<&'a Self> + Encode; /// An input share sent by a Client. type InputShare: Clone + Debug + for<'a> ParameterizedDecode<(&'a Self, usize)> + Encode; /// An output share recovered from an input share by an Aggregator. type OutputShare: Clone + Debug; /// An Aggregator's share of the aggregate result. type AggregateShare: Aggregatable + for<'a> TryFrom<&'a [u8]>; /// The number of Aggregators. The Client generates as many input shares as there are /// Aggregators. fn num_aggregators(&self) -> usize; } /// The Client's role in the execution of a VDAF. pub trait Client: Vdaf where for<'a> &'a Self::AggregateShare: Into>, { /// Shards a measurement into a public share and a sequence of input shares, one for each /// Aggregator. fn shard( &self, measurement: &Self::Measurement, ) -> Result<(Self::PublicShare, Vec), VdafError>; } /// The Aggregator's role in the execution of a VDAF. pub trait Aggregator: Vdaf where for<'a> &'a Self::AggregateShare: Into>, { /// State of the Aggregator during the Prepare process. type PrepareState: Clone + Debug; /// The type of messages broadcast by each aggregator at each round of the Prepare Process. /// /// 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 PrepareShare: Clone + Debug + ParameterizedDecode + Encode; /// Result of preprocessing a round of preparation shares. /// /// 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; /// Begins the Prepare process with the other Aggregators. The [`Self::PrepareState`] returned /// is passed to [`Aggregator::prepare_step`] to get this aggregator's first-round prepare /// message. fn prepare_init( &self, verify_key: &[u8; L], agg_id: usize, agg_param: &Self::AggregationParam, nonce: &[u8], public_share: &Self::PublicShare, input_share: &Self::InputShare, ) -> Result<(Self::PrepareState, Self::PrepareShare), VdafError>; /// Preprocess a round of preparation shares into a single input to [`Aggregator::prepare_step`]. fn prepare_preprocess>( &self, inputs: M, ) -> Result; /// Compute the next state transition from the current state and the previous round of input /// messages. If this returns [`PrepareTransition::Continue`], then the returned /// [`Self::PrepareShare`] should be combined with the other Aggregators' `PrepareShare`s from /// this round and passed into another call to this method. This continues until this method /// returns [`PrepareTransition::Finish`], at which point the returned output share may be /// aggregated. If the method returns an error, the aggregator should consider its input share /// invalid and not attempt to process it any further. fn prepare_step( &self, state: Self::PrepareState, input: Self::PrepareMessage, ) -> Result, VdafError>; /// Aggregates a sequence of output shares into an aggregate share. fn aggregate>( &self, agg_param: &Self::AggregationParam, output_shares: M, ) -> Result; } /// The Collector's role in the execution of a VDAF. pub trait Collector: Vdaf where for<'a> &'a Self::AggregateShare: Into>, { /// Combines aggregate shares into the aggregate result. fn unshard>( &self, agg_param: &Self::AggregationParam, agg_shares: M, num_measurements: usize, ) -> Result; } /// A state transition of an Aggregator during the Prepare process. #[derive(Debug)] pub enum PrepareTransition, const L: usize> where for<'a> &'a V::AggregateShare: Into>, { /// Continue processing. Continue(V::PrepareState, V::PrepareShare), /// Finish processing and return the output share. Finish(V::OutputShare), } /// An aggregate share resulting from aggregating output shares together that /// can merged with aggregate shares of the same type. pub trait Aggregatable: Clone + Debug + From { /// Type of output shares that can be accumulated into an aggregate share. type OutputShare; /// Update an aggregate share by merging it with another (`agg_share`). fn merge(&mut self, agg_share: &Self) -> Result<(), VdafError>; /// Update an aggregate share by adding `output share` fn accumulate(&mut self, output_share: &Self::OutputShare) -> Result<(), VdafError>; } /// An output share comprised of a vector of `F` elements. #[derive(Clone, Debug, PartialEq, Eq)] pub struct OutputShare(Vec); impl AsRef<[F]> for OutputShare { fn as_ref(&self) -> &[F] { &self.0 } } impl From> for OutputShare { fn from(other: Vec) -> Self { Self(other) } } impl TryFrom<&[u8]> for OutputShare { type Error = FieldError; fn try_from(bytes: &[u8]) -> Result { fieldvec_try_from_bytes(bytes) } } impl From<&OutputShare> for Vec { fn from(output_share: &OutputShare) -> Self { fieldvec_to_vec(&output_share.0) } } /// An aggregate share suitable for VDAFs whose output shares and aggregate /// shares are vectors of `F` elements, and an output share needs no special /// transformation to be merged into an aggregate share. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AggregateShare(Vec); impl AsRef<[F]> for AggregateShare { fn as_ref(&self) -> &[F] { &self.0 } } impl From> for AggregateShare { fn from(other: OutputShare) -> Self { Self(other.0) } } impl From> for AggregateShare { fn from(other: Vec) -> Self { Self(other) } } impl Aggregatable for AggregateShare { type OutputShare = OutputShare; fn merge(&mut self, agg_share: &Self) -> Result<(), VdafError> { self.sum(agg_share.as_ref()) } fn accumulate(&mut self, output_share: &Self::OutputShare) -> Result<(), VdafError> { // For prio3 and poplar1, no conversion is needed between output shares and aggregation // shares. self.sum(output_share.as_ref()) } } impl AggregateShare { fn sum(&mut self, other: &[F]) -> Result<(), VdafError> { if self.0.len() != other.len() { return Err(VdafError::Uncategorized(format!( "cannot sum shares of different lengths (left = {}, right = {}", self.0.len(), other.len() ))); } for (x, y) in self.0.iter_mut().zip(other) { *x += *y; } Ok(()) } } impl TryFrom<&[u8]> for AggregateShare { type Error = FieldError; fn try_from(bytes: &[u8]) -> Result { fieldvec_try_from_bytes(bytes) } } impl From<&AggregateShare> for Vec { fn from(aggregate_share: &AggregateShare) -> Self { fieldvec_to_vec(&aggregate_share.0) } } /// fieldvec_try_from_bytes converts a slice of bytes to a type that is equivalent to a vector of /// field elements. #[inline(always)] fn fieldvec_try_from_bytes>>( bytes: &[u8], ) -> Result { F::byte_slice_into_vec(bytes).map(T::from) } /// fieldvec_to_vec converts a type that is equivalent to a vector of field elements into a vector /// of bytes. #[inline(always)] fn fieldvec_to_vec>(val: T) -> Vec { F::slice_into_byte_vec(val.as_ref()) } #[cfg(test)] pub(crate) fn run_vdaf( vdaf: &V, agg_param: &V::AggregationParam, measurements: M, ) -> Result where V: Client + Aggregator + Collector, for<'a> &'a V::AggregateShare: Into>, M: IntoIterator, { use rand::prelude::*; let mut verify_key = [0; L]; thread_rng().fill(&mut verify_key[..]); // NOTE Here we use the same nonce for each measurement for testing purposes. However, this is // not secure. In use, the Aggregators MUST ensure that nonces are unique for each measurement. let nonce = b"this is a nonce"; let mut agg_shares: Vec> = vec![None; vdaf.num_aggregators()]; let mut num_measurements: usize = 0; for measurement in measurements.into_iter() { num_measurements += 1; let (public_share, input_shares) = vdaf.shard(&measurement)?; let out_shares = run_vdaf_prepare( vdaf, &verify_key, agg_param, nonce, public_share, input_shares, )?; for (out_share, agg_share) in out_shares.into_iter().zip(agg_shares.iter_mut()) { if let Some(ref mut inner) = agg_share { inner.merge(&out_share.into())?; } else { *agg_share = Some(out_share.into()); } } } let res = vdaf.unshard( agg_param, agg_shares.into_iter().map(|option| option.unwrap()), num_measurements, )?; Ok(res) } #[cfg(test)] pub(crate) fn run_vdaf_prepare( vdaf: &V, verify_key: &[u8; L], agg_param: &V::AggregationParam, nonce: &[u8], public_share: V::PublicShare, input_shares: M, ) -> Result, VdafError> where V: Client + Aggregator + Collector, for<'a> &'a V::AggregateShare: Into>, M: IntoIterator, { let input_shares = input_shares .into_iter() .map(|input_share| input_share.get_encoded()); let mut states = Vec::new(); let mut outbound = Vec::new(); for (agg_id, input_share) in input_shares.enumerate() { let (state, msg) = vdaf.prepare_init( verify_key, agg_id, agg_param, nonce, &public_share, &V::InputShare::get_decoded_with_param(&(vdaf, agg_id), &input_share) .expect("failed to decode input share"), )?; states.push(state); outbound.push(msg.get_encoded()); } let mut inbound = vdaf .prepare_preprocess(outbound.iter().map(|encoded| { V::PrepareShare::get_decoded_with_param(&states[0], encoded) .expect("failed to decode prep share") }))? .get_encoded(); let mut out_shares = Vec::new(); loop { let mut outbound = Vec::new(); for state in states.iter_mut() { match vdaf.prepare_step( state.clone(), V::PrepareMessage::get_decoded_with_param(state, &inbound) .expect("failed to decode prep message"), )? { PrepareTransition::Continue(new_state, msg) => { outbound.push(msg.get_encoded()); *state = new_state } PrepareTransition::Finish(out_share) => { out_shares.push(out_share); } } } if outbound.len() == vdaf.num_aggregators() { // Another round is required before output shares are computed. inbound = vdaf .prepare_preprocess(outbound.iter().map(|encoded| { V::PrepareShare::get_decoded_with_param(&states[0], encoded) .expect("failed to decode prep share") }))? .get_encoded(); } else if outbound.is_empty() { // Each Aggregator recovered an output share. break; } else { panic!("Aggregators did not finish the prepare phase at the same time"); } } Ok(out_shares) } #[cfg(test)] mod tests { use super::{AggregateShare, OutputShare}; use crate::field::{Field128, Field64, FieldElement}; use itertools::iterate; use std::convert::TryFrom; use std::fmt::Debug; fn fieldvec_roundtrip_test() where F: FieldElement, for<'a> T: Debug + PartialEq + From> + TryFrom<&'a [u8]>, for<'a> >::Error: Debug, for<'a> Vec: From<&'a T>, { // Generate a value based on an arbitrary vector of field elements. let g = F::generator(); let want_value = T::from(iterate(F::one(), |&v| g * v).take(10).collect()); // Round-trip the value through a byte-vector. let buf: Vec = (&want_value).into(); let got_value = T::try_from(&buf).unwrap(); assert_eq!(want_value, got_value); } #[test] fn roundtrip_output_share() { fieldvec_roundtrip_test::>(); fieldvec_roundtrip_test::>(); } #[test] fn roundtrip_aggregate_share() { fieldvec_roundtrip_test::>(); fieldvec_roundtrip_test::>(); } } #[cfg(feature = "crypto-dependencies")] pub mod poplar1; pub mod prg; #[cfg(feature = "prio2")] pub mod prio2; pub mod prio3; #[cfg(test)] mod prio3_test;