summaryrefslogtreecommitdiffstats
path: root/third_party/rust/prio/src/vdaf
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/prio/src/vdaf
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/prio/src/vdaf')
-rw-r--r--third_party/rust/prio/src/vdaf/dummy.rs316
-rw-r--r--third_party/rust/prio/src/vdaf/poplar1.rs2465
-rw-r--r--third_party/rust/prio/src/vdaf/prio2.rs543
-rw-r--r--third_party/rust/prio/src/vdaf/prio2/client.rs306
-rw-r--r--third_party/rust/prio/src/vdaf/prio2/server.rs386
-rw-r--r--third_party/rust/prio/src/vdaf/prio2/test_vector.rs83
-rw-r--r--third_party/rust/prio/src/vdaf/prio3.rs2127
-rw-r--r--third_party/rust/prio/src/vdaf/prio3_test.rs251
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/IdpfPoplar_0.json52
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_0.json56
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_1.json64
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_2.json64
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_3.json76
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_0.json39
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_1.json45
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_0.json52
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_1.json89
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_0.json194
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_1.json146
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_0.json40
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_1.json46
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/XofFixedKeyAes128.json8
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/07/XofShake128.json8
-rw-r--r--third_party/rust/prio/src/vdaf/test_vec/prio2/fieldpriov2.json28
-rw-r--r--third_party/rust/prio/src/vdaf/xof.rs574
25 files changed, 8058 insertions, 0 deletions
diff --git a/third_party/rust/prio/src/vdaf/dummy.rs b/third_party/rust/prio/src/vdaf/dummy.rs
new file mode 100644
index 0000000000..507e7916bb
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/dummy.rs
@@ -0,0 +1,316 @@
+// 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<dyn Fn(&AggregationParam) -> Result<(), VdafError> + 'static + Send + Sync>;
+type ArcPrepStepFn = Arc<
+ dyn Fn(&PrepareState) -> Result<PrepareTransition<Vdaf, 0, 16>, VdafError>
+ + 'static
+ + Send
+ + Sync,
+>;
+
+/// Dummy VDAF that does nothing.
+#[derive(Clone)]
+pub struct Vdaf {
+ 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_fn", &"[redacted]")
+ .field("prep_step_fn", &"[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 {
+ prep_init_fn: Arc::new(|_| -> Result<(), VdafError> { Ok(()) }),
+ prep_step_fn: Arc::new(
+ move |state| -> Result<PrepareTransition<Self, 0, 16>, VdafError> {
+ let new_round = state.current_round + 1;
+ if new_round == 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<F: Fn(&AggregationParam) -> 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<PrepareTransition<Self, 0, 16>, 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,
+ },
+ (),
+ ))
+ }
+
+ fn prepare_shares_to_prepare_message<M: IntoIterator<Item = Self::PrepareShare>>(
+ &self,
+ _: &Self::AggregationParam,
+ _: M,
+ ) -> Result<Self::PrepareMessage, VdafError> {
+ Ok(())
+ }
+
+ fn prepare_next(
+ &self,
+ state: Self::PrepareState,
+ _: Self::PrepareMessage,
+ ) -> Result<PrepareTransition<Self, 0, 16>, VdafError> {
+ (self.prep_step_fn)(&state)
+ }
+
+ fn aggregate<M: IntoIterator<Item = Self::OutputShare>>(
+ &self,
+ _: &Self::AggregationParam,
+ output_shares: M,
+ ) -> Result<Self::AggregateShare, VdafError> {
+ 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<Self::InputShare>), 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<u8>) {
+ self.0.encode(bytes)
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ self.0.encoded_len()
+ }
+}
+
+impl Decode for InputShare {
+ fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ 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<u8>) {
+ self.0.encode(bytes)
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ self.0.encoded_len()
+ }
+}
+
+impl Decode for AggregationParam {
+ fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ 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<Self, CodecError> {
+ Ok(Self(u8::decode(bytes)?))
+ }
+}
+
+impl Encode for OutputShare {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.0.encode(bytes);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ self.0.encoded_len()
+ }
+}
+
+/// Dummy prepare state.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct PrepareState {
+ input_share: u8,
+ current_round: u32,
+}
+
+impl Encode for PrepareState {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.input_share.encode(bytes);
+ self.current_round.encode(bytes);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ Some(self.input_share.encoded_len()? + self.current_round.encoded_len()?)
+ }
+}
+
+impl Decode for PrepareState {
+ fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ let input_share = u8::decode(bytes)?;
+ let current_round = u32::decode(bytes)?;
+
+ Ok(Self {
+ input_share,
+ current_round,
+ })
+ }
+}
+
+/// 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<OutputShare> 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<Self, CodecError> {
+ let val = u64::decode(bytes)?;
+ Ok(Self(val))
+ }
+}
+
+impl Encode for AggregateShare {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.0.encode(bytes)
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ self.0.encoded_len()
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/poplar1.rs b/third_party/rust/prio/src/vdaf/poplar1.rs
new file mode 100644
index 0000000000..e8591f2049
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/poplar1.rs
@@ -0,0 +1,2465 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//! Implementation of Poplar1 as specified in [[draft-irtf-cfrg-vdaf-07]].
+//!
+//! [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+
+use crate::{
+ codec::{CodecError, Decode, Encode, ParameterizedDecode},
+ field::{decode_fieldvec, merge_vector, Field255, Field64, FieldElement},
+ idpf::{Idpf, IdpfInput, IdpfOutputShare, IdpfPublicShare, IdpfValue, RingBufferCache},
+ prng::Prng,
+ vdaf::{
+ xof::{Seed, Xof, XofShake128},
+ Aggregatable, Aggregator, Client, Collector, PrepareTransition, Vdaf, VdafError,
+ },
+};
+use bitvec::{prelude::Lsb0, vec::BitVec};
+use rand_core::RngCore;
+use std::{
+ convert::TryFrom,
+ fmt::Debug,
+ io::{Cursor, Read},
+ iter,
+ marker::PhantomData,
+ num::TryFromIntError,
+ ops::{Add, AddAssign, Sub},
+};
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
+
+const DST_SHARD_RANDOMNESS: u16 = 1;
+const DST_CORR_INNER: u16 = 2;
+const DST_CORR_LEAF: u16 = 3;
+const DST_VERIFY_RANDOMNESS: u16 = 4;
+
+impl<P, const SEED_SIZE: usize> Poplar1<P, SEED_SIZE> {
+ /// Create an instance of [`Poplar1`]. The caller provides the bit length of each
+ /// measurement (`BITS` as defined in the [[draft-irtf-cfrg-vdaf-07]]).
+ ///
+ /// [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+ pub fn new(bits: usize) -> Self {
+ Self {
+ bits,
+ phantom: PhantomData,
+ }
+ }
+}
+
+impl Poplar1<XofShake128, 16> {
+ /// Create an instance of [`Poplar1`] using [`XofShake128`]. The caller provides the bit length of
+ /// each measurement (`BITS` as defined in the [[draft-irtf-cfrg-vdaf-07]]).
+ ///
+ /// [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+ pub fn new_shake128(bits: usize) -> Self {
+ Poplar1::new(bits)
+ }
+}
+
+/// The Poplar1 VDAF.
+#[derive(Debug)]
+pub struct Poplar1<P, const SEED_SIZE: usize> {
+ bits: usize,
+ phantom: PhantomData<P>,
+}
+
+impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Poplar1<P, SEED_SIZE> {
+ /// Construct a `Prng` with the given seed and info-string suffix.
+ fn init_prng<I, B, F>(
+ seed: &[u8; SEED_SIZE],
+ usage: u16,
+ binder_chunks: I,
+ ) -> Prng<F, P::SeedStream>
+ where
+ I: IntoIterator<Item = B>,
+ B: AsRef<[u8]>,
+ P: Xof<SEED_SIZE>,
+ F: FieldElement,
+ {
+ let mut xof = P::init(seed, &Self::domain_separation_tag(usage));
+ for binder_chunk in binder_chunks.into_iter() {
+ xof.update(binder_chunk.as_ref());
+ }
+ Prng::from_seed_stream(xof.into_seed_stream())
+ }
+}
+
+impl<P, const SEED_SIZE: usize> Clone for Poplar1<P, SEED_SIZE> {
+ fn clone(&self) -> Self {
+ Self {
+ bits: self.bits,
+ phantom: PhantomData,
+ }
+ }
+}
+
+/// Poplar1 public share.
+///
+/// This is comprised of the correction words generated for the IDPF.
+pub type Poplar1PublicShare =
+ IdpfPublicShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>;
+
+impl<P, const SEED_SIZE: usize> ParameterizedDecode<Poplar1<P, SEED_SIZE>> for Poplar1PublicShare {
+ fn decode_with_param(
+ poplar1: &Poplar1<P, SEED_SIZE>,
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ Self::decode_with_param(&poplar1.bits, bytes)
+ }
+}
+
+/// Poplar1 input share.
+///
+/// This is comprised of an IDPF key share and the correlated randomness used to compute the sketch
+/// during preparation.
+#[derive(Debug, Clone)]
+pub struct Poplar1InputShare<const SEED_SIZE: usize> {
+ /// IDPF key share.
+ idpf_key: Seed<16>,
+
+ /// Seed used to generate the Aggregator's share of the correlated randomness used in the first
+ /// part of the sketch.
+ corr_seed: Seed<SEED_SIZE>,
+
+ /// Aggregator's share of the correlated randomness used in the second part of the sketch. Used
+ /// for inner nodes of the IDPF tree.
+ corr_inner: Vec<[Field64; 2]>,
+
+ /// Aggregator's share of the correlated randomness used in the second part of the sketch. Used
+ /// for leaf nodes of the IDPF tree.
+ corr_leaf: [Field255; 2],
+}
+
+impl<const SEED_SIZE: usize> PartialEq for Poplar1InputShare<SEED_SIZE> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl<const SEED_SIZE: usize> Eq for Poplar1InputShare<SEED_SIZE> {}
+
+impl<const SEED_SIZE: usize> ConstantTimeEq for Poplar1InputShare<SEED_SIZE> {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ // We short-circuit on the length of corr_inner being different. Only the content is
+ // protected.
+ if self.corr_inner.len() != other.corr_inner.len() {
+ return Choice::from(0);
+ }
+
+ let mut res = self.idpf_key.ct_eq(&other.idpf_key)
+ & self.corr_seed.ct_eq(&other.corr_seed)
+ & self.corr_leaf.ct_eq(&other.corr_leaf);
+ for (x, y) in self.corr_inner.iter().zip(other.corr_inner.iter()) {
+ res &= x.ct_eq(y);
+ }
+ res
+ }
+}
+
+impl<const SEED_SIZE: usize> Encode for Poplar1InputShare<SEED_SIZE> {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.idpf_key.encode(bytes);
+ self.corr_seed.encode(bytes);
+ for corr in self.corr_inner.iter() {
+ corr[0].encode(bytes);
+ corr[1].encode(bytes);
+ }
+ self.corr_leaf[0].encode(bytes);
+ self.corr_leaf[1].encode(bytes);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ let mut len = 0;
+ len += SEED_SIZE; // idpf_key
+ len += SEED_SIZE; // corr_seed
+ len += self.corr_inner.len() * 2 * Field64::ENCODED_SIZE; // corr_inner
+ len += 2 * Field255::ENCODED_SIZE; // corr_leaf
+ Some(len)
+ }
+}
+
+impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1<P, SEED_SIZE>, usize)>
+ for Poplar1InputShare<SEED_SIZE>
+{
+ fn decode_with_param(
+ (poplar1, _agg_id): &(&'a Poplar1<P, SEED_SIZE>, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let idpf_key = Seed::decode(bytes)?;
+ let corr_seed = Seed::decode(bytes)?;
+ let mut corr_inner = Vec::with_capacity(poplar1.bits - 1);
+ for _ in 0..poplar1.bits - 1 {
+ corr_inner.push([Field64::decode(bytes)?, Field64::decode(bytes)?]);
+ }
+ let corr_leaf = [Field255::decode(bytes)?, Field255::decode(bytes)?];
+ Ok(Self {
+ idpf_key,
+ corr_seed,
+ corr_inner,
+ corr_leaf,
+ })
+ }
+}
+
+/// Poplar1 preparation state.
+#[derive(Clone, Debug)]
+pub struct Poplar1PrepareState(PrepareStateVariant);
+
+impl PartialEq for Poplar1PrepareState {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for Poplar1PrepareState {}
+
+impl ConstantTimeEq for Poplar1PrepareState {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.0.ct_eq(&other.0)
+ }
+}
+
+impl Encode for Poplar1PrepareState {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.0.encode(bytes)
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ self.0.encoded_len()
+ }
+}
+
+impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1<P, SEED_SIZE>, usize)>
+ for Poplar1PrepareState
+{
+ fn decode_with_param(
+ decoding_parameter: &(&'a Poplar1<P, SEED_SIZE>, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ Ok(Self(PrepareStateVariant::decode_with_param(
+ decoding_parameter,
+ bytes,
+ )?))
+ }
+}
+
+#[derive(Clone, Debug)]
+enum PrepareStateVariant {
+ Inner(PrepareState<Field64>),
+ Leaf(PrepareState<Field255>),
+}
+
+impl PartialEq for PrepareStateVariant {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for PrepareStateVariant {}
+
+impl ConstantTimeEq for PrepareStateVariant {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ // We allow short-circuiting on the type (Inner vs Leaf).
+ match (self, other) {
+ (Self::Inner(self_val), Self::Inner(other_val)) => self_val.ct_eq(other_val),
+ (Self::Leaf(self_val), Self::Leaf(other_val)) => self_val.ct_eq(other_val),
+ _ => Choice::from(0),
+ }
+ }
+}
+
+impl Encode for PrepareStateVariant {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ match self {
+ PrepareStateVariant::Inner(prep_state) => {
+ 0u8.encode(bytes);
+ prep_state.encode(bytes);
+ }
+ PrepareStateVariant::Leaf(prep_state) => {
+ 1u8.encode(bytes);
+ prep_state.encode(bytes);
+ }
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ Some(
+ 1 + match self {
+ PrepareStateVariant::Inner(prep_state) => prep_state.encoded_len()?,
+ PrepareStateVariant::Leaf(prep_state) => prep_state.encoded_len()?,
+ },
+ )
+ }
+}
+
+impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1<P, SEED_SIZE>, usize)>
+ for PrepareStateVariant
+{
+ fn decode_with_param(
+ decoding_parameter: &(&'a Poplar1<P, SEED_SIZE>, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ match u8::decode(bytes)? {
+ 0 => {
+ let prep_state = PrepareState::decode_with_param(decoding_parameter, bytes)?;
+ Ok(Self::Inner(prep_state))
+ }
+ 1 => {
+ let prep_state = PrepareState::decode_with_param(decoding_parameter, bytes)?;
+ Ok(Self::Leaf(prep_state))
+ }
+ _ => Err(CodecError::UnexpectedValue),
+ }
+ }
+}
+
+#[derive(Clone)]
+struct PrepareState<F> {
+ sketch: SketchState<F>,
+ output_share: Vec<F>,
+}
+
+impl<F: ConstantTimeEq> PartialEq for PrepareState<F> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl<F: ConstantTimeEq> Eq for PrepareState<F> {}
+
+impl<F: ConstantTimeEq> ConstantTimeEq for PrepareState<F> {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.sketch.ct_eq(&other.sketch) & self.output_share.ct_eq(&other.output_share)
+ }
+}
+
+impl<F> Debug for PrepareState<F> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("PrepareState")
+ .field("sketch", &"[redacted]")
+ .field("output_share", &"[redacted]")
+ .finish()
+ }
+}
+
+impl<F: FieldElement> Encode for PrepareState<F> {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.sketch.encode(bytes);
+ // `expect` safety: output_share's length is the same as the number of prefixes; the number
+ // of prefixes is capped at 2^32-1.
+ u32::try_from(self.output_share.len())
+ .expect("Couldn't convert output_share length to u32")
+ .encode(bytes);
+ for elem in &self.output_share {
+ elem.encode(bytes);
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ Some(self.sketch.encoded_len()? + 4 + self.output_share.len() * F::ENCODED_SIZE)
+ }
+}
+
+impl<'a, P, F: FieldElement, const SEED_SIZE: usize>
+ ParameterizedDecode<(&'a Poplar1<P, SEED_SIZE>, usize)> for PrepareState<F>
+{
+ fn decode_with_param(
+ decoding_parameter: &(&'a Poplar1<P, SEED_SIZE>, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let sketch = SketchState::<F>::decode_with_param(decoding_parameter, bytes)?;
+ let output_share_len = u32::decode(bytes)?
+ .try_into()
+ .map_err(|err: TryFromIntError| CodecError::Other(err.into()))?;
+ let output_share = iter::repeat_with(|| F::decode(bytes))
+ .take(output_share_len)
+ .collect::<Result<_, _>>()?;
+ Ok(Self {
+ sketch,
+ output_share,
+ })
+ }
+}
+
+#[derive(Clone, Debug)]
+enum SketchState<F> {
+ #[allow(non_snake_case)]
+ RoundOne {
+ A_share: F,
+ B_share: F,
+ is_leader: bool,
+ },
+ RoundTwo,
+}
+
+impl<F: ConstantTimeEq> PartialEq for SketchState<F> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl<F: ConstantTimeEq> Eq for SketchState<F> {}
+
+impl<F: ConstantTimeEq> ConstantTimeEq for SketchState<F> {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ // We allow short-circuiting on the round (RoundOne vs RoundTwo), as well as is_leader for
+ // RoundOne comparisons.
+ match (self, other) {
+ (
+ SketchState::RoundOne {
+ A_share: self_a_share,
+ B_share: self_b_share,
+ is_leader: self_is_leader,
+ },
+ SketchState::RoundOne {
+ A_share: other_a_share,
+ B_share: other_b_share,
+ is_leader: other_is_leader,
+ },
+ ) => {
+ if self_is_leader != other_is_leader {
+ return Choice::from(0);
+ }
+
+ self_a_share.ct_eq(other_a_share) & self_b_share.ct_eq(other_b_share)
+ }
+
+ (SketchState::RoundTwo, SketchState::RoundTwo) => Choice::from(1),
+ _ => Choice::from(0),
+ }
+ }
+}
+
+impl<F: FieldElement> Encode for SketchState<F> {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ match self {
+ SketchState::RoundOne {
+ A_share, B_share, ..
+ } => {
+ 0u8.encode(bytes);
+ A_share.encode(bytes);
+ B_share.encode(bytes);
+ }
+ SketchState::RoundTwo => 1u8.encode(bytes),
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ Some(
+ 1 + match self {
+ SketchState::RoundOne { .. } => 2 * F::ENCODED_SIZE,
+ SketchState::RoundTwo => 0,
+ },
+ )
+ }
+}
+
+impl<'a, P, F: FieldElement, const SEED_SIZE: usize>
+ ParameterizedDecode<(&'a Poplar1<P, SEED_SIZE>, usize)> for SketchState<F>
+{
+ #[allow(non_snake_case)]
+ fn decode_with_param(
+ (_, agg_id): &(&'a Poplar1<P, SEED_SIZE>, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ match u8::decode(bytes)? {
+ 0 => {
+ let A_share = F::decode(bytes)?;
+ let B_share = F::decode(bytes)?;
+ let is_leader = agg_id == &0;
+ Ok(Self::RoundOne {
+ A_share,
+ B_share,
+ is_leader,
+ })
+ }
+ 1 => Ok(Self::RoundTwo),
+ _ => Err(CodecError::UnexpectedValue),
+ }
+ }
+}
+
+impl<F: FieldElement> SketchState<F> {
+ fn decode_sketch_share(&self, bytes: &mut Cursor<&[u8]>) -> Result<Vec<F>, CodecError> {
+ match self {
+ // The sketch share is three field elements.
+ Self::RoundOne { .. } => Ok(vec![
+ F::decode(bytes)?,
+ F::decode(bytes)?,
+ F::decode(bytes)?,
+ ]),
+ // The sketch verifier share is one field element.
+ Self::RoundTwo => Ok(vec![F::decode(bytes)?]),
+ }
+ }
+
+ fn decode_sketch(&self, bytes: &mut Cursor<&[u8]>) -> Result<Option<[F; 3]>, CodecError> {
+ match self {
+ // The sketch is three field elements.
+ Self::RoundOne { .. } => Ok(Some([
+ F::decode(bytes)?,
+ F::decode(bytes)?,
+ F::decode(bytes)?,
+ ])),
+ // The sketch verifier should be zero if the sketch if valid. Instead of transmitting
+ // this zero over the wire, we just expect an empty message.
+ Self::RoundTwo => Ok(None),
+ }
+ }
+}
+
+/// Poplar1 preparation message.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Poplar1PrepareMessage(PrepareMessageVariant);
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+enum PrepareMessageVariant {
+ SketchInner([Field64; 3]),
+ SketchLeaf([Field255; 3]),
+ Done,
+}
+
+impl Encode for Poplar1PrepareMessage {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ match self.0 {
+ PrepareMessageVariant::SketchInner(vec) => {
+ vec[0].encode(bytes);
+ vec[1].encode(bytes);
+ vec[2].encode(bytes);
+ }
+ PrepareMessageVariant::SketchLeaf(vec) => {
+ vec[0].encode(bytes);
+ vec[1].encode(bytes);
+ vec[2].encode(bytes);
+ }
+ PrepareMessageVariant::Done => (),
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ match self.0 {
+ PrepareMessageVariant::SketchInner(..) => Some(3 * Field64::ENCODED_SIZE),
+ PrepareMessageVariant::SketchLeaf(..) => Some(3 * Field255::ENCODED_SIZE),
+ PrepareMessageVariant::Done => Some(0),
+ }
+ }
+}
+
+impl ParameterizedDecode<Poplar1PrepareState> for Poplar1PrepareMessage {
+ fn decode_with_param(
+ state: &Poplar1PrepareState,
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ match state.0 {
+ PrepareStateVariant::Inner(ref state_variant) => Ok(Self(
+ state_variant
+ .sketch
+ .decode_sketch(bytes)?
+ .map_or(PrepareMessageVariant::Done, |sketch| {
+ PrepareMessageVariant::SketchInner(sketch)
+ }),
+ )),
+ PrepareStateVariant::Leaf(ref state_variant) => Ok(Self(
+ state_variant
+ .sketch
+ .decode_sketch(bytes)?
+ .map_or(PrepareMessageVariant::Done, |sketch| {
+ PrepareMessageVariant::SketchLeaf(sketch)
+ }),
+ )),
+ }
+ }
+}
+
+/// A vector of field elements transmitted while evaluating Poplar1.
+#[derive(Clone, Debug)]
+pub enum Poplar1FieldVec {
+ /// Field type for inner nodes of the IDPF tree.
+ Inner(Vec<Field64>),
+
+ /// Field type for leaf nodes of the IDPF tree.
+ Leaf(Vec<Field255>),
+}
+
+impl Poplar1FieldVec {
+ fn zero(is_leaf: bool, len: usize) -> Self {
+ if is_leaf {
+ Self::Leaf(vec![<Field255 as FieldElement>::zero(); len])
+ } else {
+ Self::Inner(vec![<Field64 as FieldElement>::zero(); len])
+ }
+ }
+}
+
+impl PartialEq for Poplar1FieldVec {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for Poplar1FieldVec {}
+
+impl ConstantTimeEq for Poplar1FieldVec {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ // We allow short-circuiting on the type (Inner vs Leaf).
+ match (self, other) {
+ (Poplar1FieldVec::Inner(self_val), Poplar1FieldVec::Inner(other_val)) => {
+ self_val.ct_eq(other_val)
+ }
+ (Poplar1FieldVec::Leaf(self_val), Poplar1FieldVec::Leaf(other_val)) => {
+ self_val.ct_eq(other_val)
+ }
+ _ => Choice::from(0),
+ }
+ }
+}
+
+impl Encode for Poplar1FieldVec {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ match self {
+ Self::Inner(ref data) => {
+ for elem in data {
+ elem.encode(bytes);
+ }
+ }
+ Self::Leaf(ref data) => {
+ for elem in data {
+ elem.encode(bytes);
+ }
+ }
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ match self {
+ Self::Inner(ref data) => Some(Field64::ENCODED_SIZE * data.len()),
+ Self::Leaf(ref data) => Some(Field255::ENCODED_SIZE * data.len()),
+ }
+ }
+}
+
+impl<'a, P: Xof<SEED_SIZE>, const SEED_SIZE: usize>
+ ParameterizedDecode<(&'a Poplar1<P, SEED_SIZE>, &'a Poplar1AggregationParam)>
+ for Poplar1FieldVec
+{
+ fn decode_with_param(
+ (poplar1, agg_param): &(&'a Poplar1<P, SEED_SIZE>, &'a Poplar1AggregationParam),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ if agg_param.level() == poplar1.bits - 1 {
+ decode_fieldvec(agg_param.prefixes().len(), bytes).map(Poplar1FieldVec::Leaf)
+ } else {
+ decode_fieldvec(agg_param.prefixes().len(), bytes).map(Poplar1FieldVec::Inner)
+ }
+ }
+}
+
+impl ParameterizedDecode<Poplar1PrepareState> for Poplar1FieldVec {
+ fn decode_with_param(
+ state: &Poplar1PrepareState,
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ match state.0 {
+ PrepareStateVariant::Inner(ref state_variant) => Ok(Poplar1FieldVec::Inner(
+ state_variant.sketch.decode_sketch_share(bytes)?,
+ )),
+ PrepareStateVariant::Leaf(ref state_variant) => Ok(Poplar1FieldVec::Leaf(
+ state_variant.sketch.decode_sketch_share(bytes)?,
+ )),
+ }
+ }
+}
+
+impl Aggregatable for Poplar1FieldVec {
+ type OutputShare = Self;
+
+ fn merge(&mut self, agg_share: &Self) -> Result<(), VdafError> {
+ match (self, agg_share) {
+ (Self::Inner(ref mut left), Self::Inner(right)) => Ok(merge_vector(left, right)?),
+ (Self::Leaf(ref mut left), Self::Leaf(right)) => Ok(merge_vector(left, right)?),
+ _ => Err(VdafError::Uncategorized(
+ "cannot merge leaf nodes wiith inner nodes".into(),
+ )),
+ }
+ }
+
+ fn accumulate(&mut self, output_share: &Self) -> Result<(), VdafError> {
+ match (self, output_share) {
+ (Self::Inner(ref mut left), Self::Inner(right)) => Ok(merge_vector(left, right)?),
+ (Self::Leaf(ref mut left), Self::Leaf(right)) => Ok(merge_vector(left, right)?),
+ _ => Err(VdafError::Uncategorized(
+ "cannot accumulate leaf nodes with inner nodes".into(),
+ )),
+ }
+ }
+}
+
+/// Poplar1 aggregation parameter.
+///
+/// This includes an indication of what level of the IDPF tree is being evaluated and the set of
+/// prefixes to evaluate at that level.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct Poplar1AggregationParam {
+ level: u16,
+ prefixes: Vec<IdpfInput>,
+}
+
+impl Poplar1AggregationParam {
+ /// Construct an aggregation parameter from a set of candidate prefixes.
+ ///
+ /// # Errors
+ ///
+ /// * The list of prefixes is empty.
+ /// * The prefixes have different lengths (they must all be the same).
+ /// * The prefixes have length 0, or length longer than 2^16 bits.
+ /// * There are more than 2^32 - 1 prefixes.
+ /// * The prefixes are not unique.
+ /// * The prefixes are not in lexicographic order.
+ pub fn try_from_prefixes(prefixes: Vec<IdpfInput>) -> Result<Self, VdafError> {
+ if prefixes.is_empty() {
+ return Err(VdafError::Uncategorized(
+ "at least one prefix is required".into(),
+ ));
+ }
+ if u32::try_from(prefixes.len()).is_err() {
+ return Err(VdafError::Uncategorized("too many prefixes".into()));
+ }
+
+ let len = prefixes[0].len();
+ let mut last_prefix = None;
+ for prefix in prefixes.iter() {
+ if prefix.len() != len {
+ return Err(VdafError::Uncategorized(
+ "all prefixes must have the same length".into(),
+ ));
+ }
+ if let Some(last_prefix) = last_prefix {
+ if prefix <= last_prefix {
+ if prefix == last_prefix {
+ return Err(VdafError::Uncategorized(
+ "prefixes must be nonrepeating".into(),
+ ));
+ } else {
+ return Err(VdafError::Uncategorized(
+ "prefixes must be in lexicographic order".into(),
+ ));
+ }
+ }
+ }
+ last_prefix = Some(prefix);
+ }
+
+ let level = len
+ .checked_sub(1)
+ .ok_or_else(|| VdafError::Uncategorized("prefixes are too short".into()))?;
+ let level = u16::try_from(level)
+ .map_err(|_| VdafError::Uncategorized("prefixes are too long".into()))?;
+
+ Ok(Self { level, prefixes })
+ }
+
+ /// Return the level of the IDPF tree.
+ pub fn level(&self) -> usize {
+ usize::from(self.level)
+ }
+
+ /// Return the prefixes.
+ pub fn prefixes(&self) -> &[IdpfInput] {
+ self.prefixes.as_ref()
+ }
+}
+
+impl Encode for Poplar1AggregationParam {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ // Okay to unwrap because `try_from_prefixes()` checks this conversion succeeds.
+ let prefix_count = u32::try_from(self.prefixes.len()).unwrap();
+ self.level.encode(bytes);
+ prefix_count.encode(bytes);
+
+ // The encoding of the prefixes is defined by treating the IDPF indices as integers,
+ // shifting and ORing them together, and encoding the resulting arbitrary precision integer
+ // in big endian byte order. Thus, the first prefix will appear in the last encoded byte,
+ // aligned to its least significant bit. The last prefix will appear in the first encoded
+ // byte, not necessarily aligned to a byte boundary. If the highest bits in the first byte
+ // are unused, they will be set to zero.
+
+ // When an IDPF index is treated as an integer, the first bit is the integer's most
+ // significant bit, and bits are subsequently processed in order of decreasing significance.
+ // Thus, setting aside the order of bytes, bits within each byte are ordered with the
+ // [`Msb0`](bitvec::prelude::Msb0) convention, not [`Lsb0`](bitvec::prelude::Msb0). Yet,
+ // the entire integer is aligned to the least significant bit of the last byte, so we
+ // could not use `Msb0` directly without padding adjustments. Instead, we use `Lsb0`
+ // throughout and reverse the bit order of each prefix.
+
+ let mut packed = self
+ .prefixes
+ .iter()
+ .flat_map(|input| input.iter().rev())
+ .collect::<BitVec<u8, Lsb0>>();
+ packed.set_uninitialized(false);
+ let mut packed = packed.into_vec();
+ packed.reverse();
+ bytes.append(&mut packed);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ let packed_bit_count = (usize::from(self.level) + 1) * self.prefixes.len();
+ // 4 bytes for the number of prefixes, 2 bytes for the level, and a variable number of bytes
+ // for the packed prefixes themselves.
+ Some(6 + (packed_bit_count + 7) / 8)
+ }
+}
+
+impl Decode for Poplar1AggregationParam {
+ fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ let level = u16::decode(bytes)?;
+ let prefix_count =
+ usize::try_from(u32::decode(bytes)?).map_err(|e| CodecError::Other(e.into()))?;
+
+ let packed_bit_count = (usize::from(level) + 1) * prefix_count;
+ let mut packed = vec![0u8; (packed_bit_count + 7) / 8];
+ bytes.read_exact(&mut packed)?;
+ if packed_bit_count % 8 != 0 {
+ let unused_bits = packed[0] >> (packed_bit_count % 8);
+ if unused_bits != 0 {
+ return Err(CodecError::UnexpectedValue);
+ }
+ }
+ packed.reverse();
+ let bits = BitVec::<u8, Lsb0>::from_vec(packed);
+
+ let prefixes = bits
+ .chunks_exact(usize::from(level) + 1)
+ .take(prefix_count)
+ .map(|chunk| IdpfInput::from(chunk.iter().rev().collect::<BitVec>()))
+ .collect::<Vec<IdpfInput>>();
+
+ Poplar1AggregationParam::try_from_prefixes(prefixes)
+ .map_err(|e| CodecError::Other(e.into()))
+ }
+}
+
+impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Vdaf for Poplar1<P, SEED_SIZE> {
+ const ID: u32 = 0x00001000;
+ type Measurement = IdpfInput;
+ type AggregateResult = Vec<u64>;
+ type AggregationParam = Poplar1AggregationParam;
+ type PublicShare = Poplar1PublicShare;
+ type InputShare = Poplar1InputShare<SEED_SIZE>;
+ type OutputShare = Poplar1FieldVec;
+ type AggregateShare = Poplar1FieldVec;
+
+ fn num_aggregators(&self) -> usize {
+ 2
+ }
+}
+
+impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Poplar1<P, SEED_SIZE> {
+ fn shard_with_random(
+ &self,
+ input: &IdpfInput,
+ nonce: &[u8; 16],
+ idpf_random: &[[u8; 16]; 2],
+ poplar_random: &[[u8; SEED_SIZE]; 3],
+ ) -> Result<(Poplar1PublicShare, Vec<Poplar1InputShare<SEED_SIZE>>), VdafError> {
+ if input.len() != self.bits {
+ return Err(VdafError::Uncategorized(format!(
+ "unexpected input length ({})",
+ input.len()
+ )));
+ }
+
+ // Generate the authenticator for each inner level of the IDPF tree.
+ let mut prng =
+ Self::init_prng::<_, _, Field64>(&poplar_random[2], DST_SHARD_RANDOMNESS, [&[]]);
+ let auth_inner: Vec<Field64> = (0..self.bits - 1).map(|_| prng.get()).collect();
+
+ // Generate the authenticator for the last level of the IDPF tree (i.e., the leaves).
+ //
+ // TODO(cjpatton) spec: Consider using a different XOF for the leaf and inner nodes.
+ // "Switching" the XOF between field types is awkward.
+ let mut prng = prng.into_new_field::<Field255>();
+ let auth_leaf = prng.get();
+
+ // Generate the IDPF shares.
+ let idpf = Idpf::new((), ());
+ let (public_share, [idpf_key_0, idpf_key_1]) = idpf.gen_with_random(
+ input,
+ auth_inner
+ .iter()
+ .map(|auth| Poplar1IdpfValue([Field64::one(), *auth])),
+ Poplar1IdpfValue([Field255::one(), auth_leaf]),
+ nonce,
+ idpf_random,
+ )?;
+
+ // Generate the correlated randomness for the inner nodes. This includes additive shares of
+ // the random offsets `a, b, c` and additive shares of `A := -2*a + auth` and `B := a^2 + b
+ // - a*auth + c`, where `auth` is the authenticator for the level of the tree. These values
+ // are used, respectively, to compute and verify the sketch during the preparation phase.
+ // (See Section 4.2 of [BBCG+21].)
+ let corr_seed_0 = &poplar_random[0];
+ let corr_seed_1 = &poplar_random[1];
+ let mut prng = prng.into_new_field::<Field64>();
+ let mut corr_prng_0 = Self::init_prng::<_, _, Field64>(
+ corr_seed_0,
+ DST_CORR_INNER,
+ [[0].as_slice(), nonce.as_slice()],
+ );
+ let mut corr_prng_1 = Self::init_prng::<_, _, Field64>(
+ corr_seed_1,
+ DST_CORR_INNER,
+ [[1].as_slice(), nonce.as_slice()],
+ );
+ let mut corr_inner_0 = Vec::with_capacity(self.bits - 1);
+ let mut corr_inner_1 = Vec::with_capacity(self.bits - 1);
+ for auth in auth_inner.into_iter() {
+ let (next_corr_inner_0, next_corr_inner_1) =
+ compute_next_corr_shares(&mut prng, &mut corr_prng_0, &mut corr_prng_1, auth);
+ corr_inner_0.push(next_corr_inner_0);
+ corr_inner_1.push(next_corr_inner_1);
+ }
+
+ // Generate the correlated randomness for the leaf nodes.
+ let mut prng = prng.into_new_field::<Field255>();
+ let mut corr_prng_0 = Self::init_prng::<_, _, Field255>(
+ corr_seed_0,
+ DST_CORR_LEAF,
+ [[0].as_slice(), nonce.as_slice()],
+ );
+ let mut corr_prng_1 = Self::init_prng::<_, _, Field255>(
+ corr_seed_1,
+ DST_CORR_LEAF,
+ [[1].as_slice(), nonce.as_slice()],
+ );
+ let (corr_leaf_0, corr_leaf_1) =
+ compute_next_corr_shares(&mut prng, &mut corr_prng_0, &mut corr_prng_1, auth_leaf);
+
+ Ok((
+ public_share,
+ vec![
+ Poplar1InputShare {
+ idpf_key: idpf_key_0,
+ corr_seed: Seed::from_bytes(*corr_seed_0),
+ corr_inner: corr_inner_0,
+ corr_leaf: corr_leaf_0,
+ },
+ Poplar1InputShare {
+ idpf_key: idpf_key_1,
+ corr_seed: Seed::from_bytes(*corr_seed_1),
+ corr_inner: corr_inner_1,
+ corr_leaf: corr_leaf_1,
+ },
+ ],
+ ))
+ }
+}
+
+impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Client<16> for Poplar1<P, SEED_SIZE> {
+ fn shard(
+ &self,
+ input: &IdpfInput,
+ nonce: &[u8; 16],
+ ) -> Result<(Self::PublicShare, Vec<Poplar1InputShare<SEED_SIZE>>), VdafError> {
+ let mut idpf_random = [[0u8; 16]; 2];
+ let mut poplar_random = [[0u8; SEED_SIZE]; 3];
+ for random_seed in idpf_random.iter_mut() {
+ getrandom::getrandom(random_seed)?;
+ }
+ for random_seed in poplar_random.iter_mut() {
+ getrandom::getrandom(random_seed)?;
+ }
+ self.shard_with_random(input, nonce, &idpf_random, &poplar_random)
+ }
+}
+
+impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Aggregator<SEED_SIZE, 16>
+ for Poplar1<P, SEED_SIZE>
+{
+ type PrepareState = Poplar1PrepareState;
+ type PrepareShare = Poplar1FieldVec;
+ type PrepareMessage = Poplar1PrepareMessage;
+
+ #[allow(clippy::type_complexity)]
+ fn prepare_init(
+ &self,
+ verify_key: &[u8; SEED_SIZE],
+ agg_id: usize,
+ agg_param: &Poplar1AggregationParam,
+ nonce: &[u8; 16],
+ public_share: &Poplar1PublicShare,
+ input_share: &Poplar1InputShare<SEED_SIZE>,
+ ) -> Result<(Poplar1PrepareState, Poplar1FieldVec), VdafError> {
+ let is_leader = match agg_id {
+ 0 => true,
+ 1 => false,
+ _ => {
+ return Err(VdafError::Uncategorized(format!(
+ "invalid aggregator ID ({agg_id})"
+ )))
+ }
+ };
+
+ if usize::from(agg_param.level) < self.bits - 1 {
+ let mut corr_prng = Self::init_prng::<_, _, Field64>(
+ input_share.corr_seed.as_ref(),
+ DST_CORR_INNER,
+ [[agg_id as u8].as_slice(), nonce.as_slice()],
+ );
+ // Fast-forward the correlated randomness XOF to the level of the tree that we are
+ // aggregating.
+ for _ in 0..3 * agg_param.level {
+ corr_prng.get();
+ }
+
+ let (output_share, sketch_share) = eval_and_sketch::<P, Field64, SEED_SIZE>(
+ verify_key,
+ agg_id,
+ nonce,
+ agg_param,
+ public_share,
+ &input_share.idpf_key,
+ &mut corr_prng,
+ )?;
+
+ Ok((
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: input_share.corr_inner[usize::from(agg_param.level)][0],
+ B_share: input_share.corr_inner[usize::from(agg_param.level)][1],
+ is_leader,
+ },
+ output_share,
+ })),
+ Poplar1FieldVec::Inner(sketch_share),
+ ))
+ } else {
+ let corr_prng = Self::init_prng::<_, _, Field255>(
+ input_share.corr_seed.as_ref(),
+ DST_CORR_LEAF,
+ [[agg_id as u8].as_slice(), nonce.as_slice()],
+ );
+
+ let (output_share, sketch_share) = eval_and_sketch::<P, Field255, SEED_SIZE>(
+ verify_key,
+ agg_id,
+ nonce,
+ agg_param,
+ public_share,
+ &input_share.idpf_key,
+ &mut corr_prng.into_new_field(),
+ )?;
+
+ Ok((
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: input_share.corr_leaf[0],
+ B_share: input_share.corr_leaf[1],
+ is_leader,
+ },
+ output_share,
+ })),
+ Poplar1FieldVec::Leaf(sketch_share),
+ ))
+ }
+ }
+
+ fn prepare_shares_to_prepare_message<M: IntoIterator<Item = Poplar1FieldVec>>(
+ &self,
+ _: &Poplar1AggregationParam,
+ inputs: M,
+ ) -> Result<Poplar1PrepareMessage, VdafError> {
+ let mut inputs = inputs.into_iter();
+ let prep_share_0 = inputs
+ .next()
+ .ok_or_else(|| VdafError::Uncategorized("insufficient number of prep shares".into()))?;
+ let prep_share_1 = inputs
+ .next()
+ .ok_or_else(|| VdafError::Uncategorized("insufficient number of prep shares".into()))?;
+ if inputs.next().is_some() {
+ return Err(VdafError::Uncategorized(
+ "more prep shares than expected".into(),
+ ));
+ }
+
+ match (prep_share_0, prep_share_1) {
+ (Poplar1FieldVec::Inner(share_0), Poplar1FieldVec::Inner(share_1)) => {
+ Ok(Poplar1PrepareMessage(
+ next_message(share_0, share_1)?.map_or(PrepareMessageVariant::Done, |sketch| {
+ PrepareMessageVariant::SketchInner(sketch)
+ }),
+ ))
+ }
+ (Poplar1FieldVec::Leaf(share_0), Poplar1FieldVec::Leaf(share_1)) => {
+ Ok(Poplar1PrepareMessage(
+ next_message(share_0, share_1)?.map_or(PrepareMessageVariant::Done, |sketch| {
+ PrepareMessageVariant::SketchLeaf(sketch)
+ }),
+ ))
+ }
+ _ => Err(VdafError::Uncategorized(
+ "received prep shares with mismatched field types".into(),
+ )),
+ }
+ }
+
+ fn prepare_next(
+ &self,
+ state: Poplar1PrepareState,
+ msg: Poplar1PrepareMessage,
+ ) -> Result<PrepareTransition<Self, SEED_SIZE, 16>, VdafError> {
+ match (state.0, msg.0) {
+ // Round one
+ (
+ PrepareStateVariant::Inner(PrepareState {
+ sketch:
+ SketchState::RoundOne {
+ A_share,
+ B_share,
+ is_leader,
+ },
+ output_share,
+ }),
+ PrepareMessageVariant::SketchInner(sketch),
+ ) => Ok(PrepareTransition::Continue(
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share,
+ })),
+ Poplar1FieldVec::Inner(finish_sketch(sketch, A_share, B_share, is_leader)),
+ )),
+ (
+ PrepareStateVariant::Leaf(PrepareState {
+ sketch:
+ SketchState::RoundOne {
+ A_share,
+ B_share,
+ is_leader,
+ },
+ output_share,
+ }),
+ PrepareMessageVariant::SketchLeaf(sketch),
+ ) => Ok(PrepareTransition::Continue(
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share,
+ })),
+ Poplar1FieldVec::Leaf(finish_sketch(sketch, A_share, B_share, is_leader)),
+ )),
+
+ // Round two
+ (
+ PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share,
+ }),
+ PrepareMessageVariant::Done,
+ ) => Ok(PrepareTransition::Finish(Poplar1FieldVec::Inner(
+ output_share,
+ ))),
+ (
+ PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share,
+ }),
+ PrepareMessageVariant::Done,
+ ) => Ok(PrepareTransition::Finish(Poplar1FieldVec::Leaf(
+ output_share,
+ ))),
+
+ _ => Err(VdafError::Uncategorized(
+ "prep message field type does not match state".into(),
+ )),
+ }
+ }
+
+ fn aggregate<M: IntoIterator<Item = Poplar1FieldVec>>(
+ &self,
+ agg_param: &Poplar1AggregationParam,
+ output_shares: M,
+ ) -> Result<Poplar1FieldVec, VdafError> {
+ aggregate(
+ usize::from(agg_param.level) == self.bits - 1,
+ agg_param.prefixes.len(),
+ output_shares,
+ )
+ }
+}
+
+impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Collector for Poplar1<P, SEED_SIZE> {
+ fn unshard<M: IntoIterator<Item = Poplar1FieldVec>>(
+ &self,
+ agg_param: &Poplar1AggregationParam,
+ agg_shares: M,
+ _num_measurements: usize,
+ ) -> Result<Vec<u64>, VdafError> {
+ let result = aggregate(
+ usize::from(agg_param.level) == self.bits - 1,
+ agg_param.prefixes.len(),
+ agg_shares,
+ )?;
+
+ match result {
+ Poplar1FieldVec::Inner(vec) => Ok(vec.into_iter().map(u64::from).collect()),
+ Poplar1FieldVec::Leaf(vec) => Ok(vec
+ .into_iter()
+ .map(u64::try_from)
+ .collect::<Result<Vec<_>, _>>()?),
+ }
+ }
+}
+
+impl From<IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>>
+ for Poplar1IdpfValue<Field64>
+{
+ fn from(
+ out_share: IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>,
+ ) -> Poplar1IdpfValue<Field64> {
+ match out_share {
+ IdpfOutputShare::Inner(array) => array,
+ IdpfOutputShare::Leaf(..) => panic!("tried to convert leaf share into inner field"),
+ }
+ }
+}
+
+impl From<IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>>
+ for Poplar1IdpfValue<Field255>
+{
+ fn from(
+ out_share: IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>,
+ ) -> Poplar1IdpfValue<Field255> {
+ match out_share {
+ IdpfOutputShare::Inner(..) => panic!("tried to convert inner share into leaf field"),
+ IdpfOutputShare::Leaf(array) => array,
+ }
+ }
+}
+
+/// Derive shares of the correlated randomness for the next level of the IDPF tree.
+//
+// TODO(cjpatton) spec: Consider deriving the shares of a, b, c for each level directly from the
+// seed, rather than iteratively, as we do in Doplar. This would be more efficient for the
+// Aggregators. As long as the Client isn't significantly slower, this should be a win.
+#[allow(non_snake_case)]
+fn compute_next_corr_shares<F: FieldElement + From<u64>, S: RngCore>(
+ prng: &mut Prng<F, S>,
+ corr_prng_0: &mut Prng<F, S>,
+ corr_prng_1: &mut Prng<F, S>,
+ auth: F,
+) -> ([F; 2], [F; 2]) {
+ let two = F::from(2);
+ let a = corr_prng_0.get() + corr_prng_1.get();
+ let b = corr_prng_0.get() + corr_prng_1.get();
+ let c = corr_prng_0.get() + corr_prng_1.get();
+ let A = -two * a + auth;
+ let B = a * a + b - a * auth + c;
+ let corr_1 = [prng.get(), prng.get()];
+ let corr_0 = [A - corr_1[0], B - corr_1[1]];
+ (corr_0, corr_1)
+}
+
+/// Evaluate the IDPF at the given prefixes and compute the Aggregator's share of the sketch.
+fn eval_and_sketch<P, F, const SEED_SIZE: usize>(
+ verify_key: &[u8; SEED_SIZE],
+ agg_id: usize,
+ nonce: &[u8; 16],
+ agg_param: &Poplar1AggregationParam,
+ public_share: &Poplar1PublicShare,
+ idpf_key: &Seed<16>,
+ corr_prng: &mut Prng<F, P::SeedStream>,
+) -> Result<(Vec<F>, Vec<F>), VdafError>
+where
+ P: Xof<SEED_SIZE>,
+ F: FieldElement,
+ Poplar1IdpfValue<F>:
+ From<IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>>,
+{
+ // TODO(cjpatton) spec: Consider not encoding the prefixes here.
+ let mut verify_prng = Poplar1::<P, SEED_SIZE>::init_prng(
+ verify_key,
+ DST_VERIFY_RANDOMNESS,
+ [nonce.as_slice(), agg_param.level.to_be_bytes().as_slice()],
+ );
+
+ let mut out_share = Vec::with_capacity(agg_param.prefixes.len());
+ let mut sketch_share = vec![
+ corr_prng.get(), // a_share
+ corr_prng.get(), // b_share
+ corr_prng.get(), // c_share
+ ];
+
+ let mut idpf_eval_cache = RingBufferCache::new(agg_param.prefixes.len());
+ let idpf = Idpf::<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>::new((), ());
+ for prefix in agg_param.prefixes.iter() {
+ let share = Poplar1IdpfValue::<F>::from(idpf.eval(
+ agg_id,
+ public_share,
+ idpf_key,
+ prefix,
+ nonce,
+ &mut idpf_eval_cache,
+ )?);
+
+ let r = verify_prng.get();
+ let checked_data_share = share.0[0] * r;
+ sketch_share[0] += checked_data_share;
+ sketch_share[1] += checked_data_share * r;
+ sketch_share[2] += share.0[1] * r;
+ out_share.push(share.0[0]);
+ }
+
+ Ok((out_share, sketch_share))
+}
+
+/// Compute the Aggregator's share of the sketch verifier. The shares should sum to zero.
+#[allow(non_snake_case)]
+fn finish_sketch<F: FieldElement>(
+ sketch: [F; 3],
+ A_share: F,
+ B_share: F,
+ is_leader: bool,
+) -> Vec<F> {
+ let mut next_sketch_share = A_share * sketch[0] + B_share;
+ if !is_leader {
+ next_sketch_share += sketch[0] * sketch[0] - sketch[1] - sketch[2];
+ }
+ vec![next_sketch_share]
+}
+
+fn next_message<F: FieldElement>(
+ mut share_0: Vec<F>,
+ share_1: Vec<F>,
+) -> Result<Option<[F; 3]>, VdafError> {
+ merge_vector(&mut share_0, &share_1)?;
+
+ if share_0.len() == 1 {
+ if share_0[0] != F::zero() {
+ Err(VdafError::Uncategorized(
+ "sketch verification failed".into(),
+ )) // Invalid sketch
+ } else {
+ Ok(None) // Sketch verification succeeded
+ }
+ } else if share_0.len() == 3 {
+ Ok(Some([share_0[0], share_0[1], share_0[2]])) // Sketch verification continues
+ } else {
+ Err(VdafError::Uncategorized(format!(
+ "unexpected sketch length ({})",
+ share_0.len()
+ )))
+ }
+}
+
+fn aggregate<M: IntoIterator<Item = Poplar1FieldVec>>(
+ is_leaf: bool,
+ len: usize,
+ shares: M,
+) -> Result<Poplar1FieldVec, VdafError> {
+ let mut result = Poplar1FieldVec::zero(is_leaf, len);
+ for share in shares.into_iter() {
+ result.accumulate(&share)?;
+ }
+ Ok(result)
+}
+
+/// A vector of two field elements.
+///
+/// This represents the values that Poplar1 programs into IDPFs while sharding.
+#[derive(Debug, Clone, Copy)]
+pub struct Poplar1IdpfValue<F>([F; 2]);
+
+impl<F> Poplar1IdpfValue<F> {
+ /// Create a new value from a pair of field elements.
+ pub fn new(array: [F; 2]) -> Self {
+ Self(array)
+ }
+}
+
+impl<F> IdpfValue for Poplar1IdpfValue<F>
+where
+ F: FieldElement,
+{
+ type ValueParameter = ();
+
+ fn zero(_: &()) -> Self {
+ Self([F::zero(); 2])
+ }
+
+ fn generate<S: RngCore>(seed_stream: &mut S, _: &()) -> Self {
+ Self([F::generate(seed_stream, &()), F::generate(seed_stream, &())])
+ }
+
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ ConditionallySelectable::conditional_select(a, b, choice)
+ }
+}
+
+impl<F> Add for Poplar1IdpfValue<F>
+where
+ F: Copy + Add<Output = F>,
+{
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self {
+ Self([self.0[0] + rhs.0[0], self.0[1] + rhs.0[1]])
+ }
+}
+
+impl<F> AddAssign for Poplar1IdpfValue<F>
+where
+ F: Copy + AddAssign,
+{
+ fn add_assign(&mut self, rhs: Self) {
+ self.0[0] += rhs.0[0];
+ self.0[1] += rhs.0[1];
+ }
+}
+
+impl<F> Sub for Poplar1IdpfValue<F>
+where
+ F: Copy + Sub<Output = F>,
+{
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self {
+ Self([self.0[0] - rhs.0[0], self.0[1] - rhs.0[1]])
+ }
+}
+
+impl<F> PartialEq for Poplar1IdpfValue<F>
+where
+ F: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.0 == other.0
+ }
+}
+
+impl<F> ConstantTimeEq for Poplar1IdpfValue<F>
+where
+ F: ConstantTimeEq,
+{
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.0.ct_eq(&other.0)
+ }
+}
+
+impl<F> Encode for Poplar1IdpfValue<F>
+where
+ F: FieldElement,
+{
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.0[0].encode(bytes);
+ self.0[1].encode(bytes);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ Some(F::ENCODED_SIZE * 2)
+ }
+}
+
+impl<F> Decode for Poplar1IdpfValue<F>
+where
+ F: Decode,
+{
+ fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ Ok(Self([F::decode(bytes)?, F::decode(bytes)?]))
+ }
+}
+
+impl<F> ConditionallySelectable for Poplar1IdpfValue<F>
+where
+ F: ConditionallySelectable,
+{
+ fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
+ Self([
+ F::conditional_select(&a.0[0], &b.0[0], choice),
+ F::conditional_select(&a.0[1], &b.0[1], choice),
+ ])
+ }
+}
+
+impl<F> ConditionallyNegatable for Poplar1IdpfValue<F>
+where
+ F: ConditionallyNegatable,
+{
+ fn conditional_negate(&mut self, choice: subtle::Choice) {
+ F::conditional_negate(&mut self.0[0], choice);
+ F::conditional_negate(&mut self.0[1], choice);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::vdaf::{equality_comparison_test, run_vdaf_prepare};
+ use assert_matches::assert_matches;
+ use rand::prelude::*;
+ use serde::Deserialize;
+ use std::collections::HashSet;
+
+ fn test_prepare<P: Xof<SEED_SIZE>, const SEED_SIZE: usize>(
+ vdaf: &Poplar1<P, SEED_SIZE>,
+ verify_key: &[u8; SEED_SIZE],
+ nonce: &[u8; 16],
+ public_share: &Poplar1PublicShare,
+ input_shares: &[Poplar1InputShare<SEED_SIZE>],
+ agg_param: &Poplar1AggregationParam,
+ expected_result: Vec<u64>,
+ ) {
+ let out_shares = run_vdaf_prepare(
+ vdaf,
+ verify_key,
+ agg_param,
+ nonce,
+ public_share.clone(),
+ input_shares.to_vec(),
+ )
+ .unwrap();
+
+ // Convert aggregate shares and unshard.
+ let agg_share_0 = vdaf.aggregate(agg_param, [out_shares[0].clone()]).unwrap();
+ let agg_share_1 = vdaf.aggregate(agg_param, [out_shares[1].clone()]).unwrap();
+ let result = vdaf
+ .unshard(agg_param, [agg_share_0, agg_share_1], 1)
+ .unwrap();
+ assert_eq!(
+ result, expected_result,
+ "unexpected result (level={})",
+ agg_param.level
+ );
+ }
+
+ fn run_heavy_hitters<B: AsRef<[u8]>, P: Xof<SEED_SIZE>, const SEED_SIZE: usize>(
+ vdaf: &Poplar1<P, SEED_SIZE>,
+ verify_key: &[u8; SEED_SIZE],
+ threshold: usize,
+ measurements: impl IntoIterator<Item = B>,
+ expected_result: impl IntoIterator<Item = B>,
+ ) {
+ let mut rng = thread_rng();
+
+ // Sharding step
+ let reports: Vec<(
+ [u8; 16],
+ Poplar1PublicShare,
+ Vec<Poplar1InputShare<SEED_SIZE>>,
+ )> = measurements
+ .into_iter()
+ .map(|measurement| {
+ let nonce = rng.gen();
+ let (public_share, input_shares) = vdaf
+ .shard(&IdpfInput::from_bytes(measurement.as_ref()), &nonce)
+ .unwrap();
+ (nonce, public_share, input_shares)
+ })
+ .collect();
+
+ let mut agg_param = Poplar1AggregationParam {
+ level: 0,
+ prefixes: vec![
+ IdpfInput::from_bools(&[false]),
+ IdpfInput::from_bools(&[true]),
+ ],
+ };
+
+ let mut agg_result = Vec::new();
+ for level in 0..vdaf.bits {
+ let mut out_shares_0 = Vec::with_capacity(reports.len());
+ let mut out_shares_1 = Vec::with_capacity(reports.len());
+
+ // Preparation step
+ for (nonce, public_share, input_shares) in reports.iter() {
+ let out_shares = run_vdaf_prepare(
+ vdaf,
+ verify_key,
+ &agg_param,
+ nonce,
+ public_share.clone(),
+ input_shares.to_vec(),
+ )
+ .unwrap();
+
+ out_shares_0.push(out_shares[0].clone());
+ out_shares_1.push(out_shares[1].clone());
+ }
+
+ // Aggregation step
+ let agg_share_0 = vdaf.aggregate(&agg_param, out_shares_0).unwrap();
+ let agg_share_1 = vdaf.aggregate(&agg_param, out_shares_1).unwrap();
+
+ // Unsharding step
+ agg_result = vdaf
+ .unshard(&agg_param, [agg_share_0, agg_share_1], reports.len())
+ .unwrap();
+
+ agg_param.level += 1;
+
+ // Unless this is the last level of the tree, construct the next set of candidate
+ // prefixes.
+ if level < vdaf.bits - 1 {
+ let mut next_prefixes = Vec::new();
+ for (prefix, count) in agg_param.prefixes.into_iter().zip(agg_result.iter()) {
+ if *count >= threshold as u64 {
+ next_prefixes.push(prefix.clone_with_suffix(&[false]));
+ next_prefixes.push(prefix.clone_with_suffix(&[true]));
+ }
+ }
+
+ agg_param.prefixes = next_prefixes;
+ }
+ }
+
+ let got: HashSet<IdpfInput> = agg_param
+ .prefixes
+ .into_iter()
+ .zip(agg_result.iter())
+ .filter(|(_prefix, count)| **count >= threshold as u64)
+ .map(|(prefix, _count)| prefix)
+ .collect();
+
+ let want: HashSet<IdpfInput> = expected_result
+ .into_iter()
+ .map(|bytes| IdpfInput::from_bytes(bytes.as_ref()))
+ .collect();
+
+ assert_eq!(got, want);
+ }
+
+ #[test]
+ fn shard_prepare() {
+ let mut rng = thread_rng();
+ let vdaf = Poplar1::new_shake128(64);
+ let verify_key = rng.gen();
+ let input = IdpfInput::from_bytes(b"12341324");
+ let nonce = rng.gen();
+ let (public_share, input_shares) = vdaf.shard(&input, &nonce).unwrap();
+
+ test_prepare(
+ &vdaf,
+ &verify_key,
+ &nonce,
+ &public_share,
+ &input_shares,
+ &Poplar1AggregationParam {
+ level: 7,
+ prefixes: vec![
+ IdpfInput::from_bytes(b"0"),
+ IdpfInput::from_bytes(b"1"),
+ IdpfInput::from_bytes(b"2"),
+ IdpfInput::from_bytes(b"f"),
+ ],
+ },
+ vec![0, 1, 0, 0],
+ );
+
+ for level in 0..vdaf.bits {
+ test_prepare(
+ &vdaf,
+ &verify_key,
+ &nonce,
+ &public_share,
+ &input_shares,
+ &Poplar1AggregationParam {
+ level: level as u16,
+ prefixes: vec![input.prefix(level)],
+ },
+ vec![1],
+ );
+ }
+ }
+
+ #[test]
+ fn heavy_hitters() {
+ let mut rng = thread_rng();
+ let verify_key = rng.gen();
+ let vdaf = Poplar1::new_shake128(8);
+
+ run_heavy_hitters(
+ &vdaf,
+ &verify_key,
+ 2, // threshold
+ [
+ "a", "b", "c", "d", "e", "f", "g", "g", "h", "i", "i", "i", "j", "j", "k", "l",
+ ], // measurements
+ ["g", "i", "j"], // heavy hitters
+ );
+ }
+
+ #[test]
+ fn encoded_len() {
+ // Input share
+ let input_share = Poplar1InputShare {
+ idpf_key: Seed::<16>::generate().unwrap(),
+ corr_seed: Seed::<16>::generate().unwrap(),
+ corr_inner: vec![
+ [Field64::one(), <Field64 as FieldElement>::zero()],
+ [Field64::one(), <Field64 as FieldElement>::zero()],
+ [Field64::one(), <Field64 as FieldElement>::zero()],
+ ],
+ corr_leaf: [Field255::one(), <Field255 as FieldElement>::zero()],
+ };
+ assert_eq!(
+ input_share.get_encoded().len(),
+ input_share.encoded_len().unwrap()
+ );
+
+ // Prepaare message variants
+ let prep_msg = Poplar1PrepareMessage(PrepareMessageVariant::SketchInner([
+ Field64::one(),
+ Field64::one(),
+ Field64::one(),
+ ]));
+ assert_eq!(
+ prep_msg.get_encoded().len(),
+ prep_msg.encoded_len().unwrap()
+ );
+ let prep_msg = Poplar1PrepareMessage(PrepareMessageVariant::SketchLeaf([
+ Field255::one(),
+ Field255::one(),
+ Field255::one(),
+ ]));
+ assert_eq!(
+ prep_msg.get_encoded().len(),
+ prep_msg.encoded_len().unwrap()
+ );
+ let prep_msg = Poplar1PrepareMessage(PrepareMessageVariant::Done);
+ assert_eq!(
+ prep_msg.get_encoded().len(),
+ prep_msg.encoded_len().unwrap()
+ );
+
+ // Field vector variants.
+ let field_vec = Poplar1FieldVec::Inner(vec![Field64::one(); 23]);
+ assert_eq!(
+ field_vec.get_encoded().len(),
+ field_vec.encoded_len().unwrap()
+ );
+ let field_vec = Poplar1FieldVec::Leaf(vec![Field255::one(); 23]);
+ assert_eq!(
+ field_vec.get_encoded().len(),
+ field_vec.encoded_len().unwrap()
+ );
+
+ // Aggregation parameter.
+ let agg_param = Poplar1AggregationParam::try_from_prefixes(Vec::from([
+ IdpfInput::from_bytes(b"ab"),
+ IdpfInput::from_bytes(b"cd"),
+ ]))
+ .unwrap();
+ assert_eq!(
+ agg_param.get_encoded().len(),
+ agg_param.encoded_len().unwrap()
+ );
+ let agg_param = Poplar1AggregationParam::try_from_prefixes(Vec::from([
+ IdpfInput::from_bools(&[false]),
+ IdpfInput::from_bools(&[true]),
+ ]))
+ .unwrap();
+ assert_eq!(
+ agg_param.get_encoded().len(),
+ agg_param.encoded_len().unwrap()
+ );
+ }
+
+ #[test]
+ fn round_trip_prepare_state() {
+ let vdaf = Poplar1::new_shake128(1);
+ for (agg_id, prep_state) in [
+ (
+ 0,
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field64::from(0),
+ B_share: Field64::from(1),
+ is_leader: true,
+ },
+ output_share: Vec::from([Field64::from(2), Field64::from(3), Field64::from(4)]),
+ })),
+ ),
+ (
+ 1,
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field64::from(5),
+ B_share: Field64::from(6),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field64::from(7), Field64::from(8), Field64::from(9)]),
+ })),
+ ),
+ (
+ 0,
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([
+ Field64::from(10),
+ Field64::from(11),
+ Field64::from(12),
+ ]),
+ })),
+ ),
+ (
+ 1,
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([
+ Field64::from(13),
+ Field64::from(14),
+ Field64::from(15),
+ ]),
+ })),
+ ),
+ (
+ 0,
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field255::from(16),
+ B_share: Field255::from(17),
+ is_leader: true,
+ },
+ output_share: Vec::from([
+ Field255::from(18),
+ Field255::from(19),
+ Field255::from(20),
+ ]),
+ })),
+ ),
+ (
+ 1,
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field255::from(21),
+ B_share: Field255::from(22),
+ is_leader: false,
+ },
+ output_share: Vec::from([
+ Field255::from(23),
+ Field255::from(24),
+ Field255::from(25),
+ ]),
+ })),
+ ),
+ (
+ 0,
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([
+ Field255::from(26),
+ Field255::from(27),
+ Field255::from(28),
+ ]),
+ })),
+ ),
+ (
+ 1,
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([
+ Field255::from(29),
+ Field255::from(30),
+ Field255::from(31),
+ ]),
+ })),
+ ),
+ ] {
+ let encoded_prep_state = prep_state.get_encoded();
+ assert_eq!(prep_state.encoded_len(), Some(encoded_prep_state.len()));
+ let decoded_prep_state =
+ Poplar1PrepareState::get_decoded_with_param(&(&vdaf, agg_id), &encoded_prep_state)
+ .unwrap();
+ assert_eq!(prep_state, decoded_prep_state);
+ }
+ }
+
+ #[test]
+ fn round_trip_agg_param() {
+ // These test cases were generated using the reference Sage implementation.
+ // (https://github.com/cfrg/draft-irtf-cfrg-vdaf/tree/main/poc) Sage statements used to
+ // generate each test case are given in comments.
+ for (prefixes, reference_encoding) in [
+ // poplar.encode_agg_param(0, [0])
+ (
+ Vec::from([IdpfInput::from_bools(&[false])]),
+ [0, 0, 0, 0, 0, 1, 0].as_slice(),
+ ),
+ // poplar.encode_agg_param(0, [1])
+ (
+ Vec::from([IdpfInput::from_bools(&[true])]),
+ [0, 0, 0, 0, 0, 1, 1].as_slice(),
+ ),
+ // poplar.encode_agg_param(0, [0, 1])
+ (
+ Vec::from([
+ IdpfInput::from_bools(&[false]),
+ IdpfInput::from_bools(&[true]),
+ ]),
+ [0, 0, 0, 0, 0, 2, 2].as_slice(),
+ ),
+ // poplar.encode_agg_param(1, [0b00, 0b01, 0b10, 0b11])
+ (
+ Vec::from([
+ IdpfInput::from_bools(&[false, false]),
+ IdpfInput::from_bools(&[false, true]),
+ IdpfInput::from_bools(&[true, false]),
+ IdpfInput::from_bools(&[true, true]),
+ ]),
+ [0, 1, 0, 0, 0, 4, 0xe4].as_slice(),
+ ),
+ // poplar.encode_agg_param(1, [0b00, 0b10, 0b11])
+ (
+ Vec::from([
+ IdpfInput::from_bools(&[false, false]),
+ IdpfInput::from_bools(&[true, false]),
+ IdpfInput::from_bools(&[true, true]),
+ ]),
+ [0, 1, 0, 0, 0, 3, 0x38].as_slice(),
+ ),
+ // poplar.encode_agg_param(2, [0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111])
+ (
+ Vec::from([
+ IdpfInput::from_bools(&[false, false, false]),
+ IdpfInput::from_bools(&[false, false, true]),
+ IdpfInput::from_bools(&[false, true, false]),
+ IdpfInput::from_bools(&[false, true, true]),
+ IdpfInput::from_bools(&[true, false, false]),
+ IdpfInput::from_bools(&[true, false, true]),
+ IdpfInput::from_bools(&[true, true, false]),
+ IdpfInput::from_bools(&[true, true, true]),
+ ]),
+ [0, 2, 0, 0, 0, 8, 0xfa, 0xc6, 0x88].as_slice(),
+ ),
+ // poplar.encode_agg_param(9, [0b01_1011_0010, 0b10_1101_1010])
+ (
+ Vec::from([
+ IdpfInput::from_bools(&[
+ false, true, true, false, true, true, false, false, true, false,
+ ]),
+ IdpfInput::from_bools(&[
+ true, false, true, true, false, true, true, false, true, false,
+ ]),
+ ]),
+ [0, 9, 0, 0, 0, 2, 0x0b, 0x69, 0xb2].as_slice(),
+ ),
+ // poplar.encode_agg_param(15, [0xcafe])
+ (
+ Vec::from([IdpfInput::from_bytes(b"\xca\xfe")]),
+ [0, 15, 0, 0, 0, 1, 0xca, 0xfe].as_slice(),
+ ),
+ ] {
+ let agg_param = Poplar1AggregationParam::try_from_prefixes(prefixes).unwrap();
+ let encoded = agg_param.get_encoded();
+ assert_eq!(encoded, reference_encoding);
+ let decoded = Poplar1AggregationParam::get_decoded(reference_encoding).unwrap();
+ assert_eq!(decoded, agg_param);
+ }
+ }
+
+ #[test]
+ fn agg_param_wrong_unused_bit() {
+ let err = Poplar1AggregationParam::get_decoded(&[0, 0, 0, 0, 0, 1, 2]).unwrap_err();
+ assert_matches!(err, CodecError::UnexpectedValue);
+ }
+
+ #[test]
+ fn agg_param_ordering() {
+ let err = Poplar1AggregationParam::get_decoded(&[0, 0, 0, 0, 0, 2, 1]).unwrap_err();
+ assert_matches!(err, CodecError::Other(_));
+ let err = Poplar1AggregationParam::get_decoded(&[0, 0, 0, 0, 0, 2, 0]).unwrap_err();
+ assert_matches!(err, CodecError::Other(_));
+ let err = Poplar1AggregationParam::get_decoded(&[0, 0, 0, 0, 0, 2, 3]).unwrap_err();
+ assert_matches!(err, CodecError::Other(_));
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct HexEncoded(#[serde(with = "hex")] Vec<u8>);
+
+ impl AsRef<[u8]> for HexEncoded {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PoplarTestVector {
+ agg_param: (usize, Vec<u64>),
+ agg_result: Vec<u64>,
+ agg_shares: Vec<HexEncoded>,
+ bits: usize,
+ prep: Vec<PreparationTestVector>,
+ verify_key: HexEncoded,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PreparationTestVector {
+ input_shares: Vec<HexEncoded>,
+ measurement: u64,
+ nonce: HexEncoded,
+ out_shares: Vec<Vec<HexEncoded>>,
+ prep_messages: Vec<HexEncoded>,
+ prep_shares: Vec<Vec<HexEncoded>>,
+ public_share: HexEncoded,
+ rand: HexEncoded,
+ }
+
+ fn check_test_vec(input: &str) {
+ let test_vector: PoplarTestVector = serde_json::from_str(input).unwrap();
+ assert_eq!(test_vector.prep.len(), 1);
+ let prep = &test_vector.prep[0];
+ let measurement_bits = (0..test_vector.bits)
+ .rev()
+ .map(|i| (prep.measurement >> i) & 1 != 0)
+ .collect::<BitVec>();
+ let measurement = IdpfInput::from(measurement_bits);
+ let (agg_param_level, agg_param_prefixes_int) = test_vector.agg_param;
+ let agg_param_prefixes = agg_param_prefixes_int
+ .iter()
+ .map(|int| {
+ let bits = (0..=agg_param_level)
+ .rev()
+ .map(|i| (*int >> i) & 1 != 0)
+ .collect::<BitVec>();
+ bits.into()
+ })
+ .collect::<Vec<IdpfInput>>();
+ let agg_param = Poplar1AggregationParam::try_from_prefixes(agg_param_prefixes).unwrap();
+ let verify_key = test_vector.verify_key.as_ref().try_into().unwrap();
+ let nonce = prep.nonce.as_ref().try_into().unwrap();
+
+ let mut idpf_random = [[0u8; 16]; 2];
+ let mut poplar_random = [[0u8; 16]; 3];
+ for (input, output) in prep
+ .rand
+ .as_ref()
+ .chunks_exact(16)
+ .zip(idpf_random.iter_mut().chain(poplar_random.iter_mut()))
+ {
+ output.copy_from_slice(input);
+ }
+
+ // Shard measurement.
+ let poplar = Poplar1::new_shake128(test_vector.bits);
+ let (public_share, input_shares) = poplar
+ .shard_with_random(&measurement, &nonce, &idpf_random, &poplar_random)
+ .unwrap();
+
+ // Run aggregation.
+ let (init_prep_state_0, init_prep_share_0) = poplar
+ .prepare_init(
+ &verify_key,
+ 0,
+ &agg_param,
+ &nonce,
+ &public_share,
+ &input_shares[0],
+ )
+ .unwrap();
+ let (init_prep_state_1, init_prep_share_1) = poplar
+ .prepare_init(
+ &verify_key,
+ 1,
+ &agg_param,
+ &nonce,
+ &public_share,
+ &input_shares[1],
+ )
+ .unwrap();
+
+ let r1_prep_msg = poplar
+ .prepare_shares_to_prepare_message(
+ &agg_param,
+ [init_prep_share_0.clone(), init_prep_share_1.clone()],
+ )
+ .unwrap();
+
+ let (r1_prep_state_0, r1_prep_share_0) = assert_matches!(
+ poplar
+ .prepare_next(init_prep_state_0.clone(), r1_prep_msg.clone())
+ .unwrap(),
+ PrepareTransition::Continue(state, share) => (state, share)
+ );
+ let (r1_prep_state_1, r1_prep_share_1) = assert_matches!(
+ poplar
+ .prepare_next(init_prep_state_1.clone(), r1_prep_msg.clone())
+ .unwrap(),
+ PrepareTransition::Continue(state, share) => (state, share)
+ );
+
+ let r2_prep_msg = poplar
+ .prepare_shares_to_prepare_message(
+ &agg_param,
+ [r1_prep_share_0.clone(), r1_prep_share_1.clone()],
+ )
+ .unwrap();
+
+ let out_share_0 = assert_matches!(
+ poplar
+ .prepare_next(r1_prep_state_0.clone(), r2_prep_msg.clone())
+ .unwrap(),
+ PrepareTransition::Finish(out) => out
+ );
+ let out_share_1 = assert_matches!(
+ poplar
+ .prepare_next(r1_prep_state_1, r2_prep_msg.clone())
+ .unwrap(),
+ PrepareTransition::Finish(out) => out
+ );
+
+ let agg_share_0 = poplar.aggregate(&agg_param, [out_share_0.clone()]).unwrap();
+ let agg_share_1 = poplar.aggregate(&agg_param, [out_share_1.clone()]).unwrap();
+
+ // Collect result.
+ let agg_result = poplar
+ .unshard(&agg_param, [agg_share_0.clone(), agg_share_1.clone()], 1)
+ .unwrap();
+
+ // Check all intermediate results against the test vector, and exercise both encoding and decoding.
+ assert_eq!(
+ public_share,
+ Poplar1PublicShare::get_decoded_with_param(&poplar, prep.public_share.as_ref())
+ .unwrap()
+ );
+ assert_eq!(&public_share.get_encoded(), prep.public_share.as_ref());
+ assert_eq!(
+ input_shares[0],
+ Poplar1InputShare::get_decoded_with_param(&(&poplar, 0), prep.input_shares[0].as_ref())
+ .unwrap()
+ );
+ assert_eq!(
+ &input_shares[0].get_encoded(),
+ prep.input_shares[0].as_ref()
+ );
+ assert_eq!(
+ input_shares[1],
+ Poplar1InputShare::get_decoded_with_param(&(&poplar, 1), prep.input_shares[1].as_ref())
+ .unwrap()
+ );
+ assert_eq!(
+ &input_shares[1].get_encoded(),
+ prep.input_shares[1].as_ref()
+ );
+ assert_eq!(
+ init_prep_share_0,
+ Poplar1FieldVec::get_decoded_with_param(
+ &init_prep_state_0,
+ prep.prep_shares[0][0].as_ref()
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ &init_prep_share_0.get_encoded(),
+ prep.prep_shares[0][0].as_ref()
+ );
+ assert_eq!(
+ init_prep_share_1,
+ Poplar1FieldVec::get_decoded_with_param(
+ &init_prep_state_1,
+ prep.prep_shares[0][1].as_ref()
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ &init_prep_share_1.get_encoded(),
+ prep.prep_shares[0][1].as_ref()
+ );
+ assert_eq!(
+ r1_prep_msg,
+ Poplar1PrepareMessage::get_decoded_with_param(
+ &init_prep_state_0,
+ prep.prep_messages[0].as_ref()
+ )
+ .unwrap()
+ );
+ assert_eq!(&r1_prep_msg.get_encoded(), prep.prep_messages[0].as_ref());
+
+ assert_eq!(
+ r1_prep_share_0,
+ Poplar1FieldVec::get_decoded_with_param(
+ &r1_prep_state_0,
+ prep.prep_shares[1][0].as_ref()
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ &r1_prep_share_0.get_encoded(),
+ prep.prep_shares[1][0].as_ref()
+ );
+ assert_eq!(
+ r1_prep_share_1,
+ Poplar1FieldVec::get_decoded_with_param(
+ &r1_prep_state_0,
+ prep.prep_shares[1][1].as_ref()
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ &r1_prep_share_1.get_encoded(),
+ prep.prep_shares[1][1].as_ref()
+ );
+ assert_eq!(
+ r2_prep_msg,
+ Poplar1PrepareMessage::get_decoded_with_param(
+ &r1_prep_state_0,
+ prep.prep_messages[1].as_ref()
+ )
+ .unwrap()
+ );
+ assert_eq!(&r2_prep_msg.get_encoded(), prep.prep_messages[1].as_ref());
+ for (out_share, expected_out_share) in [
+ (out_share_0, &prep.out_shares[0]),
+ (out_share_1, &prep.out_shares[1]),
+ ] {
+ match out_share {
+ Poplar1FieldVec::Inner(vec) => {
+ assert_eq!(vec.len(), expected_out_share.len());
+ for (element, expected) in vec.iter().zip(expected_out_share.iter()) {
+ assert_eq!(&element.get_encoded(), expected.as_ref());
+ }
+ }
+ Poplar1FieldVec::Leaf(vec) => {
+ assert_eq!(vec.len(), expected_out_share.len());
+ for (element, expected) in vec.iter().zip(expected_out_share.iter()) {
+ assert_eq!(&element.get_encoded(), expected.as_ref());
+ }
+ }
+ };
+ }
+ assert_eq!(
+ agg_share_0,
+ Poplar1FieldVec::get_decoded_with_param(
+ &(&poplar, &agg_param),
+ test_vector.agg_shares[0].as_ref()
+ )
+ .unwrap()
+ );
+
+ assert_eq!(
+ &agg_share_0.get_encoded(),
+ test_vector.agg_shares[0].as_ref()
+ );
+ assert_eq!(
+ agg_share_1,
+ Poplar1FieldVec::get_decoded_with_param(
+ &(&poplar, &agg_param),
+ test_vector.agg_shares[1].as_ref()
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ &agg_share_1.get_encoded(),
+ test_vector.agg_shares[1].as_ref()
+ );
+ assert_eq!(agg_result, test_vector.agg_result);
+ }
+
+ #[test]
+ fn test_vec_poplar1_0() {
+ check_test_vec(include_str!("test_vec/07/Poplar1_0.json"));
+ }
+
+ #[test]
+ fn test_vec_poplar1_1() {
+ check_test_vec(include_str!("test_vec/07/Poplar1_1.json"));
+ }
+
+ #[test]
+ fn test_vec_poplar1_2() {
+ check_test_vec(include_str!("test_vec/07/Poplar1_2.json"));
+ }
+
+ #[test]
+ fn test_vec_poplar1_3() {
+ check_test_vec(include_str!("test_vec/07/Poplar1_3.json"));
+ }
+
+ #[test]
+ fn input_share_equality_test() {
+ equality_comparison_test(&[
+ // Default.
+ Poplar1InputShare {
+ idpf_key: Seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
+ corr_seed: Seed([16, 17, 18]),
+ corr_inner: Vec::from([
+ [Field64::from(19), Field64::from(20)],
+ [Field64::from(21), Field64::from(22)],
+ [Field64::from(23), Field64::from(24)],
+ ]),
+ corr_leaf: [Field255::from(25), Field255::from(26)],
+ },
+ // Modified idpf_key.
+ Poplar1InputShare {
+ idpf_key: Seed([15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]),
+ corr_seed: Seed([16, 17, 18]),
+ corr_inner: Vec::from([
+ [Field64::from(19), Field64::from(20)],
+ [Field64::from(21), Field64::from(22)],
+ [Field64::from(23), Field64::from(24)],
+ ]),
+ corr_leaf: [Field255::from(25), Field255::from(26)],
+ },
+ // Modified corr_seed.
+ Poplar1InputShare {
+ idpf_key: Seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
+ corr_seed: Seed([18, 17, 16]),
+ corr_inner: Vec::from([
+ [Field64::from(19), Field64::from(20)],
+ [Field64::from(21), Field64::from(22)],
+ [Field64::from(23), Field64::from(24)],
+ ]),
+ corr_leaf: [Field255::from(25), Field255::from(26)],
+ },
+ // Modified corr_inner.
+ Poplar1InputShare {
+ idpf_key: Seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
+ corr_seed: Seed([16, 17, 18]),
+ corr_inner: Vec::from([
+ [Field64::from(24), Field64::from(23)],
+ [Field64::from(22), Field64::from(21)],
+ [Field64::from(20), Field64::from(19)],
+ ]),
+ corr_leaf: [Field255::from(25), Field255::from(26)],
+ },
+ // Modified corr_leaf.
+ Poplar1InputShare {
+ idpf_key: Seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
+ corr_seed: Seed([16, 17, 18]),
+ corr_inner: Vec::from([
+ [Field64::from(19), Field64::from(20)],
+ [Field64::from(21), Field64::from(22)],
+ [Field64::from(23), Field64::from(24)],
+ ]),
+ corr_leaf: [Field255::from(26), Field255::from(25)],
+ },
+ ])
+ }
+
+ #[test]
+ fn prepare_state_equality_test() {
+ // This test effectively covers PrepareStateVariant, PrepareState, SketchState as well.
+ equality_comparison_test(&[
+ // Inner, round one. (default)
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field64::from(0),
+ B_share: Field64::from(1),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field64::from(2), Field64::from(3)]),
+ })),
+ // Inner, round one, modified A_share.
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field64::from(100),
+ B_share: Field64::from(1),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field64::from(2), Field64::from(3)]),
+ })),
+ // Inner, round one, modified B_share.
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field64::from(0),
+ B_share: Field64::from(101),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field64::from(2), Field64::from(3)]),
+ })),
+ // Inner, round one, modified is_leader.
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field64::from(0),
+ B_share: Field64::from(1),
+ is_leader: true,
+ },
+ output_share: Vec::from([Field64::from(2), Field64::from(3)]),
+ })),
+ // Inner, round one, modified output_share.
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field64::from(0),
+ B_share: Field64::from(1),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field64::from(3), Field64::from(2)]),
+ })),
+ // Inner, round two. (default)
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([Field64::from(2), Field64::from(3)]),
+ })),
+ // Inner, round two, modified output_share.
+ Poplar1PrepareState(PrepareStateVariant::Inner(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([Field64::from(3), Field64::from(2)]),
+ })),
+ // Leaf, round one. (default)
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field255::from(0),
+ B_share: Field255::from(1),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field255::from(2), Field255::from(3)]),
+ })),
+ // Leaf, round one, modified A_share.
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field255::from(100),
+ B_share: Field255::from(1),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field255::from(2), Field255::from(3)]),
+ })),
+ // Leaf, round one, modified B_share.
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field255::from(0),
+ B_share: Field255::from(101),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field255::from(2), Field255::from(3)]),
+ })),
+ // Leaf, round one, modified is_leader.
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field255::from(0),
+ B_share: Field255::from(1),
+ is_leader: true,
+ },
+ output_share: Vec::from([Field255::from(2), Field255::from(3)]),
+ })),
+ // Leaf, round one, modified output_share.
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundOne {
+ A_share: Field255::from(0),
+ B_share: Field255::from(1),
+ is_leader: false,
+ },
+ output_share: Vec::from([Field255::from(3), Field255::from(2)]),
+ })),
+ // Leaf, round two. (default)
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([Field255::from(2), Field255::from(3)]),
+ })),
+ // Leaf, round two, modified output_share.
+ Poplar1PrepareState(PrepareStateVariant::Leaf(PrepareState {
+ sketch: SketchState::RoundTwo,
+ output_share: Vec::from([Field255::from(3), Field255::from(2)]),
+ })),
+ ])
+ }
+
+ #[test]
+ fn field_vec_equality_test() {
+ equality_comparison_test(&[
+ // Inner. (default)
+ Poplar1FieldVec::Inner(Vec::from([Field64::from(0), Field64::from(1)])),
+ // Inner, modified value.
+ Poplar1FieldVec::Inner(Vec::from([Field64::from(1), Field64::from(0)])),
+ // Leaf. (deafult)
+ Poplar1FieldVec::Leaf(Vec::from([Field255::from(0), Field255::from(1)])),
+ // Leaf, modified value.
+ Poplar1FieldVec::Leaf(Vec::from([Field255::from(1), Field255::from(0)])),
+ ])
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/prio2.rs b/third_party/rust/prio/src/vdaf/prio2.rs
new file mode 100644
index 0000000000..4669c47d00
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/prio2.rs
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//! Backwards-compatible port of the ENPA Prio system to a VDAF.
+
+use crate::{
+ codec::{CodecError, Decode, Encode, ParameterizedDecode},
+ field::{
+ decode_fieldvec, FftFriendlyFieldElement, FieldElement, FieldElementWithInteger, FieldPrio2,
+ },
+ prng::Prng,
+ vdaf::{
+ prio2::{
+ client::{self as v2_client, proof_length},
+ server as v2_server,
+ },
+ xof::Seed,
+ Aggregatable, AggregateShare, Aggregator, Client, Collector, OutputShare,
+ PrepareTransition, Share, ShareDecodingParameter, Vdaf, VdafError,
+ },
+};
+use hmac::{Hmac, Mac};
+use rand_core::RngCore;
+use sha2::Sha256;
+use std::{convert::TryFrom, io::Cursor};
+use subtle::{Choice, ConstantTimeEq};
+
+mod client;
+mod server;
+#[cfg(test)]
+mod test_vector;
+
+/// The Prio2 VDAF. It supports the same measurement type as
+/// [`Prio3SumVec`](crate::vdaf::prio3::Prio3SumVec) with `bits == 1` but uses the proof system and
+/// finite field deployed in ENPA.
+#[derive(Clone, Debug)]
+pub struct Prio2 {
+ input_len: usize,
+}
+
+impl Prio2 {
+ /// Returns an instance of the VDAF for the given input length.
+ pub fn new(input_len: usize) -> Result<Self, VdafError> {
+ let n = (input_len + 1).next_power_of_two();
+ if let Ok(size) = u32::try_from(2 * n) {
+ if size > FieldPrio2::generator_order() {
+ return Err(VdafError::Uncategorized(
+ "input size exceeds field capacity".into(),
+ ));
+ }
+ } else {
+ return Err(VdafError::Uncategorized(
+ "input size exceeds memory capacity".into(),
+ ));
+ }
+
+ Ok(Prio2 { input_len })
+ }
+
+ /// Prepare an input share for aggregation using the given field element `query_rand` to
+ /// compute the verifier share.
+ ///
+ /// In the [`Aggregator`] trait implementation for [`Prio2`], the query randomness is computed
+ /// jointly by the Aggregators. This method is designed to be used in applications, like ENPA,
+ /// in which the query randomness is instead chosen by a third-party.
+ pub fn prepare_init_with_query_rand(
+ &self,
+ query_rand: FieldPrio2,
+ input_share: &Share<FieldPrio2, 32>,
+ is_leader: bool,
+ ) -> Result<(Prio2PrepareState, Prio2PrepareShare), VdafError> {
+ let expanded_data: Option<Vec<FieldPrio2>> = match input_share {
+ Share::Leader(_) => None,
+ Share::Helper(ref seed) => {
+ let prng = Prng::from_prio2_seed(seed.as_ref());
+ Some(prng.take(proof_length(self.input_len)).collect())
+ }
+ };
+ let data = match input_share {
+ Share::Leader(ref data) => data,
+ Share::Helper(_) => expanded_data.as_ref().unwrap(),
+ };
+
+ let verifier_share = v2_server::generate_verification_message(
+ self.input_len,
+ query_rand,
+ data, // Combined input and proof shares
+ is_leader,
+ )
+ .map_err(|e| VdafError::Uncategorized(e.to_string()))?;
+
+ Ok((
+ Prio2PrepareState(input_share.truncated(self.input_len)),
+ Prio2PrepareShare(verifier_share),
+ ))
+ }
+
+ /// Choose a random point for polynomial evaluation.
+ ///
+ /// The point returned is not one of the roots used for polynomial interpolation.
+ pub(crate) fn choose_eval_at<S>(&self, prng: &mut Prng<FieldPrio2, S>) -> FieldPrio2
+ where
+ S: RngCore,
+ {
+ // Make sure the query randomness isn't a root of unity. Evaluating the proof at any of
+ // these points would be a privacy violation, since these points were used by the prover to
+ // construct the wire polynomials.
+ let n = (self.input_len + 1).next_power_of_two();
+ let proof_length = 2 * n;
+ loop {
+ let eval_at: FieldPrio2 = prng.get();
+ // Unwrap safety: the constructor checks that this conversion succeeds.
+ if eval_at.pow(u32::try_from(proof_length).unwrap()) != FieldPrio2::one() {
+ return eval_at;
+ }
+ }
+ }
+}
+
+impl Vdaf for Prio2 {
+ const ID: u32 = 0xFFFF0000;
+ type Measurement = Vec<u32>;
+ type AggregateResult = Vec<u32>;
+ type AggregationParam = ();
+ type PublicShare = ();
+ type InputShare = Share<FieldPrio2, 32>;
+ type OutputShare = OutputShare<FieldPrio2>;
+ type AggregateShare = AggregateShare<FieldPrio2>;
+
+ fn num_aggregators(&self) -> usize {
+ // Prio2 can easily be extended to support more than two Aggregators.
+ 2
+ }
+}
+
+impl Client<16> for Prio2 {
+ fn shard(
+ &self,
+ measurement: &Vec<u32>,
+ _nonce: &[u8; 16],
+ ) -> Result<(Self::PublicShare, Vec<Share<FieldPrio2, 32>>), VdafError> {
+ if measurement.len() != self.input_len {
+ return Err(VdafError::Uncategorized("incorrect input length".into()));
+ }
+ let mut input: Vec<FieldPrio2> = Vec::with_capacity(measurement.len());
+ for int in measurement {
+ input.push((*int).into());
+ }
+
+ let mut mem = v2_client::ClientMemory::new(self.input_len)?;
+ let copy_data = |share_data: &mut [FieldPrio2]| {
+ share_data[..].clone_from_slice(&input);
+ };
+ let mut leader_data = mem.prove_with(self.input_len, copy_data);
+
+ let helper_seed = Seed::generate()?;
+ let helper_prng = Prng::from_prio2_seed(helper_seed.as_ref());
+ for (s1, d) in leader_data.iter_mut().zip(helper_prng.into_iter()) {
+ *s1 -= d;
+ }
+
+ Ok((
+ (),
+ vec![Share::Leader(leader_data), Share::Helper(helper_seed)],
+ ))
+ }
+}
+
+/// State of each [`Aggregator`] during the Preparation phase.
+#[derive(Clone, Debug)]
+pub struct Prio2PrepareState(Share<FieldPrio2, 32>);
+
+impl PartialEq for Prio2PrepareState {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for Prio2PrepareState {}
+
+impl ConstantTimeEq for Prio2PrepareState {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.0.ct_eq(&other.0)
+ }
+}
+
+impl Encode for Prio2PrepareState {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.0.encode(bytes);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ self.0.encoded_len()
+ }
+}
+
+impl<'a> ParameterizedDecode<(&'a Prio2, usize)> for Prio2PrepareState {
+ fn decode_with_param(
+ (prio2, agg_id): &(&'a Prio2, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let share_decoder = if *agg_id == 0 {
+ ShareDecodingParameter::Leader(prio2.input_len)
+ } else {
+ ShareDecodingParameter::Helper
+ };
+ let out_share = Share::decode_with_param(&share_decoder, bytes)?;
+ Ok(Self(out_share))
+ }
+}
+
+/// Message emitted by each [`Aggregator`] during the Preparation phase.
+#[derive(Clone, Debug)]
+pub struct Prio2PrepareShare(v2_server::VerificationMessage<FieldPrio2>);
+
+impl Encode for Prio2PrepareShare {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ self.0.f_r.encode(bytes);
+ self.0.g_r.encode(bytes);
+ self.0.h_r.encode(bytes);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ Some(FieldPrio2::ENCODED_SIZE * 3)
+ }
+}
+
+impl ParameterizedDecode<Prio2PrepareState> for Prio2PrepareShare {
+ fn decode_with_param(
+ _state: &Prio2PrepareState,
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ Ok(Self(v2_server::VerificationMessage {
+ f_r: FieldPrio2::decode(bytes)?,
+ g_r: FieldPrio2::decode(bytes)?,
+ h_r: FieldPrio2::decode(bytes)?,
+ }))
+ }
+}
+
+impl Aggregator<32, 16> for Prio2 {
+ type PrepareState = Prio2PrepareState;
+ type PrepareShare = Prio2PrepareShare;
+ type PrepareMessage = ();
+
+ fn prepare_init(
+ &self,
+ agg_key: &[u8; 32],
+ agg_id: usize,
+ _agg_param: &Self::AggregationParam,
+ nonce: &[u8; 16],
+ _public_share: &Self::PublicShare,
+ input_share: &Share<FieldPrio2, 32>,
+ ) -> Result<(Prio2PrepareState, Prio2PrepareShare), VdafError> {
+ let is_leader = role_try_from(agg_id)?;
+
+ // In the ENPA Prio system, the query randomness is generated by a third party and
+ // distributed to the Aggregators after they receive their input shares. In a VDAF, shared
+ // randomness is derived from a nonce selected by the client. For Prio2 we compute the
+ // query using HMAC-SHA256 evaluated over the nonce.
+ //
+ // Unwrap safety: new_from_slice() is infallible for Hmac.
+ let mut mac = Hmac::<Sha256>::new_from_slice(agg_key).unwrap();
+ mac.update(nonce);
+ let hmac_tag = mac.finalize();
+ let mut prng = Prng::from_prio2_seed(&hmac_tag.into_bytes().into());
+ let query_rand = self.choose_eval_at(&mut prng);
+
+ self.prepare_init_with_query_rand(query_rand, input_share, is_leader)
+ }
+
+ fn prepare_shares_to_prepare_message<M: IntoIterator<Item = Prio2PrepareShare>>(
+ &self,
+ _: &Self::AggregationParam,
+ inputs: M,
+ ) -> Result<(), VdafError> {
+ let verifier_shares: Vec<v2_server::VerificationMessage<FieldPrio2>> =
+ inputs.into_iter().map(|msg| msg.0).collect();
+ if verifier_shares.len() != 2 {
+ return Err(VdafError::Uncategorized(
+ "wrong number of verifier shares".into(),
+ ));
+ }
+
+ if !v2_server::is_valid_share(&verifier_shares[0], &verifier_shares[1]) {
+ return Err(VdafError::Uncategorized(
+ "proof verifier check failed".into(),
+ ));
+ }
+
+ Ok(())
+ }
+
+ fn prepare_next(
+ &self,
+ state: Prio2PrepareState,
+ _input: (),
+ ) -> Result<PrepareTransition<Self, 32, 16>, VdafError> {
+ let data = match state.0 {
+ Share::Leader(data) => data,
+ Share::Helper(seed) => {
+ let prng = Prng::from_prio2_seed(seed.as_ref());
+ prng.take(self.input_len).collect()
+ }
+ };
+ Ok(PrepareTransition::Finish(OutputShare::from(data)))
+ }
+
+ fn aggregate<M: IntoIterator<Item = OutputShare<FieldPrio2>>>(
+ &self,
+ _agg_param: &Self::AggregationParam,
+ out_shares: M,
+ ) -> Result<AggregateShare<FieldPrio2>, VdafError> {
+ let mut agg_share = AggregateShare(vec![FieldPrio2::zero(); self.input_len]);
+ for out_share in out_shares.into_iter() {
+ agg_share.accumulate(&out_share)?;
+ }
+
+ Ok(agg_share)
+ }
+}
+
+impl Collector for Prio2 {
+ fn unshard<M: IntoIterator<Item = AggregateShare<FieldPrio2>>>(
+ &self,
+ _agg_param: &Self::AggregationParam,
+ agg_shares: M,
+ _num_measurements: usize,
+ ) -> Result<Vec<u32>, VdafError> {
+ let mut agg = AggregateShare(vec![FieldPrio2::zero(); self.input_len]);
+ for agg_share in agg_shares.into_iter() {
+ agg.merge(&agg_share)?;
+ }
+
+ Ok(agg.0.into_iter().map(u32::from).collect())
+ }
+}
+
+impl<'a> ParameterizedDecode<(&'a Prio2, usize)> for Share<FieldPrio2, 32> {
+ fn decode_with_param(
+ (prio2, agg_id): &(&'a Prio2, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let is_leader = role_try_from(*agg_id).map_err(|e| CodecError::Other(Box::new(e)))?;
+ let decoder = if is_leader {
+ ShareDecodingParameter::Leader(proof_length(prio2.input_len))
+ } else {
+ ShareDecodingParameter::Helper
+ };
+
+ Share::decode_with_param(&decoder, bytes)
+ }
+}
+
+impl<'a, F> ParameterizedDecode<(&'a Prio2, &'a ())> for OutputShare<F>
+where
+ F: FieldElement,
+{
+ fn decode_with_param(
+ (prio2, _): &(&'a Prio2, &'a ()),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ decode_fieldvec(prio2.input_len, bytes).map(Self)
+ }
+}
+
+impl<'a, F> ParameterizedDecode<(&'a Prio2, &'a ())> for AggregateShare<F>
+where
+ F: FieldElement,
+{
+ fn decode_with_param(
+ (prio2, _): &(&'a Prio2, &'a ()),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ decode_fieldvec(prio2.input_len, bytes).map(Self)
+ }
+}
+
+fn role_try_from(agg_id: usize) -> Result<bool, VdafError> {
+ match agg_id {
+ 0 => Ok(true),
+ 1 => Ok(false),
+ _ => Err(VdafError::Uncategorized("unexpected aggregator id".into())),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::vdaf::{
+ equality_comparison_test, fieldvec_roundtrip_test, prio2::test_vector::Priov2TestVector,
+ run_vdaf,
+ };
+ use assert_matches::assert_matches;
+ use rand::prelude::*;
+
+ #[test]
+ fn run_prio2() {
+ let prio2 = Prio2::new(6).unwrap();
+
+ assert_eq!(
+ run_vdaf(
+ &prio2,
+ &(),
+ [
+ vec![0, 0, 0, 0, 1, 0],
+ vec![0, 1, 0, 0, 0, 0],
+ vec![0, 1, 1, 0, 0, 0],
+ vec![1, 1, 1, 0, 0, 0],
+ vec![0, 0, 0, 0, 1, 1],
+ ]
+ )
+ .unwrap(),
+ vec![1, 3, 2, 0, 2, 1],
+ );
+ }
+
+ #[test]
+ fn prepare_state_serialization() {
+ let mut rng = thread_rng();
+ let verify_key = rng.gen::<[u8; 32]>();
+ let nonce = rng.gen::<[u8; 16]>();
+ let data = vec![0, 0, 1, 1, 0];
+ let prio2 = Prio2::new(data.len()).unwrap();
+ let (public_share, input_shares) = prio2.shard(&data, &nonce).unwrap();
+ for (agg_id, input_share) in input_shares.iter().enumerate() {
+ let (prepare_state, prepare_share) = prio2
+ .prepare_init(
+ &verify_key,
+ agg_id,
+ &(),
+ &[0; 16],
+ &public_share,
+ input_share,
+ )
+ .unwrap();
+
+ let encoded_prepare_state = prepare_state.get_encoded();
+ let decoded_prepare_state = Prio2PrepareState::get_decoded_with_param(
+ &(&prio2, 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();
+ let decoded_prepare_share =
+ Prio2PrepareShare::get_decoded_with_param(&prepare_state, &encoded_prepare_share)
+ .expect("failed to decode prepare share");
+ assert_eq!(decoded_prepare_share.0.f_r, prepare_share.0.f_r);
+ assert_eq!(decoded_prepare_share.0.g_r, prepare_share.0.g_r);
+ assert_eq!(decoded_prepare_share.0.h_r, prepare_share.0.h_r);
+ assert_eq!(
+ prepare_share.encoded_len().unwrap(),
+ encoded_prepare_share.len()
+ );
+ }
+ }
+
+ #[test]
+ fn roundtrip_output_share() {
+ let vdaf = Prio2::new(31).unwrap();
+ fieldvec_roundtrip_test::<FieldPrio2, Prio2, OutputShare<FieldPrio2>>(&vdaf, &(), 31);
+ }
+
+ #[test]
+ fn roundtrip_aggregate_share() {
+ let vdaf = Prio2::new(31).unwrap();
+ fieldvec_roundtrip_test::<FieldPrio2, Prio2, AggregateShare<FieldPrio2>>(&vdaf, &(), 31);
+ }
+
+ #[test]
+ fn priov2_backward_compatibility() {
+ let test_vector: Priov2TestVector =
+ serde_json::from_str(include_str!("test_vec/prio2/fieldpriov2.json")).unwrap();
+ let vdaf = Prio2::new(test_vector.dimension).unwrap();
+ let mut leader_output_shares = Vec::new();
+ let mut helper_output_shares = Vec::new();
+ for (server_1_share, server_2_share) in test_vector
+ .server_1_decrypted_shares
+ .iter()
+ .zip(&test_vector.server_2_decrypted_shares)
+ {
+ let input_share_1 = Share::get_decoded_with_param(&(&vdaf, 0), server_1_share).unwrap();
+ let input_share_2 = Share::get_decoded_with_param(&(&vdaf, 1), server_2_share).unwrap();
+ let (prepare_state_1, prepare_share_1) = vdaf
+ .prepare_init(&[0; 32], 0, &(), &[0; 16], &(), &input_share_1)
+ .unwrap();
+ let (prepare_state_2, prepare_share_2) = vdaf
+ .prepare_init(&[0; 32], 1, &(), &[0; 16], &(), &input_share_2)
+ .unwrap();
+ vdaf.prepare_shares_to_prepare_message(&(), [prepare_share_1, prepare_share_2])
+ .unwrap();
+ let transition_1 = vdaf.prepare_next(prepare_state_1, ()).unwrap();
+ let output_share_1 =
+ assert_matches!(transition_1, PrepareTransition::Finish(out) => out);
+ let transition_2 = vdaf.prepare_next(prepare_state_2, ()).unwrap();
+ let output_share_2 =
+ assert_matches!(transition_2, PrepareTransition::Finish(out) => out);
+ leader_output_shares.push(output_share_1);
+ helper_output_shares.push(output_share_2);
+ }
+
+ let leader_aggregate_share = vdaf.aggregate(&(), leader_output_shares).unwrap();
+ let helper_aggregate_share = vdaf.aggregate(&(), helper_output_shares).unwrap();
+ let aggregate_result = vdaf
+ .unshard(
+ &(),
+ [leader_aggregate_share, helper_aggregate_share],
+ test_vector.server_1_decrypted_shares.len(),
+ )
+ .unwrap();
+ let reconstructed = aggregate_result
+ .into_iter()
+ .map(FieldPrio2::from)
+ .collect::<Vec<_>>();
+
+ assert_eq!(reconstructed, test_vector.reference_sum);
+ }
+
+ #[test]
+ fn prepare_state_equality_test() {
+ equality_comparison_test(&[
+ Prio2PrepareState(Share::Leader(Vec::from([
+ FieldPrio2::from(0),
+ FieldPrio2::from(1),
+ ]))),
+ Prio2PrepareState(Share::Leader(Vec::from([
+ FieldPrio2::from(1),
+ FieldPrio2::from(0),
+ ]))),
+ Prio2PrepareState(Share::Helper(Seed(
+ (0..32).collect::<Vec<_>>().try_into().unwrap(),
+ ))),
+ Prio2PrepareState(Share::Helper(Seed(
+ (1..33).collect::<Vec<_>>().try_into().unwrap(),
+ ))),
+ ])
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/prio2/client.rs b/third_party/rust/prio/src/vdaf/prio2/client.rs
new file mode 100644
index 0000000000..dbce39ee3f
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/prio2/client.rs
@@ -0,0 +1,306 @@
+// Copyright (c) 2020 Apple Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+//! Primitives for the Prio2 client.
+
+use crate::{
+ field::{FftFriendlyFieldElement, FieldError},
+ polynomial::{poly_fft, PolyAuxMemory},
+ prng::{Prng, PrngError},
+ vdaf::{xof::SeedStreamAes128, VdafError},
+};
+
+use std::convert::TryFrom;
+
+/// Errors that might be emitted by the client.
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum ClientError {
+ /// PRNG error
+ #[error("prng error: {0}")]
+ Prng(#[from] PrngError),
+ /// VDAF error
+ #[error("vdaf error: {0}")]
+ Vdaf(#[from] VdafError),
+ /// failure when calling getrandom().
+ #[error("getrandom: {0}")]
+ GetRandom(#[from] getrandom::Error),
+}
+
+/// Serialization errors
+#[derive(Debug, thiserror::Error)]
+pub enum SerializeError {
+ /// Emitted by `unpack_proof[_mut]` if the serialized share+proof has the wrong length
+ #[error("serialized input has wrong length")]
+ UnpackInputSizeMismatch,
+ /// Finite field operation error.
+ #[error("finite field operation error")]
+ Field(#[from] FieldError),
+}
+
+#[derive(Debug)]
+pub(crate) struct ClientMemory<F> {
+ prng: Prng<F, SeedStreamAes128>,
+ points_f: Vec<F>,
+ points_g: Vec<F>,
+ evals_f: Vec<F>,
+ evals_g: Vec<F>,
+ poly_mem: PolyAuxMemory<F>,
+}
+
+impl<F: FftFriendlyFieldElement> ClientMemory<F> {
+ pub(crate) fn new(dimension: usize) -> Result<Self, VdafError> {
+ let n = (dimension + 1).next_power_of_two();
+ if let Ok(size) = F::Integer::try_from(2 * n) {
+ if size > F::generator_order() {
+ return Err(VdafError::Uncategorized(
+ "input size exceeds field capacity".into(),
+ ));
+ }
+ } else {
+ return Err(VdafError::Uncategorized(
+ "input size exceeds field capacity".into(),
+ ));
+ }
+
+ Ok(Self {
+ prng: Prng::new()?,
+ points_f: vec![F::zero(); n],
+ points_g: vec![F::zero(); n],
+ evals_f: vec![F::zero(); 2 * n],
+ evals_g: vec![F::zero(); 2 * n],
+ poly_mem: PolyAuxMemory::new(n),
+ })
+ }
+}
+
+impl<F: FftFriendlyFieldElement> ClientMemory<F> {
+ pub(crate) fn prove_with<G>(&mut self, dimension: usize, init_function: G) -> Vec<F>
+ where
+ G: FnOnce(&mut [F]),
+ {
+ let mut proof = vec![F::zero(); proof_length(dimension)];
+ // unpack one long vector to different subparts
+ let unpacked = unpack_proof_mut(&mut proof, dimension).unwrap();
+ // initialize the data part
+ init_function(unpacked.data);
+ // fill in the rest
+ construct_proof(
+ unpacked.data,
+ dimension,
+ unpacked.f0,
+ unpacked.g0,
+ unpacked.h0,
+ unpacked.points_h_packed,
+ self,
+ );
+
+ proof
+ }
+}
+
+/// Returns the number of field elements in the proof for given dimension of
+/// data elements
+///
+/// Proof is a vector, where the first `dimension` elements are the data
+/// elements, the next 3 elements are the zero terms for polynomials f, g and h
+/// and the remaining elements are non-zero points of h(x).
+pub(crate) fn proof_length(dimension: usize) -> usize {
+ // number of data items + number of zero terms + N
+ dimension + 3 + (dimension + 1).next_power_of_two()
+}
+
+/// Unpacked proof with subcomponents
+#[derive(Debug)]
+pub(crate) struct UnpackedProof<'a, F: FftFriendlyFieldElement> {
+ /// Data
+ pub data: &'a [F],
+ /// Zeroth coefficient of polynomial f
+ pub f0: &'a F,
+ /// Zeroth coefficient of polynomial g
+ pub g0: &'a F,
+ /// Zeroth coefficient of polynomial h
+ pub h0: &'a F,
+ /// Non-zero points of polynomial h
+ pub points_h_packed: &'a [F],
+}
+
+/// Unpacked proof with mutable subcomponents
+#[derive(Debug)]
+pub(crate) struct UnpackedProofMut<'a, F: FftFriendlyFieldElement> {
+ /// Data
+ pub data: &'a mut [F],
+ /// Zeroth coefficient of polynomial f
+ pub f0: &'a mut F,
+ /// Zeroth coefficient of polynomial g
+ pub g0: &'a mut F,
+ /// Zeroth coefficient of polynomial h
+ pub h0: &'a mut F,
+ /// Non-zero points of polynomial h
+ pub points_h_packed: &'a mut [F],
+}
+
+/// Unpacks the proof vector into subcomponents
+pub(crate) fn unpack_proof<F: FftFriendlyFieldElement>(
+ proof: &[F],
+ dimension: usize,
+) -> Result<UnpackedProof<F>, SerializeError> {
+ // check the proof length
+ if proof.len() != proof_length(dimension) {
+ return Err(SerializeError::UnpackInputSizeMismatch);
+ }
+ // split share into components
+ let (data, rest) = proof.split_at(dimension);
+ if let ([f0, g0, h0], points_h_packed) = rest.split_at(3) {
+ Ok(UnpackedProof {
+ data,
+ f0,
+ g0,
+ h0,
+ points_h_packed,
+ })
+ } else {
+ Err(SerializeError::UnpackInputSizeMismatch)
+ }
+}
+
+/// Unpacks a mutable proof vector into mutable subcomponents
+pub(crate) fn unpack_proof_mut<F: FftFriendlyFieldElement>(
+ proof: &mut [F],
+ dimension: usize,
+) -> Result<UnpackedProofMut<F>, SerializeError> {
+ // check the share length
+ if proof.len() != proof_length(dimension) {
+ return Err(SerializeError::UnpackInputSizeMismatch);
+ }
+ // split share into components
+ let (data, rest) = proof.split_at_mut(dimension);
+ if let ([f0, g0, h0], points_h_packed) = rest.split_at_mut(3) {
+ Ok(UnpackedProofMut {
+ data,
+ f0,
+ g0,
+ h0,
+ points_h_packed,
+ })
+ } else {
+ Err(SerializeError::UnpackInputSizeMismatch)
+ }
+}
+
+fn interpolate_and_evaluate_at_2n<F: FftFriendlyFieldElement>(
+ n: usize,
+ points_in: &[F],
+ evals_out: &mut [F],
+ mem: &mut PolyAuxMemory<F>,
+) {
+ // interpolate through roots of unity
+ poly_fft(
+ &mut mem.coeffs,
+ points_in,
+ &mem.roots_n_inverted,
+ n,
+ true,
+ &mut mem.fft_memory,
+ );
+ // evaluate at 2N roots of unity
+ poly_fft(
+ evals_out,
+ &mem.coeffs,
+ &mem.roots_2n,
+ 2 * n,
+ false,
+ &mut mem.fft_memory,
+ );
+}
+
+/// Proof construction
+///
+/// Based on Theorem 2.3.3 from Henry Corrigan-Gibbs' dissertation
+/// This constructs the output \pi by doing the necessesary calculations
+fn construct_proof<F: FftFriendlyFieldElement>(
+ data: &[F],
+ dimension: usize,
+ f0: &mut F,
+ g0: &mut F,
+ h0: &mut F,
+ points_h_packed: &mut [F],
+ mem: &mut ClientMemory<F>,
+) {
+ let n = (dimension + 1).next_power_of_two();
+
+ // set zero terms to random
+ *f0 = mem.prng.get();
+ *g0 = mem.prng.get();
+ mem.points_f[0] = *f0;
+ mem.points_g[0] = *g0;
+
+ // set zero term for the proof polynomial
+ *h0 = *f0 * *g0;
+
+ // set f_i = data_(i - 1)
+ // set g_i = f_i - 1
+ for ((f_coeff, g_coeff), data_val) in mem.points_f[1..1 + dimension]
+ .iter_mut()
+ .zip(mem.points_g[1..1 + dimension].iter_mut())
+ .zip(data[..dimension].iter())
+ {
+ *f_coeff = *data_val;
+ *g_coeff = *data_val - F::one();
+ }
+
+ // interpolate and evaluate at roots of unity
+ interpolate_and_evaluate_at_2n(n, &mem.points_f, &mut mem.evals_f, &mut mem.poly_mem);
+ interpolate_and_evaluate_at_2n(n, &mem.points_g, &mut mem.evals_g, &mut mem.poly_mem);
+
+ // calculate the proof polynomial as evals_f(r) * evals_g(r)
+ // only add non-zero points
+ let mut j: usize = 0;
+ let mut i: usize = 1;
+ while i < 2 * n {
+ points_h_packed[j] = mem.evals_f[i] * mem.evals_g[i];
+ j += 1;
+ i += 2;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use assert_matches::assert_matches;
+
+ use crate::{
+ field::{Field64, FieldPrio2},
+ vdaf::prio2::client::{proof_length, unpack_proof, unpack_proof_mut, SerializeError},
+ };
+
+ #[test]
+ fn test_unpack_share_mut() {
+ let dim = 15;
+ let len = proof_length(dim);
+
+ let mut share = vec![FieldPrio2::from(0); len];
+ let unpacked = unpack_proof_mut(&mut share, dim).unwrap();
+ *unpacked.f0 = FieldPrio2::from(12);
+ assert_eq!(share[dim], 12);
+
+ let mut short_share = vec![FieldPrio2::from(0); len - 1];
+ assert_matches!(
+ unpack_proof_mut(&mut short_share, dim),
+ Err(SerializeError::UnpackInputSizeMismatch)
+ );
+ }
+
+ #[test]
+ fn test_unpack_share() {
+ let dim = 15;
+ let len = proof_length(dim);
+
+ let share = vec![Field64::from(0); len];
+ unpack_proof(&share, dim).unwrap();
+
+ let short_share = vec![Field64::from(0); len - 1];
+ assert_matches!(
+ unpack_proof(&short_share, dim),
+ Err(SerializeError::UnpackInputSizeMismatch)
+ );
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/prio2/server.rs b/third_party/rust/prio/src/vdaf/prio2/server.rs
new file mode 100644
index 0000000000..11c161babf
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/prio2/server.rs
@@ -0,0 +1,386 @@
+// Copyright (c) 2020 Apple Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+//! Primitives for the Prio2 server.
+use crate::{
+ field::{FftFriendlyFieldElement, FieldError},
+ polynomial::poly_interpret_eval,
+ prng::PrngError,
+ vdaf::prio2::client::{unpack_proof, SerializeError},
+};
+use serde::{Deserialize, Serialize};
+
+/// Possible errors from server operations
+#[derive(Debug, thiserror::Error)]
+pub enum ServerError {
+ /// Unexpected Share Length
+ #[allow(unused)]
+ #[error("unexpected share length")]
+ ShareLength,
+ /// Finite field operation error
+ #[error("finite field operation error")]
+ Field(#[from] FieldError),
+ /// Serialization/deserialization error
+ #[error("serialization/deserialization error")]
+ Serialize(#[from] SerializeError),
+ /// Failure when calling getrandom().
+ #[error("getrandom: {0}")]
+ GetRandom(#[from] getrandom::Error),
+ /// PRNG error.
+ #[error("prng error: {0}")]
+ Prng(#[from] PrngError),
+}
+
+/// Verification message for proof validation
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct VerificationMessage<F> {
+ /// f evaluated at random point
+ pub f_r: F,
+ /// g evaluated at random point
+ pub g_r: F,
+ /// h evaluated at random point
+ pub h_r: F,
+}
+
+/// Given a proof and evaluation point, this constructs the verification
+/// message.
+pub(crate) fn generate_verification_message<F: FftFriendlyFieldElement>(
+ dimension: usize,
+ eval_at: F,
+ proof: &[F],
+ is_first_server: bool,
+) -> Result<VerificationMessage<F>, ServerError> {
+ let unpacked = unpack_proof(proof, dimension)?;
+ let n: usize = (dimension + 1).next_power_of_two();
+ let proof_length = 2 * n;
+ let mut fft_in = vec![F::zero(); proof_length];
+ let mut fft_mem = vec![F::zero(); proof_length];
+
+ // construct and evaluate polynomial f at the random point
+ fft_in[0] = *unpacked.f0;
+ fft_in[1..unpacked.data.len() + 1].copy_from_slice(unpacked.data);
+ let f_r = poly_interpret_eval(&fft_in[..n], eval_at, &mut fft_mem);
+
+ // construct and evaluate polynomial g at the random point
+ fft_in[0] = *unpacked.g0;
+ if is_first_server {
+ for x in fft_in[1..unpacked.data.len() + 1].iter_mut() {
+ *x -= F::one();
+ }
+ }
+ let g_r = poly_interpret_eval(&fft_in[..n], eval_at, &mut fft_mem);
+
+ // construct and evaluate polynomial h at the random point
+ fft_in[0] = *unpacked.h0;
+ fft_in[1] = unpacked.points_h_packed[0];
+ for (x, chunk) in unpacked.points_h_packed[1..]
+ .iter()
+ .zip(fft_in[2..proof_length].chunks_exact_mut(2))
+ {
+ chunk[0] = F::zero();
+ chunk[1] = *x;
+ }
+ let h_r = poly_interpret_eval(&fft_in, eval_at, &mut fft_mem);
+
+ Ok(VerificationMessage { f_r, g_r, h_r })
+}
+
+/// Decides if the distributed proof is valid
+pub(crate) fn is_valid_share<F: FftFriendlyFieldElement>(
+ v1: &VerificationMessage<F>,
+ v2: &VerificationMessage<F>,
+) -> bool {
+ // reconstruct f_r, g_r, h_r
+ let f_r = v1.f_r + v2.f_r;
+ let g_r = v1.g_r + v2.g_r;
+ let h_r = v1.h_r + v2.h_r;
+ // validity check
+ f_r * g_r == h_r
+}
+
+#[cfg(test)]
+mod test_util {
+ use crate::{
+ field::{merge_vector, FftFriendlyFieldElement},
+ prng::Prng,
+ vdaf::prio2::client::proof_length,
+ };
+
+ use super::{generate_verification_message, is_valid_share, ServerError, VerificationMessage};
+
+ /// Main workhorse of the server.
+ #[derive(Debug)]
+ pub(crate) struct Server<F> {
+ dimension: usize,
+ is_first_server: bool,
+ accumulator: Vec<F>,
+ }
+
+ impl<F: FftFriendlyFieldElement> Server<F> {
+ /// Construct a new server instance
+ ///
+ /// Params:
+ /// * `dimension`: the number of elements in the aggregation vector.
+ /// * `is_first_server`: only one of the servers should have this true.
+ pub fn new(dimension: usize, is_first_server: bool) -> Result<Server<F>, ServerError> {
+ Ok(Server {
+ dimension,
+ is_first_server,
+ accumulator: vec![F::zero(); dimension],
+ })
+ }
+
+ /// Deserialize
+ fn deserialize_share(&self, share: &[u8]) -> Result<Vec<F>, ServerError> {
+ let len = proof_length(self.dimension);
+ Ok(if self.is_first_server {
+ F::byte_slice_into_vec(share)?
+ } else {
+ if share.len() != 32 {
+ return Err(ServerError::ShareLength);
+ }
+
+ Prng::from_prio2_seed(&share.try_into().unwrap())
+ .take(len)
+ .collect()
+ })
+ }
+
+ /// Generate verification message from an encrypted share
+ ///
+ /// This decrypts the share of the proof and constructs the
+ /// [`VerificationMessage`](struct.VerificationMessage.html).
+ /// The `eval_at` field should be generate by
+ /// [choose_eval_at](#method.choose_eval_at).
+ pub fn generate_verification_message(
+ &mut self,
+ eval_at: F,
+ share: &[u8],
+ ) -> Result<VerificationMessage<F>, ServerError> {
+ let share_field = self.deserialize_share(share)?;
+ generate_verification_message(
+ self.dimension,
+ eval_at,
+ &share_field,
+ self.is_first_server,
+ )
+ }
+
+ /// Add the content of the encrypted share into the accumulator
+ ///
+ /// This only changes the accumulator if the verification messages `v1` and
+ /// `v2` indicate that the share passed validation.
+ pub fn aggregate(
+ &mut self,
+ share: &[u8],
+ v1: &VerificationMessage<F>,
+ v2: &VerificationMessage<F>,
+ ) -> Result<bool, ServerError> {
+ let share_field = self.deserialize_share(share)?;
+ let is_valid = is_valid_share(v1, v2);
+ if is_valid {
+ // Add to the accumulator. share_field also includes the proof
+ // encoding, so we slice off the first dimension fields, which are
+ // the actual data share.
+ merge_vector(&mut self.accumulator, &share_field[..self.dimension])?;
+ }
+
+ Ok(is_valid)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ codec::Encode,
+ field::{FieldElement, FieldPrio2},
+ prng::Prng,
+ vdaf::{
+ prio2::{client::unpack_proof_mut, server::test_util::Server, Prio2},
+ Client,
+ },
+ };
+ use rand::{random, Rng};
+
+ fn secret_share(share: &mut [FieldPrio2]) -> Vec<FieldPrio2> {
+ let mut rng = rand::thread_rng();
+ let mut share2 = vec![FieldPrio2::zero(); share.len()];
+ for (f1, f2) in share.iter_mut().zip(share2.iter_mut()) {
+ let f = FieldPrio2::from(rng.gen::<u32>());
+ *f2 = f;
+ *f1 -= f;
+ }
+ share2
+ }
+
+ #[test]
+ fn test_validation() {
+ let dim = 8;
+ let proof_u32: Vec<u32> = vec![
+ 1, 0, 0, 0, 0, 0, 0, 0, 2052337230, 3217065186, 1886032198, 2533724497, 397524722,
+ 3820138372, 1535223968, 4291254640, 3565670552, 2447741959, 163741941, 335831680,
+ 2567182742, 3542857140, 124017604, 4201373647, 431621210, 1618555683, 267689149,
+ ];
+
+ let mut proof: Vec<FieldPrio2> = proof_u32.iter().map(|x| FieldPrio2::from(*x)).collect();
+ let share2 = secret_share(&mut proof);
+ let eval_at = FieldPrio2::from(12313);
+
+ let v1 = generate_verification_message(dim, eval_at, &proof, true).unwrap();
+ let v2 = generate_verification_message(dim, eval_at, &share2, false).unwrap();
+ assert!(is_valid_share(&v1, &v2));
+ }
+
+ #[test]
+ fn test_verification_message_serde() {
+ let dim = 8;
+ let proof_u32: Vec<u32> = vec![
+ 1, 0, 0, 0, 0, 0, 0, 0, 2052337230, 3217065186, 1886032198, 2533724497, 397524722,
+ 3820138372, 1535223968, 4291254640, 3565670552, 2447741959, 163741941, 335831680,
+ 2567182742, 3542857140, 124017604, 4201373647, 431621210, 1618555683, 267689149,
+ ];
+
+ let mut proof: Vec<FieldPrio2> = proof_u32.iter().map(|x| FieldPrio2::from(*x)).collect();
+ let share2 = secret_share(&mut proof);
+ let eval_at = FieldPrio2::from(12313);
+
+ let v1 = generate_verification_message(dim, eval_at, &proof, true).unwrap();
+ let v2 = generate_verification_message(dim, eval_at, &share2, false).unwrap();
+
+ // serialize and deserialize the first verification message
+ let serialized = serde_json::to_string(&v1).unwrap();
+ let deserialized: VerificationMessage<FieldPrio2> =
+ serde_json::from_str(&serialized).unwrap();
+
+ assert!(is_valid_share(&deserialized, &v2));
+ }
+
+ #[derive(Debug, Clone, Copy, PartialEq)]
+ enum Tweak {
+ None,
+ WrongInput,
+ DataPartOfShare,
+ ZeroTermF,
+ ZeroTermG,
+ ZeroTermH,
+ PointsH,
+ VerificationF,
+ VerificationG,
+ VerificationH,
+ }
+
+ fn tweaks(tweak: Tweak) {
+ let dim = 123;
+
+ let mut server1 = Server::<FieldPrio2>::new(dim, true).unwrap();
+ let mut server2 = Server::new(dim, false).unwrap();
+
+ // all zero data
+ let mut data = vec![0; dim];
+
+ if let Tweak::WrongInput = tweak {
+ data[0] = 2;
+ }
+
+ let vdaf = Prio2::new(dim).unwrap();
+ let (_, shares) = vdaf.shard(&data, &[0; 16]).unwrap();
+ let share1_original = shares[0].get_encoded();
+ let share2 = shares[1].get_encoded();
+
+ let mut share1_field = FieldPrio2::byte_slice_into_vec(&share1_original).unwrap();
+ let unpacked_share1 = unpack_proof_mut(&mut share1_field, dim).unwrap();
+
+ let one = FieldPrio2::from(1);
+
+ match tweak {
+ Tweak::DataPartOfShare => unpacked_share1.data[0] += one,
+ Tweak::ZeroTermF => *unpacked_share1.f0 += one,
+ Tweak::ZeroTermG => *unpacked_share1.g0 += one,
+ Tweak::ZeroTermH => *unpacked_share1.h0 += one,
+ Tweak::PointsH => unpacked_share1.points_h_packed[0] += one,
+ _ => (),
+ };
+
+ // reserialize altered share1
+ let share1_modified = FieldPrio2::slice_into_byte_vec(&share1_field);
+
+ let mut prng = Prng::from_prio2_seed(&random());
+ let eval_at = vdaf.choose_eval_at(&mut prng);
+
+ let mut v1 = server1
+ .generate_verification_message(eval_at, &share1_modified)
+ .unwrap();
+ let v2 = server2
+ .generate_verification_message(eval_at, &share2)
+ .unwrap();
+
+ match tweak {
+ Tweak::VerificationF => v1.f_r += one,
+ Tweak::VerificationG => v1.g_r += one,
+ Tweak::VerificationH => v1.h_r += one,
+ _ => (),
+ }
+
+ let should_be_valid = matches!(tweak, Tweak::None);
+ assert_eq!(
+ server1.aggregate(&share1_modified, &v1, &v2).unwrap(),
+ should_be_valid
+ );
+ assert_eq!(
+ server2.aggregate(&share2, &v1, &v2).unwrap(),
+ should_be_valid
+ );
+ }
+
+ #[test]
+ fn tweak_none() {
+ tweaks(Tweak::None);
+ }
+
+ #[test]
+ fn tweak_input() {
+ tweaks(Tweak::WrongInput);
+ }
+
+ #[test]
+ fn tweak_data() {
+ tweaks(Tweak::DataPartOfShare);
+ }
+
+ #[test]
+ fn tweak_f_zero() {
+ tweaks(Tweak::ZeroTermF);
+ }
+
+ #[test]
+ fn tweak_g_zero() {
+ tweaks(Tweak::ZeroTermG);
+ }
+
+ #[test]
+ fn tweak_h_zero() {
+ tweaks(Tweak::ZeroTermH);
+ }
+
+ #[test]
+ fn tweak_h_points() {
+ tweaks(Tweak::PointsH);
+ }
+
+ #[test]
+ fn tweak_f_verif() {
+ tweaks(Tweak::VerificationF);
+ }
+
+ #[test]
+ fn tweak_g_verif() {
+ tweaks(Tweak::VerificationG);
+ }
+
+ #[test]
+ fn tweak_h_verif() {
+ tweaks(Tweak::VerificationH);
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/prio2/test_vector.rs b/third_party/rust/prio/src/vdaf/prio2/test_vector.rs
new file mode 100644
index 0000000000..ae2b8b0f9d
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/prio2/test_vector.rs
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//! Test vectors of serialized Prio inputs, enabling backward compatibility testing.
+
+use crate::{field::FieldPrio2, vdaf::prio2::client::ClientError};
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+
+/// Errors propagated by functions in this module.
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum TestVectorError {
+ /// Error from Prio client
+ #[error("Prio client error {0}")]
+ Client(#[from] ClientError),
+}
+
+/// A test vector of serialized Priov2 inputs, along with a reference sum. The field is always
+/// [`FieldPrio2`].
+#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
+pub(crate) struct Priov2TestVector {
+ /// Dimension (number of buckets) of the inputs
+ pub dimension: usize,
+ /// Decrypted shares of Priov2 format inputs for the "first" a.k.a. "PHA"
+ /// server. The inner `Vec`s are encrypted bytes.
+ #[serde(
+ serialize_with = "base64::serialize_bytes",
+ deserialize_with = "base64::deserialize_bytes"
+ )]
+ pub server_1_decrypted_shares: Vec<Vec<u8>>,
+ /// Decrypted share of Priov2 format inputs for the non-"first" a.k.a.
+ /// "facilitator" server.
+ #[serde(
+ serialize_with = "base64::serialize_bytes",
+ deserialize_with = "base64::deserialize_bytes"
+ )]
+ pub server_2_decrypted_shares: Vec<Vec<u8>>,
+ /// The sum over the inputs.
+ #[serde(
+ serialize_with = "base64::serialize_field",
+ deserialize_with = "base64::deserialize_field"
+ )]
+ pub reference_sum: Vec<FieldPrio2>,
+}
+
+mod base64 {
+ //! Custom serialization module used for some members of struct
+ //! `Priov2TestVector` so that byte slices are serialized as base64 strings
+ //! instead of an array of an array of integers when serializing to JSON.
+ //
+ // Thank you, Alice! https://users.rust-lang.org/t/serialize-a-vec-u8-to-json-as-base64/57781/2
+ use crate::field::{FieldElement, FieldPrio2};
+ use base64::{engine::Engine, prelude::BASE64_STANDARD};
+ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
+
+ pub fn serialize_bytes<S: Serializer>(v: &[Vec<u8>], s: S) -> Result<S::Ok, S::Error> {
+ let base64_vec = v
+ .iter()
+ .map(|bytes| BASE64_STANDARD.encode(bytes))
+ .collect();
+ <Vec<String>>::serialize(&base64_vec, s)
+ }
+
+ pub fn deserialize_bytes<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Vec<u8>>, D::Error> {
+ <Vec<String>>::deserialize(d)?
+ .iter()
+ .map(|s| BASE64_STANDARD.decode(s.as_bytes()).map_err(Error::custom))
+ .collect()
+ }
+
+ pub fn serialize_field<S: Serializer>(v: &[FieldPrio2], s: S) -> Result<S::Ok, S::Error> {
+ String::serialize(
+ &BASE64_STANDARD.encode(FieldPrio2::slice_into_byte_vec(v)),
+ s,
+ )
+ }
+
+ pub fn deserialize_field<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<FieldPrio2>, D::Error> {
+ let bytes = BASE64_STANDARD
+ .decode(String::deserialize(d)?.as_bytes())
+ .map_err(Error::custom)?;
+ FieldPrio2::byte_slice_into_vec(&bytes).map_err(Error::custom)
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/prio3.rs b/third_party/rust/prio/src/vdaf/prio3.rs
new file mode 100644
index 0000000000..4a7cdefb84
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/prio3.rs
@@ -0,0 +1,2127 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//! Implementation of the Prio3 VDAF [[draft-irtf-cfrg-vdaf-07]].
+//!
+//! **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-07]] 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-07]].
+//!
+//! [BBCG+19]: https://ia.cr/2019/188
+//! [CGB17]: https://crypto.stanford.edu/prio/
+//! [DPRS23]: https://ia.cr/2023/130
+//! [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+
+use super::xof::XofShake128;
+#[cfg(feature = "experimental")]
+use super::AggregatorWithNoise;
+use crate::codec::{CodecError, Decode, Encode, ParameterizedDecode};
+#[cfg(feature = "experimental")]
+use crate::dp::DifferentialPrivacyStrategy;
+use crate::field::{decode_fieldvec, FftFriendlyFieldElement, FieldElement};
+use crate::field::{Field128, Field64};
+#[cfg(feature = "multithreaded")]
+use crate::flp::gadgets::ParallelSumMultithreaded;
+#[cfg(feature = "experimental")]
+use crate::flp::gadgets::PolyEval;
+use crate::flp::gadgets::{Mul, ParallelSum};
+#[cfg(feature = "experimental")]
+use crate::flp::types::fixedpoint_l2::{
+ compatible_float::CompatibleFloat, FixedPointBoundedL2VecSum,
+};
+use crate::flp::types::{Average, Count, Histogram, Sum, SumVec};
+use crate::flp::Type;
+#[cfg(feature = "experimental")]
+use crate::flp::TypeWithNoise;
+use crate::prng::Prng;
+use crate::vdaf::xof::{IntoFieldVec, Seed, Xof};
+use crate::vdaf::{
+ Aggregatable, AggregateShare, Aggregator, Client, Collector, OutputShare, PrepareTransition,
+ Share, ShareDecodingParameter, Vdaf, VdafError,
+};
+#[cfg(feature = "experimental")]
+use fixed::traits::Fixed;
+use std::convert::TryFrom;
+use std::fmt::Debug;
+use std::io::Cursor;
+use std::iter::{self, IntoIterator};
+use std::marker::PhantomData;
+use subtle::{Choice, ConstantTimeEq};
+
+const DST_MEASUREMENT_SHARE: u16 = 1;
+const DST_PROOF_SHARE: u16 = 2;
+const DST_JOINT_RANDOMNESS: u16 = 3;
+const DST_PROVE_RANDOMNESS: u16 = 4;
+const DST_QUERY_RANDOMNESS: u16 = 5;
+const DST_JOINT_RAND_SEED: u16 = 6;
+const DST_JOINT_RAND_PART: u16 = 7;
+
+/// The count type. Each measurement is an integer in `[0,2)` and the aggregate result is the sum.
+pub type Prio3Count = Prio3<Count<Field64>, XofShake128, 16>;
+
+impl Prio3Count {
+ /// Construct an instance of Prio3Count with the given number of aggregators.
+ pub fn new_count(num_aggregators: u8) -> Result<Self, VdafError> {
+ Prio3::new(num_aggregators, 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.
+pub type Prio3SumVec =
+ Prio3<SumVec<Field128, ParallelSum<Field128, Mul<Field128>>>, XofShake128, 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.
+ pub fn new_sum_vec(
+ num_aggregators: u8,
+ bits: usize,
+ len: usize,
+ chunk_length: usize,
+ ) -> Result<Self, VdafError> {
+ Prio3::new(num_aggregators, 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")))]
+pub type Prio3SumVecMultithreaded =
+ Prio3<SumVec<Field128, ParallelSumMultithreaded<Field128, Mul<Field128>>>, XofShake128, 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.
+ pub fn new_sum_vec_multithreaded(
+ num_aggregators: u8,
+ bits: usize,
+ len: usize,
+ chunk_length: usize,
+ ) -> Result<Self, VdafError> {
+ Prio3::new(num_aggregators, 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.
+pub type Prio3Sum = Prio3<Sum<Field128>, XofShake128, 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.
+ pub fn 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)"
+ )));
+ }
+
+ Prio3::new(num_aggregators, Sum::new(bits)?)
+ }
+}
+
+/// 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")))]
+pub type Prio3FixedPointBoundedL2VecSum<Fx> = Prio3<
+ FixedPointBoundedL2VecSum<
+ Fx,
+ ParallelSum<Field128, PolyEval<Field128>>,
+ ParallelSum<Field128, Mul<Field128>>,
+ >,
+ XofShake128,
+ 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.
+ pub fn new_fixedpoint_boundedl2_vec_sum(
+ num_aggregators: u8,
+ entries: usize,
+ ) -> Result<Self, VdafError> {
+ check_num_aggregators(num_aggregators)?;
+ Prio3::new(num_aggregators, 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")))
+)]
+pub type Prio3FixedPointBoundedL2VecSumMultithreaded<Fx> = Prio3<
+ FixedPointBoundedL2VecSum<
+ Fx,
+ ParallelSumMultithreaded<Field128, PolyEval<Field128>>,
+ ParallelSumMultithreaded<Field128, Mul<Field128>>,
+ >,
+ XofShake128,
+ 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.
+ pub fn new_fixedpoint_boundedl2_vec_sum_multithreaded(
+ num_aggregators: u8,
+ entries: usize,
+ ) -> Result<Self, VdafError> {
+ check_num_aggregators(num_aggregators)?;
+ Prio3::new(num_aggregators, 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.
+pub type Prio3Histogram =
+ Prio3<Histogram<Field128, ParallelSum<Field128, Mul<Field128>>>, XofShake128, 16>;
+
+impl Prio3Histogram {
+ /// Constructs an instance of Prio3Histogram with the given number of aggregators,
+ /// number of buckets, and parallel sum gadget chunk length.
+ pub fn new_histogram(
+ num_aggregators: u8,
+ length: usize,
+ chunk_length: usize,
+ ) -> Result<Self, VdafError> {
+ Prio3::new(num_aggregators, 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")))]
+pub type Prio3HistogramMultithreaded =
+ Prio3<Histogram<Field128, ParallelSumMultithreaded<Field128, Mul<Field128>>>, XofShake128, 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.
+ pub fn new_histogram_multithreaded(
+ num_aggregators: u8,
+ length: usize,
+ chunk_length: usize,
+ ) -> Result<Self, VdafError> {
+ Prio3::new(num_aggregators, 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.
+pub type Prio3Average = Prio3<Average<Field128>, XofShake128, 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.
+ pub fn 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)"
+ )));
+ }
+
+ Ok(Prio3 {
+ num_aggregators,
+ typ: Average::new(bits)?,
+ phantom: PhantomData,
+ })
+ }
+}
+
+/// 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>, XofShake128, 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 = [0, 1, 1, 1, 0];
+/// 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_step(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)]
+pub struct Prio3<T, P, const SEED_SIZE: usize>
+where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ num_aggregators: u8,
+ 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 and the
+ /// underlying type.
+ pub fn new(num_aggregators: u8, typ: T) -> Result<Self, VdafError> {
+ check_num_aggregators(num_aggregators)?;
+ Ok(Self {
+ num_aggregators,
+ typ,
+ phantom: PhantomData,
+ })
+ }
+
+ /// The output length of the underlying FLP.
+ pub fn output_len(&self) -> usize {
+ self.typ.output_len()
+ }
+
+ /// The verifier length of the underlying FLP.
+ pub fn verifier_len(&self) -> usize {
+ self.typ.verifier_len()
+ }
+
+ fn derive_joint_rand_seed<'a>(
+ parts: impl Iterator<Item = &'a Seed<SEED_SIZE>>,
+ ) -> Seed<SEED_SIZE> {
+ let mut xof = P::init(
+ &[0; SEED_SIZE],
+ &Self::domain_separation_tag(DST_JOINT_RAND_SEED),
+ );
+ for part in parts {
+ xof.update(part.as_ref());
+ }
+ xof.into_seed()
+ }
+
+ fn random_size(&self) -> usize {
+ if self.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
+ }
+ }
+
+ #[allow(clippy::type_complexity)]
+ pub(crate) fn shard_with_random<const N: usize>(
+ &self,
+ measurement: &T::Measurement,
+ nonce: &[u8; N],
+ random: &[u8],
+ ) -> Result<
+ (
+ Prio3PublicShare<SEED_SIZE>,
+ Vec<Prio3InputShare<T::Field, SEED_SIZE>>,
+ ),
+ VdafError,
+ > {
+ if random.len() != self.random_size() {
+ return Err(VdafError::Uncategorized(
+ "incorrect random input length".to_string(),
+ ));
+ }
+ let mut random_seeds = random.chunks_exact(SEED_SIZE);
+ let num_aggregators = self.num_aggregators;
+ let encoded_measurement = self.typ.encode_measurement(measurement)?;
+
+ // Generate the measurement shares and compute the joint randomness.
+ let mut helper_shares = Vec::with_capacity(num_aggregators as usize - 1);
+ let mut helper_joint_rand_parts = if self.typ.joint_rand_len() > 0 {
+ Some(Vec::with_capacity(num_aggregators as usize - 1))
+ } else {
+ None
+ };
+ let mut leader_measurement_share = encoded_measurement.clone();
+ for agg_id in 1..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 =
+ if let Some(helper_joint_rand_parts) = helper_joint_rand_parts.as_mut() {
+ let joint_rand_blind = random_seeds.next().unwrap().try_into().unwrap();
+ let mut 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);
+
+ let mut 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);
+ joint_rand_part_xof.update(&encoding_buffer);
+ encoding_buffer.clear();
+ }
+
+ helper_joint_rand_parts.push(joint_rand_part_xof.into_seed());
+
+ Some(joint_rand_blind)
+ } else {
+ for (x, y) in leader_measurement_share
+ .iter_mut()
+ .zip(measurement_share_prng)
+ {
+ *x -= y;
+ }
+ None
+ };
+ let helper =
+ HelperShare::from_seeds(measurement_share_seed, proof_share_seed, joint_rand_blind);
+ helper_shares.push(helper);
+ }
+
+ let mut leader_blind_opt = None;
+ let public_share = Prio3PublicShare {
+ joint_rand_parts: helper_joint_rand_parts
+ .as_ref()
+ .map(|helper_joint_rand_parts| {
+ let leader_blind_bytes = random_seeds.next().unwrap().try_into().unwrap();
+ let leader_blind = Seed::from_bytes(leader_blind_bytes);
+
+ let mut joint_rand_part_xof = P::init(
+ leader_blind.as_ref(),
+ &Self::domain_separation_tag(DST_JOINT_RAND_PART),
+ );
+ joint_rand_part_xof.update(&[0]); // Aggregator ID
+ joint_rand_part_xof.update(nonce);
+ let mut encoding_buffer = Vec::with_capacity(T::Field::ENCODED_SIZE);
+ for x in leader_measurement_share.iter() {
+ x.encode(&mut encoding_buffer);
+ joint_rand_part_xof.update(&encoding_buffer);
+ encoding_buffer.clear();
+ }
+ leader_blind_opt = Some(leader_blind);
+
+ let leader_joint_rand_seed_part = joint_rand_part_xof.into_seed();
+
+ let mut vec = Vec::with_capacity(self.num_aggregators());
+ vec.push(leader_joint_rand_seed_part);
+ vec.extend(helper_joint_rand_parts.iter().cloned());
+ vec
+ }),
+ };
+
+ // Compute the joint randomness.
+ let joint_rand: Vec<T::Field> = public_share
+ .joint_rand_parts
+ .as_ref()
+ .map(|joint_rand_parts| {
+ let joint_rand_seed = Self::derive_joint_rand_seed(joint_rand_parts.iter());
+ P::seed_stream(
+ &joint_rand_seed,
+ &Self::domain_separation_tag(DST_JOINT_RANDOMNESS),
+ &[],
+ )
+ .into_field_vec(self.typ.joint_rand_len())
+ })
+ .unwrap_or_default();
+
+ // Run the proof-generation algorithm.
+ let prove_rand_seed = random_seeds.next().unwrap().try_into().unwrap();
+ let prove_rand = P::seed_stream(
+ &Seed::from_bytes(prove_rand_seed),
+ &Self::domain_separation_tag(DST_PROVE_RANDOMNESS),
+ &[],
+ )
+ .into_field_vec(self.typ.prove_rand_len());
+ let mut leader_proof_share =
+ self.typ
+ .prove(&encoded_measurement, &prove_rand, &joint_rand)?;
+
+ // Generate the proof shares and distribute the joint randomness seed hints.
+ for (j, helper) in helper_shares.iter_mut().enumerate() {
+ let proof_share_prng: Prng<T::Field, _> = Prng::from_seed_stream(P::seed_stream(
+ &helper.proof_share,
+ &Self::domain_separation_tag(DST_PROOF_SHARE),
+ &[j as u8 + 1],
+ ));
+ for (x, y) in leader_proof_share
+ .iter_mut()
+ .zip(proof_share_prng)
+ .take(self.typ.proof_len())
+ {
+ *x -= y;
+ }
+ }
+
+ // Prep the output messages.
+ let mut out = Vec::with_capacity(num_aggregators as usize);
+ out.push(Prio3InputShare {
+ measurement_share: Share::Leader(leader_measurement_share),
+ proof_share: Share::Leader(leader_proof_share),
+ joint_rand_blind: leader_blind_opt,
+ });
+
+ for helper in helper_shares.into_iter() {
+ out.push(Prio3InputShare {
+ measurement_share: Share::Helper(helper.measurement_share),
+ proof_share: Share::Helper(helper.proof_share),
+ joint_rand_blind: helper.joint_rand_blind,
+ });
+ }
+
+ Ok((public_share, out))
+ }
+
+ fn role_try_from(&self, agg_id: usize) -> Result<u8, VdafError> {
+ if agg_id >= self.num_aggregators as usize {
+ return Err(VdafError::Uncategorized("unexpected aggregator id".into()));
+ }
+ Ok(u8::try_from(agg_id).unwrap())
+ }
+}
+
+impl<T, P, const SEED_SIZE: usize> Vdaf for Prio3<T, P, SEED_SIZE>
+where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ const ID: u32 = T::ID;
+ 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)]
+pub struct 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>) {
+ if let Some(joint_rand_parts) = self.joint_rand_parts.as_ref() {
+ for part in joint_rand_parts.iter() {
+ part.encode(bytes);
+ }
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ if let 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> PartialEq for Prio3PublicShare<SEED_SIZE> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+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(),
+ )
+ }
+}
+
+impl<T, P, const SEED_SIZE: usize> ParameterizedDecode<Prio3<T, P, SEED_SIZE>>
+ for Prio3PublicShare<SEED_SIZE>
+where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ fn decode_with_param(
+ decoding_parameter: &Prio3<T, P, SEED_SIZE>,
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ if decoding_parameter.typ.joint_rand_len() > 0 {
+ let joint_rand_parts = iter::repeat_with(|| Seed::<SEED_SIZE>::decode(bytes))
+ .take(decoding_parameter.num_aggregators.into())
+ .collect::<Result<Vec<_>, _>>()?;
+ Ok(Self {
+ joint_rand_parts: Some(joint_rand_parts),
+ })
+ } else {
+ Ok(Self {
+ joint_rand_parts: None,
+ })
+ }
+ }
+}
+
+/// Message sent by the [`Client`] to each [`Aggregator`] during the Sharding phase.
+#[derive(Clone, Debug)]
+pub struct Prio3InputShare<F, const SEED_SIZE: usize> {
+ /// The measurement share.
+ measurement_share: Share<F, SEED_SIZE>,
+
+ /// The proof share.
+ proof_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>>,
+}
+
+impl<F: ConstantTimeEq, const SEED_SIZE: usize> PartialEq for Prio3InputShare<F, SEED_SIZE> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl<F: ConstantTimeEq, const SEED_SIZE: usize> Eq for Prio3InputShare<F, SEED_SIZE> {}
+
+impl<F: ConstantTimeEq, const SEED_SIZE: usize> ConstantTimeEq for Prio3InputShare<F, SEED_SIZE> {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ // We allow short-circuiting on the presence or absence of the joint_rand_blind.
+ option_ct_eq(
+ self.joint_rand_blind.as_ref(),
+ other.joint_rand_blind.as_ref(),
+ ) & self.measurement_share.ct_eq(&other.measurement_share)
+ & self.proof_share.ct_eq(&other.proof_share)
+ }
+}
+
+impl<F: FftFriendlyFieldElement, const SEED_SIZE: usize> Encode for Prio3InputShare<F, SEED_SIZE> {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ if matches!(
+ (&self.measurement_share, &self.proof_share),
+ (Share::Leader(_), Share::Helper(_)) | (Share::Helper(_), Share::Leader(_))
+ ) {
+ panic!("tried to encode input share with ambiguous encoding")
+ }
+
+ self.measurement_share.encode(bytes);
+ self.proof_share.encode(bytes);
+ if let Some(ref blind) = self.joint_rand_blind {
+ blind.encode(bytes);
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ let mut len = self.measurement_share.encoded_len()? + self.proof_share.encoded_len()?;
+ if let Some(ref blind) = self.joint_rand_blind {
+ len += blind.encoded_len()?;
+ }
+ Some(len)
+ }
+}
+
+impl<'a, T, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Prio3<T, P, SEED_SIZE>, usize)>
+ for Prio3InputShare<T::Field, SEED_SIZE>
+where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ fn decode_with_param(
+ (prio3, agg_id): &(&'a Prio3<T, P, SEED_SIZE>, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let agg_id = prio3
+ .role_try_from(*agg_id)
+ .map_err(|e| CodecError::Other(Box::new(e)))?;
+ let (input_decoder, proof_decoder) = if agg_id == 0 {
+ (
+ ShareDecodingParameter::Leader(prio3.typ.input_len()),
+ ShareDecodingParameter::Leader(prio3.typ.proof_len()),
+ )
+ } else {
+ (
+ ShareDecodingParameter::Helper,
+ ShareDecodingParameter::Helper,
+ )
+ };
+
+ let measurement_share = Share::decode_with_param(&input_decoder, bytes)?;
+ let proof_share = Share::decode_with_param(&proof_decoder, bytes)?;
+ let joint_rand_blind = if prio3.typ.joint_rand_len() > 0 {
+ let blind = Seed::decode(bytes)?;
+ Some(blind)
+ } else {
+ None
+ };
+
+ Ok(Prio3InputShare {
+ measurement_share,
+ proof_share,
+ joint_rand_blind,
+ })
+ }
+}
+
+#[derive(Clone, Debug)]
+/// Message broadcast by each [`Aggregator`] in each round of the Preparation phase.
+pub struct Prio3PrepareShare<F, const SEED_SIZE: usize> {
+ /// A share of the FLP verifier message. (See [`Type`].)
+ verifier: Vec<F>,
+
+ /// A part of the joint randomness seed.
+ joint_rand_part: Option<Seed<SEED_SIZE>>,
+}
+
+impl<F: ConstantTimeEq, const SEED_SIZE: usize> PartialEq for Prio3PrepareShare<F, SEED_SIZE> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+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.verifier.ct_eq(&other.verifier)
+ }
+}
+
+impl<F: FftFriendlyFieldElement, const SEED_SIZE: usize> Encode
+ for Prio3PrepareShare<F, SEED_SIZE>
+{
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ for x in &self.verifier {
+ x.encode(bytes);
+ }
+ if let Some(ref seed) = self.joint_rand_part {
+ seed.encode(bytes);
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ // Each element of the verifier has the same size.
+ let mut len = F::ENCODED_SIZE * self.verifier.len();
+ if let Some(ref seed) = self.joint_rand_part {
+ len += seed.encoded_len()?;
+ }
+ Some(len)
+ }
+}
+
+impl<F: FftFriendlyFieldElement, const SEED_SIZE: usize>
+ ParameterizedDecode<Prio3PrepareState<F, SEED_SIZE>> for Prio3PrepareShare<F, SEED_SIZE>
+{
+ fn decode_with_param(
+ decoding_parameter: &Prio3PrepareState<F, SEED_SIZE>,
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let mut verifier = Vec::with_capacity(decoding_parameter.verifier_len);
+ for _ in 0..decoding_parameter.verifier_len {
+ verifier.push(F::decode(bytes)?);
+ }
+
+ let joint_rand_part = if decoding_parameter.joint_rand_seed.is_some() {
+ Some(Seed::decode(bytes)?)
+ } else {
+ None
+ };
+
+ Ok(Prio3PrepareShare {
+ verifier,
+ joint_rand_part,
+ })
+ }
+}
+
+#[derive(Clone, Debug)]
+/// Result of combining a round of [`Prio3PrepareShare`] messages.
+pub struct 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> PartialEq for Prio3PrepareMessage<SEED_SIZE> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+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<const SEED_SIZE: usize> Encode for Prio3PrepareMessage<SEED_SIZE> {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ if let Some(ref seed) = self.joint_rand_seed {
+ seed.encode(bytes);
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ if let Some(ref seed) = self.joint_rand_seed {
+ seed.encoded_len()
+ } else {
+ Some(0)
+ }
+ }
+}
+
+impl<F: FftFriendlyFieldElement, const SEED_SIZE: usize>
+ ParameterizedDecode<Prio3PrepareState<F, SEED_SIZE>> for Prio3PrepareMessage<SEED_SIZE>
+{
+ fn decode_with_param(
+ decoding_parameter: &Prio3PrepareState<F, SEED_SIZE>,
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let joint_rand_seed = if decoding_parameter.joint_rand_seed.is_some() {
+ Some(Seed::decode(bytes)?)
+ } else {
+ None
+ };
+
+ Ok(Prio3PrepareMessage { joint_rand_seed })
+ }
+}
+
+impl<T, P, const SEED_SIZE: usize> Client<16> for Prio3<T, P, SEED_SIZE>
+where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ #[allow(clippy::type_complexity)]
+ fn shard(
+ &self,
+ measurement: &T::Measurement,
+ nonce: &[u8; 16],
+ ) -> Result<(Self::PublicShare, Vec<Prio3InputShare<T::Field, SEED_SIZE>>), VdafError> {
+ let mut random = vec![0u8; self.random_size()];
+ getrandom::getrandom(&mut random)?;
+ self.shard_with_random(measurement, nonce, &random)
+ }
+}
+
+/// State of each [`Aggregator`] during the Preparation phase.
+#[derive(Clone)]
+pub struct Prio3PrepareState<F, const SEED_SIZE: usize> {
+ measurement_share: Share<F, SEED_SIZE>,
+ joint_rand_seed: Option<Seed<SEED_SIZE>>,
+ agg_id: u8,
+ verifier_len: usize,
+}
+
+impl<F: ConstantTimeEq, const SEED_SIZE: usize> PartialEq for Prio3PrepareState<F, SEED_SIZE> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+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.
+ if self.agg_id != other.agg_id || self.verifier_len != other.verifier_len {
+ return Choice::from(0);
+ }
+
+ option_ct_eq(
+ self.joint_rand_seed.as_ref(),
+ other.joint_rand_seed.as_ref(),
+ ) & self.measurement_share.ct_eq(&other.measurement_share)
+ }
+}
+
+impl<F, const SEED_SIZE: usize> Debug for Prio3PrepareState<F, SEED_SIZE> {
+ 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<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>) {
+ self.measurement_share.encode(bytes);
+ if let Some(ref seed) = self.joint_rand_seed {
+ seed.encode(bytes);
+ }
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ let mut len = self.measurement_share.encoded_len()?;
+ if let Some(ref seed) = self.joint_rand_seed {
+ len += seed.encoded_len()?;
+ }
+ Some(len)
+ }
+}
+
+impl<'a, T, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Prio3<T, P, SEED_SIZE>, usize)>
+ for Prio3PrepareState<T::Field, SEED_SIZE>
+where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ fn decode_with_param(
+ (prio3, agg_id): &(&'a Prio3<T, P, SEED_SIZE>, usize),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ let agg_id = prio3
+ .role_try_from(*agg_id)
+ .map_err(|e| CodecError::Other(Box::new(e)))?;
+
+ let share_decoder = if agg_id == 0 {
+ ShareDecodingParameter::Leader(prio3.typ.input_len())
+ } else {
+ ShareDecodingParameter::Helper
+ };
+ let measurement_share = Share::decode_with_param(&share_decoder, bytes)?;
+
+ let joint_rand_seed = if prio3.typ.joint_rand_len() > 0 {
+ Some(Seed::decode(bytes)?)
+ } else {
+ None
+ };
+
+ Ok(Self {
+ measurement_share,
+ joint_rand_seed,
+ agg_id,
+ verifier_len: prio3.typ.verifier_len(),
+ })
+ }
+}
+
+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)?;
+ let mut query_rand_xof = P::init(
+ verify_key,
+ &Self::domain_separation_tag(DST_QUERY_RANDOMNESS),
+ );
+ query_rand_xof.update(nonce);
+ let query_rand = query_rand_xof
+ .into_seed_stream()
+ .into_field_vec(self.typ.query_rand_len());
+
+ // 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_proof_share: Option<Vec<T::Field>> = match msg.proof_share {
+ Share::Leader(_) => None,
+ Share::Helper(ref seed) => Some(
+ P::seed_stream(
+ seed,
+ &Self::domain_separation_tag(DST_PROOF_SHARE),
+ &[agg_id],
+ )
+ .into_field_vec(self.typ.proof_len()),
+ ),
+ };
+ let proof_share = match msg.proof_share {
+ Share::Leader(ref data) => data,
+ Share::Helper(_) => expanded_proof_share.as_ref().unwrap(),
+ };
+
+ // Compute the joint randomness.
+ let (joint_rand_seed, joint_rand_part, joint_rand) = if self.typ.joint_rand_len() > 0 {
+ let mut 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);
+ let mut encoding_buffer = Vec::with_capacity(T::Field::ENCODED_SIZE);
+ for x in measurement_share {
+ x.encode(&mut encoding_buffer);
+ 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 = Self::derive_joint_rand_seed(corrected_joint_rand_parts);
+
+ let joint_rand = P::seed_stream(
+ &joint_rand_seed,
+ &Self::domain_separation_tag(DST_JOINT_RANDOMNESS),
+ &[],
+ )
+ .into_field_vec(self.typ.joint_rand_len());
+ (Some(joint_rand_seed), Some(own_joint_rand_part), joint_rand)
+ } else {
+ (None, None, Vec::new())
+ };
+
+ // Run the query-generation algorithm.
+ let verifier_share = self.typ.query(
+ measurement_share,
+ proof_share,
+ &query_rand,
+ &joint_rand,
+ self.num_aggregators as usize,
+ )?;
+
+ Ok((
+ Prio3PrepareState {
+ measurement_share: msg.measurement_share.clone(),
+ joint_rand_seed,
+ agg_id,
+ verifier_len: verifier_share.len(),
+ },
+ Prio3PrepareShare {
+ verifier: verifier_share,
+ joint_rand_part,
+ },
+ ))
+ }
+
+ fn prepare_shares_to_prepare_message<
+ M: IntoIterator<Item = Prio3PrepareShare<T::Field, SEED_SIZE>>,
+ >(
+ &self,
+ _: &Self::AggregationParam,
+ inputs: M,
+ ) -> Result<Prio3PrepareMessage<SEED_SIZE>, VdafError> {
+ let mut verifier = vec![T::Field::zero(); self.typ.verifier_len()];
+ let mut joint_rand_parts = Vec::with_capacity(self.num_aggregators());
+ let mut count = 0;
+ for share in inputs.into_iter() {
+ count += 1;
+
+ if share.verifier.len() != verifier.len() {
+ return Err(VdafError::Uncategorized(format!(
+ "unexpected verifier share length: got {}; want {}",
+ share.verifier.len(),
+ verifier.len(),
+ )));
+ }
+
+ if self.typ.joint_rand_len() > 0 {
+ let joint_rand_seed_part = share.joint_rand_part.unwrap();
+ joint_rand_parts.push(joint_rand_seed_part);
+ }
+
+ for (x, y) in verifier.iter_mut().zip(share.verifier) {
+ *x += y;
+ }
+ }
+
+ if count != self.num_aggregators {
+ return Err(VdafError::Uncategorized(format!(
+ "unexpected message count: got {}; want {}",
+ count, self.num_aggregators,
+ )));
+ }
+
+ // Check the proof verifier.
+ match self.typ.decide(&verifier) {
+ Ok(true) => (),
+ Ok(false) => {
+ return Err(VdafError::Uncategorized(
+ "proof verifier check failed".into(),
+ ))
+ }
+ Err(err) => return Err(VdafError::from(err)),
+ };
+
+ let joint_rand_seed = if self.typ.joint_rand_len() > 0 {
+ Some(Self::derive_joint_rand_seed(joint_rand_parts.iter()))
+ } else {
+ None
+ };
+
+ Ok(Prio3PrepareMessage { joint_rand_seed })
+ }
+
+ fn prepare_next(
+ &self,
+ step: Prio3PrepareState<T::Field, SEED_SIZE>,
+ msg: Prio3PrepareMessage<SEED_SIZE>,
+ ) -> Result<PrepareTransition<Self, SEED_SIZE, 16>, VdafError> {
+ if self.typ.joint_rand_len() > 0 {
+ // Check that the joint randomness was correct.
+ if step
+ .joint_rand_seed
+ .as_ref()
+ .unwrap()
+ .ct_ne(msg.joint_rand_seed.as_ref().unwrap())
+ .into()
+ {
+ return Err(VdafError::Uncategorized(
+ "joint randomness mismatch".to_string(),
+ ));
+ }
+ }
+
+ // Compute the output share.
+ let measurement_share = match step.measurement_share {
+ Share::Leader(data) => data,
+ Share::Helper(seed) => {
+ let dst = Self::domain_separation_tag(DST_MEASUREMENT_SHARE);
+ P::seed_stream(&seed, &dst, &[step.agg_id]).into_field_vec(self.typ.input_len())
+ }
+ };
+
+ let output_share = match self.typ.truncate(measurement_share) {
+ Ok(data) => OutputShare(data),
+ Err(err) => {
+ return Err(VdafError::from(err));
+ }
+ };
+
+ Ok(PrepareTransition::Finish(output_share))
+ }
+
+ /// Aggregates a sequence of output shares into an aggregate share.
+ fn aggregate<It: IntoIterator<Item = OutputShare<T::Field>>>(
+ &self,
+ _agg_param: &(),
+ output_shares: It,
+ ) -> Result<AggregateShare<T::Field>, VdafError> {
+ let mut agg_share = AggregateShare(vec![T::Field::zero(); self.typ.output_len()]);
+ for output_share in output_shares.into_iter() {
+ agg_share.accumulate(&output_share)?;
+ }
+
+ Ok(agg_share)
+ }
+}
+
+#[cfg(feature = "experimental")]
+impl<T, P, S, const SEED_SIZE: usize> AggregatorWithNoise<SEED_SIZE, 16, S>
+ for Prio3<T, P, SEED_SIZE>
+where
+ T: TypeWithNoise<S>,
+ P: Xof<SEED_SIZE>,
+ S: DifferentialPrivacyStrategy,
+{
+ fn add_noise_to_agg_share(
+ &self,
+ dp_strategy: &S,
+ _agg_param: &Self::AggregationParam,
+ agg_share: &mut Self::AggregateShare,
+ num_measurements: usize,
+ ) -> Result<(), VdafError> {
+ self.typ
+ .add_noise_to_result(dp_strategy, &mut agg_share.0, num_measurements)?;
+ Ok(())
+ }
+}
+
+impl<T, P, const SEED_SIZE: usize> Collector for Prio3<T, P, SEED_SIZE>
+where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ /// Combines aggregate shares into the aggregate result.
+ fn unshard<It: IntoIterator<Item = AggregateShare<T::Field>>>(
+ &self,
+ _agg_param: &Self::AggregationParam,
+ agg_shares: It,
+ num_measurements: usize,
+ ) -> Result<T::AggregateResult, VdafError> {
+ let mut agg = AggregateShare(vec![T::Field::zero(); self.typ.output_len()]);
+ for agg_share in agg_shares.into_iter() {
+ agg.merge(&agg_share)?;
+ }
+
+ Ok(self.typ.decode_result(&agg.0, num_measurements)?)
+ }
+}
+
+#[derive(Clone)]
+struct HelperShare<const SEED_SIZE: usize> {
+ measurement_share: Seed<SEED_SIZE>,
+ proof_share: Seed<SEED_SIZE>,
+ joint_rand_blind: Option<Seed<SEED_SIZE>>,
+}
+
+impl<const SEED_SIZE: usize> HelperShare<SEED_SIZE> {
+ fn from_seeds(
+ measurement_share: [u8; SEED_SIZE],
+ proof_share: [u8; SEED_SIZE],
+ joint_rand_blind: Option<[u8; SEED_SIZE]>,
+ ) -> Self {
+ HelperShare {
+ measurement_share: Seed::from_bytes(measurement_share),
+ proof_share: Seed::from_bytes(proof_share),
+ joint_rand_blind: joint_rand_blind.map(Seed::from_bytes),
+ }
+ }
+}
+
+fn check_num_aggregators(num_aggregators: u8) -> Result<(), VdafError> {
+ if num_aggregators == 0 {
+ return Err(VdafError::Uncategorized(format!(
+ "at least one aggregator is required; got {num_aggregators}"
+ )));
+ } else if num_aggregators > 254 {
+ return Err(VdafError::Uncategorized(format!(
+ "number of aggregators must not exceed 254; got {num_aggregators}"
+ )));
+ }
+
+ Ok(())
+}
+
+impl<'a, F, T, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Prio3<T, P, SEED_SIZE>, &'a ())>
+ for OutputShare<F>
+where
+ F: FieldElement,
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ fn decode_with_param(
+ (vdaf, _): &(&'a Prio3<T, P, SEED_SIZE>, &'a ()),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ decode_fieldvec(vdaf.output_len(), bytes).map(Self)
+ }
+}
+
+impl<'a, F, T, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Prio3<T, P, SEED_SIZE>, &'a ())>
+ for AggregateShare<F>
+where
+ F: FieldElement,
+ T: Type,
+ P: Xof<SEED_SIZE>,
+{
+ fn decode_with_param(
+ (vdaf, _): &(&'a Prio3<T, P, SEED_SIZE>, &'a ()),
+ bytes: &mut Cursor<&[u8]>,
+ ) -> Result<Self, CodecError> {
+ decode_fieldvec(vdaf.output_len(), bytes).map(Self)
+ }
+}
+
+// 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.
+pub fn optimal_chunk_length(measurement_length: usize) -> usize {
+ if measurement_length <= 1 {
+ return 1;
+ }
+
+ /// 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 {
+ use super::*;
+ #[cfg(feature = "experimental")]
+ use crate::flp::gadgets::ParallelSumGadget;
+ use crate::vdaf::{
+ equality_comparison_test, fieldvec_roundtrip_test, 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();
+
+ assert_eq!(run_vdaf(&prio3, &(), [1, 0, 0, 1, 1]).unwrap(), 3);
+
+ let mut nonce = [0; 16];
+ let mut verify_key = [0; 16];
+ thread_rng().fill(&mut verify_key[..]);
+ thread_rng().fill(&mut nonce[..]);
+
+ let (public_share, input_shares) = prio3.shard(&0, &nonce).unwrap();
+ run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares).unwrap();
+
+ let (public_share, input_shares) = prio3.shard(&1, &nonce).unwrap();
+ run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares).unwrap();
+
+ test_serialization(&prio3, &1, &nonce).unwrap();
+
+ let prio3_extra_helper = Prio3::new_count(3).unwrap();
+ assert_eq!(
+ run_vdaf(&prio3_extra_helper, &(), [1, 0, 0, 1, 1]).unwrap(),
+ 3,
+ );
+ }
+
+ #[test]
+ fn test_prio3_sum() {
+ let prio3 = Prio3::new_sum(3, 16).unwrap();
+
+ assert_eq!(
+ run_vdaf(&prio3, &(), [0, (1 << 16) - 1, 0, 1, 1]).unwrap(),
+ (1 << 16) + 1
+ );
+
+ let mut verify_key = [0; 16];
+ thread_rng().fill(&mut verify_key[..]);
+ let nonce = [0; 16];
+
+ let (public_share, mut input_shares) = prio3.shard(&1, &nonce).unwrap();
+ input_shares[0].joint_rand_blind.as_mut().unwrap().0[0] ^= 255;
+ let result = run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares);
+ assert_matches!(result, Err(VdafError::Uncategorized(_)));
+
+ let (public_share, mut input_shares) = prio3.shard(&1, &nonce).unwrap();
+ assert_matches!(input_shares[0].measurement_share, Share::Leader(ref mut data) => {
+ data[0] += Field128::one();
+ });
+ let result = run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares);
+ assert_matches!(result, Err(VdafError::Uncategorized(_)));
+
+ let (public_share, mut input_shares) = prio3.shard(&1, &nonce).unwrap();
+ assert_matches!(input_shares[0].proof_share, Share::Leader(ref mut data) => {
+ data[0] += Field128::one();
+ });
+ let result = run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares);
+ assert_matches!(result, Err(VdafError::Uncategorized(_)));
+
+ test_serialization(&prio3, &1, &nonce).unwrap();
+ }
+
+ #[test]
+ fn test_prio3_sum_vec() {
+ let prio3 = Prio3::new_sum_vec(2, 2, 20, 4).unwrap();
+ assert_eq!(
+ run_vdaf(
+ &prio3,
+ &(),
+ [
+ vec![0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1],
+ vec![0, 2, 0, 0, 1, 0, 0, 0, 1, 1, 1, 3, 0, 3, 0, 0, 0, 1, 0, 0],
+ vec![1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1],
+ ]
+ )
+ .unwrap(),
+ vec![1, 3, 1, 0, 3, 1, 0, 1, 2, 2, 3, 3, 1, 5, 1, 2, 1, 3, 0, 2],
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "multithreaded")]
+ fn test_prio3_sum_vec_multithreaded() {
+ let prio3 = Prio3::new_sum_vec_multithreaded(2, 2, 20, 4).unwrap();
+ assert_eq!(
+ run_vdaf(
+ &prio3,
+ &(),
+ [
+ vec![0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1],
+ vec![0, 2, 0, 0, 1, 0, 0, 0, 1, 1, 1, 3, 0, 3, 0, 0, 0, 1, 0, 0],
+ vec![1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1],
+ ]
+ )
+ .unwrap(),
+ vec![1, 3, 1, 0, 3, 1, 0, 1, 2, 2, 3, 3, 1, 5, 1, 2, 1, 3, 0, 2],
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "experimental")]
+ fn test_prio3_bounded_fpvec_sum_unaligned() {
+ type P<Fx> = Prio3FixedPointBoundedL2VecSum<Fx>;
+ #[cfg(feature = "multithreaded")]
+ type PM<Fx> = Prio3FixedPointBoundedL2VecSumMultithreaded<Fx>;
+ let ctor_32 = P::<FixedI32<U31>>::new_fixedpoint_boundedl2_vec_sum;
+ #[cfg(feature = "multithreaded")]
+ let ctor_mt_32 = PM::<FixedI32<U31>>::new_fixedpoint_boundedl2_vec_sum_multithreaded;
+
+ {
+ const SIZE: usize = 5;
+ let fp32_0 = fixed!(0: I1F31);
+
+ // 32 bit fixedpoint, non-power-of-2 vector, single-threaded
+ {
+ let prio3_32 = ctor_32(2, SIZE).unwrap();
+ test_fixed_vec::<_, _, _, SIZE>(fp32_0, prio3_32);
+ }
+
+ // 32 bit fixedpoint, non-power-of-2 vector, multi-threaded
+ #[cfg(feature = "multithreaded")]
+ {
+ let prio3_mt_32 = ctor_mt_32(2, SIZE).unwrap();
+ test_fixed_vec::<_, _, _, SIZE>(fp32_0, prio3_mt_32);
+ }
+ }
+
+ fn test_fixed_vec<Fx, PE, M, const SIZE: usize>(
+ fp_0: Fx,
+ prio3: Prio3<FixedPointBoundedL2VecSum<Fx, PE, M>, XofShake128, 16>,
+ ) where
+ Fx: Fixed + CompatibleFloat + std::ops::Neg<Output = Fx>,
+ PE: Eq + ParallelSumGadget<Field128, PolyEval<Field128>> + Clone + 'static,
+ M: Eq + ParallelSumGadget<Field128, Mul<Field128>> + Clone + 'static,
+ {
+ let fp_vec = vec![fp_0; SIZE];
+
+ let measurements = [fp_vec.clone(), fp_vec];
+ assert_eq!(
+ run_vdaf(&prio3, &(), measurements).unwrap(),
+ vec![0.0; SIZE]
+ );
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "experimental")]
+ fn test_prio3_bounded_fpvec_sum() {
+ type P<Fx> = Prio3FixedPointBoundedL2VecSum<Fx>;
+ let ctor_16 = P::<FixedI16<U15>>::new_fixedpoint_boundedl2_vec_sum;
+ let ctor_32 = P::<FixedI32<U31>>::new_fixedpoint_boundedl2_vec_sum;
+ let ctor_64 = P::<FixedI64<U63>>::new_fixedpoint_boundedl2_vec_sum;
+
+ #[cfg(feature = "multithreaded")]
+ type PM<Fx> = Prio3FixedPointBoundedL2VecSumMultithreaded<Fx>;
+ #[cfg(feature = "multithreaded")]
+ let ctor_mt_16 = PM::<FixedI16<U15>>::new_fixedpoint_boundedl2_vec_sum_multithreaded;
+ #[cfg(feature = "multithreaded")]
+ let ctor_mt_32 = PM::<FixedI32<U31>>::new_fixedpoint_boundedl2_vec_sum_multithreaded;
+ #[cfg(feature = "multithreaded")]
+ let ctor_mt_64 = PM::<FixedI64<U63>>::new_fixedpoint_boundedl2_vec_sum_multithreaded;
+
+ {
+ // 16 bit fixedpoint
+ let fp16_4_inv = fixed!(0.25: I1F15);
+ let fp16_8_inv = fixed!(0.125: I1F15);
+ let fp16_16_inv = fixed!(0.0625: I1F15);
+
+ // two aggregators, three entries per vector.
+ {
+ let prio3_16 = ctor_16(2, 3).unwrap();
+ test_fixed(fp16_4_inv, fp16_8_inv, fp16_16_inv, prio3_16);
+ }
+
+ #[cfg(feature = "multithreaded")]
+ {
+ let prio3_16_mt = ctor_mt_16(2, 3).unwrap();
+ test_fixed(fp16_4_inv, fp16_8_inv, fp16_16_inv, prio3_16_mt);
+ }
+ }
+
+ {
+ // 32 bit fixedpoint
+ let fp32_4_inv = fixed!(0.25: I1F31);
+ let fp32_8_inv = fixed!(0.125: I1F31);
+ let fp32_16_inv = fixed!(0.0625: I1F31);
+
+ {
+ let prio3_32 = ctor_32(2, 3).unwrap();
+ test_fixed(fp32_4_inv, fp32_8_inv, fp32_16_inv, prio3_32);
+ }
+
+ #[cfg(feature = "multithreaded")]
+ {
+ let prio3_32_mt = ctor_mt_32(2, 3).unwrap();
+ test_fixed(fp32_4_inv, fp32_8_inv, fp32_16_inv, prio3_32_mt);
+ }
+ }
+
+ {
+ // 64 bit fixedpoint
+ let fp64_4_inv = fixed!(0.25: I1F63);
+ let fp64_8_inv = fixed!(0.125: I1F63);
+ let fp64_16_inv = fixed!(0.0625: I1F63);
+
+ {
+ let prio3_64 = ctor_64(2, 3).unwrap();
+ test_fixed(fp64_4_inv, fp64_8_inv, fp64_16_inv, prio3_64);
+ }
+
+ #[cfg(feature = "multithreaded")]
+ {
+ let prio3_64_mt = ctor_mt_64(2, 3).unwrap();
+ test_fixed(fp64_4_inv, fp64_8_inv, fp64_16_inv, prio3_64_mt);
+ }
+ }
+
+ fn test_fixed<Fx, PE, M>(
+ fp_4_inv: Fx,
+ fp_8_inv: Fx,
+ fp_16_inv: Fx,
+ prio3: Prio3<FixedPointBoundedL2VecSum<Fx, PE, M>, XofShake128, 16>,
+ ) where
+ Fx: Fixed + CompatibleFloat + std::ops::Neg<Output = Fx>,
+ PE: Eq + ParallelSumGadget<Field128, PolyEval<Field128>> + Clone + 'static,
+ M: Eq + ParallelSumGadget<Field128, Mul<Field128>> + Clone + 'static,
+ {
+ let fp_vec1 = vec![fp_4_inv, fp_8_inv, fp_16_inv];
+ let fp_vec2 = vec![fp_4_inv, fp_8_inv, fp_16_inv];
+
+ let fp_vec3 = vec![-fp_4_inv, -fp_8_inv, -fp_16_inv];
+ let fp_vec4 = vec![-fp_4_inv, -fp_8_inv, -fp_16_inv];
+
+ let fp_vec5 = vec![fp_4_inv, -fp_8_inv, -fp_16_inv];
+ let fp_vec6 = vec![fp_4_inv, fp_8_inv, fp_16_inv];
+
+ // positive entries
+ let fp_list = [fp_vec1, fp_vec2];
+ assert_eq!(
+ run_vdaf(&prio3, &(), fp_list).unwrap(),
+ vec!(0.5, 0.25, 0.125),
+ );
+
+ // negative entries
+ let fp_list2 = [fp_vec3, fp_vec4];
+ assert_eq!(
+ run_vdaf(&prio3, &(), fp_list2).unwrap(),
+ vec!(-0.5, -0.25, -0.125),
+ );
+
+ // both
+ let fp_list3 = [fp_vec5, fp_vec6];
+ assert_eq!(
+ run_vdaf(&prio3, &(), fp_list3).unwrap(),
+ vec!(0.5, 0.0, 0.0),
+ );
+
+ let mut verify_key = [0; 16];
+ let mut nonce = [0; 16];
+ thread_rng().fill(&mut verify_key);
+ thread_rng().fill(&mut nonce);
+
+ let (public_share, mut input_shares) = prio3
+ .shard(&vec![fp_4_inv, fp_8_inv, fp_16_inv], &nonce)
+ .unwrap();
+ input_shares[0].joint_rand_blind.as_mut().unwrap().0[0] ^= 255;
+ let result =
+ run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares);
+ assert_matches!(result, Err(VdafError::Uncategorized(_)));
+
+ let (public_share, mut input_shares) = prio3
+ .shard(&vec![fp_4_inv, fp_8_inv, fp_16_inv], &nonce)
+ .unwrap();
+ assert_matches!(input_shares[0].measurement_share, Share::Leader(ref mut data) => {
+ data[0] += Field128::one();
+ });
+ let result =
+ run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares);
+ assert_matches!(result, Err(VdafError::Uncategorized(_)));
+
+ let (public_share, mut input_shares) = prio3
+ .shard(&vec![fp_4_inv, fp_8_inv, fp_16_inv], &nonce)
+ .unwrap();
+ assert_matches!(input_shares[0].proof_share, Share::Leader(ref mut data) => {
+ data[0] += Field128::one();
+ });
+ let result =
+ run_vdaf_prepare(&prio3, &verify_key, &(), &nonce, public_share, input_shares);
+ assert_matches!(result, Err(VdafError::Uncategorized(_)));
+
+ test_serialization(&prio3, &vec![fp_4_inv, fp_8_inv, fp_16_inv], &nonce).unwrap();
+ }
+ }
+
+ #[test]
+ fn test_prio3_histogram() {
+ let prio3 = Prio3::new_histogram(2, 4, 2).unwrap();
+
+ assert_eq!(
+ run_vdaf(&prio3, &(), [0, 1, 2, 3]).unwrap(),
+ vec![1, 1, 1, 1]
+ );
+ assert_eq!(run_vdaf(&prio3, &(), [0]).unwrap(), vec![1, 0, 0, 0]);
+ assert_eq!(run_vdaf(&prio3, &(), [1]).unwrap(), vec![0, 1, 0, 0]);
+ assert_eq!(run_vdaf(&prio3, &(), [2]).unwrap(), vec![0, 0, 1, 0]);
+ assert_eq!(run_vdaf(&prio3, &(), [3]).unwrap(), vec![0, 0, 0, 1]);
+ test_serialization(&prio3, &3, &[0; 16]).unwrap();
+ }
+
+ #[test]
+ #[cfg(feature = "multithreaded")]
+ fn test_prio3_histogram_multithreaded() {
+ let prio3 = Prio3::new_histogram_multithreaded(2, 4, 2).unwrap();
+
+ assert_eq!(
+ run_vdaf(&prio3, &(), [0, 1, 2, 3]).unwrap(),
+ vec![1, 1, 1, 1]
+ );
+ assert_eq!(run_vdaf(&prio3, &(), [0]).unwrap(), vec![1, 0, 0, 0]);
+ assert_eq!(run_vdaf(&prio3, &(), [1]).unwrap(), vec![0, 1, 0, 0]);
+ assert_eq!(run_vdaf(&prio3, &(), [2]).unwrap(), vec![0, 0, 1, 0]);
+ assert_eq!(run_vdaf(&prio3, &(), [3]).unwrap(), vec![0, 0, 0, 1]);
+ test_serialization(&prio3, &3, &[0; 16]).unwrap();
+ }
+
+ #[test]
+ fn test_prio3_average() {
+ let prio3 = Prio3::new_average(2, 64).unwrap();
+
+ assert_eq!(run_vdaf(&prio3, &(), [17, 8]).unwrap(), 12.5f64);
+ assert_eq!(run_vdaf(&prio3, &(), [1, 1, 1, 1]).unwrap(), 1f64);
+ assert_eq!(run_vdaf(&prio3, &(), [0, 0, 0, 1]).unwrap(), 0.25f64);
+ assert_eq!(
+ run_vdaf(&prio3, &(), [1, 11, 111, 1111, 3, 8]).unwrap(),
+ 207.5f64
+ );
+ }
+
+ #[test]
+ fn test_prio3_input_share() {
+ let prio3 = Prio3::new_sum(5, 16).unwrap();
+ let (_public_share, input_shares) = prio3.shard(&1, &[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 {
+ if let (Share::Helper(left), Share::Helper(right)) =
+ (&x.measurement_share, &y.measurement_share)
+ {
+ assert_ne!(left, right);
+ }
+
+ if let (Share::Helper(left), Share::Helper(right)) =
+ (&x.proof_share, &y.proof_share)
+ {
+ assert_ne!(left, right);
+ }
+
+ assert_ne!(x.joint_rand_blind, y.joint_rand_blind);
+ }
+ }
+ }
+ }
+
+ fn test_serialization<T, P, const SEED_SIZE: usize>(
+ prio3: &Prio3<T, P, SEED_SIZE>,
+ measurement: &T::Measurement,
+ nonce: &[u8; 16],
+ ) -> Result<(), VdafError>
+ where
+ T: Type,
+ P: Xof<SEED_SIZE>,
+ {
+ let mut verify_key = [0; SEED_SIZE];
+ thread_rng().fill(&mut verify_key[..]);
+ let (public_share, input_shares) = prio3.shard(measurement, nonce)?;
+
+ let encoded_public_share = public_share.get_encoded();
+ 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();
+ 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()
+ );
+ }
+
+ let mut prepare_shares = Vec::new();
+ let mut 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();
+ 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();
+ 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()
+ );
+
+ prepare_shares.push(prepare_share);
+ last_prepare_state = Some(prepare_state);
+ }
+
+ let prepare_message = prio3
+ .prepare_shares_to_prepare_message(&(), prepare_shares)
+ .unwrap();
+
+ let encoded_prepare_message = prepare_message.get_encoded();
+ let decoded_prepare_message = Prio3PrepareMessage::get_decoded_with_param(
+ &last_prepare_state.unwrap(),
+ &encoded_prepare_message,
+ )
+ .expect("failed to decode prepare message");
+ assert_eq!(decoded_prepare_message, prepare_message);
+ assert_eq!(
+ prepare_message.encoded_len().unwrap(),
+ encoded_prepare_message.len()
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn roundtrip_output_share() {
+ let vdaf = Prio3::new_count(2).unwrap();
+ fieldvec_roundtrip_test::<Field64, Prio3Count, OutputShare<Field64>>(&vdaf, &(), 1);
+
+ let vdaf = Prio3::new_sum(2, 17).unwrap();
+ fieldvec_roundtrip_test::<Field128, Prio3Sum, OutputShare<Field128>>(&vdaf, &(), 1);
+
+ let vdaf = Prio3::new_histogram(2, 12, 3).unwrap();
+ fieldvec_roundtrip_test::<Field128, Prio3Histogram, OutputShare<Field128>>(&vdaf, &(), 12);
+ }
+
+ #[test]
+ fn roundtrip_aggregate_share() {
+ let vdaf = Prio3::new_count(2).unwrap();
+ fieldvec_roundtrip_test::<Field64, Prio3Count, AggregateShare<Field64>>(&vdaf, &(), 1);
+
+ let vdaf = Prio3::new_sum(2, 17).unwrap();
+ fieldvec_roundtrip_test::<Field128, Prio3Sum, AggregateShare<Field128>>(&vdaf, &(), 1);
+
+ let vdaf = Prio3::new_histogram(2, 12, 3).unwrap();
+ fieldvec_roundtrip_test::<Field128, Prio3Histogram, AggregateShare<Field128>>(
+ &vdaf,
+ &(),
+ 12,
+ );
+ }
+
+ #[test]
+ fn public_share_equality_test() {
+ equality_comparison_test(&[
+ Prio3PublicShare {
+ joint_rand_parts: Some(Vec::from([Seed([0])])),
+ },
+ Prio3PublicShare {
+ joint_rand_parts: Some(Vec::from([Seed([1])])),
+ },
+ Prio3PublicShare {
+ joint_rand_parts: None,
+ },
+ ])
+ }
+
+ #[test]
+ fn input_share_equality_test() {
+ equality_comparison_test(&[
+ // Default.
+ Prio3InputShare {
+ measurement_share: Share::Leader(Vec::from([0])),
+ proof_share: Share::Leader(Vec::from([1])),
+ joint_rand_blind: Some(Seed([2])),
+ },
+ // Modified measurement share.
+ Prio3InputShare {
+ measurement_share: Share::Leader(Vec::from([100])),
+ proof_share: Share::Leader(Vec::from([1])),
+ joint_rand_blind: Some(Seed([2])),
+ },
+ // Modified proof share.
+ Prio3InputShare {
+ measurement_share: Share::Leader(Vec::from([0])),
+ proof_share: Share::Leader(Vec::from([101])),
+ joint_rand_blind: Some(Seed([2])),
+ },
+ // Modified joint_rand_blind.
+ Prio3InputShare {
+ measurement_share: Share::Leader(Vec::from([0])),
+ proof_share: Share::Leader(Vec::from([1])),
+ joint_rand_blind: Some(Seed([102])),
+ },
+ // Missing joint_rand_blind.
+ Prio3InputShare {
+ measurement_share: Share::Leader(Vec::from([0])),
+ proof_share: Share::Leader(Vec::from([1])),
+ joint_rand_blind: None,
+ },
+ ])
+ }
+
+ #[test]
+ fn prepare_share_equality_test() {
+ equality_comparison_test(&[
+ // Default.
+ Prio3PrepareShare {
+ verifier: Vec::from([0]),
+ joint_rand_part: Some(Seed([1])),
+ },
+ // Modified verifier.
+ Prio3PrepareShare {
+ verifier: Vec::from([100]),
+ joint_rand_part: Some(Seed([1])),
+ },
+ // Modified joint_rand_part.
+ Prio3PrepareShare {
+ verifier: Vec::from([0]),
+ joint_rand_part: Some(Seed([101])),
+ },
+ // Missing joint_rand_part.
+ Prio3PrepareShare {
+ verifier: Vec::from([0]),
+ joint_rand_part: None,
+ },
+ ])
+ }
+
+ #[test]
+ fn prepare_message_equality_test() {
+ equality_comparison_test(&[
+ // Default.
+ Prio3PrepareMessage {
+ joint_rand_seed: Some(Seed([0])),
+ },
+ // Modified joint_rand_seed.
+ Prio3PrepareMessage {
+ joint_rand_seed: Some(Seed([100])),
+ },
+ // Missing joint_rand_seed.
+ Prio3PrepareMessage {
+ joint_rand_seed: None,
+ },
+ ])
+ }
+
+ #[test]
+ fn prepare_state_equality_test() {
+ equality_comparison_test(&[
+ // Default.
+ Prio3PrepareState {
+ measurement_share: Share::Leader(Vec::from([0])),
+ joint_rand_seed: Some(Seed([1])),
+ agg_id: 2,
+ verifier_len: 3,
+ },
+ // Modified measurement share.
+ Prio3PrepareState {
+ measurement_share: Share::Leader(Vec::from([100])),
+ joint_rand_seed: Some(Seed([1])),
+ agg_id: 2,
+ verifier_len: 3,
+ },
+ // Modified joint_rand_seed.
+ Prio3PrepareState {
+ measurement_share: Share::Leader(Vec::from([0])),
+ joint_rand_seed: Some(Seed([101])),
+ agg_id: 2,
+ verifier_len: 3,
+ },
+ // Missing joint_rand_seed.
+ Prio3PrepareState {
+ measurement_share: Share::Leader(Vec::from([0])),
+ joint_rand_seed: None,
+ agg_id: 2,
+ verifier_len: 3,
+ },
+ // Modified agg_id.
+ Prio3PrepareState {
+ measurement_share: Share::Leader(Vec::from([0])),
+ joint_rand_seed: Some(Seed([1])),
+ agg_id: 102,
+ verifier_len: 3,
+ },
+ // Modified verifier_len.
+ Prio3PrepareState {
+ measurement_share: Share::Leader(Vec::from([0])),
+ joint_rand_seed: Some(Seed([1])),
+ agg_id: 2,
+ verifier_len: 103,
+ },
+ ])
+ }
+
+ #[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);
+
+ // additional arbitrary test cases
+ assert_eq!(optimal_chunk_length(40), 6);
+ assert_eq!(optimal_chunk_length(10_000), 79);
+ assert_eq!(optimal_chunk_length(100_000), 393);
+
+ // confirm that the chunk lengths are truly optimal
+ for measurement_length in [2, 3, 4, 5, 18, 19, 40] {
+ let optimal_chunk_length = optimal_chunk_length(measurement_length);
+ let optimal_proof_length = Histogram::<Field128, ParallelSum<_, _>>::new(
+ measurement_length,
+ optimal_chunk_length,
+ )
+ .unwrap()
+ .proof_len();
+ for chunk_length in 1..=measurement_length {
+ let proof_length =
+ Histogram::<Field128, ParallelSum<_, _>>::new(measurement_length, chunk_length)
+ .unwrap()
+ .proof_len();
+ assert!(proof_length >= optimal_proof_length);
+ }
+ }
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/prio3_test.rs b/third_party/rust/prio/src/vdaf/prio3_test.rs
new file mode 100644
index 0000000000..372a2c8560
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/prio3_test.rs
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: MPL-2.0
+
+use crate::{
+ codec::{Encode, ParameterizedDecode},
+ flp::Type,
+ vdaf::{
+ prio3::{Prio3, Prio3InputShare, Prio3PrepareShare, Prio3PublicShare},
+ xof::Xof,
+ Aggregator, Collector, OutputShare, PrepareTransition, Vdaf,
+ },
+};
+use serde::{Deserialize, Serialize};
+use std::{collections::HashMap, convert::TryInto, fmt::Debug};
+
+#[derive(Debug, Deserialize, Serialize)]
+struct TEncoded(#[serde(with = "hex")] Vec<u8>);
+
+impl AsRef<[u8]> for TEncoded {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+#[derive(Deserialize, Serialize)]
+struct TPrio3Prep<M> {
+ measurement: M,
+ #[serde(with = "hex")]
+ nonce: Vec<u8>,
+ #[serde(with = "hex")]
+ rand: Vec<u8>,
+ public_share: TEncoded,
+ input_shares: Vec<TEncoded>,
+ prep_shares: Vec<Vec<TEncoded>>,
+ prep_messages: Vec<TEncoded>,
+ out_shares: Vec<Vec<TEncoded>>,
+}
+
+#[derive(Deserialize, Serialize)]
+struct TPrio3<M> {
+ verify_key: TEncoded,
+ shares: u8,
+ prep: Vec<TPrio3Prep<M>>,
+ agg_shares: Vec<TEncoded>,
+ agg_result: serde_json::Value,
+ #[serde(flatten)]
+ other_params: HashMap<String, serde_json::Value>,
+}
+
+macro_rules! err {
+ (
+ $test_num:ident,
+ $error:expr,
+ $msg:expr
+ ) => {
+ panic!("test #{} failed: {} err: {}", $test_num, $msg, $error)
+ };
+}
+
+// TODO Generalize this method to work with any VDAF. To do so we would need to add
+// `shard_with_random()` to traits. (There may be a less invasive alternative.)
+fn check_prep_test_vec<M, T, P, const SEED_SIZE: usize>(
+ prio3: &Prio3<T, P, SEED_SIZE>,
+ verify_key: &[u8; SEED_SIZE],
+ test_num: usize,
+ t: &TPrio3Prep<M>,
+) -> Vec<OutputShare<T::Field>>
+where
+ T: Type<Measurement = M>,
+ P: Xof<SEED_SIZE>,
+{
+ let nonce = <[u8; 16]>::try_from(t.nonce.clone()).unwrap();
+ let (public_share, input_shares) = prio3
+ .shard_with_random(&t.measurement, &nonce, &t.rand)
+ .expect("failed to generate input shares");
+
+ assert_eq!(
+ public_share,
+ Prio3PublicShare::get_decoded_with_param(prio3, t.public_share.as_ref())
+ .unwrap_or_else(|e| err!(test_num, e, "decode test vector (public share)")),
+ );
+ for (agg_id, want) in t.input_shares.iter().enumerate() {
+ assert_eq!(
+ input_shares[agg_id],
+ Prio3InputShare::get_decoded_with_param(&(prio3, agg_id), want.as_ref())
+ .unwrap_or_else(|e| err!(test_num, e, "decode test vector (input share)")),
+ "#{test_num}"
+ );
+ assert_eq!(
+ input_shares[agg_id].get_encoded(),
+ want.as_ref(),
+ "#{test_num}"
+ )
+ }
+
+ let mut states = Vec::new();
+ let mut prep_shares = Vec::new();
+ for (agg_id, input_share) in input_shares.iter().enumerate() {
+ let (state, prep_share) = prio3
+ .prepare_init(verify_key, agg_id, &(), &nonce, &public_share, input_share)
+ .unwrap_or_else(|e| err!(test_num, e, "prep state init"));
+ states.push(state);
+ prep_shares.push(prep_share);
+ }
+
+ assert_eq!(1, t.prep_shares.len(), "#{test_num}");
+ for (i, want) in t.prep_shares[0].iter().enumerate() {
+ assert_eq!(
+ prep_shares[i],
+ Prio3PrepareShare::get_decoded_with_param(&states[i], want.as_ref())
+ .unwrap_or_else(|e| err!(test_num, e, "decode test vector (prep share)")),
+ "#{test_num}"
+ );
+ assert_eq!(prep_shares[i].get_encoded(), want.as_ref(), "#{test_num}");
+ }
+
+ let inbound = prio3
+ .prepare_shares_to_prepare_message(&(), prep_shares)
+ .unwrap_or_else(|e| err!(test_num, e, "prep preprocess"));
+ assert_eq!(t.prep_messages.len(), 1);
+ assert_eq!(inbound.get_encoded(), t.prep_messages[0].as_ref());
+
+ let mut out_shares = Vec::new();
+ for state in states.iter_mut() {
+ match prio3.prepare_next(state.clone(), inbound.clone()).unwrap() {
+ PrepareTransition::Finish(out_share) => {
+ out_shares.push(out_share);
+ }
+ _ => panic!("unexpected transition"),
+ }
+ }
+
+ for (got, want) in out_shares.iter().zip(t.out_shares.iter()) {
+ let got: Vec<Vec<u8>> = got.as_ref().iter().map(|x| x.get_encoded()).collect();
+ assert_eq!(got.len(), want.len());
+ for (got_elem, want_elem) in got.iter().zip(want.iter()) {
+ assert_eq!(got_elem.as_slice(), want_elem.as_ref());
+ }
+ }
+
+ out_shares
+}
+
+#[must_use]
+fn check_aggregate_test_vec<M, T, P, const SEED_SIZE: usize>(
+ prio3: &Prio3<T, P, SEED_SIZE>,
+ t: &TPrio3<M>,
+) -> T::AggregateResult
+where
+ T: Type<Measurement = M>,
+ P: Xof<SEED_SIZE>,
+{
+ let verify_key = t.verify_key.as_ref().try_into().unwrap();
+
+ let mut all_output_shares = vec![Vec::new(); prio3.num_aggregators()];
+ for (test_num, p) in t.prep.iter().enumerate() {
+ let output_shares = check_prep_test_vec(prio3, verify_key, test_num, p);
+ for (aggregator_output_shares, output_share) in
+ all_output_shares.iter_mut().zip(output_shares.into_iter())
+ {
+ aggregator_output_shares.push(output_share);
+ }
+ }
+
+ let aggregate_shares = all_output_shares
+ .into_iter()
+ .map(|aggregator_output_shares| prio3.aggregate(&(), aggregator_output_shares).unwrap())
+ .collect::<Vec<_>>();
+
+ for (got, want) in aggregate_shares.iter().zip(t.agg_shares.iter()) {
+ let got = got.get_encoded();
+ assert_eq!(got.as_slice(), want.as_ref());
+ }
+
+ prio3.unshard(&(), aggregate_shares, 1).unwrap()
+}
+
+#[test]
+fn test_vec_prio3_count() {
+ for test_vector_str in [
+ include_str!("test_vec/07/Prio3Count_0.json"),
+ include_str!("test_vec/07/Prio3Count_1.json"),
+ ] {
+ let t: TPrio3<u64> = serde_json::from_str(test_vector_str).unwrap();
+ let prio3 = Prio3::new_count(t.shares).unwrap();
+
+ let aggregate_result = check_aggregate_test_vec(&prio3, &t);
+ assert_eq!(aggregate_result, t.agg_result.as_u64().unwrap());
+ }
+}
+
+#[test]
+fn test_vec_prio3_sum() {
+ for test_vector_str in [
+ include_str!("test_vec/07/Prio3Sum_0.json"),
+ include_str!("test_vec/07/Prio3Sum_1.json"),
+ ] {
+ let t: TPrio3<u128> = serde_json::from_str(test_vector_str).unwrap();
+ let bits = t.other_params["bits"].as_u64().unwrap() as usize;
+ let prio3 = Prio3::new_sum(t.shares, bits).unwrap();
+
+ let aggregate_result = check_aggregate_test_vec(&prio3, &t);
+ assert_eq!(aggregate_result, t.agg_result.as_u64().unwrap() as u128);
+ }
+}
+
+#[test]
+fn test_vec_prio3_sum_vec() {
+ for test_vector_str in [
+ include_str!("test_vec/07/Prio3SumVec_0.json"),
+ include_str!("test_vec/07/Prio3SumVec_1.json"),
+ ] {
+ let t: TPrio3<Vec<u128>> = serde_json::from_str(test_vector_str).unwrap();
+ let bits = t.other_params["bits"].as_u64().unwrap() as usize;
+ let length = t.other_params["length"].as_u64().unwrap() as usize;
+ let chunk_length = t.other_params["chunk_length"].as_u64().unwrap() as usize;
+ let prio3 = Prio3::new_sum_vec(t.shares, bits, length, chunk_length).unwrap();
+
+ let aggregate_result = check_aggregate_test_vec(&prio3, &t);
+ let expected_aggregate_result = t
+ .agg_result
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|val| val.as_u64().unwrap() as u128)
+ .collect::<Vec<u128>>();
+ assert_eq!(aggregate_result, expected_aggregate_result);
+ }
+}
+
+#[test]
+fn test_vec_prio3_histogram() {
+ for test_vector_str in [
+ include_str!("test_vec/07/Prio3Histogram_0.json"),
+ include_str!("test_vec/07/Prio3Histogram_1.json"),
+ ] {
+ let t: TPrio3<usize> = serde_json::from_str(test_vector_str).unwrap();
+ let length = t.other_params["length"].as_u64().unwrap() as usize;
+ let chunk_length = t.other_params["chunk_length"].as_u64().unwrap() as usize;
+ let prio3 = Prio3::new_histogram(t.shares, length, chunk_length).unwrap();
+
+ let aggregate_result = check_aggregate_test_vec(&prio3, &t);
+ let expected_aggregate_result = t
+ .agg_result
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|val| val.as_u64().unwrap() as u128)
+ .collect::<Vec<u128>>();
+ assert_eq!(aggregate_result, expected_aggregate_result);
+ }
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/IdpfPoplar_0.json b/third_party/rust/prio/src/vdaf/test_vec/07/IdpfPoplar_0.json
new file mode 100644
index 0000000000..2ff7aa7ffd
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/IdpfPoplar_0.json
@@ -0,0 +1,52 @@
+{
+ "alpha": "0",
+ "beta_inner": [
+ [
+ "0",
+ "0"
+ ],
+ [
+ "1",
+ "1"
+ ],
+ [
+ "2",
+ "2"
+ ],
+ [
+ "3",
+ "3"
+ ],
+ [
+ "4",
+ "4"
+ ],
+ [
+ "5",
+ "5"
+ ],
+ [
+ "6",
+ "6"
+ ],
+ [
+ "7",
+ "7"
+ ],
+ [
+ "8",
+ "8"
+ ]
+ ],
+ "beta_leaf": [
+ "9",
+ "9"
+ ],
+ "binder": "736f6d65206e6f6e6365",
+ "bits": 10,
+ "keys": [
+ "000102030405060708090a0b0c0d0e0f",
+ "101112131415161718191a1b1c1d1e1f"
+ ],
+ "public_share": "921909356f44964d29c537aeeaeba92e573e4298c88dcc35bd3ae6acb4367236226b1af3151d5814f308f04e208fde2110c72523338563bc1c5fb47d22b5c34ae102e1e82fa250c7e23b95e985f91d7d91887fa7fb301ec20a06b1d4408d9a594754dcd86ec00c91f40f17c1ff52ed99fcd59965fe243a6cec7e672fefc5e3a29e653d5dcca8917e8af2c4f19d122c6dd30a3e2a80fb809383ced9d24fcd86516025174f5183fddfc6d74dde3b78834391c785defc8e4fbff92214df4c8322ee433a8eaeed7369419e0d6037a536e081df333aaab9e8e4d207d846961f015d96d57e3b59e24927773d6e0d66108955c1da134baab4eacd363c8e452b8c3845d5fb5c0ff6c27d7423a73d32742ccc3c750a17cd1f6026dd98a2cf6d2bff2dd339017b25af23d6db00ae8975e3f7e6aaef4af71f3e8cd14eb5c4373db9c3a76fc04659b761e650a97cb873df894064ecb2043a4317ef237ffe8f130eb5c2ca2a132c16f14943cd7e462568c8544b82e29329eb2a"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_0.json b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_0.json
new file mode 100644
index 0000000000..79fadca3df
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_0.json
@@ -0,0 +1,56 @@
+{
+ "agg_param": [
+ 0,
+ [
+ 0,
+ 1
+ ]
+ ],
+ "agg_result": [
+ 0,
+ 1
+ ],
+ "agg_shares": [
+ "70f1cb8dc03c9eea88d270d6211a8667",
+ "910e34723ec361157a2d8f29dde57998"
+ ],
+ "bits": 4,
+ "prep": [
+ {
+ "input_shares": [
+ "000102030405060708090a0b0c0d0e0f202122232425262728292a2b2c2d2e2f311e448ab125690bc3a084a34301982c6aa325ab3f268338c9f4db9bda518743ee75c6c8ef7655d2d167d5385213d3bd1be920fad83c1b35fd9239efb406370db4d0a8e97eb41413957e264ded24e074ca433b6b5d451d0f65ec1d4ac246a36da12ecb2a537a449aeeb9d70bd064e930",
+ "101112131415161718191a1b1c1d1e1f303132333435363738393a3b3c3d3e3f96998a1415c9876c2887f2dcc52f090ec64bcaeb3165e98f47d261a0bed49a156c054b063cad6277a526505ac31807288abfb796b6cee614e5c41ab75c2b9912cc246c66c5e248a7cfc30ad92eefec7e67c2da39726d5b7277eb1449c779f834b0ab75f383f07bd2f3747cb7b98f617c"
+ ],
+ "measurement": 13,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "70f1cb8dc03c9eea",
+ "88d270d6211a8667"
+ ],
+ [
+ "910e34723ec36115",
+ "7a2d8f29dde57998"
+ ]
+ ],
+ "prep_messages": [
+ "d4cd54eb29f676c2d10fab848e6e85ebd51804e3562cf23b",
+ ""
+ ],
+ "prep_shares": [
+ [
+ "bd68d28c9fff9a30f84122278759025501b83270bf27b41d",
+ "1765825e8af6db91d9cd885d07158396d460d17297043e1e"
+ ],
+ [
+ "7c9659b7c681b4a4",
+ "8569a648387e4b5b"
+ ]
+ ],
+ "public_share": "b2c16aa5676c3188a74dff403c179dfb28c515d5a9892f38e5eb7be1c96bfdb0ebf761e6500e206a4ce363d09ab0a1d9b225e51798fd599f9dcd204058958c2d625646e5662534ff6650a9af834a248d46304f6d7a3b845f46c71433c833a86846147e264aaee1eb3e0bb19e53cd521e92ab9991265b731bfdb508fb164cd9d48d2c43953e7144a8b97e395bdd8aa2db7a1088f3bf8d245e15172e88764bba8271f6a19f70dc47a279e899394ea8658958",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_1.json b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_1.json
new file mode 100644
index 0000000000..a566fe8b4d
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_1.json
@@ -0,0 +1,64 @@
+{
+ "agg_param": [
+ 1,
+ [
+ 0,
+ 1,
+ 2,
+ 3
+ ]
+ ],
+ "agg_result": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "agg_shares": [
+ "d83fbcbf13566502f5849058b8b089e568a4e8aab8565425f69a56f809fc4527",
+ "29c04340eba99afd0c7b6fa7464f761a995b175546a9abda0c65a907f503bad8"
+ ],
+ "bits": 4,
+ "prep": [
+ {
+ "input_shares": [
+ "000102030405060708090a0b0c0d0e0f202122232425262728292a2b2c2d2e2f311e448ab125690bc3a084a34301982c6aa325ab3f268338c9f4db9bda518743ee75c6c8ef7655d2d167d5385213d3bd1be920fad83c1b35fd9239efb406370db4d0a8e97eb41413957e264ded24e074ca433b6b5d451d0f65ec1d4ac246a36da12ecb2a537a449aeeb9d70bd064e930",
+ "101112131415161718191a1b1c1d1e1f303132333435363738393a3b3c3d3e3f96998a1415c9876c2887f2dcc52f090ec64bcaeb3165e98f47d261a0bed49a156c054b063cad6277a526505ac31807288abfb796b6cee614e5c41ab75c2b9912cc246c66c5e248a7cfc30ad92eefec7e67c2da39726d5b7277eb1449c779f834b0ab75f383f07bd2f3747cb7b98f617c"
+ ],
+ "measurement": 13,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "d83fbcbf13566502",
+ "f5849058b8b089e5",
+ "68a4e8aab8565425",
+ "f69a56f809fc4527"
+ ],
+ [
+ "29c04340eba99afd",
+ "0c7b6fa7464f761a",
+ "995b175546a9abda",
+ "0c65a907f503bad8"
+ ]
+ ],
+ "prep_messages": [
+ "d45c0eabcc906acfb8239f3d0ef2b69a0f465979b04e355c",
+ ""
+ ],
+ "prep_shares": [
+ [
+ "5d1b91841835491251436306076eaaa674d4b95b84b2a084",
+ "77417d26b45b21bd68e03b3706840cf49c719f1d2b9c94d7"
+ ],
+ [
+ "6e703a28b5960604",
+ "938fc5d74969f9fb"
+ ]
+ ],
+ "public_share": "b2c16aa5676c3188a74dff403c179dfb28c515d5a9892f38e5eb7be1c96bfdb0ebf761e6500e206a4ce363d09ab0a1d9b225e51798fd599f9dcd204058958c2d625646e5662534ff6650a9af834a248d46304f6d7a3b845f46c71433c833a86846147e264aaee1eb3e0bb19e53cd521e92ab9991265b731bfdb508fb164cd9d48d2c43953e7144a8b97e395bdd8aa2db7a1088f3bf8d245e15172e88764bba8271f6a19f70dc47a279e899394ea8658958",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_2.json b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_2.json
new file mode 100644
index 0000000000..8141bc942e
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_2.json
@@ -0,0 +1,64 @@
+{
+ "agg_param": [
+ 2,
+ [
+ 0,
+ 2,
+ 4,
+ 6
+ ]
+ ],
+ "agg_result": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "agg_shares": [
+ "7ea47022f22f6be9bce8e0ee2eb522bcbc2d246c17704beed7043426b646fe26",
+ "835b8fdd0cd0941645171f11d04add4345d2db93e78fb4112bfbcbd948b901d9"
+ ],
+ "bits": 4,
+ "prep": [
+ {
+ "input_shares": [
+ "000102030405060708090a0b0c0d0e0f202122232425262728292a2b2c2d2e2f311e448ab125690bc3a084a34301982c6aa325ab3f268338c9f4db9bda518743ee75c6c8ef7655d2d167d5385213d3bd1be920fad83c1b35fd9239efb406370db4d0a8e97eb41413957e264ded24e074ca433b6b5d451d0f65ec1d4ac246a36da12ecb2a537a449aeeb9d70bd064e930",
+ "101112131415161718191a1b1c1d1e1f303132333435363738393a3b3c3d3e3f96998a1415c9876c2887f2dcc52f090ec64bcaeb3165e98f47d261a0bed49a156c054b063cad6277a526505ac31807288abfb796b6cee614e5c41ab75c2b9912cc246c66c5e248a7cfc30ad92eefec7e67c2da39726d5b7277eb1449c779f834b0ab75f383f07bd2f3747cb7b98f617c"
+ ],
+ "measurement": 13,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "7ea47022f22f6be9",
+ "bce8e0ee2eb522bc",
+ "bc2d246c17704bee",
+ "d7043426b646fe26"
+ ],
+ [
+ "835b8fdd0cd09416",
+ "45171f11d04add43",
+ "45d2db93e78fb411",
+ "2bfbcbd948b901d9"
+ ]
+ ],
+ "prep_messages": [
+ "6fb240ce8b8a2a8ce62112240f676105e0398515599f04b4",
+ ""
+ ],
+ "prep_shares": [
+ [
+ "ca0f02c7c61655263bf76d954b8abd16eb6e5ce2b26911b2",
+ "a5a23e07c573d565ac2aa48ec2dca3eef5ca2833a635f301"
+ ],
+ [
+ "f5171a3cc9d49422",
+ "0ce8e5c3352b6bdd"
+ ]
+ ],
+ "public_share": "b2c16aa5676c3188a74dff403c179dfb28c515d5a9892f38e5eb7be1c96bfdb0ebf761e6500e206a4ce363d09ab0a1d9b225e51798fd599f9dcd204058958c2d625646e5662534ff6650a9af834a248d46304f6d7a3b845f46c71433c833a86846147e264aaee1eb3e0bb19e53cd521e92ab9991265b731bfdb508fb164cd9d48d2c43953e7144a8b97e395bdd8aa2db7a1088f3bf8d245e15172e88764bba8271f6a19f70dc47a279e899394ea8658958",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_3.json b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_3.json
new file mode 100644
index 0000000000..1741ec0ebc
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Poplar1_3.json
@@ -0,0 +1,76 @@
+{
+ "agg_param": [
+ 3,
+ [
+ 1,
+ 3,
+ 5,
+ 7,
+ 9,
+ 13,
+ 15
+ ]
+ ],
+ "agg_result": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0
+ ],
+ "agg_shares": [
+ "ec2be80f01fd1ded599b1a18d6ef112c400f421cca2c080d4ccc5cdd09562b3e556c1aaabe9dd47e8bc25979394c7bb5c61fd1db34b8dfdcc3eff4a5304fb7706b5462025bb400e644f2e0752f38098702491691494a2b498176ef41c4e6a962f716473c53087a3e80db0b9acb50cb15081b5ea4b50c48093f67a8c75875422dfd64ab2fa71fa3f3b55ec708ba4086672aff514d0cffe6f1c07f117c22af9b2c67b2a0c7ec1366ce474721174edb8b9eb33faef5f9c9d0c956e4407a86473120cfa46e8c634c1bc66c63a2009911f82c8426a45013e637aaba0e471b03f0a67a",
+ "01d417f0fe02e212a664e5e72910eed3bff0bde335d3f7f2b333a322f6a9d4419893e55541622b81743da686c6b3844a39e02e24cb4720233c100b5acfb0480f82ab9dfda44bff19bb0d1f8ad0c7f678fdb6e96eb6b5d4b67e8910be3b19561df6e8b8c3acf785c17f24f46534af34eaf7e4a15b4af3b7f6c0985738a78abd52f09a54d058e05c0c4aa138f745bf7998d500aeb2f300190e3f80ee83dd506453874d5f3813ec9931b8b8dee8b12474614cc0510a06362f36a91bbf8579b8ce5f1e5b91739cb3e439939c5dff66ee07d37bd95bafec19c85545f1b8e4fc0f5905"
+ ],
+ "bits": 4,
+ "prep": [
+ {
+ "input_shares": [
+ "000102030405060708090a0b0c0d0e0f202122232425262728292a2b2c2d2e2f311e448ab125690bc3a084a34301982c6aa325ab3f268338c9f4db9bda518743ee75c6c8ef7655d2d167d5385213d3bd1be920fad83c1b35fd9239efb406370db4d0a8e97eb41413957e264ded24e074ca433b6b5d451d0f65ec1d4ac246a36da12ecb2a537a449aeeb9d70bd064e930",
+ "101112131415161718191a1b1c1d1e1f303132333435363738393a3b3c3d3e3f96998a1415c9876c2887f2dcc52f090ec64bcaeb3165e98f47d261a0bed49a156c054b063cad6277a526505ac31807288abfb796b6cee614e5c41ab75c2b9912cc246c66c5e248a7cfc30ad92eefec7e67c2da39726d5b7277eb1449c779f834b0ab75f383f07bd2f3747cb7b98f617c"
+ ],
+ "measurement": 13,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "ec2be80f01fd1ded599b1a18d6ef112c400f421cca2c080d4ccc5cdd09562b3e",
+ "556c1aaabe9dd47e8bc25979394c7bb5c61fd1db34b8dfdcc3eff4a5304fb770",
+ "6b5462025bb400e644f2e0752f38098702491691494a2b498176ef41c4e6a962",
+ "f716473c53087a3e80db0b9acb50cb15081b5ea4b50c48093f67a8c75875422d",
+ "fd64ab2fa71fa3f3b55ec708ba4086672aff514d0cffe6f1c07f117c22af9b2c",
+ "67b2a0c7ec1366ce474721174edb8b9eb33faef5f9c9d0c956e4407a86473120",
+ "cfa46e8c634c1bc66c63a2009911f82c8426a45013e637aaba0e471b03f0a67a"
+ ],
+ [
+ "01d417f0fe02e212a664e5e72910eed3bff0bde335d3f7f2b333a322f6a9d441",
+ "9893e55541622b81743da686c6b3844a39e02e24cb4720233c100b5acfb0480f",
+ "82ab9dfda44bff19bb0d1f8ad0c7f678fdb6e96eb6b5d4b67e8910be3b19561d",
+ "f6e8b8c3acf785c17f24f46534af34eaf7e4a15b4af3b7f6c0985738a78abd52",
+ "f09a54d058e05c0c4aa138f745bf7998d500aeb2f300190e3f80ee83dd506453",
+ "874d5f3813ec9931b8b8dee8b12474614cc0510a06362f36a91bbf8579b8ce5f",
+ "1e5b91739cb3e439939c5dff66ee07d37bd95bafec19c85545f1b8e4fc0f5905"
+ ]
+ ],
+ "prep_messages": [
+ "4a2b97cf17e54b126a86c6791c50d6507ee8b74b3d9903bcf3881121bc6e0975c4efb2d8b8a132b8a6caa4eb39ac2bbb5bdc351604fa9e78d1a6f5a5f615bb0c8819f485d8b24a4e48da47d3b7458a9cfde1e85c66453319a3f6d43dc40a0135",
+ ""
+ ],
+ "prep_shares": [
+ [
+ "4e64e5ed76c69ef68d3e144918a719986e40ab82f34bd30298b0085a3265d16988b8f646731ef47cb2fb1598e4cb817747623f1cc70ee7843ce1a9d6e3cf5c456801c9a3ae0c7c7663349a3daaf8fb51d165085c751e5bdd4e800df9e1e0193e",
+ "fcc6b1e1a01ead1bdc47b23004a9bcb80fa80cc9494d30b95bd808c78909380b2937bc9145833e3bf4ce8e5355e0a943147af6f93cebb7f394c54bcf12465e470d182be229a6ced7e4a5ad950d4d8e4a2c7ce000f126d83b5476c744e229e776"
+ ],
+ [
+ "003c39f76240f6f9bcc6065a247b4432a651d5d72a35aff45928eec28c8a9d07",
+ "edc3c6089dbf09064339f9a5db84bbcd59ae2a28d5ca500ba6d7113d73756278"
+ ]
+ ],
+ "public_share": "b2c16aa5676c3188a74dff403c179dfb28c515d5a9892f38e5eb7be1c96bfdb0ebf761e6500e206a4ce363d09ab0a1d9b225e51798fd599f9dcd204058958c2d625646e5662534ff6650a9af834a248d46304f6d7a3b845f46c71433c833a86846147e264aaee1eb3e0bb19e53cd521e92ab9991265b731bfdb508fb164cd9d48d2c43953e7144a8b97e395bdd8aa2db7a1088f3bf8d245e15172e88764bba8271f6a19f70dc47a279e899394ea8658958",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_0.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_0.json
new file mode 100644
index 0000000000..c27ad93435
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_0.json
@@ -0,0 +1,39 @@
+{
+ "agg_param": null,
+ "agg_result": 1,
+ "agg_shares": [
+ "afead111dacc0c7e",
+ "53152eee2433f381"
+ ],
+ "prep": [
+ {
+ "input_shares": [
+ "afead111dacc0c7ec08c411babd6e2404df512ddfa0a81736b7607f4ccb3f39e414fdb4bc89a63569702c92aed6a6a96",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ ],
+ "measurement": 1,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "afead111dacc0c7e"
+ ],
+ [
+ "53152eee2433f381"
+ ]
+ ],
+ "prep_messages": [
+ ""
+ ],
+ "prep_shares": [
+ [
+ "123f23c117b7ed6099be9e6a31a42a9caa60882a3b4aa50303f8b588c9efe60b",
+ "efc0dc3ee748129f2da661f47a625a57d64a5b62ab38647c34bb161c7576d721"
+ ]
+ ],
+ "public_share": "",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_1.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_1.json
new file mode 100644
index 0000000000..148fe6df58
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Count_1.json
@@ -0,0 +1,45 @@
+{
+ "agg_param": null,
+ "agg_result": 1,
+ "agg_shares": [
+ "c5647e016eea69f6",
+ "53152eee2433f381",
+ "eb8553106be2a287"
+ ],
+ "prep": [
+ {
+ "input_shares": [
+ "c5647e016eea69f6d10e90d05e2ad8b402b8580f394a719b371ae8f1a364b280d08ca7177946a1a0b9643e2469b0a2e9",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
+ ],
+ "measurement": 1,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "c5647e016eea69f6"
+ ],
+ [
+ "53152eee2433f381"
+ ],
+ [
+ "eb8553106be2a287"
+ ]
+ ],
+ "prep_messages": [
+ ""
+ ],
+ "prep_shares": [
+ [
+ "5c8d00fd24e449d375581d6adbeaf9cf4bdface6d368fd7b1562e5bf47b9fa68",
+ "efc0dc3ee748129f2da661f47a625a57d64a5b62ab38647c34bb161c7576d721",
+ "b7b122c4f1d2a38df764c623c266f02f7b5178c3d64735ec06037585d643f528"
+ ]
+ ],
+ "public_share": "",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 3,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_0.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_0.json
new file mode 100644
index 0000000000..099f786669
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_0.json
@@ -0,0 +1,52 @@
+{
+ "agg_param": null,
+ "agg_result": [
+ 0,
+ 0,
+ 1,
+ 0
+ ],
+ "agg_shares": [
+ "14be9c4ef7a6e12e963fdeac21cebdd4d36e13f4bc25306322e56303c62c90afd73f6b4aa9fdf33cb0afb55426d645ff8cd7e78cebf9d4f1087f6d4a033c8eae",
+ "ed4163b108591ed14dc02153de31422b2e91ec0b43dacf9cc11a9cfc39d36f502bc094b556020cc333504aabd929ba007528187314062b0edb8092b5fcc37151"
+ ],
+ "chunk_length": 2,
+ "length": 4,
+ "prep": [
+ {
+ "input_shares": [
+ "14be9c4ef7a6e12e963fdeac21cebdd4d36e13f4bc25306322e56303c62c90afd73f6b4aa9fdf33cb0afb55426d645ff8cd7e78cebf9d4f1087f6d4a033c8eaeec786d3b212d968c939de66318dbacafe73c1f5aa3e9078ba2f63ec5179e6b4694612c36f5d4d539d46dab1ac20e43963978d9dd36f19f31c83e58c903c2cd94215c68b15f5d6071e9e19fa973829dc71b536351b0db1072e77b7570e3e06c65fac248d21dd970f29640050e901d06775f05a897850cab5707ac25543ed6ce7061b9cd70c783e0483727236d0cbb05dafefd78ec4e6419efe93d6f82cdadbfd4e860661238040229f60205bbba983790303132333435363738393a3b3c3d3e3f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"
+ ],
+ "measurement": 2,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "14be9c4ef7a6e12e963fdeac21cebdd4",
+ "d36e13f4bc25306322e56303c62c90af",
+ "d73f6b4aa9fdf33cb0afb55426d645ff",
+ "8cd7e78cebf9d4f1087f6d4a033c8eae"
+ ],
+ [
+ "ed4163b108591ed14dc02153de31422b",
+ "2e91ec0b43dacf9cc11a9cfc39d36f50",
+ "2bc094b556020cc333504aabd929ba00",
+ "7528187314062b0edb8092b5fcc37151"
+ ]
+ ],
+ "prep_messages": [
+ "7556ccbddbd14d509ee89124d31d1feb"
+ ],
+ "prep_shares": [
+ [
+ "806b1f8537500ce0b4b501b0ae5ed8f82679ba11ad995d6f605b000e32c41afb6d0070287fe7b99b8304d264cba1e3c6f4456e1c06f3b9d3d4947b2041c86b020d26c74d7663817e6a91960489806931b304fcd3755b43b96c806d2bbeb0166bbec7c61c35f886f3f539890522388f43",
+ "8194e07ac8aff31f2f4afe4f51a12707b692a56a1745315a1022b4eb257b2a8725c610416af7b0d1a296f409cdb3fbf4f4c0d488206d794254e4755fd124cdc9a67364ddc7865afe3554de5f52f1ac910f3f8e110cfbad4113861316dc73ec60de4f6c512adaa41de631eda8d6d8c189"
+ ]
+ ],
+ "public_share": "bec7c61c35f886f3f539890522388f43de4f6c512adaa41de631eda8d6d8c189",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_1.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_1.json
new file mode 100644
index 0000000000..0b9a9b4d5d
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Histogram_1.json
@@ -0,0 +1,89 @@
+{
+ "agg_param": null,
+ "agg_result": [
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "agg_shares": [
+ "a6d2b4756e63a10bf5f650e258c73b0ccb20bace98e225dea29d625d527fdd4ded86beb9a0a0ac5c4216f5add7a297cece34b479a568a327f1e259839f813df97b34de254be5b9b9c8d9e56dbff50b7a6bf1e5967686755a1dc42e0ab170add8c88f8ca68f945e768a5007c775fd27cfecb4495e257a2f2f94ca48830aa16ec0decaeee645e295c5dc2ebe491aae1a7f17b2807fcb33ee08127db466067bf84ec613dac9c93adbe73dd262c1859b2865",
+ "ed4163b108591ed14dc02153de31422b2e91ec0b43dacf9cc11a9cfc39d36f502bc094b556020cc333504aabd929ba007528187314062b0edb8092b5fcc3715126e16ce274ad58caaa14d22608269a4c41a256d3c9e847c0a6ac1a4fbaf6309e9ccbe74a9442ca956d843d6bd5adf9797a84557597d9cc81ddfa281ae5048d686bdb289ec2f3c96cdfa79b6974e6d15aec047748636d4358226283e11a78e045f59db2dda566162a56c85936ac0f4696",
+ "6eebe7d888434023a1488dcac80682c8084e592524430a857f4701a673adb261eab8ac90085d47e06d99c0a64e33ae30bfa23313469131cafb9b13c763ba50b560eab4f73f6ded7b7011486b38e45939566cc395bf9042e5038fb6a6949821899ea48b0edc28d7f3cf2abbcdb454deb69cc6602c43ac034f563a8e62105a04d7b859e87af729a0cd2729a64c716b1326fe480838d15ece9eaf20c8b7de0c276b464e7358905e0eee4f654308ce549104"
+ ],
+ "chunk_length": 3,
+ "length": 11,
+ "prep": [
+ {
+ "input_shares": [
+ "a6d2b4756e63a10bf5f650e258c73b0ccb20bace98e225dea29d625d527fdd4ded86beb9a0a0ac5c4216f5add7a297cece34b479a568a327f1e259839f813df97b34de254be5b9b9c8d9e56dbff50b7a6bf1e5967686755a1dc42e0ab170add8c88f8ca68f945e768a5007c775fd27cfecb4495e257a2f2f94ca48830aa16ec0decaeee645e295c5dc2ebe491aae1a7f17b2807fcb33ee08127db466067bf84ec613dac9c93adbe73dd262c1859b2865508d344dda6c4339e650c401324c31481780ef7e7dcc07120ac004c05ab75ee5d22e2d0eb229dcdd3755fab49a1c2916e17c8ed2d975cfe76d576569bf05233c07f94417fccaf73d1cc33e17dae74650badffdd639a9b9f9e89de4b9fd13e258b90fbb2b3817b607dc14e6e5327746ca20d1f1918bce9714b135ffe01eb4e6aefab92b0462f7e676e26007e8c2e5a66e16f32f7c8457a6dfba39d9082f640006d560b4d64e86e2e2358c84e03b857c980f51b1a78b53f7cb44343ed184d8dc87ebf8698609eeefae5d8882224ebd28b9531015badea8ae9fe01c7495cafecdc4f13389ea4eb0bbce0a5ab85aa6fc06aabd96d28c84ecf039bfeb4c350049485f8a4c706a109164ff4c640edaedd0ad50820b1d1ed7ab08fc69c48b39aff1eebc02ef1ea40bd70784bfa50511c3dd64b107f4297842280c3cff8d94be202a0e2cb0090f3adb2189f445fcf291f452f162606162636465666768696a6b6c6d6e6f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
+ "303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
+ ],
+ "measurement": 2,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "a6d2b4756e63a10bf5f650e258c73b0c",
+ "cb20bace98e225dea29d625d527fdd4d",
+ "ed86beb9a0a0ac5c4216f5add7a297ce",
+ "ce34b479a568a327f1e259839f813df9",
+ "7b34de254be5b9b9c8d9e56dbff50b7a",
+ "6bf1e5967686755a1dc42e0ab170add8",
+ "c88f8ca68f945e768a5007c775fd27cf",
+ "ecb4495e257a2f2f94ca48830aa16ec0",
+ "decaeee645e295c5dc2ebe491aae1a7f",
+ "17b2807fcb33ee08127db466067bf84e",
+ "c613dac9c93adbe73dd262c1859b2865"
+ ],
+ [
+ "ed4163b108591ed14dc02153de31422b",
+ "2e91ec0b43dacf9cc11a9cfc39d36f50",
+ "2bc094b556020cc333504aabd929ba00",
+ "7528187314062b0edb8092b5fcc37151",
+ "26e16ce274ad58caaa14d22608269a4c",
+ "41a256d3c9e847c0a6ac1a4fbaf6309e",
+ "9ccbe74a9442ca956d843d6bd5adf979",
+ "7a84557597d9cc81ddfa281ae5048d68",
+ "6bdb289ec2f3c96cdfa79b6974e6d15a",
+ "ec047748636d4358226283e11a78e045",
+ "f59db2dda566162a56c85936ac0f4696"
+ ],
+ [
+ "6eebe7d888434023a1488dcac80682c8",
+ "084e592524430a857f4701a673adb261",
+ "eab8ac90085d47e06d99c0a64e33ae30",
+ "bfa23313469131cafb9b13c763ba50b5",
+ "60eab4f73f6ded7b7011486b38e45939",
+ "566cc395bf9042e5038fb6a694982189",
+ "9ea48b0edc28d7f3cf2abbcdb454deb6",
+ "9cc6602c43ac034f563a8e62105a04d7",
+ "b859e87af729a0cd2729a64c716b1326",
+ "fe480838d15ece9eaf20c8b7de0c276b",
+ "464e7358905e0eee4f654308ce549104"
+ ]
+ ],
+ "prep_messages": [
+ "4b7dc5c1b2a08aec5dcfc13de800559b"
+ ],
+ "prep_shares": [
+ [
+ "e80c098526d9321dd0801f97a648722016fa117f10cb2b062fc5fb1e55705894007f838333ef348c6306e141369bd88d123c66d2faeb132e330a73882c38765d425847bd86e5f784b3348ee4840c5df103b49f04c4dcca4667abb956187da58c91c946d9d5fdf496d95428f8a625dddfc8b7bb469397ebd4b177f902896febdaac39a8d9ec0aa1a24132036c2430929c",
+ "4f6d4137ddffd58b243b8f845a1684550b240ea3e91a68335f717b83056e9b45c5e62d7a24da54147fcb9260d023cb7f9c8d036f0100f5fea0ce22f49e3d7672bc83fd5c724f2684f3442e8c5291c41509151808d1da447cddc3fe11cf5cd8d7fe662cf035eff88b583f6b32499b332aa6dee37947ef482e15fcb3a7f04b20813162d162b9bf30eee4953b6fdabd10f1",
+ "ca85b543fc26f756ef4351e4fea0098a73787eeab8b613029af310882b7e87d28fc5502fd76bd704626d9f0f662e531feaf1fa912cc209de6541401d8508a4788d92e549f58241334cfa29abc1fb80a28ae61d7a4060d9582e3e20f182e4519ab1bb9547c545aafc21416e779856c80cf3690155119111aebf3800757989229e4966453f7aa269163b272848de80227f"
+ ]
+ ],
+ "public_share": "ac39a8d9ec0aa1a24132036c2430929c3162d162b9bf30eee4953b6fdabd10f14966453f7aa269163b272848de80227f",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+ }
+ ],
+ "shares": 3,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_0.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_0.json
new file mode 100644
index 0000000000..a7178fcfee
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_0.json
@@ -0,0 +1,194 @@
+{
+ "agg_param": null,
+ "agg_result": [
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265
+ ],
+ "agg_shares": [
+ "cdeb52d4615d1c718ef21a6560939efcb5024c89ea9b0f0018302087c0e978b5b5c84c8c4f217b14584cc3939963f56a2718c17af81d1f85129347fc73b2548262e047a67c3e0583eeef7c4a98a5da4ccdb558491081a00a8de5a46b8c2f299bc69162a7411b3ecf2b47670db337083dc50b8fc6d5c5d21da52fc7f538166b0e4564edd8fbb75bc8c4fdd0c02be2b6d11bc4a159297b72e2c635a9250feb2445",
+ "3415ad2b9ea2e38e550de59a9f6c61034dfeb3761564f0ffcbcfdf783f16874a4e38b373b0de84eb8bb33c6c669c0a95dde83e8507e2e07ad16cb8038c4dab7da320b85983c1fa7cf50f83b5675a25b3394ba7b6ef7e5ff5561a5b9473d0d664416f9d58bee4c130b8b898f24cc8f7c243f570392a3a2de23ed0380ac7e994f1c49c12270448a4371f022f3fd41d492eef3c5ea6d6848d1d1dca56daf014dbba"
+ ],
+ "bits": 8,
+ "chunk_length": 9,
+ "length": 10,
+ "prep": [
+ {
+ "input_shares": [
+ "2451f59efba2edc493c9246ff1f0e0a7f8f6f22ee46e662c899e485d7ce288d6becdfee804a39618972fbaa595eeec25423e412cbe51d44a62747de627630302368ec3535a2545a2799e8a0b9a144c811158dda278865d834b34fbe77ad11dbb9fdcf0637c24e10d5ab36d03cdc5f6b95e400a0a81608d96c25733c376376de3927c3570e8ab671358a1686d0c44ac938368d5621cff66585454ef124daa5f18efd7e791a4bcb11caf74b378e2c4feff3e5bad16e7c3fab987eb4d4a0c675bb4f4e70e1373fb00a5dd30a1118355c20e2e4c3700be3d3c1cf25d3e4a729836ba564aa074f99be0d23d4cc0dc9f263c986988e0d16a3d28c262d34f220b1ed127cddea3e2a1bd075c653d4b6f1c3d35e25d2804e7960250dea42dc4a52c9545bedc182ee8391b4c6849366af8e15f30bd06872e5ed651ef7db0b0c442886de32eeeeacc5f2dfe87f9375b4774153fc9e442105b5f8e452e80874c84131400d4d588a1a5d94bac9e68dbf917ef6405b0bc13fa89daf46f84405aedb166ac93f6545256b1da6ac65e01d580bb26eef82c34b9d728fc0c96ff898ed46bd289abbec9917397552ebf6d1eb3f916f69ee9f80e9466512bff70af2d8f3a9ed599f24e33550a09304e1b4f51948e2d8cbf5a1bb14455b1786ae3af4670111bc3983293ad9ae029128efd86d0a05cb3f442b43f466cec5cc9c4989bf5a29eb5c2401bc8bba0d5b7487bc0bf010c968fe76e3a9924459dce6704528d56540081240ed0d2f301a8c9baca5c183b1b5c3a9c03dce5036926d06e1470c2e63d15fdc3a61056154fca9439c595098ff3794c7d7e62af5e3139b43e22a0f8864c254a069a083604762d77ea000177a7b908efe27f6e00db7ea25f573734af803044fb2dc333ed9ad589d7677e23614143fa6836d68ed311dcedf0ea031688b2cf8c5248f21be444f1c61b050a0ab7dca04992673afb737bd27526a72dda3b03dad4d3b0bb81f0887ee6f25ec4cf35d58ea5f085e97609cfb6a8e97d84fdf8755b8e81ff29614bf1b03bcd2b8d9ab06dc4d60785f83eb6ee4573859223214ecdad734d114e15e1971a8b82222910fd041a1123a4e792a9239f99252de3e3e8d5bb209e2c9bda506a79853c482546940364a8246392fb5e18e85847458445fe3a970b29db6d3d0e4a806cfec7c8538f24896d2d10669113f2b724161d2007ee75c0b651f4934046142b04b2015212997c609625bbeb81b9fa0249c167196557c08ae9ea2defcf7859eed4d35b2183c628cb82fe01255e558e7c8b13bded6ba43ed8b92f6ba7879b39932468260c5768ca0909aae899ad1252c5dbcd741d971f179bc36e88a0a10981f73202cb25db324da405fdd5ca5331431afe362c5f933b3c1216c3e19140cd27f7c2ef67898887856a46a518a3afec78ee0d9dce778289a38d2df906932c40019afadab12fe7d0695316e5a3c1e38aa630a44bc8cc01a5a8cae060b7de435e54963b9354182d64e340ec9dc3e37f8b2bbaaab23608b86827991df4367839f443c160c1eb77f41159f69592c3eb37c21a521afcd34036a13a145e9cb1039704b8e523359ea5c3a50f705118ea7d8b1063eb85bfcdc941e0235579e97856ee6f6bfe9c4d0d161b5662de26a2fbddc530ce918a98514903c63a3476d6ebe68e2503e6bf255691fbd8a006e9c77f5a4ad9e3e8d21a56bc4f7bc90d61ebb31eaa4dce48eb9a8069a584ae35266a4bc4af970860d2e9a0df7b87e8fc8b597e73a85d8eeb91def6057d7a77e8f859ee9ee07ef2fb2260660e59ee16458eafbd7bab979ed9bf72c1c27cadc9011f563aeed9a4b2f09ce5455857bcf3acbe0e1cf15537594469c777a885ad24ac5a8c894a8257c5212fb46184ad7f280bc25600129b25bf941460fcdd2e45a0216f1f2fec84d4792a15f877d15c649991ef998621a50a04251257ed6ccd803fdbc83ce4b5c4d7e8ee4487848768384e0b3970ec899dfb423560e755c4716deb3e188ca780cc7a97e8fb80076e9c44c6b7575725253ae4605844c3748bf90c14f17ee80a42fea450c05eb1f251eb09855f2ab368047d68cd7f4b8898d0113d52117375eed77707e7abff8554dc7da30cca674ac587198c0f165a47a5db79e9cc1c7bda9cdc36b94f14141a307850062f4d7208b8c612691699e3e3c16c3fc2dc6604e0fcb27acd6b5d326d2663a3f819589176c70423bc4d37d8c2a17e97468cf923a5388eb0d1de61358e9651a7e76b033d32d6c84e7ed5831a990b46e8228b6ef120643049645b82e100a7ed6ddd2ebfe2dcbd8b0e7ac1e5ee021d4279f164acc47875ade2c0acff5dbbf3a6eb0e8601632c926780be1660270420aa02c99fb39af1852b09904791e90cfa1f02aec0ab2de111524394819527819e52d495196ab3aff1e323dfec07af91e18b9a04e37552a23b13177bdcfc64ec7108e5e9b3679ccdf6b1e998e2bbcd5fbbbebf5ad8008e727cae6499cc06aa03809947e298683a4340f51d6eecad38d0a7a5437dd6e72bce6543b81fd3a438d71e232845cdb403f1011295f9aee5e33352b86e92343985884284c9646da13545f37b9d6da7d0cf902c19a5ca1f4f1818a2c2644807fcc54be35c29f96fb4fea5efdc88b270f1c5504bd8ba558834786020cc2f03ab5c56eaea38532b9faf6208f57d970b2e5ff92872713c9e0ad07b26e72dca6f9a9c02bad6c9db4d1d738f306292f14415d2856c2b073c5d8faf89e9713ceb375b6eefabc240bf6c6bf39cafb99993767dbaf5ee5f4b3f93e638e904fb55f443312c145b809fd203b5b3a16bd229b952e100bbfc0e49bbd05d54c3e5fa1a44fe55de16cfa52f3b169e0bfe95b1b8b6367f9309adfe3df079104fd720d46d772def3c0534d73615071fa22a79af875f796478d2f599dbb4c1ed303132333435363738393a3b3c3d3e3f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"
+ ],
+ "measurement": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9
+ ],
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "9aa31b9c201fb42526a6b32120318aa9",
+ "e8551983a3deafaafe0f608295f8d291",
+ "3eed6ed96f607eb1be6e96868876fc78",
+ "65b295d3525f0ad74886c2fe7b3b1cd6",
+ "794a6d37d41457d6f04fd418888cf36e",
+ "483cc86d052be058d0a1e12384ba0d89",
+ "9c85cb376b5ebfeffb6c22af3bbd02bf",
+ "9c0385979cecf00983ba97fc12b2235a",
+ "c7cbf9f2533dc942eca94540b9a0e745",
+ "0f418bc80d2926f6ec11e3615a4e0c17"
+ ],
+ [
+ "675ce463dfe04bdabd594cdedfce7556",
+ "1aaae67c5c215055e5ef9f7d6a072d6e",
+ "c5129126909f814e2591697977890387",
+ "9f4d6a2cada0f5289b793d0184c4e329",
+ "8cb592c82beba829f3af2be777730c91",
+ "bec33792fad41fa7135e1edc7b45f276",
+ "6b7a34c894a14010e892dd50c442fd40",
+ "6cfc7a6863130ff660456803ed4ddca5",
+ "4234060dacc236bdf755babf465f18ba",
+ "fbbe7437f2d6d909f7ed1c9ea5b1f3e8"
+ ]
+ ],
+ "prep_messages": [
+ "db085315822777376b4d0f962d8f06d9"
+ ],
+ "prep_shares": [
+ [
+ "e6a4fe7264f95c384446bd51db14f78e7f4133afed0604aeb3b87125fc076c7447d795723adfe93d85f9fe2993c52420e45694fd2ec164a54a7267ee5efc8cb40b6659ac81f2e850786218bcec469ec4f7bb28e875d75ee98d54d566186c61c35448a50cb11e195d886622861a78bbb74325b7972e7b4c47f0e2e10a15d7a33c3daecb2dfc507b1b6676c1e9bfc52a4873f408a5788e7b77ce6943e67f3f457280544d93b81b08e427f699ba54adcbb0ffab83366d9b336846c0c989f0bc25bdd14683f1a85e844b9dbac26daae84cc8d57ef6b0c340798ac5ade63150e8d7a9673b64d798a97cf2715f399fd371e342c1ad50e28431f54180ef63ad7dd21f3e5d8d67159cacfd56f5d99c39d53047c8d7bf11ad83a2e3e569e1393b12d87d01701fa71b50b51e092ca6b797bb97890efb6327f1c4e488663dca5f00675c2af7368a9ab95b3c4e9e1a8dd5430d336833",
+ "1b5b018d9b06a3c79fb942ae24eb087131693625114418115cb3e54a45056eabd0f6e371501957e00796db78ea8f3388eb8345e938f5fbdd8b24c3a968276d7c457ff43ce93631942f823f5bb9c6d1335b8022e804072711cb8fa5fb3afb209e696c9cac47da44cdc3eb3874eb0d8c89692408b463df12bb2d6e8193d5829cce221e486a579b91cd10a0fb38fec7214a9008d574ba32615f6215aef827a2962a31df892814bd8b8f828d029f07f6acf490e7ecd3377f10b86ec2d5741ba37a3a9522b897e840e315a614a89d0bcf8296bdd45e330eaf3f34b3ce4e1dd41306eae92147fc6676eedff2cb239581f46750df341390e066dabb01ef6362694f923d5a65dbc5252a8da8702a979aca3e211af7124485c1c7dc68f6fb1bdfb9a4d0d993cece17c818d8fb1151f1ef1b32745f09c155f42259af588b424c1dafe8d0e90a3dfc6f55bb428773ce15071cb720fb"
+ ]
+ ],
+ "public_share": "368a9ab95b3c4e9e1a8dd5430d3368330a3dfc6f55bb428773ce15071cb720fb",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ },
+ {
+ "input_shares": [
+ "2551f59efba2edc493c9246ff1f0e0a7f8f6f22ee46e662c899e485d7ce288d6becdfee804a39618972fbaa595eeec25423e412cbe51d44a62747de627630302368ec3535a2545a2799e8a0b9a144c811158dda278865d834b34fbe77ad11dbb9fdcf0637c24e10d5ab36d03cdc5f6b95e400a0a81608d96c25733c376376de3927c3570e8ab671358a1686d0c44ac938368d5621cff66585454ef124daa5f18efd7e791a4bcb11caf74b378e2c4feff3e5bad16e7c3fab987eb4d4a0c675bb4f4e70e1373fb00a5dd30a1118355c20e2e4c3700be3d3c1cf25d3e4a729836ba564aa074f99be0d23d4cc0dc9f263c986988e0d16a3d28c262d34f220b1ed127cedea3e2a1bd075c653d4b6f1c3d35e25c2804e7960250dea42dc4a52c9545bedc182ee8391b4c6849366af8e15f30bd06872e5ed651ef7db0b0c442886de32eeeeacc5f2dfe87f9375b4774153fc9e442105b5f8e452e80874c84131400d4d588a1a5d94bac9e68dbf917ef6405b0bc13fa89daf46f84405aedb166ac93f6545256b1da6ac65e01d580bb26eef82c34b8d728fc0c96ff898ed46bd289abbec9917397552ebf6d1eb3f916f69ee9f80e9466512bff70af2d8f3a9ed599f24e33550a09304e1b4f51948e2d8cbf5a1bb14455b1786ae3af4670111bc3983293ad9ae029128efd86d0a05cb3f442b43f466cec5cc9c4989bf5a29eb5c2401bc8bba1d5b7487bc0bf010c968fe76e3a9924459dce6704528d56540081240ed0d2f300a8c9baca5c183b1b5c3a9c03dce5036926d06e1470c2e63d15fdc3a61056154fca9439c595098ff3794c7d7e62af5e3139b43e22a0f8864c254a069a083604762d77ea000177a7b908efe27f6e00db7ea25f573734af803044fb2dc333ed9ad589d7677e23614143fa6836d68ed311dcedf0ea031688b2cf8c5248f21be444f0c61b050a0ab7dca04992673afb737bd27526a72dda3b03dad4d3b0bb81f0887ee6f25ec4cf35d58ea5f085e97609cfb6a8e97d84fdf8755b8e81ff29614bf1b03bcd2b8d9ab06dc4d60785f83eb6ee4573859223214ecdad734d114e15e1971b8b82222910fd041a1123a4e792a9239e99252de3e3e8d5bb209e2c9bda506a78853c482546940364a8246392fb5e18e85847458445fe3a970b29db6d3d0e4a806cfec7c8538f24896d2d10669113f2b724161d2007ee75c0b651f4934046142b04b2015212997c609625bbeb81b9fa0249c167196557c08ae9ea2defcf7859eed4d35b2183c628cb82fe01255e558e7b8b13bded6ba43ed8b92f6ba7879b39922468260c5768ca0909aae899ad1252c5dbcd741d971f179bc36e88a0a10981f73202cb25db324da405fdd5ca5331431afe362c5f933b3c1216c3e19140cd27f7c2ef67898887856a46a518a3afec78ee0d9dce778289a38d2df906932c40019bfadab12fe7d0695316e5a3c1e38aa630a44bc8cc01a5a8cae060b7de435e54963b9354182d64e340ec9dc3e37f8b2bb9aab23608b86827991df4367839f443c160c1eb77f41159f69592c3eb37c21a521afcd34036a13a145e9cb1039704b8e523359ea5c3a50f705118ea7d8b1063eb85bfcdc941e0235579e97856ee6f6bfe9c4d0d161b5662de26a2fbddc530ce918a98514903c63a3476d6ebe68e2503e6bf255691fbd8a006e9c77f5a4ad9e3e7d21a56bc4f7bc90d61ebb31eaa4dce48eb9a8069a584ae35266a4bc4af970860d2e9a0df7b87e8fc8b597e73a85d8eeb91def6057d7a77e8f859ee9ee07ef2fb2260660e59ee16458eafbd7bab979ed9bf72c1c27cadc9011f563aeed9a4b2f09ce5455857bcf3acbe0e1cf15537594469c777a885ad24ac5a8c894a8257c5212fb46184ad7f280bc25600129b25bf941460fcdd2e45a0216f1f2fec84d4792a15f877d15c649991ef998621a50a04251257ed6ccd803fdbc83ce4b5c4d7e8ee4487848768384e0b3970ec899dfb423560e755c4716deb3e188ca780cc7a97e8fb80076e9c44c6b7575725253ae4605844c3748bf90c14f17ee80a42fea450c05eb1f251eb09855f2ab368047d68cd7f4b8898d0113d52117375eed77707e7abff8554dc7da30cca674ac587198c0f165a47a5db79e9cc1c7bda9cdc36b94f14141a307850062f4d7208b8c612691699e3e3c16c3fc2dc6604e0fcb27acd6b5d326d2663a3f819589176c70423bc4d42a21c0a30addfe4b4176740f9a418eca631cda9a8b94d20c7f9b834ed87751464dc5e5b446d920312003e4673b48c6c12b407af1ed90002507883f78d166f0b90bc14ed77d4aec6220cdd51948cdad29ab70513aadccd0e3c8c8108d3b0722602d9612aa6feb323a4ff3e8fe0e3d5701467491acdd3c71c34bc019047647779922216ccd61c47958461e3017adf446c4bd2ab7fbf70e41419679f6a9b3fa4c9aa5e9ef8469ace0d88bc35a3374f462573d2ba24b712359ef36e413006a9883bfa4fad43d89c7f1732725e3cad482d17a9499e1fb0f57d1ca93cafa7fd6d654a70cd7318bd7ace30e981217317105bcfe5e33352b86e92343985884284c9646d966beb8aca87d44e15dcce24aeb08312091cd98e6b52e2525409c58438f00131d33ce09fde0343f84db73369954f2d77a3a559189bc4dfd7e7c043b1364b36550595f624483c4eccccb1c4958a9284e43522dcc72ad9b01162d964605eab990dd1ddd25796f55991e1201f22526117662c0cad518f191effe5608b444b9e8973f5a11a8c154bc501bc47bb4fb5832d67f4c5ea5c221e64ff88ff5d5117aadac704a8b94beb036e87fc9a9462c355231b9bbe8a9122f12390073600f2d7f6262f1758eced79619900cad1910286c5bae553c6525c63c52ca97bf452957c5fc1a7d695dcb5ed0cf0004454c528d63960cd303132333435363738393a3b3c3d3e3f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"
+ ],
+ "measurement": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "9ba31b9c201fb42526a6b32120318aa9",
+ "e8551983a3deafaafe0f608295f8d291",
+ "3ded6ed96f607eb1be6e96868876fc78",
+ "63b295d3525f0ad74886c2fe7b3b1cd6",
+ "764a6d37d41457d6f04fd418888cf36e",
+ "443cc86d052be058d0a1e12384ba0d89",
+ "9785cb376b5ebfeffb6c22af3bbd02bf",
+ "960385979cecf00983ba97fc12b2235a",
+ "c0cbf9f2533dc942eca94540b9a0e745",
+ "07418bc80d2926f6ec11e3615a4e0c17"
+ ],
+ [
+ "675ce463dfe04bdabd594cdedfce7556",
+ "1aaae67c5c215055e5ef9f7d6a072d6e",
+ "c5129126909f814e2591697977890387",
+ "9f4d6a2cada0f5289b793d0184c4e329",
+ "8cb592c82beba829f3af2be777730c91",
+ "bec33792fad41fa7135e1edc7b45f276",
+ "6b7a34c894a14010e892dd50c442fd40",
+ "6cfc7a6863130ff660456803ed4ddca5",
+ "4234060dacc236bdf755babf465f18ba",
+ "fbbe7437f2d6d909f7ed1c9ea5b1f3e8"
+ ]
+ ],
+ "prep_messages": [
+ "fdecd6d197e824492dd550fcdd0aa3c7"
+ ],
+ "prep_shares": [
+ [
+ "e6a4fe7264f95c384446bd51db14f78eec654b5beb7d60677c0b8385ca51a5bf896c79a069b04f339fdcb675885a26f533ec0d6e805beef6d7683a8ecdfa46cb87fdbc532efa5b1041347ed94f54fbca15041d013729d5eb78dcb1a1c293e0035448a50cb11e195d886622861a78bbb7bccc68d521fac002ece757cc5d82afb3b0f9f8766a1714047b50417a8e7f63eacf222c675bdf1d3e8362806ef5f9c16c446a1e5e06ce539aa300bc68c837c9207c5dbc7d85f896fb1be725e461ceacd303716a2cd8005e370a8ac1062d966b5c1499813d0dc63d697c4fd44dcfe81b3cfc37952de43649650c52ca9f2044fd3dfc42cd08bf0659e6a7facee2468ad1b07903a933ec3cbd06a5f81461e434449c32ef6678f3c8fd250c0b3318e80235144a96f07af2169dfddb503b38a2039f80f6bdfbbec6b3ad1025a43a90249221d20e627a73e2ed92bf1920574f42775675",
+ "1b5b018d9b06a3c79fb942ae24eb0871aba638b677efec2418d7921020c44ff8d0f6e371501957e00796db78ea8f338813d41fd058418e6056a93bb2785cec1d457ff43ce93631942f823f5bb9c6d133fbfbf9d10fa7922dccd1e570b0246775696c9cac47da44cdc3eb3874eb0d8c89fcdfd722a57116825749c8972129304c221e486a579b91cd10a0fb38fec7214af0fe63f4a24e9e189e180a8aaed3b22931df892814bd8b8f828d029f07f6acf402eb7c22aea07b55e871ebdf6475ae509522b897e840e315a614a89d0bcf8296c0b0d5b745f277689643e3da46c09a1ce92147fc6676eedff2cb239581f46750fa1947870c81f9d565f51ed7e09d86dc5a65dbc5252a8da8702a979aca3e211ae9def20e5f2fa85d16e3ccb0a917510793cece17c818d8fb1151f1ef1b32745f09c155f42259af588b424c1dafe8d0e90a3dfc6f55bb428773ce15071cb720fb"
+ ]
+ ],
+ "public_share": "0e627a73e2ed92bf1920574f427756750a3dfc6f55bb428773ce15071cb720fb",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ },
+ {
+ "input_shares": [
+ "2551f59efba2edc493c9246ff1f0e0a7f9f6f22ee46e662c899e485d7ce288d6bfcdfee804a39618972fbaa595eeec25433e412cbe51d44a62747de627630302378ec3535a2545a2799e8a0b9a144c811258dda278865d834b34fbe77ad11dbba0dcf0637c24e10d5ab36d03cdc5f6b95f400a0a81608d96c25733c376376de3927c3570e8ab671358a1686d0c44ac938468d5621cff66585454ef124daa5f18f0d7e791a4bcb11caf74b378e2c4feff3f5bad16e7c3fab987eb4d4a0c675bb4f5e70e1373fb00a5dd30a1118355c20e2f4c3700be3d3c1cf25d3e4a729836ba574aa074f99be0d23d4cc0dc9f263c986a88e0d16a3d28c262d34f220b1ed127cedea3e2a1bd075c653d4b6f1c3d35e25d2804e7960250dea42dc4a52c9545bedd182ee8391b4c6849366af8e15f30bd07872e5ed651ef7db0b0c442886de32eefeacc5f2dfe87f9375b4774153fc9e443105b5f8e452e80874c84131400d4d589a1a5d94bac9e68dbf917ef6405b0bc14fa89daf46f84405aedb166ac93f6545256b1da6ac65e01d580bb26eef82c34b9d728fc0c96ff898ed46bd289abbec9927397552ebf6d1eb3f916f69ee9f80e9566512bff70af2d8f3a9ed599f24e33560a09304e1b4f51948e2d8cbf5a1bb14555b1786ae3af4670111bc3983293ad9be029128efd86d0a05cb3f442b43f466dec5cc9c4989bf5a29eb5c2401bc8bba1d5b7487bc0bf010c968fe76e3a9924469dce6704528d56540081240ed0d2f301a8c9baca5c183b1b5c3a9c03dce5036a26d06e1470c2e63d15fdc3a610561550ca9439c595098ff3794c7d7e62af5e3239b43e22a0f8864c254a069a083604772d77ea000177a7b908efe27f6e00db7fa25f573734af803044fb2dc333ed9ad589d7677e23614143fa6836d68ed311ddedf0ea031688b2cf8c5248f21be444f1c61b050a0ab7dca04992673afb737bd37526a72dda3b03dad4d3b0bb81f0887fe6f25ec4cf35d58ea5f085e97609cfb7a8e97d84fdf8755b8e81ff29614bf1b13bcd2b8d9ab06dc4d60785f83eb6ee4673859223214ecdad734d114e15e1971b8b82222910fd041a1123a4e792a9239f99252de3e3e8d5bb209e2c9bda506a79853c482546940364a8246392fb5e18e95847458445fe3a970b29db6d3d0e4a816cfec7c8538f24896d2d10669113f2b824161d2007ee75c0b651f4934046142c04b2015212997c609625bbeb81b9fa0349c167196557c08ae9ea2defcf7859eed4d35b2183c628cb82fe01255e558e7c8b13bded6ba43ed8b92f6ba7879b39932468260c5768ca0909aae899ad1252c6dbcd741d971f179bc36e88a0a10981f83202cb25db324da405fdd5ca5331431bfe362c5f933b3c1216c3e19140cd27f8c2ef67898887856a46a518a3afec78ef0d9dce778289a38d2df906932c40019bfadab12fe7d0695316e5a3c1e38aa631a44bc8cc01a5a8cae060b7de435e54973b9354182d64e340ec9dc3e37f8b2bbaaab23608b86827991df4367839f443c260c1eb77f41159f69592c3eb37c21a531afcd34036a13a145e9cb1039704b8e623359ea5c3a50f705118ea7d8b1063ec85bfcdc941e0235579e97856ee6f6bfe9c4d0d161b5662de26a2fbddc530ce928a98514903c63a3476d6ebe68e2503e7bf255691fbd8a006e9c77f5a4ad9e3e8d21a56bc4f7bc90d61ebb31eaa4dce49eb9a8069a584ae35266a4bc4af970861d2e9a0df7b87e8fc8b597e73a85d8eec91def6057d7a77e8f859ee9ee07ef2fc2260660e59ee16458eafbd7bab979ed9bf72c1c27cadc9011f563aeed9a4b2f09ce5455857bcf3acbe0e1cf15537594469c777a885ad24ac5a8c894a8257c5212fb46184ad7f280bc25600129b25bf941460fcdd2e45a0216f1f2fec84d4792a15f877d15c649991ef998621a50a04251257ed6ccd803fdbc83ce4b5c4d7e8ee4487848768384e0b3970ec899dfb423560e755c4716deb3e188ca780cc7a97e8fb80076e9c44c6b7575725253ae4605844c3748bf90c14f17ee80a42fea450c05eb1f251eb09855f2ab368047d68cd7f4b8898d0113d52117375eed77707e7abff8554dc7da30cca674ac587198c0f165a47a5db79e9cc1c7bda9cdc36b94f14141a307850062f4d7208b8c612691699e3e3c16c3fc2dc6604e0fcb27acd6b5d326d2663a3f819589176c70423bc4dc97f402ea5053f2aa068d42c371d327339bd8637472eb9be88e409688a53bff392a8891c96c7804998d761fcf34dbf8da8cb17567f75ab4be29ecb6c33c85bf572b645bb7b226e4b99ccc0d959e6d809ec45b70cefe5ada611ea14962c9788d3e15100872669baf3c07a424cc205db97c0e2f72880a40a48d3820f89a16c7ee9dbc44bea18d787e0b730093a7b1fe4f9fd3e50128c61f6d4179fde88b02629629bf9f56f15abe3860ad2c99eaee9eba2310ca898fee5103f7bcb11ec9f4154007cf7d2b51a173331576526665d9f90879a2122b2bdef3a2cff68e57b146ae6d99a4e247d0daa693ef0a07aadc1a4b25ce5e33352b86e92343985884284c9646d0f8ec766552f75092a8b613870386a8b77901f01cddd76b4761e74519b24b851a570b5de8ca954b2c7df0fb314b6fa550e8e49713a28358e399afb3b9199496b229bc55644ee8e4772f1e00dc53886ade4932acee5cfd079707bd1d204c58360f26434fb158b53c1c4a51b65703f123f8090fe42dc48dbd3469a7d4bf1958203adffe46dd39084b66c789517b4438ed9415946ca552d523fa6c71e3302c3552f140d62d41cf3580e5e8500674cbb7d9ddd849d1ddb1d48ef7fd92f363e5e5b6a95b0c67b37e7e5e6a4dec9d8d56e577562eecec955cb6f9925c81cc165634018ab142c519ddd54f358356cee2ba50840303132333435363738393a3b3c3d3e3f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"
+ ],
+ "measurement": [
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255
+ ],
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "99a41b9c201fb42526a6b32120318aa9",
+ "e6561983a3deafaafe0f608295f8d291",
+ "3bee6ed96f607eb1be6e96868876fc78",
+ "61b395d3525f0ad74886c2fe7b3b1cd6",
+ "744b6d37d41457d6f04fd418888cf36e",
+ "423dc86d052be058d0a1e12384ba0d89",
+ "9586cb376b5ebfeffb6c22af3bbd02bf",
+ "940485979cecf00983ba97fc12b2235a",
+ "beccf9f2533dc942eca94540b9a0e745",
+ "05428bc80d2926f6ec11e3615a4e0c17"
+ ],
+ [
+ "675ce463dfe04bdabd594cdedfce7556",
+ "1aaae67c5c215055e5ef9f7d6a072d6e",
+ "c5129126909f814e2591697977890387",
+ "9f4d6a2cada0f5289b793d0184c4e329",
+ "8cb592c82beba829f3af2be777730c91",
+ "bec33792fad41fa7135e1edc7b45f276",
+ "6b7a34c894a14010e892dd50c442fd40",
+ "6cfc7a6863130ff660456803ed4ddca5",
+ "4234060dacc236bdf755babf465f18ba",
+ "fbbe7437f2d6d909f7ed1c9ea5b1f3e8"
+ ]
+ ],
+ "prep_messages": [
+ "190c0ef07d6f2cbd1bed12d71b5f118d"
+ ],
+ "prep_shares": [
+ [
+ "e6a4fe7264f95c384446bd51db14f78ef1ffdfc96013014baff7bd0684b8ff3360f6a2a23b1d7b71796b845eeb21e7d1682128dc8b87837803fec0e3bd6c548d5cc9c041a482cbfc38120743a2f1a0985053eaeccc339c56c2edf76451e22c9a1bca6ab2181850f44d904964d1f227a970e70b59328028ddafdb1649a8c4f1f7cbeb366e42f8603a7b627f7519f25617a33726ede06ab438714b4bd3cda025dc2bcab64974eb02b9e2a23bf0cab4e5ef1e87bb1a72767098c768a20e1090a712ed38c1e8803fd18181cc0069355b40f5f98ff3cbd2b31022df719d9c660e7d5bab239819730ad9165a38641379444093ba148996166ccf5e2826bddb7a7dc8eda4dd5928b12e1ea715538788804b5ce231dfc98ff45027ff8cb1f92007f2339621201a7dc483c83bb6df082105cb5f5d9eda1f847b3d43b4e16502062d5a4a158f651439d7666c683a6283d308c91b25",
+ "1b5b018d9b06a3c79fb942ae24eb08714d70b996c916bc83063fda0f9ec82780d0f6e371501957e00796db78ea8f338874640cdcd2ca1496ea55d62aa3709498457ff43ce93631942f823f5bb9c6d133b1f04db02cc745245af6a31e4dfe912a696c9cac47da44cdc3eb3874eb0d8c89cc603d2ceec1678996fe311424b56e65221e486a579b91cd10a0fb38fec7214a46c9ce7f890afed4efa31fb5ca8766ae31df892814bd8b8f828d029f07f6acf47620063e9fa98ab947b376e068b9be689522b897e840e315a614a89d0bcf8296332affc2e6b52204d1d7994260e415c6e92147fc6676eedff2cb239581f4675066213f3b302497abbf10c1729131a0125a65dbc5252a8da8702a979aca3e211afb99a0edb76c92f2bd5941c6071db19d93cece17c818d8fb1151f1ef1b32745f09c155f42259af588b424c1dafe8d0e90a3dfc6f55bb428773ce15071cb720fb"
+ ]
+ ],
+ "public_share": "8f651439d7666c683a6283d308c91b250a3dfc6f55bb428773ce15071cb720fb",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_1.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_1.json
new file mode 100644
index 0000000000..af95aac5a0
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3SumVec_1.json
@@ -0,0 +1,146 @@
+{
+ "agg_param": null,
+ "agg_result": [
+ 45328,
+ 76286,
+ 26980
+ ],
+ "agg_shares": [
+ "598a4207618eab7bd108c110106e84cd9498b18b79cace4b383274cef0ab280eae74b07f9a7f087a89a4f51f3adb97ed",
+ "ea61abdf14b8477f6de1b47a18ac778ad0149cb235e666ccce92a9246a2858403e5903013ab179dcf6719d10fccdf589",
+ "cfc412198ab90c0589158a74d7e503a89b7cb3c1504fcae7dc3ae20ca52b7fb17a9b4c7f2bcf7da947e96ccfc9567288"
+ ],
+ "bits": 16,
+ "chunk_length": 7,
+ "length": 3,
+ "prep": [
+ {
+ "input_shares": [
+ "db3fe5357f56f6cfe9a0a34ed08e231765b423d5670741c151f7bc28cb15c4c8678df838b6ef52d74c5bb5e8c8c607b2674c024da705b558163e3127ae09f4a5c061dc6129d71755ec77b5d5a2fc088541fa612bf1273d94718e0d28b654ea9524148bf910a5c6d55b596e323d4f55ab62b0851e0985987ea39cf500bfd4d5faed37f519120b574aead76d706e149c54b6e790f7c35553f830f67553ff3f08db0f5d8a2e157f9c1ef6eedcb845a19fffd3f9c6e705b035c95f3ebe51219a91c6ffff33c52b8fd7e90417636bb443c98c3e6933b1599c09151095fff99d7af43d327fcf2afdc50144e5b8f5b1ff1f8cc8d442613dc76dd48100dd4637b38fc5fa239333c6d1e2bec2132a3e3cf5fd11f7e823fbf275071a71535740ac64de88b83ba002bd4490de5c7540e454331d46ebb825725b964ecfcc5c3bb076bf57fd819c0bd788b4495da456594503c8f5b618c4713e957a7589b151ead3a27ae80153b0fbc219f5524eec22dc91aa56ed87a71d2b6e6b857ee03a700fb5af5b23513fb73023c2764f74b40f00e44e2e7acd463e7f38c783a898015b51863659502645e7d7fdf14abf76c605d42e523a540f40e816db420d599cea502baa131d6b61b8aed86a09b9f7587e107c6fc9043d72644153b54234146535e9ec25223d293f4a2a4481b5649a7625116aef735ecbbdd9e99076985e1c424e91c29ae231c01f2b12007c81a9b3c72a84522014f4cdcb949bb9850b62b1c9c5d35f0fc3dbf4f637ae85d3082ffd15890041a455a5dd38efca02eb415afcd2c93a2b6ab46c88847f380affbc3a9af4745718044c9f23978026af66871e610e1beee21360fc5ae4f05dd32689328216db3c512fddacefb2713187f4f5069170db246ae0cb13b3b88f8c832d11ae6b26fe6f0f89b9a9056c28ac6160134ab1af4be606a2ca7d1256cfc223c823ae4921ba11aab9e4f8104885b2d17ac7826ad06b0fd0e54397e67179755b29c6d724f3e9d9a6934619ef0bfee72cdcf0031672bb6e55be53d3ef0fccf7beba7dea636eff7fdecee99f60a0bc5d5cb11e24683ca8085cf218a3607c3f5a051c33717f39834435c5c542f42bd9c9d9103173cb11436e2a626e228415924037ee4a4078f0d9a5bd0fef1dc9415740c4b32f51d199d912907a103c7e25928acadfb4dc1e8d32c34b4f0100bfe95feb850e0d7b359aae9a332913d1f8eca0d1cd93dee2f4b75076536037ca2b6bec5003ae89e03db9813d0dd2165bf50a836ca492f05750a3b5fff4d089fc54fdf225aa112d72ab021b0cef1b8d31c852133fae501406668db0cb4c3589934bde5ec218876b0dc4087b61d706234635d5d9065787a21ed895fb7c05902a554f879dae44d0905292815c5ae2338be1faa9249c7a0530623084f3a7c8bdddf0cf0123597127f9416dcad20f71bb98bf0078d9d20d4ae09c46fc1882cae650f48bebfcc443dfd7963c9944f0ce0a9bda91fe8b8f307fb3da48e573224324c35e867599262fa281ee6bc537928747dd4a1370440bda92e28eb5ba088481e476f19e2a03a7c7619eb79e2184afff7bb585319fa6a58dbfb8862f5d193ccfe0e4aeb58c1633d9e983861d4976615b11514160e5d77ed9a3e2179893c65d9de03813d27aec3485d96098764ee1e7779d47850e6b96ba064b4b913da8390416afb16719a38b725d5b27db351049bdb322cfc93a905a108d07e49764eb5f3f66fe8f3aec7580606162636465666768696a6b6c6d6e6f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
+ "303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
+ ],
+ "measurement": [
+ 10000,
+ 32000,
+ 9
+ ],
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "7e6f6b02cb848e7e3c5840b05acfd699",
+ "dda1902ed398efc35ebb269aa58e0d5a",
+ "cd03902ade7fad281b8cfc5f1349ddf9"
+ ],
+ [
+ "f9758e4a5c3d6d2a1b4b3c7e5d397d83",
+ "9bb1de90bc4c2244e630e3b6780dc86a",
+ "15735600bee57d499ed0890554ef5183"
+ ],
+ [
+ "9b4106b3d83d0457705c83d147f7abe2",
+ "89299140701aeef79e13f6aee1632a3b",
+ "298919d5639ad48d0ea3799a98c7d082"
+ ]
+ ],
+ "prep_messages": [
+ "429a80b8b2f73ce00d066d0c0e540cfb"
+ ],
+ "prep_shares": [
+ [
+ "574ce7d79da173f0652f7d1e699d5fe756c90210a093e0c893b2b7510374465c35014cdcef24d6044e63ece20db245a982c5a00f16227c6530ade1e5fd4e925f8505da8add8b97fa3216790b11b46ad76bec9b6b3f0c1e9b8284f81f0c83bb690033a6c4a8e962d34b51e27aeaa33a0d41e852a78af96c6dfba1d36364996df4eaaacc993e5633528fb0d106753112ecbfe7fec74ea75d2d1b16267ee1b3d6145e746f19d0840be93d38baa3a5c7d42c6a05311092b4fe05e38aa9e24fb9c99a5aa4bc485752d049703409ccb6656150eeb34048030f19b5353df1e23b376b1c4be2925034d8b7a847690ca7f0c4f338c30b461d9e8c1a1de094fea67efdc4d3da6e96a43cd51241f828f01028e5b0e9",
+ "21c1bcbdd38fa9e8d86272f8c66c2b16768d00a79945c6014bb55374174299e4e7967f80f8df630af6f5aac52647e36437d08d0aabfd406b0984557a1a69b28f3706033ed34cde0d6bfd39a81a0fca04117cd7ac37f06815b6dc1a356454d669b0e7871238066b280f7c09e0365214235dcd39dfdf6e46ad632d50101029ff490910f79b754d99088059cf1d5da8a791b1a51d8d96fe428bac8f547dc2e9632c4e00b3a6e95d36d03d83969de2989c120aa3c54040bc7f49f910df6c5e4003721a03a3f1d743c457d0cbadbc0bd25ca7f464f404a1f39f852872314ca44ea35a32c150fc25094cd2147739d1a1e03e8f6e636170867687f1c511dbb936fb521b6639ebc145f03b6d1751fcdf14c5108e",
+ "89f25b6a8ecee226a56d10e9cff57402c3198e7629d8c4152d9319172f0df07727e57ad2e170a0a32cf748f9dc0fe45597d26743a7254aebceb106f0f172a5f2aecf563787471334096bb202a07696b7e0cf22d7dda51e1cf0fcc3d0d5d0a9f4c7ea8999a3f4edb4bd7d3ecb26f2137fe9d443f34248265c25a12290866130c9ccd2dd263bce8b78c14ddbc0c94ed85da0c401ac614c789711c0e2db27ad6ef59aabfdbb6bf174fff2358c4a8d6b7e6cd2a0d41883f3563506755a920aaa7695cf35ffecb0b195d01d1d86ee1a015df5de2f145b61ef3bc5fff1634d911a69540c03444dcb256fa982b24cf064dd6e4dffe71f8d5648cf336acd9485f9d70e24f2ad6b4aa14511e7b660c0866f73672a"
+ ]
+ ],
+ "public_share": "da6e96a43cd51241f828f01028e5b0e96639ebc145f03b6d1751fcdf14c5108ef2ad6b4aa14511e7b660c0866f73672a",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+ },
+ {
+ "input_shares": [
+ "db3fe5357f56f6cfe9a0a34ed08e231766b423d5670741c151f7bc28cb15c4c8688df838b6ef52d74c5bb5e8c8c607b2684c024da705b558163e3127ae09f4a5bf61dc6129d71755ec77b5d5a2fc088541fa612bf1273d94718e0d28b654ea9524148bf910a5c6d55b596e323d4f55ab63b0851e0985987ea39cf500bfd4d5faed37f519120b574aead76d706e149c54b6e790f7c35553f830f67553ff3f08db0e5d8a2e157f9c1ef6eedcb845a19fffd4f9c6e705b035c95f3ebe51219a91c6ffff33c52b8fd7e90417636bb443c98c3d6933b1599c09151095fff99d7af43d337fcf2afdc50144e5b8f5b1ff1f8cc8d442613dc76dd48100dd4637b38fc5fa249333c6d1e2bec2132a3e3cf5fd11f7e923fbf275071a71535740ac64de88b83ca002bd4490de5c7540e454331d46ebb925725b964ecfcc5c3bb076bf57fd819d0bd788b4495da456594503c8f5b618c4713e957a7589b151ead3a27ae80153b0fbc219f5524eec22dc91aa56ed87a71e2b6e6b857ee03a700fb5af5b23513fb63023c2764f74b40f00e44e2e7acd463e7f38c783a898015b51863659502645e7d7fdf14abf76c605d42e523a540f40e816db420d599cea502baa131d6b61b8add86a09b9f7587e107c6fc9043d72644053b54234146535e9ec25223d293f4a2a4481b5649a7625116aef735ecbbdd9e99076985e1c424e91c29ae231c01f2b12007c81a9b3c72a84522014f4cdcb949bb9850b62b1c9c5d35f0fc3dbf4f637af85d3082ffd15890041a455a5dd38efc902eb415afcd2c93a2b6ab46c88847f390affbc3a9af4745718044c9f23978027af66871e610e1beee21360fc5ae4f05ed32689328216db3c512fddacefb2713287f4f5069170db246ae0cb13b3b88f8d832d11ae6b26fe6f0f89b9a9056c28ad6160134ab1af4be606a2ca7d1256cfc223c823ae4921ba11aab9e4f8104885b3d17ac7826ad06b0fd0e54397e67179755b29c6d724f3e9d9a6934619ef0bfee72cdcf0031672bb6e55be53d3ef0fccf7beba7dea636eff7fdecee99f60a0bc5d5cb11e24683ca8085cf218a3607c3f5a051c33717f39834435c5c542f42bd9c9d9103173cb11436e2a626e228415924037ee4a4078f0d9a5bd0fef1dc9415740c4b32f51d199d912907a103c7e25928acadfb4dc1e8d32c34b4f0100bfe95feb850e0d7b359aae9a332913d1f8eca0d1cd93dee2f4b75076536037ca2b6bec5003ae89e03db9813d0dd2165bf50a836ca492f05750a3b5fff4d089fc54fdf225aa112d72ab021b0cef1b8d31c852133fae501406668db0cb4c3589934bde5ec218876b0dc4087b61d706234635d5d9065787a21ed895fb7c05902a554f879dae44d0905292815c5ae2338be1faa924d95673e4d904cc4215bfda1c9c0ca55fa38af6b696ef4f1a452d6870c1d82fa311cee716b00a11313a0ba7c4038e5e195b5e1d3502a44d9a9ab1636394821eaf157e6c77316f6e26b98ca81220752396890cce205581aead60bfcc3916a6c2d6d360c7e845e70c8c68e4acb5eb2877a9a7c7619eb79e2184afff7bb585319fa669b151040f5b15cab2d8c3a50379e9d9e8bf1ac6319bc32e489f64793f882d0e3e1906ac04d47eaec15c20c503d007d09d6a9b032d0f9a8b3d95447fcb1d4b7334b95d873a171f876dcc2a62a62af58e10802f88742027d3d27b9d72fea73dc84906d3dde03299dc3e033651406229da606162636465666768696a6b6c6d6e6f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
+ "303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
+ ],
+ "measurement": [
+ 19342,
+ 19615,
+ 3061
+ ],
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "fc936b02cb848e7e3c5840b05acfd699",
+ "7c71902ed398efc35ebb269aa58e0d5a",
+ "b90f902ade7fad281b8cfc5f1349ddf9"
+ ],
+ [
+ "f9758e4a5c3d6d2a1b4b3c7e5d397d83",
+ "9bb1de90bc4c2244e630e3b6780dc86a",
+ "15735600bee57d499ed0890554ef5183"
+ ],
+ [
+ "9b4106b3d83d0457705c83d147f7abe2",
+ "89299140701aeef79e13f6aee1632a3b",
+ "298919d5639ad48d0ea3799a98c7d082"
+ ]
+ ],
+ "prep_messages": [
+ "e129a25d4bb45ae8f5ff9d97d72f6d86"
+ ],
+ "prep_shares": [
+ [
+ "574ce7d79da173f0652f7d1e699d5fe7865e23ddbe20c3646ca1d41f8d0d123a831f3b90d5b917779d25ca7aafd5c903b4b996399d29d3af9fcb1c6549c36a639e45759c53b3bf6a1014fbdf7ea4d5d3726af036968d80aed52a7e48dc0a06ee66a6e34bf38ce1083cb56057cfbe98ae9138b635f2b64b60daf151ce214d33b99062b201463033142fc734e4c163fd8a5d2ea4503567c0a9739027883ab0b37ece24259b5ddcc88b3e272bbad93857a52a48abf5eb9fb135ccaf27fc0379f49f203cc0f5a4f14183ef3b935076893f0bc56dcd9b0ffbf8f711039bfd3fe894c0c007d8702db35ded99e2605fa3d9d4b70f6dfcdea9883c839e2a91aa358a38359e2d3cb20272a3ecb52e1edc17a48940",
+ "21c1bcbdd38fa9e8d86272f8c66c2b167ab67945b007b46536ece1073c4ac681e7967f80f8df630af6f5aac52647e36454b87d9c2d6aa2d4c2186b47a6bef8153706033ed34cde0d6bfd39a81a0fca04618cc495486eccfbcba29099fb101871b0e7871238066b280f7c09e0365214230a741f21625c730fae5e37a7a3cbe0c00910f79b754d99088059cf1d5da8a7911221c38f8a6f79877dd34cb4fd2990744e00b3a6e95d36d03d83969de2989c12e44eb18d466e8954631aa1dbe84e78111a03a3f1d743c457d0cbadbc0bd25ca7de998de032e28d1a8ca88d584ad7a64032c150fc25094cd2147739d1a1e03e8f6e636170867687f1c511dbb936fb521b6639ebc145f03b6d1751fcdf14c5108e",
+ "89f25b6a8ecee226a56d10e9cff57402a204a4e83b6b78a5aa32317f68443eac27e57ad2e170a0a32cf748f9dc0fe45524ab48c23361b4b327d498fd702f23b6aecf563787471334096bb202a07696b7eb8d79193777673f93547e14b15adb2ac7ea8999a3f4edb4bd7d3ecb26f2137f5e2881b52fd4d3ec538b978ebba54312ccd2dd263bce8b78c14ddbc0c94ed85d38fdf5fb7ee2eeb21ef066fdde8a94f89aabfdbb6bf174fff2358c4a8d6b7e6cdb080672d15b2ae06d6ca64387b56c00cf35ffecb0b195d01d1d86ee1a015df53e771738ec1faca3fcdfe4f033977dd50c03444dcb256fa982b24cf064dd6e4dffe71f8d5648cf336acd9485f9d70e24f2ad6b4aa14511e7b660c0866f73672a"
+ ]
+ ],
+ "public_share": "9e2d3cb20272a3ecb52e1edc17a489406639ebc145f03b6d1751fcdf14c5108ef2ad6b4aa14511e7b660c0866f73672a",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+ },
+ {
+ "input_shares": [
+ "db3fe5357f56f6cfe9a0a34ed08e231766b423d5670741c151f7bc28cb15c4c8678df838b6ef52d74c5bb5e8c8c607b2674c024da705b558163e3127ae09f4a5c061dc6129d71755ec77b5d5a2fc088542fa612bf1273d94718e0d28b654ea9525148bf910a5c6d55b596e323d4f55ab62b0851e0985987ea39cf500bfd4d5faec37f519120b574aead76d706e149c54b6e790f7c35553f830f67553ff3f08db0f5d8a2e157f9c1ef6eedcb845a19fffd4f9c6e705b035c95f3ebe51219a91c6000034c52b8fd7e90417636bb443c98c3e6933b1599c09151095fff99d7af43d327fcf2afdc50144e5b8f5b1ff1f8cc8d442613dc76dd48100dd4637b38fc5fa249333c6d1e2bec2132a3e3cf5fd11f7e923fbf275071a71535740ac64de88b83ca002bd4490de5c7540e454331d46ebb925725b964ecfcc5c3bb076bf57fd819d0bd788b4495da456594503c8f5b618c4713e957a7589b151ead3a27ae80153b1fbc219f5524eec22dc91aa56ed87a71d2b6e6b857ee03a700fb5af5b23513fb63023c2764f74b40f00e44e2e7acd463e7f38c783a898015b51863659502645e6d7fdf14abf76c605d42e523a540f40e716db420d599cea502baa131d6b61b8add86a09b9f7587e107c6fc9043d72644153b54234146535e9ec25223d293f4a2a4481b5649a7625116aef735ecbbdd9e99076985e1c424e91c29ae231c01f2b11007c81a9b3c72a84522014f4cdcb949cb9850b62b1c9c5d35f0fc3dbf4f637af85d3082ffd15890041a455a5dd38efc902eb415afcd2c93a2b6ab46c88847f380affbc3a9af4745718044c9f23978027af66871e610e1beee21360fc5ae4f05ed32689328216db3c512fddacefb2713187f4f5069170db246ae0cb13b3b88f8d832d11ae6b26fe6f0f89b9a9056c28ac6160134ab1af4be606a2ca7d1256cfc323c823ae4921ba11aab9e4f8104885b3d17ac7826ad06b0fd0e54397e67179765b29c6d724f3e9d9a6934619ef0bfee72cdcf0031672bb6e55be53d3ef0fccf8beba7dea636eff7fdecee99f60a0bc5d5cb11e24683ca8085cf218a3607c3f5a051c33717f39834435c5c542f42bd9c9d9103173cb11436e2a626e228415924037ee4a4078f0d9a5bd0fef1dc9415740c4b32f51d199d912907a103c7e25928acadfb4dc1e8d32c34b4f0100bfe95feb850e0d7b359aae9a332913d1f8eca0d1cd93dee2f4b75076536037ca2b6bec5003ae89e03db9813d0dd2165bf50a836ca492f05750a3b5fff4d089fc54fdf225aa112d72ab021b0cef1b8d31c852133fae501406668db0cb4c3589934bde5ec218876b0dc4087b61d706234635d5d9065787a21ed895fb7c05902a554f879dae44d0905292815c5ae2338be1faa924c67468654e6880c69828616e9d011132b27aa457c6dcbd554ec4374ec882357626d6f323bfadc3b23ac6d390e40f8f2008e462458194a1f9d63e0b977f4be907f34aecb0a12ad13b95bfc858c412abab064106faf149e9b25cb557c12a54e6ae957097b8b11f174094d134cc213bf98da7c7619eb79e2184afff7bb585319fa67b935c839af760464b6f3d5402847d07d9cf6c2502ae55f33e08959b38de273b2911fa9ef530cc2cc1a1f3f8224ed7c8efe455f3ad1e462c1d089d4be054801a56ecdd4dca5bbc7191990a1c028d6d79934bf7aed757eccdd68512ebe9f919f087f6020e75fa8e281316ae3a0a50a7f5606162636465666768696a6b6c6d6e6f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
+ "303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
+ ],
+ "measurement": [
+ 15986,
+ 24671,
+ 23910
+ ],
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "e0866b02cb848e7e3c5840b05acfd699",
+ "3c85902ed398efc35ebb269aa58e0d5a",
+ "2a61902ade7fad281b8cfc5f1349ddf9"
+ ],
+ [
+ "f9758e4a5c3d6d2a1b4b3c7e5d397d83",
+ "9bb1de90bc4c2244e630e3b6780dc86a",
+ "15735600bee57d499ed0890554ef5183"
+ ],
+ [
+ "9b4106b3d83d0457705c83d147f7abe2",
+ "89299140701aeef79e13f6aee1632a3b",
+ "298919d5639ad48d0ea3799a98c7d082"
+ ]
+ ],
+ "prep_messages": [
+ "62b225fa36c2cbc5896a40b1360f0ce0"
+ ],
+ "prep_shares": [
+ [
+ "574ce7d79da173f0652f7d1e699d5fe74186663960507c6ac01fafa7046bc88a5c4146feb2861bcb9f3ad8bb294028812c631ce293cde53b6fc09c6bdbcb839ede24393da7141c26a1a594ccdab1fba7ed2b2e38faf952343a2871b854699ff9533b4f88f9bc7e935cc62e3dea6efff7e2d396d8276281db4e789dc30d3fe01c397b64d685e87e7dccae1b7636b1a1486293f29e5c3433ee211e1f66ec5a7a0f1d79ed261735f53fdb51cbd51c1a6b057bf2845e34bfa091f0f8fd7565242c8b247064dfac759c1c646d085bca6ad0845b9cc2093fd49d493ebf2d48a90b99815f0bd165f56453c6b27dff04d3a161d9fc0bc4177e423791d4064faccfbbd3218c6815a24e38fb752efc839e59043cbb",
+ "21c1bcbdd38fa9e8d86272f8c66c2b16ce028e4f460022aed5c3e47011081a9ce7967f80f8df630af6f5aac52647e36481e9f91e23071ea7d98af1e76a9dd5823706033ed34cde0d6bfd39a81a0fca048271f4696baf9fd2e8a37e7b5e1be75eb0e7871238066b280f7c09e036521423abafe75c716165e86a573bab2e46efda0910f79b754d99088059cf1d5da8a79146cedb83d4b00d96b9871a66d73147984e00b3a6e95d36d03d83969de2989c12e222e1015cad12a219308643f8e1c1511a03a3f1d743c457d0cbadbc0bd25ca7cb9908430d0604df4468ab54b36f81ef32c150fc25094cd2147739d1a1e03e8f6e636170867687f1c511dbb936fb521b6639ebc145f03b6d1751fcdf14c5108e",
+ "89f25b6a8ecee226a56d10e9cff5740294125c8939f32731654a11d38906f94a27e57ad2e170a0a32cf748f9dc0fe45503b71c971d945163a09158bc06295e77aecf563787471334096bb202a07696b7f7c98da9fca5e00ff5ce6eb4b9d52274c7ea8999a3f4edb4bd7d3ecb26f2137fbd6dc71ed450947219eef408d88af7dbccd2dd263bce8b78c14ddbc0c94ed85db3255ece317e82a0f3b4a873e9b72b949aabfdbb6bf174fff2358c4a8d6b7e6c545473085c7c9b5c2aa9d290b01670ffcf35ffecb0b195d01d1d86ee1a015df56465c0991dd00dfda6948958c3ff38510c03444dcb256fa982b24cf064dd6e4dffe71f8d5648cf336acd9485f9d70e24f2ad6b4aa14511e7b660c0866f73672a"
+ ]
+ ],
+ "public_share": "8c6815a24e38fb752efc839e59043cbb6639ebc145f03b6d1751fcdf14c5108ef2ad6b4aa14511e7b660c0866f73672a",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+ }
+ ],
+ "shares": 3,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_0.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_0.json
new file mode 100644
index 0000000000..4dd3798668
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_0.json
@@ -0,0 +1,40 @@
+{
+ "agg_param": null,
+ "agg_result": 100,
+ "agg_shares": [
+ "0467d4fd6a7ec85386c9f3ef0790dc10",
+ "61992b02958137ac5d360c10f86f23ef"
+ ],
+ "bits": 8,
+ "prep": [
+ {
+ "input_shares": [
+ "1ac8a54e6804d575f85957d45c728487722ad069fc1ed413da970ea56ae482d81057f898a367319a14f402d072c24bb71aa13cf4f9cdcd731e779aaa4a5d561ff40e41797cf2d43308849ff9080f846c2e78e2736a1d3cdaa68f3ec890d633cc13f5bf96466d3f02f93612bc827ff53357d52ae00dd234b9758f2cbb7aa9682d06041da1507aa12446de554a945924f445d3715279ef00f4c55ae987cec4bb9f1316efdc8737b7f924d046a6b8ef222b0dc1205ce9b464076fa80d2dfe37af4d836d597ade7d51e18e9c95d13158942d249efd0a1a226759e4bc1d46d3a41bdb227703fe0a7554cf4769935bc99cd1f35b274ecec240816af4915c7abe3e16b7be5ab5e105f9ae7b2e683191c9400cf99ab0c687e4929f87e6e64f712ca02f07a1b29fcebdbfde7655797f9c1b6b3114420d8a19736ae614116782278b7a71f9ef6928ad44ce588644886523d6fbe0b7bbb47248edbaa0b5ce33f74a07005e2a6842eb2c05778e170112f6e6a5f206d7830aa122e29069dcb4a4c064e63c29b3c6e2b22dfb5ab344ca0f1be8e8ce36d26435413de2dc4f53e158ebb8478b4a98de014a688db9470106fd7e73a65c2e656b5a627b5584ca0594ba10cc39c5612bcef576625c37c5249ad5c04e42c66d6a9653c4ec47e2bcd860870bef64f812974654f17f77c08eaa395803d33bdf31db17d76dbb9d2407d7c4f9efbce274542ff6aa0dcf188803eb586108317db430ad517ce7cb0f56d225c835161eb348949ebe253bedc338c6b939ce837561f01d7f0304963eab2a28b38c36bb169a4ee0637635818bd5e4798a8319152a2678b0aa7b837cb0f24df6148ae2c84b78db8892f4415f90f3804e7a29cdcd32a0a8625fd20aca47ee0ef12ebd6138b3534a1b42303132333435363738393a3b3c3d3e3f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"
+ ],
+ "measurement": 100,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "0467d4fd6a7ec85386c9f3ef0790dc10"
+ ],
+ [
+ "61992b02958137ac5d360c10f86f23ef"
+ ]
+ ],
+ "prep_messages": [
+ "0fd2bb14ac123437f6520fdc4a817934"
+ ],
+ "prep_shares": [
+ [
+ "61428b8d7e326827ac832bc4074ad61652efcfdb8d95b6f06b83dd9f5d55ce9f142d1a1fd437eb8c84581ad15dcd9a57417942e63a1a46e6b0ffc8b6d6300f7d",
+ "a0bd747281cd97d8377cd43bf8b529e9eb5e4b1153111bd6cd06aa3a5493a6da4470f696b9afff52ec10fc00040e4538470fdb8d3e05e188aba2b16e24c71b69"
+ ]
+ ],
+ "public_share": "417942e63a1a46e6b0ffc8b6d6300f7d470fdb8d3e05e188aba2b16e24c71b69",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ }
+ ],
+ "shares": 2,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_1.json b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_1.json
new file mode 100644
index 0000000000..8e7163ae2a
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/Prio3Sum_1.json
@@ -0,0 +1,46 @@
+{
+ "agg_param": null,
+ "agg_result": 100,
+ "agg_shares": [
+ "b3916e4086c52aa0439356b05082885a",
+ "61992b02958137ac5d360c10f86f23ef",
+ "52d565bde4b89db326369d3fb70d54b6"
+ ],
+ "bits": 8,
+ "prep": [
+ {
+ "input_shares": [
+ "ec863a16981eee70b71dc7d396a85ba412dcb4e86769d6db0c60f668456f5d6fbb844d9503580fb7b662bbb2ed7d92002e6ab4a2d31a6f611cbb5c48ca6df69811d536f74a3ff61eb29bd9b9f1b64c35eddd5c4ac97376057883a317f2989b545a682775f948f28f80f366f36b4eb90f931bca79e229eae377102295d9c46da2e239f74f045084747039c0a955726b4258bc0d14da7474bea6cd136eb5e55e9531e6a68703003a64943a5650b16674c82d9c4b526a7ed3d69f8f13ae83609cf056f3fed8d6593fdad7b367d2d248413072651073ea91b8162d42af168698f0f0928c8238b2df218e26d004d2bdb5f9f20d0a43c0286d08cfc26971f282992f82ff14d51cee3e0f3fc7411869c2176cabc6b1a68e33ff5eb217490de9f0d85cb84e9115bb7e208a190d25bf9cc138485892802a50b790ba6f45804de487a3353e54b5471adb5ab612d9ee6416649e136456215503637e0daab367149bc5cdf02a2dabc2790f84cadec1510263fe6aa27df5df395b7a241777a8ed28da27276b48f599dd895a005746cfd1f3c874e6f52407f4c417934d7091685c0b38b1d76b398ad263ec73f4f811aed38febf67a19a001a2c7ab8071f986939713cccd146c7a049c5129783359fcf86410765028fbfbbe62c2474a6b75de0ba49c037e07946deae971207f4f74b8b1d6a7b225eb0b66ed1f3878bc14d9d7a38b2162247b7ed9ac3df6fd2a98a3e4bf2855c8fb13f39487481fe03f5b5cb5123d11aaef180ff8ae69709322459a01a72e9304295ae5721d6eac6dae140677d0dd60f192f0475bacfd131d4ff3393238caa00fe0847c3a43c97a31f84f58b3c7487c5c0a09e85b39ed4b69fcdfa071da15216fd5f1fad125328e40689acce1a6cb113c2a16f599606162636465666768696a6b6c6d6e6f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
+ "303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
+ ],
+ "measurement": 100,
+ "nonce": "000102030405060708090a0b0c0d0e0f",
+ "out_shares": [
+ [
+ "b3916e4086c52aa0439356b05082885a"
+ ],
+ [
+ "61992b02958137ac5d360c10f86f23ef"
+ ],
+ [
+ "52d565bde4b89db326369d3fb70d54b6"
+ ]
+ ],
+ "prep_messages": [
+ "e385da3bc2246be76ff12a7093ecb45e"
+ ],
+ "prep_shares": [
+ [
+ "7b6a9ad01449ec86dc6736dced3ecd24b47ab2a3768908b10696d537f2b02c98cf3314686f94ac37c7d81b14fea51f784e037bbdd56b2ee8486757acad61db1e",
+ "40a478cd7376c1e9ea339ddcf96ab1a7eb5e4b1153111bd6cd06aa3a5493a6da4470f696b9afff52ec10fc00040e4538470fdb8d3e05e188aba2b16e24c71b69",
+ "46f1ec617740528f1c642c47185681331adc83aace0cc5ddd256cf295c93c64d207d424f5056a8d59748ba9e423c4cf5f560fb4c6505c9a773629e12f21ee230"
+ ]
+ ],
+ "public_share": "4e037bbdd56b2ee8486757acad61db1e470fdb8d3e05e188aba2b16e24c71b69f560fb4c6505c9a773629e12f21ee230",
+ "rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+ }
+ ],
+ "shares": 3,
+ "verify_key": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/XofFixedKeyAes128.json b/third_party/rust/prio/src/vdaf/test_vec/07/XofFixedKeyAes128.json
new file mode 100644
index 0000000000..ea76c50ff8
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/XofFixedKeyAes128.json
@@ -0,0 +1,8 @@
+{
+ "binder": "62696e64657220737472696e67",
+ "derived_seed": "9cb53deb2feda2f9a7f34fde29a833f4",
+ "dst": "646f6d61696e2073657061726174696f6e20746167",
+ "expanded_vec_field128": "9cb53deb2feda2f9a7f34fde29a833f44ade288b2f55f2cd257e5f40595b5069543b40b740dfcf8ab5c863924f4510716b625f633a2f7e55a50b24a5fec9155dec199170f9ebe46768e9d120f7e8f62840441ef53dd5d2ba2d3fd39032e2da99498f4abf815b09c667cef08f0882fa945ba3335d2c7407de1b1650a5f4fe52884caf3ef1f5802eabb8f238c4d9d419bed904dcf79b22da49f68fb547c287a9cd4a38d58017eb2031a6bf1b4defb8905c3b777e9287f62a7fb0f97e4d8a26c4e5b909958bc73a6f7512b5b845488d98a7fcedf711ada6972f4c06818d3c3e7a070a88af60dc0323b59f304935fbbbd3792e590c9b6bce7459deba3599c7f30fe64a638219dde4bde4b1a51df8d85c2f36604c44f5f188148e3ba1dca3fd8073240ee577ef322df19a13d9ffa486a6833f4eb2838a58746707b8bf531cc86098f43809276b5f02914b26cb75938ca16eafa73397920a2f5e607af30e62ff60b83e15699d4d0265affe185b307ed330941a41b2b628e44d9a19412f7d9513cacd7b1fd740b7708e3bc764a0cf2146bca7c94d1901c43f509d7dcc9dfec54476789284e53f3760610a0ac5fce205e9b9aa0355c29702a5c9395bf1de8c974c800e1037a6bf5e0bd2af7d96b7f000ff6ab93299966b6832c493b600f2595a3db99353d2f8889019cd3ec5a73fa457f5442ed5edf349e78c9cf0cbf4f65aea03754c381c3efc206b7f17447cc51ac68eceacab9d92b13b0bc700c99a26ce2b6c3271f7639aa72dc27bbd54984907abb10ef1047ef352d378ddae48bf381804c89aa1847f5027537cf6af1b30aa44cead6495e98ca5b3205d39beb49d2db6752a4e57158e8c83464002b0b4a9838bc381c1dbdc3e9a584554fb76671a15f907c0b395a5",
+ "length": 40,
+ "seed": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/07/XofShake128.json b/third_party/rust/prio/src/vdaf/test_vec/07/XofShake128.json
new file mode 100644
index 0000000000..edafb1bd4d
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/07/XofShake128.json
@@ -0,0 +1,8 @@
+{
+ "binder": "62696e64657220737472696e67",
+ "derived_seed": "87c4d0dd654bf8eec8805c68b5eb0182",
+ "dst": "646f6d61696e2073657061726174696f6e20746167",
+ "expanded_vec_field128": "87c4d0dd654bf8eec8805c68b5eb0182b1b8ede598cfb8d8b234038fd0492eb14268bbb2ac15a55d463c8227c2d4fae8607631d13157c4935c5d2d56e4b1e2bdfe0f80b286d82e631704acee29ab6f7acaa316d3623cc3371297604caf57bc2eafe72056143971345901b9fb9f95b6a7384c6a88143124ff693ce9e453675f87a6b6338a1e1c9f72d19e32b51f60a1d7469de1fbe25407cc338d896b34c5fc437d2551297027eeefca9aaccdb78d655a6c220cbc2d76cc4a64b04806ae893c952996abb91f6ec32b6de27fe51be59514352d31af4967c0a85c5823ff73be7f15b9c0769321e4b69cb931a4e88f9da1fde1c5df9d84a7eadb41cf25681fc64a84a1c4accded794c1e6fec1fb26a286712425bfc29521273dcfc76cbab9b3c3c2b840ab6a4f9fd73ea434fc1c22a91943ed38fef0136f0f18f680c191978ab77c750d577c3526a327564da05cfc7bb9ef52c140d9e63b1f39761648772eaa61e2efb15890aed8340e6854b428f16dff5654c8a0852d46e817b49bbe91db3c46620adbd009a0d7d40843c1b6b7786833d3c1ae097b4fa35815dbcfca78e00a34f15936ed6d0f5bf50fc25adbecd3adfa55ba6bc7052f0662595cf7a933dfcc3d0ad5d825ec3bc191586a1c36a037d1c9e73c24777825d6afe59774abdb2918c2147a0436b17bafd967e07c46c3d6240c771f4fd4f9b3fff38b294508b8af5a1b71385f90f407620b7aa636fd2b55435b3688fc26ad3c23b2ad48158c4c475c07eb58569a8d1a906452b82d582397c4c69f5e79d3082d03b4dd85b5277a8b44c933d52d168caae8c602376f5487670a172d138364cb975c569c9c2d79506746090ea8102907c91b66764fd8740ca7bd3acb59173df29b5fa7542e51bce67b97c9ee2",
+ "length": 40,
+ "seed": "000102030405060708090a0b0c0d0e0f"
+}
diff --git a/third_party/rust/prio/src/vdaf/test_vec/prio2/fieldpriov2.json b/third_party/rust/prio/src/vdaf/test_vec/prio2/fieldpriov2.json
new file mode 100644
index 0000000000..674e682ac1
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/test_vec/prio2/fieldpriov2.json
@@ -0,0 +1,28 @@
+{
+ "dimension": 10,
+ "server_1_decrypted_shares": [
+ "x+y6F2RY3Y+toaLjU4a0WDqmNPYgHW1w9Z3svKirr+qcM9eDxhWSUPY4/N3A3PYGVFKa+i867MSiouE7Fq3iykBPKPMuNS4T8e1FA2uJ5PJHzPEobZNQWKG6ax5WYEbpmongi1kgK656OkATqMcXHnmkBC8=",
+ "u7Z0TCmpNSSrhCf27lo8qfmg7bUAAvcAbnntWm2lcoIa7yE8h1Mi4H7YDLv+t/9pzCHhyXC248VnyLWasFEfZ7wwzI2D+3U0XBjWZIXXufRvsfo8ZlUfbRtwYXYYwjxB5FV0jksJvZxaOFuNGP0IxIR1J1M=",
+ "iUazYtUedl8JNc8oSU+cLagEfJIBvQC7yUUjF5BhKyvDk54SWsCNbCTM8GigTWj5fobgebFqDlMbWaPceeO1F5S+8AwSpH8zSo20sckmYpeia8daPRDQu27e+ijLtxGkig82No/SniO+PPG96/xJ3e7wO74=",
+ "WUkmvNOpVZVb08GsZZuN7iqg6eUusF3wZY6U0NA8cAvzDiysSByNROP+cKCuy5l4r4JpkvP2n03WouKfyW4CWqQKC9vB+hCxVyWmNilyOTbACM0mxYU9lqvi43C3XEj4gflaOc7eUi8NTdAzvOThI/pmn3w=",
+ "Tp5mTet94KPUfzgs/XC8eal0iayw513glL8qUBAjAubTY97A/oO3Mevjm2Gn0uLFq4Add0A+O0Vo3l/Ar+etRUV9h4cEkKk5W7AEpY7JND224plOZ+3gsNiSUFm+hm6uOWXkanNNbPaSC3nyA81PF9fffAY=",
+ "2awOe4Bc1b7xsPvQxA9oTfadU3ueOw1EVbQ4poETuxmjPsvHbZhJXnYURfAvqfKCfz6eOUPhml7qcwWpht913JN9IQ5kpCM0So31xB99FkAuglwzZY7s4SLmWSrxjnvp9sjg8IDgiJAyCNQlIZtgjqq1TIE=",
+ "hLF/NXH1yZu4sf6m0jNfsZZS/cxVnoS5c1BtRSxkpeBuiXv+qnO3kko7A43CZ0b8C4kp1/Rfc5bnxn4+liYtsmu0ZGoW2AJdIfQTmkFfQlV/bi6Lcto121CDFY89/jrernWu3urzN1jeeNc1RmkpbmdtOrw=",
+ "xiufG50lmIHTVsvr6zpMXqTxhfVUlfe15eXfnwMkOEFfjMz/njV0HmqkvKtw9P/Z6gbN1Z0B0XnARknYz5OVNQRIaiu/AHTKWd19pnYu25VtFlHibbKhz32zHbfoHAoZeUXYr7x3j1vbJVNnkbwnz5WkNnc=",
+ "oEv/wE1+O6dVD/LKV0coeGq46zb8oDMFmL6GEEuZnC6REQdiJhHBa9fyWm1O+NtjUGu08r20R4r7f20dOqmYCOIMaNvejMTcXui9WXjrl8YQzQ86hkHSQyaXR0nkWOlCZsEF1kDX35TIGOWlCpjCjCwifPI=",
+ "OecAaoAKkmBgkmcVoCiq7NQkfXY/FS9M3zKZtE1pfbIwavWcqr9ucgFSs6zsas5aKVF/yJrNQwlbVE41YlClDuND6jD4NGuYKubzfT6I+saUAacdiDGp1fRA4RWcJFSxULG23XBP2b7D3l5wzeauGWDylkw="
+ ],
+ "server_2_decrypted_shares": [
+ "Kge/qYK/UnjrS8Q55v2K5Neg6iHORxEONcsaoLtX7wU=",
+ "rMt+kStRyxF2V7AWLLnWofVQPf6n7wrkhsTaMcqOZdo=",
+ "iMFcL/WOs264bO1dDOlyGjicF8/u6M5FdrPOFkkHuAE=",
+ "B1Dp6k0WFB252R4cAl2vtG7XGmvXmbDYM+iBn+U2QFU=",
+ "MO0aL0ZRKCfKkgaTmlsfn8Y/K6gRZoM9lOmOwBDxgPA=",
+ "Flz9wOjYAGLPjo+c89ve8fjaAhm+w1LqngA33ro+Q/M=",
+ "dfD3DWQfzkjubvNucxMRSbaaAz1O+4VLg2najHfPaxA=",
+ "ZgUbOVCMvhejv4LzOu3oSi+PgQBHUghfwxp4vJ5Ig9A=",
+ "Cqig9M1LE6iEl1PLB9Qc8cRfiF81TM4EqMCXVPT0SwE=",
+ "ZUpeNfk32vpkPoSh5ZNauB1pw5QeDJ5CMmvsDV0F6eA="
+ ],
+ "reference_sum": "BgAAAAcAAAAEAAAABQAAAAkAAAAGAAAAAgAAAAYAAAAEAAAABQAAAA=="
+} \ No newline at end of file
diff --git a/third_party/rust/prio/src/vdaf/xof.rs b/third_party/rust/prio/src/vdaf/xof.rs
new file mode 100644
index 0000000000..b38d176467
--- /dev/null
+++ b/third_party/rust/prio/src/vdaf/xof.rs
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//! Implementations of XOFs specified in [[draft-irtf-cfrg-vdaf-07]].
+//!
+//! [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+
+use crate::{
+ field::FieldElement,
+ prng::Prng,
+ vdaf::{CodecError, Decode, Encode},
+};
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+use aes::{
+ cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit},
+ Block,
+};
+#[cfg(feature = "crypto-dependencies")]
+use aes::{
+ cipher::{KeyIvInit, StreamCipher},
+ Aes128,
+};
+#[cfg(feature = "crypto-dependencies")]
+use ctr::Ctr64BE;
+use rand_core::{
+ impls::{next_u32_via_fill, next_u64_via_fill},
+ RngCore, SeedableRng,
+};
+use sha3::{
+ digest::{ExtendableOutput, Update, XofReader},
+ Shake128, Shake128Core, Shake128Reader,
+};
+#[cfg(feature = "crypto-dependencies")]
+use std::fmt::Formatter;
+use std::{
+ fmt::Debug,
+ io::{Cursor, Read},
+};
+use subtle::{Choice, ConstantTimeEq};
+
+/// Input of [`Xof`].
+#[derive(Clone, Debug)]
+pub struct Seed<const SEED_SIZE: usize>(pub(crate) [u8; SEED_SIZE]);
+
+impl<const SEED_SIZE: usize> Seed<SEED_SIZE> {
+ /// Generate a uniform random seed.
+ pub fn generate() -> Result<Self, getrandom::Error> {
+ let mut seed = [0; SEED_SIZE];
+ getrandom::getrandom(&mut seed)?;
+ Ok(Self::from_bytes(seed))
+ }
+
+ /// Construct seed from a byte slice.
+ pub(crate) fn from_bytes(seed: [u8; SEED_SIZE]) -> Self {
+ Self(seed)
+ }
+}
+
+impl<const SEED_SIZE: usize> AsRef<[u8; SEED_SIZE]> for Seed<SEED_SIZE> {
+ fn as_ref(&self) -> &[u8; SEED_SIZE] {
+ &self.0
+ }
+}
+
+impl<const SEED_SIZE: usize> PartialEq for Seed<SEED_SIZE> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl<const SEED_SIZE: usize> Eq for Seed<SEED_SIZE> {}
+
+impl<const SEED_SIZE: usize> ConstantTimeEq for Seed<SEED_SIZE> {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.0.ct_eq(&other.0)
+ }
+}
+
+impl<const SEED_SIZE: usize> Encode for Seed<SEED_SIZE> {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ bytes.extend_from_slice(&self.0[..]);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ Some(SEED_SIZE)
+ }
+}
+
+impl<const SEED_SIZE: usize> Decode for Seed<SEED_SIZE> {
+ fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ let mut seed = [0; SEED_SIZE];
+ bytes.read_exact(&mut seed)?;
+ Ok(Seed(seed))
+ }
+}
+
+/// Trait for deriving a vector of field elements.
+pub trait IntoFieldVec: RngCore + Sized {
+ /// Generate a finite field vector from the seed stream.
+ fn into_field_vec<F: FieldElement>(self, length: usize) -> Vec<F>;
+}
+
+impl<S: RngCore> IntoFieldVec for S {
+ fn into_field_vec<F: FieldElement>(self, length: usize) -> Vec<F> {
+ Prng::from_seed_stream(self).take(length).collect()
+ }
+}
+
+/// An extendable output function (XOF) with the interface specified in [[draft-irtf-cfrg-vdaf-07]].
+///
+/// [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+pub trait Xof<const SEED_SIZE: usize>: Clone + Debug {
+ /// The type of stream produced by this XOF.
+ type SeedStream: RngCore + Sized;
+
+ /// Construct an instance of [`Xof`] with the given seed.
+ fn init(seed_bytes: &[u8; SEED_SIZE], dst: &[u8]) -> Self;
+
+ /// Update the XOF state by passing in the next fragment of the info string. The final info
+ /// string is assembled from the concatenation of sequence of fragments passed to this method.
+ fn update(&mut self, data: &[u8]);
+
+ /// Finalize the XOF state, producing a seed stream.
+ fn into_seed_stream(self) -> Self::SeedStream;
+
+ /// Finalize the XOF state, producing a seed.
+ fn into_seed(self) -> Seed<SEED_SIZE> {
+ let mut new_seed = [0; SEED_SIZE];
+ let mut seed_stream = self.into_seed_stream();
+ seed_stream.fill_bytes(&mut new_seed);
+ Seed(new_seed)
+ }
+
+ /// Construct a seed stream from the given seed and info string.
+ fn seed_stream(seed: &Seed<SEED_SIZE>, dst: &[u8], binder: &[u8]) -> Self::SeedStream {
+ let mut xof = Self::init(seed.as_ref(), dst);
+ xof.update(binder);
+ xof.into_seed_stream()
+ }
+}
+
+/// The key stream produced by AES128 in CTR-mode.
+#[cfg(feature = "crypto-dependencies")]
+#[cfg_attr(docsrs, doc(cfg(feature = "crypto-dependencies")))]
+pub struct SeedStreamAes128(Ctr64BE<Aes128>);
+
+#[cfg(feature = "crypto-dependencies")]
+impl SeedStreamAes128 {
+ pub(crate) fn new(key: &[u8], iv: &[u8]) -> Self {
+ SeedStreamAes128(<Ctr64BE<Aes128> as KeyIvInit>::new(key.into(), iv.into()))
+ }
+
+ fn fill(&mut self, buf: &mut [u8]) {
+ buf.fill(0);
+ self.0.apply_keystream(buf);
+ }
+}
+
+#[cfg(feature = "crypto-dependencies")]
+impl RngCore for SeedStreamAes128 {
+ fn fill_bytes(&mut self, dest: &mut [u8]) {
+ self.fill(dest);
+ }
+
+ fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
+ self.fill(dest);
+ Ok(())
+ }
+
+ fn next_u32(&mut self) -> u32 {
+ next_u32_via_fill(self)
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ next_u64_via_fill(self)
+ }
+}
+
+#[cfg(feature = "crypto-dependencies")]
+impl Debug for SeedStreamAes128 {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ // Ctr64BE<Aes128> does not implement Debug, but [`ctr::CtrCore`][1] does, and we get that
+ // with [`cipher::StreamCipherCoreWrapper::get_core`][2].
+ //
+ // [1]: https://docs.rs/ctr/latest/ctr/struct.CtrCore.html
+ // [2]: https://docs.rs/cipher/latest/cipher/struct.StreamCipherCoreWrapper.html
+ self.0.get_core().fmt(f)
+ }
+}
+
+/// The XOF based on SHA-3 as specified in [[draft-irtf-cfrg-vdaf-07]].
+///
+/// [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+#[derive(Clone, Debug)]
+pub struct XofShake128(Shake128);
+
+impl Xof<16> for XofShake128 {
+ type SeedStream = SeedStreamSha3;
+
+ fn init(seed_bytes: &[u8; 16], dst: &[u8]) -> Self {
+ let mut xof = Self(Shake128::from_core(Shake128Core::default()));
+ Update::update(
+ &mut xof.0,
+ &[dst.len().try_into().expect("dst must be at most 255 bytes")],
+ );
+ Update::update(&mut xof.0, dst);
+ Update::update(&mut xof.0, seed_bytes);
+ xof
+ }
+
+ fn update(&mut self, data: &[u8]) {
+ Update::update(&mut self.0, data);
+ }
+
+ fn into_seed_stream(self) -> SeedStreamSha3 {
+ SeedStreamSha3::new(self.0.finalize_xof())
+ }
+}
+
+/// The seed stream produced by SHAKE128.
+pub struct SeedStreamSha3(Shake128Reader);
+
+impl SeedStreamSha3 {
+ pub(crate) fn new(reader: Shake128Reader) -> Self {
+ Self(reader)
+ }
+}
+
+impl RngCore for SeedStreamSha3 {
+ fn fill_bytes(&mut self, dest: &mut [u8]) {
+ XofReader::read(&mut self.0, dest);
+ }
+
+ fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
+ XofReader::read(&mut self.0, dest);
+ Ok(())
+ }
+
+ fn next_u32(&mut self) -> u32 {
+ next_u32_via_fill(self)
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ next_u64_via_fill(self)
+ }
+}
+
+/// A `rand`-compatible interface to construct XofShake128 seed streams, with the domain separation tag
+/// and binder string both fixed as the empty string.
+impl SeedableRng for SeedStreamSha3 {
+ type Seed = [u8; 16];
+
+ fn from_seed(seed: Self::Seed) -> Self {
+ XofShake128::init(&seed, b"").into_seed_stream()
+ }
+}
+
+/// Factory to produce multiple [`XofFixedKeyAes128`] instances with the same fixed key and
+/// different seeds.
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+#[cfg_attr(
+ docsrs,
+ doc(cfg(all(feature = "crypto-dependencies", feature = "experimental")))
+)]
+pub struct XofFixedKeyAes128Key {
+ cipher: Aes128,
+}
+
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+impl XofFixedKeyAes128Key {
+ /// Derive the fixed key from the domain separation tag and binder string.
+ pub fn new(dst: &[u8], binder: &[u8]) -> Self {
+ let mut fixed_key_deriver = Shake128::from_core(Shake128Core::default());
+ Update::update(
+ &mut fixed_key_deriver,
+ &[dst.len().try_into().expect("dst must be at most 255 bytes")],
+ );
+ Update::update(&mut fixed_key_deriver, dst);
+ Update::update(&mut fixed_key_deriver, binder);
+ let mut key = GenericArray::from([0; 16]);
+ XofReader::read(&mut fixed_key_deriver.finalize_xof(), key.as_mut());
+ Self {
+ cipher: Aes128::new(&key),
+ }
+ }
+
+ /// Combine a fixed key with a seed to produce a new stream of bytes.
+ pub fn with_seed(&self, seed: &[u8; 16]) -> SeedStreamFixedKeyAes128 {
+ SeedStreamFixedKeyAes128 {
+ cipher: self.cipher.clone(),
+ base_block: (*seed).into(),
+ length_consumed: 0,
+ }
+ }
+}
+
+/// XofFixedKeyAes128 as specified in [[draft-irtf-cfrg-vdaf-07]]. This XOF is NOT RECOMMENDED for
+/// general use; see Section 9 ("Security Considerations") for details.
+///
+/// This XOF combines SHA-3 and a fixed-key mode of operation for AES-128. The key is "fixed" in
+/// the sense that it is derived (using SHAKE128) from the domain separation tag and binder
+/// strings, and depending on the application, these strings can be hard-coded. The seed is used to
+/// construct each block of input passed to a hash function built from AES-128.
+///
+/// [draft-irtf-cfrg-vdaf-07]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/07/
+#[derive(Clone, Debug)]
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+#[cfg_attr(
+ docsrs,
+ doc(cfg(all(feature = "crypto-dependencies", feature = "experimental")))
+)]
+pub struct XofFixedKeyAes128 {
+ fixed_key_deriver: Shake128,
+ base_block: Block,
+}
+
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+impl Xof<16> for XofFixedKeyAes128 {
+ type SeedStream = SeedStreamFixedKeyAes128;
+
+ fn init(seed_bytes: &[u8; 16], dst: &[u8]) -> Self {
+ let mut fixed_key_deriver = Shake128::from_core(Shake128Core::default());
+ Update::update(
+ &mut fixed_key_deriver,
+ &[dst.len().try_into().expect("dst must be at most 255 bytes")],
+ );
+ Update::update(&mut fixed_key_deriver, dst);
+ Self {
+ fixed_key_deriver,
+ base_block: (*seed_bytes).into(),
+ }
+ }
+
+ fn update(&mut self, data: &[u8]) {
+ Update::update(&mut self.fixed_key_deriver, data);
+ }
+
+ fn into_seed_stream(self) -> SeedStreamFixedKeyAes128 {
+ let mut fixed_key = GenericArray::from([0; 16]);
+ XofReader::read(
+ &mut self.fixed_key_deriver.finalize_xof(),
+ fixed_key.as_mut(),
+ );
+ SeedStreamFixedKeyAes128 {
+ base_block: self.base_block,
+ cipher: Aes128::new(&fixed_key),
+ length_consumed: 0,
+ }
+ }
+}
+
+/// Seed stream for [`XofFixedKeyAes128`].
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+#[cfg_attr(
+ docsrs,
+ doc(cfg(all(feature = "crypto-dependencies", feature = "experimental")))
+)]
+pub struct SeedStreamFixedKeyAes128 {
+ cipher: Aes128,
+ base_block: Block,
+ length_consumed: u64,
+}
+
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+impl SeedStreamFixedKeyAes128 {
+ fn hash_block(&self, block: &mut Block) {
+ let sigma = Block::from([
+ // hi
+ block[8],
+ block[9],
+ block[10],
+ block[11],
+ block[12],
+ block[13],
+ block[14],
+ block[15],
+ // xor(hi, lo)
+ block[8] ^ block[0],
+ block[9] ^ block[1],
+ block[10] ^ block[2],
+ block[11] ^ block[3],
+ block[12] ^ block[4],
+ block[13] ^ block[5],
+ block[14] ^ block[6],
+ block[15] ^ block[7],
+ ]);
+ self.cipher.encrypt_block_b2b(&sigma, block);
+ for (b, s) in block.iter_mut().zip(sigma.iter()) {
+ *b ^= s;
+ }
+ }
+
+ fn fill(&mut self, buf: &mut [u8]) {
+ let next_length_consumed = self.length_consumed + u64::try_from(buf.len()).unwrap();
+ let mut offset = usize::try_from(self.length_consumed % 16).unwrap();
+ let mut index = 0;
+ let mut block = Block::from([0; 16]);
+
+ // NOTE(cjpatton) We might be able to speed this up by unrolling this loop and encrypting
+ // multiple blocks at the same time via `self.cipher.encrypt_blocks()`.
+ for block_counter in self.length_consumed / 16..(next_length_consumed + 15) / 16 {
+ block.clone_from(&self.base_block);
+ for (b, i) in block.iter_mut().zip(block_counter.to_le_bytes().iter()) {
+ *b ^= i;
+ }
+ self.hash_block(&mut block);
+ let read = std::cmp::min(16 - offset, buf.len() - index);
+ buf[index..index + read].copy_from_slice(&block[offset..offset + read]);
+ offset = 0;
+ index += read;
+ }
+
+ self.length_consumed = next_length_consumed;
+ }
+}
+
+#[cfg(all(feature = "crypto-dependencies", feature = "experimental"))]
+impl RngCore for SeedStreamFixedKeyAes128 {
+ fn fill_bytes(&mut self, dest: &mut [u8]) {
+ self.fill(dest);
+ }
+
+ fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
+ self.fill(dest);
+ Ok(())
+ }
+
+ fn next_u32(&mut self) -> u32 {
+ next_u32_via_fill(self)
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ next_u64_via_fill(self)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{field::Field128, vdaf::equality_comparison_test};
+ use serde::{Deserialize, Serialize};
+ use std::{convert::TryInto, io::Cursor};
+
+ #[derive(Deserialize, Serialize)]
+ struct XofTestVector {
+ #[serde(with = "hex")]
+ seed: Vec<u8>,
+ #[serde(with = "hex")]
+ dst: Vec<u8>,
+ #[serde(with = "hex")]
+ binder: Vec<u8>,
+ length: usize,
+ #[serde(with = "hex")]
+ derived_seed: Vec<u8>,
+ #[serde(with = "hex")]
+ expanded_vec_field128: Vec<u8>,
+ }
+
+ // Test correctness of dervied methods.
+ fn test_xof<P, const SEED_SIZE: usize>()
+ where
+ P: Xof<SEED_SIZE>,
+ {
+ let seed = Seed::generate().unwrap();
+ let dst = b"algorithm and usage";
+ let binder = b"bind to artifact";
+
+ let mut xof = P::init(seed.as_ref(), dst);
+ xof.update(binder);
+
+ let mut want = Seed([0; SEED_SIZE]);
+ xof.clone().into_seed_stream().fill_bytes(&mut want.0[..]);
+ let got = xof.clone().into_seed();
+ assert_eq!(got, want);
+
+ let mut want = [0; 45];
+ xof.clone().into_seed_stream().fill_bytes(&mut want);
+ let mut got = [0; 45];
+ P::seed_stream(&seed, dst, binder).fill_bytes(&mut got);
+ assert_eq!(got, want);
+ }
+
+ #[test]
+ fn xof_shake128() {
+ let t: XofTestVector =
+ serde_json::from_str(include_str!("test_vec/07/XofShake128.json")).unwrap();
+ let mut xof = XofShake128::init(&t.seed.try_into().unwrap(), &t.dst);
+ xof.update(&t.binder);
+
+ assert_eq!(
+ xof.clone().into_seed(),
+ Seed(t.derived_seed.try_into().unwrap())
+ );
+
+ let mut bytes = Cursor::new(t.expanded_vec_field128.as_slice());
+ let mut want = Vec::with_capacity(t.length);
+ while (bytes.position() as usize) < t.expanded_vec_field128.len() {
+ want.push(Field128::decode(&mut bytes).unwrap())
+ }
+ let got: Vec<Field128> = xof.clone().into_seed_stream().into_field_vec(t.length);
+ assert_eq!(got, want);
+
+ test_xof::<XofShake128, 16>();
+ }
+
+ #[cfg(feature = "experimental")]
+ #[test]
+ fn xof_fixed_key_aes128() {
+ let t: XofTestVector =
+ serde_json::from_str(include_str!("test_vec/07/XofFixedKeyAes128.json")).unwrap();
+ let mut xof = XofFixedKeyAes128::init(&t.seed.try_into().unwrap(), &t.dst);
+ xof.update(&t.binder);
+
+ assert_eq!(
+ xof.clone().into_seed(),
+ Seed(t.derived_seed.try_into().unwrap())
+ );
+
+ let mut bytes = Cursor::new(t.expanded_vec_field128.as_slice());
+ let mut want = Vec::with_capacity(t.length);
+ while (bytes.position() as usize) < t.expanded_vec_field128.len() {
+ want.push(Field128::decode(&mut bytes).unwrap())
+ }
+ let got: Vec<Field128> = xof.clone().into_seed_stream().into_field_vec(t.length);
+ assert_eq!(got, want);
+
+ test_xof::<XofFixedKeyAes128, 16>();
+ }
+
+ #[cfg(feature = "experimental")]
+ #[test]
+ fn xof_fixed_key_aes128_incomplete_block() {
+ let seed = Seed::generate().unwrap();
+ let mut expected = [0; 32];
+ XofFixedKeyAes128::seed_stream(&seed, b"dst", b"binder").fill(&mut expected);
+
+ for len in 0..=32 {
+ let mut buf = vec![0; len];
+ XofFixedKeyAes128::seed_stream(&seed, b"dst", b"binder").fill(&mut buf);
+ assert_eq!(buf, &expected[..len]);
+ }
+ }
+
+ #[cfg(feature = "experimental")]
+ #[test]
+ fn xof_fixed_key_aes128_alternate_apis() {
+ let dst = b"domain separation tag";
+ let binder = b"AAAAAAAAAAAAAAAAAAAAAAAA";
+ let seed_1 = Seed::generate().unwrap();
+ let seed_2 = Seed::generate().unwrap();
+
+ let mut stream_1_trait_api = XofFixedKeyAes128::seed_stream(&seed_1, dst, binder);
+ let mut output_1_trait_api = [0u8; 32];
+ stream_1_trait_api.fill(&mut output_1_trait_api);
+ let mut stream_2_trait_api = XofFixedKeyAes128::seed_stream(&seed_2, dst, binder);
+ let mut output_2_trait_api = [0u8; 32];
+ stream_2_trait_api.fill(&mut output_2_trait_api);
+
+ let fixed_key = XofFixedKeyAes128Key::new(dst, binder);
+ let mut stream_1_alternate_api = fixed_key.with_seed(seed_1.as_ref());
+ let mut output_1_alternate_api = [0u8; 32];
+ stream_1_alternate_api.fill(&mut output_1_alternate_api);
+ let mut stream_2_alternate_api = fixed_key.with_seed(seed_2.as_ref());
+ let mut output_2_alternate_api = [0u8; 32];
+ stream_2_alternate_api.fill(&mut output_2_alternate_api);
+
+ assert_eq!(output_1_trait_api, output_1_alternate_api);
+ assert_eq!(output_2_trait_api, output_2_alternate_api);
+ }
+
+ #[test]
+ fn seed_equality_test() {
+ equality_comparison_test(&[Seed([1, 2, 3]), Seed([3, 2, 1])])
+ }
+}