//! Implementation of the Prio3 VDAF [[draft-irtf-cfrg-vdaf-08]]. //! //! **WARNING:** This code has not undergone significant security analysis. Use at your own risk. //! //! Prio3 is based on the Prio system desigend by Dan Boneh and Henry Corrigan-Gibbs and presented //! at NSDI 2017 [[CGB17]]. However, it incorporates a few techniques from Boneh et al., CRYPTO //! 2019 [[BBCG+19]], that lead to substantial improvements in terms of run time and communication //! cost. The security of the construction was analyzed in [[DPRS23]]. //! //! Prio3 is a transformation of a Fully Linear Proof (FLP) system [[draft-irtf-cfrg-vdaf-08]] into //! a VDAF. The base type, [`Prio3`], supports a wide variety of aggregation functions, some of //! which are instantiated here: //! //! - [`Prio3Count`] for aggregating a counter (*) //! - [`Prio3Sum`] for copmputing the sum of integers (*) //! - [`Prio3SumVec`] for aggregating a vector of integers //! - [`Prio3Histogram`] for estimating a distribution via a histogram (*) //! //! Additional types can be constructed from [`Prio3`] as needed. //! //! (*) denotes that the type is specified in [[draft-irtf-cfrg-vdaf-08]]. //! //! [BBCG+19]: https://ia.cr/2019/188 //! [CGB17]: https://crypto.stanford.edu/prio/ //! [DPRS23]: https://ia.cr/2023/130 //! [draft-irtf-cfrg-vdaf-08]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/08/
/// The count type. Each measurement is an integer in `[0,2)` and the aggregate result is the sum. pubtype Prio3Count = Prio3<Count<Field64>, XofTurboShake128, 16>;
impl Prio3Count { /// Construct an instance of Prio3Count with the given number of aggregators. pubfn new_count(num_aggregators: u8) -> Result<Self, VdafError> {
Prio3::new(num_aggregators, 1, 0x00000000, Count::new())
}
}
/// The count-vector type. Each measurement is a vector of integers in `[0,2^bits)` and the /// aggregate is the element-wise sum. pubtype Prio3SumVec =
Prio3<SumVec<Field128, ParallelSum<Field128, Mul<Field128>>>, XofTurboShake128, 16>;
impl Prio3SumVec { /// Construct an instance of Prio3SumVec with the given number of aggregators. `bits` defines /// the bit width of each summand of the measurement; `len` defines the length of the /// measurement vector. pubfn new_sum_vec(
num_aggregators: u8,
bits: usize,
len: usize,
chunk_length: usize,
) -> Result<Self, VdafError> {
Prio3::new(
num_aggregators, 1, 0x00000002,
SumVec::new(bits, len, chunk_length)?,
)
}
}
/// Like [`Prio3SumVec`] except this type uses multithreading to improve sharding and preparation /// time. Note that the improvement is only noticeable for very large input lengths. #[cfg(feature = "multithreaded")] #[cfg_attr(docsrs, doc(cfg(feature = "multithreaded")))] pubtype Prio3SumVecMultithreaded = Prio3<
SumVec<Field128, ParallelSumMultithreaded<Field128, Mul<Field128>>>,
XofTurboShake128, 16,
>;
#[cfg(feature = "multithreaded")] impl Prio3SumVecMultithreaded { /// Construct an instance of Prio3SumVecMultithreaded with the given number of /// aggregators. `bits` defines the bit width of each summand of the measurement; `len` defines /// the length of the measurement vector. pubfn new_sum_vec_multithreaded(
num_aggregators: u8,
bits: usize,
len: usize,
chunk_length: usize,
) -> Result<Self, VdafError> {
Prio3::new(
num_aggregators, 1, 0x00000002,
SumVec::new(bits, len, chunk_length)?,
)
}
}
/// The sum type. Each measurement is an integer in `[0,2^bits)` for some `0 < bits < 64` and the /// aggregate is the sum. pubtype Prio3Sum = Prio3<Sum<Field128>, XofTurboShake128, 16>;
impl Prio3Sum { /// Construct an instance of Prio3Sum with the given number of aggregators and required bit /// length. The bit length must not exceed 64. pubfn new_sum(num_aggregators: u8, bits: usize) -> Result<Self, VdafError> { if bits > 64 { return Err(VdafError::Uncategorized(format!( "bit length ({bits}) exceeds limit for aggregate type (64)"
)));
}
/// The fixed point vector sum type. Each measurement is a vector of fixed point numbers /// and the aggregate is the sum represented as 64-bit floats. The preparation phase /// ensures the L2 norm of the input vector is < 1. /// /// This is useful for aggregating gradients in a federated version of /// [gradient descent](https://en.wikipedia.org/wiki/Gradient_descent) with /// [differential privacy](https://en.wikipedia.org/wiki/Differential_privacy), /// useful, e.g., for [differentially private deep learning](https://arxiv.org/pdf/1607.00133.pdf). /// The bound on input norms is required for differential privacy. The fixed point representation /// allows an easy conversion to the integer type used in internal computation, while leaving /// conversion to the client. The model itself will have floating point parameters, so the output /// sum has that type as well. #[cfg(feature = "experimental")] #[cfg_attr(docsrs, doc(cfg(feature = "experimental")))] pubtype Prio3FixedPointBoundedL2VecSum<Fx> = Prio3<
FixedPointBoundedL2VecSum<
Fx,
ParallelSum<Field128, PolyEval<Field128>>,
ParallelSum<Field128, Mul<Field128>>,
>,
XofTurboShake128, 16,
>;
#[cfg(feature = "experimental")] impl<Fx: Fixed + CompatibleFloat> Prio3FixedPointBoundedL2VecSum<Fx> { /// Construct an instance of this VDAF with the given number of aggregators and number of /// vector entries. pubfn new_fixedpoint_boundedl2_vec_sum(
num_aggregators: u8,
entries: usize,
) -> Result<Self, VdafError> {
check_num_aggregators(num_aggregators)?;
Prio3::new(
num_aggregators, 1, 0xFFFF0000,
FixedPointBoundedL2VecSum::new(entries)?,
)
}
}
/// The fixed point vector sum type. Each measurement is a vector of fixed point numbers /// and the aggregate is the sum represented as 64-bit floats. The verification function /// ensures the L2 norm of the input vector is < 1. #[cfg(all(feature = "experimental", feature = "multithreaded"))] #[cfg_attr(
docsrs,
doc(cfg(all(feature = "experimental", feature = "multithreaded")))
)] pubtype Prio3FixedPointBoundedL2VecSumMultithreaded<Fx> = Prio3<
FixedPointBoundedL2VecSum<
Fx,
ParallelSumMultithreaded<Field128, PolyEval<Field128>>,
ParallelSumMultithreaded<Field128, Mul<Field128>>,
>,
XofTurboShake128, 16,
>;
#[cfg(all(feature = "experimental", feature = "multithreaded"))] impl<Fx: Fixed + CompatibleFloat> Prio3FixedPointBoundedL2VecSumMultithreaded<Fx> { /// Construct an instance of this VDAF with the given number of aggregators and number of /// vector entries. pubfn new_fixedpoint_boundedl2_vec_sum_multithreaded(
num_aggregators: u8,
entries: usize,
) -> Result<Self, VdafError> {
check_num_aggregators(num_aggregators)?;
Prio3::new(
num_aggregators, 1, 0xFFFF0000,
FixedPointBoundedL2VecSum::new(entries)?,
)
}
}
/// The histogram type. Each measurement is an integer in `[0, length)` and the result is a /// histogram counting the number of occurrences of each measurement. pubtype Prio3Histogram =
Prio3<Histogram<Field128, ParallelSum<Field128, Mul<Field128>>>, XofTurboShake128, 16>;
impl Prio3Histogram { /// Constructs an instance of Prio3Histogram with the given number of aggregators, /// number of buckets, and parallel sum gadget chunk length. pubfn new_histogram(
num_aggregators: u8,
length: usize,
chunk_length: usize,
) -> Result<Self, VdafError> {
Prio3::new(
num_aggregators, 1, 0x00000003,
Histogram::new(length, chunk_length)?,
)
}
}
/// Like [`Prio3Histogram`] except this type uses multithreading to improve sharding and preparation /// time. Note that this improvement is only noticeable for very large input lengths. #[cfg(feature = "multithreaded")] #[cfg_attr(docsrs, doc(cfg(feature = "multithreaded")))] pubtype Prio3HistogramMultithreaded = Prio3<
Histogram<Field128, ParallelSumMultithreaded<Field128, Mul<Field128>>>,
XofTurboShake128, 16,
>;
#[cfg(feature = "multithreaded")] impl Prio3HistogramMultithreaded { /// Construct an instance of Prio3HistogramMultithreaded with the given number of aggregators, /// number of buckets, and parallel sum gadget chunk length. pubfn new_histogram_multithreaded(
num_aggregators: u8,
length: usize,
chunk_length: usize,
) -> Result<Self, VdafError> {
Prio3::new(
num_aggregators, 1, 0x00000003,
Histogram::new(length, chunk_length)?,
)
}
}
/// The average type. Each measurement is an integer in `[0,2^bits)` for some `0 < bits < 64` and /// the aggregate is the arithmetic average. pubtype Prio3Average = Prio3<Average<Field128>, XofTurboShake128, 16>;
impl Prio3Average { /// Construct an instance of Prio3Average with the given number of aggregators and required bit /// length. The bit length must not exceed 64. pubfn new_average(num_aggregators: u8, bits: usize) -> Result<Self, VdafError> {
check_num_aggregators(num_aggregators)?;
if bits > 64 { return Err(VdafError::Uncategorized(format!( "bit length ({bits}) exceeds limit for aggregate type (64)"
)));
}
/// The base type for Prio3. /// /// An instance of Prio3 is determined by: /// /// - a [`Type`] that defines the set of valid input measurements; and /// - a [`Xof`] for deriving vectors of field elements from seeds. /// /// New instances can be defined by aliasing the base type. For example, [`Prio3Count`] is an alias /// for `Prio3<Count<Field64>, XofTurboShake128, 16>`. /// /// ``` /// use prio::vdaf::{ /// Aggregator, Client, Collector, PrepareTransition, /// prio3::Prio3, /// }; /// use rand::prelude::*; /// /// let num_shares = 2; /// let vdaf = Prio3::new_count(num_shares).unwrap(); /// /// let mut out_shares = vec![vec![]; num_shares.into()]; /// let mut rng = thread_rng(); /// let verify_key = rng.gen(); /// let measurements = [false, true, true, true, false]; /// for measurement in measurements { /// // Shard /// let nonce = rng.gen::<[u8; 16]>(); /// let (public_share, input_shares) = vdaf.shard(&measurement, &nonce).unwrap(); /// /// // Prepare /// let mut prep_states = vec![]; /// let mut prep_shares = vec![]; /// for (agg_id, input_share) in input_shares.iter().enumerate() { /// let (state, share) = vdaf.prepare_init( /// &verify_key, /// agg_id, /// &(), /// &nonce, /// &public_share, /// input_share /// ).unwrap(); /// prep_states.push(state); /// prep_shares.push(share); /// } /// let prep_msg = vdaf.prepare_shares_to_prepare_message(&(), prep_shares).unwrap(); /// /// for (agg_id, state) in prep_states.into_iter().enumerate() { /// let out_share = match vdaf.prepare_next(state, prep_msg.clone()).unwrap() { /// PrepareTransition::Finish(out_share) => out_share, /// _ => panic!("unexpected transition"), /// }; /// out_shares[agg_id].push(out_share); /// } /// } /// /// // Aggregate /// let agg_shares = out_shares.into_iter() /// .map(|o| vdaf.aggregate(&(), o).unwrap()); /// /// // Unshard /// let agg_res = vdaf.unshard(&(), agg_shares, measurements.len()).unwrap(); /// assert_eq!(agg_res, 3); /// ``` #[derive(Clone, Debug)] pubstruct Prio3<T, P, const SEED_SIZE: usize> where
T: Type,
P: Xof<SEED_SIZE>,
{
num_aggregators: u8,
num_proofs: u8,
algorithm_id: u32,
typ: T,
phantom: PhantomData<P>,
}
impl<T, P, const SEED_SIZE: usize> Prio3<T, P, SEED_SIZE> where
T: Type,
P: Xof<SEED_SIZE>,
{ /// Construct an instance of this Prio3 VDAF with the given number of aggregators, number of /// proofs to generate and verify, the algorithm ID, and the underlying type. pubfn new(
num_aggregators: u8,
num_proofs: u8,
algorithm_id: u32,
typ: T,
) -> Result<Self, VdafError> {
check_num_aggregators(num_aggregators)?; if num_proofs == 0 { return Err(VdafError::Uncategorized( "num_proofs must be at least 1".to_string(),
));
}
fn random_size(&self) -> usize { ifself.typ.joint_rand_len() == 0 { // Two seeds per helper for measurement and proof shares, plus one seed for proving // randomness.
(usize::from(self.num_aggregators - 1) * 2 + 1) * SEED_SIZE
} else {
( // Two seeds per helper for measurement and proof shares
usize::from(self.num_aggregators - 1) * 2 // One seed for proving randomness
+ 1 // One seed per aggregator for joint randomness blinds
+ usize::from(self.num_aggregators)
) * SEED_SIZE
}
}
// Generate the measurement shares and compute the joint randomness. letmut helper_shares = Vec::with_capacity(num_aggregators as usize - 1); letmut helper_joint_rand_parts = ifself.typ.joint_rand_len() > 0 {
Some(Vec::with_capacity(num_aggregators as usize - 1))
} else {
None
}; letmut leader_measurement_share = encoded_measurement.clone(); for agg_id in1..num_aggregators { // The Option from the ChunksExact iterator is okay to unwrap because we checked that // the randomness slice is long enough for this VDAF. The slice-to-array conversion // Result is okay to unwrap because the ChunksExact iterator always returns slices of // the correct length. let measurement_share_seed = random_seeds.next().unwrap().try_into().unwrap(); let proof_share_seed = random_seeds.next().unwrap().try_into().unwrap(); let measurement_share_prng: Prng<T::Field, _> = Prng::from_seed_stream(P::seed_stream(
&Seed(measurement_share_seed),
&self.domain_separation_tag(DST_MEASUREMENT_SHARE),
&[agg_id],
)); let joint_rand_blind = iflet Some(helper_joint_rand_parts) =
helper_joint_rand_parts.as_mut()
{ let joint_rand_blind = random_seeds.next().unwrap().try_into().unwrap(); letmut joint_rand_part_xof = P::init(
&joint_rand_blind,
&self.domain_separation_tag(DST_JOINT_RAND_PART),
);
joint_rand_part_xof.update(&[agg_id]); // Aggregator ID
joint_rand_part_xof.update(nonce);
letmut encoding_buffer = Vec::with_capacity(T::Field::ENCODED_SIZE); for (x, y) in leader_measurement_share
.iter_mut()
.zip(measurement_share_prng)
{
*x -= y;
y.encode(&mut encoding_buffer).map_err(|_| {
VdafError::Uncategorized("failed to encode measurement share".to_string())
})?;
joint_rand_part_xof.update(&encoding_buffer);
encoding_buffer.clear();
}
impl<T, P, const SEED_SIZE: usize> Vdaf for Prio3<T, P, SEED_SIZE> where
T: Type,
P: Xof<SEED_SIZE>,
{ type Measurement = T::Measurement; type AggregateResult = T::AggregateResult; type AggregationParam = (); type PublicShare = Prio3PublicShare<SEED_SIZE>; type InputShare = Prio3InputShare<T::Field, SEED_SIZE>; type OutputShare = OutputShare<T::Field>; type AggregateShare = AggregateShare<T::Field>;
fn num_aggregators(&self) -> usize { self.num_aggregators as usize
}
}
/// Message broadcast by the [`Client`] to every [`Aggregator`] during the Sharding phase. #[derive(Clone, Debug)] pubstruct Prio3PublicShare<const SEED_SIZE: usize> { /// Contributions to the joint randomness from every aggregator's share.
joint_rand_parts: Option<Vec<Seed<SEED_SIZE>>>,
}
impl<const SEED_SIZE: usize> Encode for Prio3PublicShare<SEED_SIZE> { fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> { iflet Some(joint_rand_parts) = self.joint_rand_parts.as_ref() { for part in joint_rand_parts.iter() {
part.encode(bytes)?;
}
}
Ok(())
}
fn encoded_len(&self) -> Option<usize> { iflet Some(joint_rand_parts) = self.joint_rand_parts.as_ref() { // Each seed has the same size.
Some(SEED_SIZE * joint_rand_parts.len())
} else {
Some(0)
}
}
}
impl<const SEED_SIZE: usize> Eq for Prio3PublicShare<SEED_SIZE> {}
impl<const SEED_SIZE: usize> ConstantTimeEq for Prio3PublicShare<SEED_SIZE> { fn ct_eq(&self, other: &Self) -> Choice { // We allow short-circuiting on the presence or absence of the joint_rand_parts.
option_ct_eq( self.joint_rand_parts.as_deref(),
other.joint_rand_parts.as_deref(),
)
}
}
/// Message sent by the [`Client`] to each [`Aggregator`] during the Sharding phase. #[derive(Clone, Debug)] pubstruct Prio3InputShare<F, const SEED_SIZE: usize> { /// The measurement share.
measurement_share: Share<F, SEED_SIZE>,
/// The proof share.
proofs_share: Share<F, SEED_SIZE>,
/// Blinding seed used by the Aggregator to compute the joint randomness. This field is optional /// because not every [`Type`] requires joint randomness.
joint_rand_blind: Option<Seed<SEED_SIZE>>,
}
#[derive(Clone, Debug)] /// Message broadcast by each [`Aggregator`] in each round of the Preparation phase. pubstruct Prio3PrepareShare<F, const SEED_SIZE: usize> { /// A share of the FLP verifier message. (See [`Type`].)
verifiers: Vec<F>,
/// A part of the joint randomness seed.
joint_rand_part: Option<Seed<SEED_SIZE>>,
}
impl<F: ConstantTimeEq, const SEED_SIZE: usize> Eq for Prio3PrepareShare<F, SEED_SIZE> {}
impl<F: ConstantTimeEq, const SEED_SIZE: usize> ConstantTimeEq for Prio3PrepareShare<F, SEED_SIZE> { fn ct_eq(&self, other: &Self) -> Choice { // We allow short-circuiting on the presence or absence of the joint_rand_part.
option_ct_eq( self.joint_rand_part.as_ref(),
other.joint_rand_part.as_ref(),
) & self.verifiers.ct_eq(&other.verifiers)
}
}
impl<F: FftFriendlyFieldElement, const SEED_SIZE: usize> Encode for Prio3PrepareShare<F, SEED_SIZE>
{ fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> { for x in &self.verifiers {
x.encode(bytes)?;
} iflet Some(ref seed) = self.joint_rand_part {
seed.encode(bytes)?;
}
Ok(())
}
fn encoded_len(&self) -> Option<usize> { // Each element of the verifier has the same size. letmut len = F::ENCODED_SIZE * self.verifiers.len(); iflet Some(ref seed) = self.joint_rand_part {
len += seed.encoded_len()?;
}
Some(len)
}
}
#[derive(Clone, Debug)] /// Result of combining a round of [`Prio3PrepareShare`] messages. pubstruct Prio3PrepareMessage<const SEED_SIZE: usize> { /// The joint randomness seed computed by the Aggregators.
joint_rand_seed: Option<Seed<SEED_SIZE>>,
}
impl<const SEED_SIZE: usize> Eq for Prio3PrepareMessage<SEED_SIZE> {}
impl<const SEED_SIZE: usize> ConstantTimeEq for Prio3PrepareMessage<SEED_SIZE> { fn ct_eq(&self, other: &Self) -> Choice { // We allow short-circuiting on the presnce or absence of the joint_rand_seed.
option_ct_eq( self.joint_rand_seed.as_ref(),
other.joint_rand_seed.as_ref(),
)
}
}
impl<F: ConstantTimeEq, const SEED_SIZE: usize> Eq for Prio3PrepareState<F, SEED_SIZE> {}
impl<F: ConstantTimeEq, const SEED_SIZE: usize> ConstantTimeEq for Prio3PrepareState<F, SEED_SIZE> { fn ct_eq(&self, other: &Self) -> Choice { // We allow short-circuiting on the presence or absence of the joint_rand_seed, as well as // the aggregator ID & verifier length parameters. ifself.agg_id != other.agg_id || self.verifiers_len != other.verifiers_len { return Choice::from(0);
}
impl<F: FftFriendlyFieldElement, const SEED_SIZE: usize> Encode for Prio3PrepareState<F, SEED_SIZE>
{ /// Append the encoded form of this object to the end of `bytes`, growing the vector as needed. fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> { self.measurement_share.encode(bytes)?; iflet Some(ref seed) = self.joint_rand_seed {
seed.encode(bytes)?;
}
Ok(())
}
impl<T, P, const SEED_SIZE: usize> Aggregator<SEED_SIZE, 16> for Prio3<T, P, SEED_SIZE> where
T: Type,
P: Xof<SEED_SIZE>,
{ type PrepareState = Prio3PrepareState<T::Field, SEED_SIZE>; type PrepareShare = Prio3PrepareShare<T::Field, SEED_SIZE>; type PrepareMessage = Prio3PrepareMessage<SEED_SIZE>;
/// Begins the Prep process with the other aggregators. The result of this process is /// the aggregator's output share. #[allow(clippy::type_complexity)] fn prepare_init(
&self,
verify_key: &[u8; SEED_SIZE],
agg_id: usize,
_agg_param: &Self::AggregationParam,
nonce: &[u8; 16],
public_share: &Self::PublicShare,
msg: &Prio3InputShare<T::Field, SEED_SIZE>,
) -> Result<
(
Prio3PrepareState<T::Field, SEED_SIZE>,
Prio3PrepareShare<T::Field, SEED_SIZE>,
),
VdafError,
> { let agg_id = self.role_try_from(agg_id)?;
// Create a reference to the (expanded) measurement share. let expanded_measurement_share: Option<Vec<T::Field>> = match msg.measurement_share {
Share::Leader(_) => None,
Share::Helper(ref seed) => Some(
P::seed_stream(
seed,
&self.domain_separation_tag(DST_MEASUREMENT_SHARE),
&[agg_id],
)
.into_field_vec(self.typ.input_len()),
),
}; let measurement_share = match msg.measurement_share {
Share::Leader(ref data) => data,
Share::Helper(_) => expanded_measurement_share.as_ref().unwrap(),
};
// Create a reference to the (expanded) proof share. let expanded_proofs_share: Option<Vec<T::Field>> = match msg.proofs_share {
Share::Leader(_) => None,
Share::Helper(ref proof_shares_seed) => Some( self.derive_helper_proofs_share(proof_shares_seed, agg_id)
.take(self.typ.proof_len() * self.num_proofs())
.collect(),
),
}; let proofs_share = match msg.proofs_share {
Share::Leader(ref data) => data,
Share::Helper(_) => expanded_proofs_share.as_ref().unwrap(),
};
// Compute the joint randomness. let (joint_rand_seed, joint_rand_part, joint_rands) = ifself.typ.joint_rand_len() > 0 { letmut joint_rand_part_xof = P::init(
msg.joint_rand_blind.as_ref().unwrap().as_ref(),
&self.domain_separation_tag(DST_JOINT_RAND_PART),
);
joint_rand_part_xof.update(&[agg_id]);
joint_rand_part_xof.update(nonce); letmut encoding_buffer = Vec::with_capacity(T::Field::ENCODED_SIZE); for x in measurement_share {
x.encode(&mut encoding_buffer).map_err(|_| {
VdafError::Uncategorized("failed to encode measurement share".to_string())
})?;
joint_rand_part_xof.update(&encoding_buffer);
encoding_buffer.clear();
} let own_joint_rand_part = joint_rand_part_xof.into_seed();
// Make an iterator over the joint randomness parts, but use this aggregator's // contribution, computed from the input share, in lieu of the the corresponding part // from the public share. // // The locally computed part should match the part from the public share for honestly // generated reports. If they do not match, the joint randomness seed check during the // next round of preparation should fail. let corrected_joint_rand_parts = public_share
.joint_rand_parts
.iter()
.flatten()
.take(agg_id as usize)
.chain(iter::once(&own_joint_rand_part))
.chain(
public_share
.joint_rand_parts
.iter()
.flatten()
.skip(agg_id as usize + 1),
);
let (joint_rand_seed, joint_rands) = self.derive_joint_rands(corrected_joint_rand_parts);
// This function determines equality between two optional, constant-time comparable values. It // short-circuits on the existence (but not contents) of the values -- a timing side-channel may // reveal whether the values match on Some or None. #[inline] fn option_ct_eq<T>(left: Option<&T>, right: Option<&T>) -> Choice where
T: ConstantTimeEq + ?Sized,
{ match (left, right) {
(Some(left), Some(right)) => left.ct_eq(right),
(None, None) => Choice::from(1),
_ => Choice::from(0),
}
}
/// This is a polyfill for `usize::ilog2()`, which is only available in Rust 1.67 and later. It is /// based on the implementation in the standard library. It can be removed when the MSRV has been /// advanced past 1.67. /// /// # Panics /// /// This function will panic if `input` is zero. fn ilog2(input: usize) -> u32 { if input == 0 {
panic!("Tried to take the logarithm of zero");
}
(usize::BITS - 1) - input.leading_zeros()
}
/// Finds the optimal choice of chunk length for [`Prio3Histogram`] or [`Prio3SumVec`], given its /// encoded measurement length. For [`Prio3Histogram`], the measurement length is equal to the /// length parameter. For [`Prio3SumVec`], the measurement length is equal to the product of the /// length and bits parameters. pubfn optimal_chunk_length(measurement_length: usize) -> usize { if measurement_length <= 1 { return1;
}
/// Candidate set of parameter choices for the parallel sum optimization. struct Candidate {
gadget_calls: usize,
chunk_length: usize,
}
let max_log2 = ilog2(measurement_length + 1); let best_opt = (1..=max_log2)
.rev()
.map(|log2| { let gadget_calls = (1 << log2) - 1; let chunk_length = (measurement_length + gadget_calls - 1) / gadget_calls;
Candidate {
gadget_calls,
chunk_length,
}
})
.min_by_key(|candidate| { // Compute the proof length, in field elements, for either Prio3Histogram or Prio3SumVec
(candidate.chunk_length * 2)
+ 2 * ((1 + candidate.gadget_calls).next_power_of_two() - 1)
}); // Unwrap safety: max_log2 must be at least 1, because smaller measurement_length inputs are // dealt with separately. Thus, the range iterator that the search is over will be nonempty, // and min_by_key() will always return Some.
best_opt.unwrap().chunk_length
}
#[cfg(test)] mod tests { usesuper::*; #[cfg(feature = "experimental")] usecrate::flp::gadgets::ParallelSumGadget; usecrate::vdaf::{
equality_comparison_test, fieldvec_roundtrip_test,
test_utils::{run_vdaf, run_vdaf_prepare},
}; use assert_matches::assert_matches; #[cfg(feature = "experimental")] use fixed::{
types::extra::{U15, U31, U63},
FixedI16, FixedI32, FixedI64,
}; #[cfg(feature = "experimental")] use fixed_macro::fixed; use rand::prelude::*;
#[test] fn test_prio3_count() { let prio3 = Prio3::new_count(2).unwrap();
#[test] fn test_prio3_input_share() { let prio3 = Prio3::new_sum(5, 16).unwrap(); let (_public_share, input_shares) = prio3.shard(&1, &[le='color: green'>0; 16]).unwrap();
// Check that seed shares are distinct. for (i, x) in input_shares.iter().enumerate() { for (j, y) in input_shares.iter().enumerate() { if i != j { iflet (Share::Helper(left), Share::Helper(right)) =
(&x.measurement_share, &y.measurement_share)
{
assert_ne!(left, right);
}
let encoded_public_share = public_share.get_encoded().unwrap(); let decoded_public_share =
Prio3PublicShare::get_decoded_with_param(prio3, &encoded_public_share)
.expect("failed to decode public share");
assert_eq!(decoded_public_share, public_share);
assert_eq!(
public_share.encoded_len().unwrap(),
encoded_public_share.len()
);
for (agg_id, input_share) in input_shares.iter().enumerate() { let encoded_input_share = input_share.get_encoded().unwrap(); let decoded_input_share =
Prio3InputShare::get_decoded_with_param(&(prio3, agg_id), &encoded_input_share)
.expect("failed to decode input share");
assert_eq!(&decoded_input_share, input_share);
assert_eq!(
input_share.encoded_len().unwrap(),
encoded_input_share.len()
);
}
letmut prepare_shares = Vec::new(); letmut last_prepare_state = None; for (agg_id, input_share) in input_shares.iter().enumerate() { let (prepare_state, prepare_share) =
prio3.prepare_init(&verify_key, agg_id, &(), nonce, &public_share, input_share)?;
let encoded_prepare_state = prepare_state.get_encoded().unwrap(); let decoded_prepare_state =
Prio3PrepareState::get_decoded_with_param(&(prio3, agg_id), &encoded_prepare_state)
.expect("failed to decode prepare state");
assert_eq!(decoded_prepare_state, prepare_state);
assert_eq!(
prepare_state.encoded_len().unwrap(),
encoded_prepare_state.len()
);
let encoded_prepare_share = prepare_share.get_encoded().unwrap(); let decoded_prepare_share =
Prio3PrepareShare::get_decoded_with_param(&prepare_state, &encoded_prepare_share)
.expect("failed to decode prepare share");
assert_eq!(decoded_prepare_share, prepare_share);
assert_eq!(
prepare_share.encoded_len().unwrap(),
encoded_prepare_share.len()
);
#[test] fn test_optimal_chunk_length() { // nonsense argument, but make sure it doesn't panic.
optimal_chunk_length(0);
// edge cases on either side of power-of-two jumps
assert_eq!(optimal_chunk_length(1), 1);
assert_eq!(optimal_chunk_length(2), 2);
assert_eq!(optimal_chunk_length(3), 1);
assert_eq!(optimal_chunk_length(18), 6);
assert_eq!(optimal_chunk_length(19), 3);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.