summaryrefslogtreecommitdiffstats
path: root/third_party/rust/prio/src/idpf.rs
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/idpf.rs
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/idpf.rs')
-rw-r--r--third_party/rust/prio/src/idpf.rs2200
1 files changed, 2200 insertions, 0 deletions
diff --git a/third_party/rust/prio/src/idpf.rs b/third_party/rust/prio/src/idpf.rs
new file mode 100644
index 0000000000..2bb73f2159
--- /dev/null
+++ b/third_party/rust/prio/src/idpf.rs
@@ -0,0 +1,2200 @@
+//! This module implements the incremental distributed point function (IDPF) described 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::{FieldElement, FieldElementExt},
+ vdaf::{
+ xof::{Seed, XofFixedKeyAes128Key},
+ VdafError, VERSION,
+ },
+};
+use bitvec::{
+ bitvec,
+ boxed::BitBox,
+ prelude::{Lsb0, Msb0},
+ slice::BitSlice,
+ vec::BitVec,
+ view::BitView,
+};
+use rand_core::RngCore;
+use std::{
+ collections::{HashMap, VecDeque},
+ fmt::Debug,
+ io::{Cursor, Read},
+ ops::{Add, AddAssign, ControlFlow, Index, Sub},
+};
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
+
+/// IDPF-related errors.
+#[derive(Debug, thiserror::Error)]
+pub enum IdpfError {
+ /// Error from incompatible shares at different levels.
+ #[error("tried to merge shares from incompatible levels")]
+ MismatchedLevel,
+
+ /// Invalid parameter, indicates an invalid input to either [`Idpf::gen`] or [`Idpf::eval`].
+ #[error("invalid parameter: {0}")]
+ InvalidParameter(String),
+}
+
+/// An index used as the input to an IDPF evaluation.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct IdpfInput {
+ /// The index as a boxed bit slice.
+ index: BitBox,
+}
+
+impl IdpfInput {
+ /// Convert a slice of bytes into an IDPF input, where the bits of each byte are processed in
+ /// MSB-to-LSB order. (Subsequent bytes are processed in their natural order.)
+ pub fn from_bytes(bytes: &[u8]) -> IdpfInput {
+ let bit_slice_u8_storage = bytes.view_bits::<Msb0>();
+ let mut bit_vec_usize_storage = bitvec![0; bit_slice_u8_storage.len()];
+ bit_vec_usize_storage.clone_from_bitslice(bit_slice_u8_storage);
+ IdpfInput {
+ index: bit_vec_usize_storage.into_boxed_bitslice(),
+ }
+ }
+
+ /// Convert a slice of booleans into an IDPF input.
+ pub fn from_bools(bools: &[bool]) -> IdpfInput {
+ let bits = bools.iter().collect::<BitVec>();
+ IdpfInput {
+ index: bits.into_boxed_bitslice(),
+ }
+ }
+
+ /// Create a new IDPF input by appending to this input.
+ pub fn clone_with_suffix(&self, suffix: &[bool]) -> IdpfInput {
+ let mut vec = BitVec::with_capacity(self.index.len() + suffix.len());
+ vec.extend_from_bitslice(&self.index);
+ vec.extend(suffix);
+ IdpfInput {
+ index: vec.into_boxed_bitslice(),
+ }
+ }
+
+ /// Get the length of the input in bits.
+ pub fn len(&self) -> usize {
+ self.index.len()
+ }
+
+ /// Check if the input is empty, i.e. it does not contain any bits.
+ pub fn is_empty(&self) -> bool {
+ self.index.is_empty()
+ }
+
+ /// Get an iterator over the bits that make up this input.
+ pub fn iter(&self) -> impl DoubleEndedIterator<Item = bool> + '_ {
+ self.index.iter().by_vals()
+ }
+
+ /// Convert the IDPF into a byte slice. If the length of the underlying bit vector is not a
+ /// multiple of `8`, then the least significant bits of the last byte are `0`-padded.
+ pub fn to_bytes(&self) -> Vec<u8> {
+ let mut vec = BitVec::<u8, Msb0>::with_capacity(self.index.len());
+ vec.extend_from_bitslice(&self.index);
+ vec.set_uninitialized(false);
+ vec.into_vec()
+ }
+
+ /// Return the `level`-bit prefix of this IDPF input.
+ pub fn prefix(&self, level: usize) -> Self {
+ Self {
+ index: self.index[..=level].to_owned().into(),
+ }
+ }
+}
+
+impl From<BitVec<usize, Lsb0>> for IdpfInput {
+ fn from(bit_vec: BitVec<usize, Lsb0>) -> Self {
+ IdpfInput {
+ index: bit_vec.into_boxed_bitslice(),
+ }
+ }
+}
+
+impl From<BitBox<usize, Lsb0>> for IdpfInput {
+ fn from(bit_box: BitBox<usize, Lsb0>) -> Self {
+ IdpfInput { index: bit_box }
+ }
+}
+
+impl<I> Index<I> for IdpfInput
+where
+ BitSlice: Index<I>,
+{
+ type Output = <BitSlice as Index<I>>::Output;
+
+ fn index(&self, index: I) -> &Self::Output {
+ &self.index[index]
+ }
+}
+
+/// Trait for values to be programmed into an IDPF.
+///
+/// Values must form an Abelian group, so that they can be secret-shared, and the group operation
+/// must be represented by [`Add`]. Values must be encodable and decodable, without need for a
+/// decoding parameter. Values can be pseudorandomly generated, with a uniform probability
+/// distribution, from XOF output.
+pub trait IdpfValue:
+ Add<Output = Self>
+ + AddAssign
+ + Sub<Output = Self>
+ + ConditionallyNegatable
+ + Encode
+ + Decode
+ + Sized
+{
+ /// Any run-time parameters needed to produce a value.
+ type ValueParameter;
+
+ /// Generate a pseudorandom value from a seed stream.
+ fn generate<S>(seed_stream: &mut S, parameter: &Self::ValueParameter) -> Self
+ where
+ S: RngCore;
+
+ /// Returns the additive identity.
+ fn zero(parameter: &Self::ValueParameter) -> Self;
+
+ /// Conditionally select between two values. Implementations must perform this operation in
+ /// constant time.
+ ///
+ /// This is the same as in [`subtle::ConditionallySelectable`], but without the [`Copy`] bound.
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self;
+}
+
+impl<F> IdpfValue for F
+where
+ F: FieldElement,
+{
+ type ValueParameter = ();
+
+ fn generate<S>(seed_stream: &mut S, _: &()) -> Self
+ where
+ S: RngCore,
+ {
+ // This is analogous to `Prng::get()`, but does not make use of a persistent buffer of
+ // output.
+ let mut buffer = [0u8; 64];
+ assert!(
+ buffer.len() >= F::ENCODED_SIZE,
+ "field is too big for buffer"
+ );
+ loop {
+ seed_stream.fill_bytes(&mut buffer[..F::ENCODED_SIZE]);
+ match F::from_random_rejection(&buffer[..F::ENCODED_SIZE]) {
+ ControlFlow::Break(x) => return x,
+ ControlFlow::Continue(()) => continue,
+ }
+ }
+ }
+
+ fn zero(_: &()) -> Self {
+ <Self as FieldElement>::zero()
+ }
+
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ <F as ConditionallySelectable>::conditional_select(a, b, choice)
+ }
+}
+
+/// An output from evaluation of an IDPF at some level and index.
+#[derive(Debug, PartialEq, Eq)]
+pub enum IdpfOutputShare<VI, VL> {
+ /// An IDPF output share corresponding to an inner tree node.
+ Inner(VI),
+ /// An IDPF output share corresponding to a leaf tree node.
+ Leaf(VL),
+}
+
+impl<VI, VL> IdpfOutputShare<VI, VL>
+where
+ VI: IdpfValue,
+ VL: IdpfValue,
+{
+ /// Combine two output share values into one.
+ pub fn merge(self, other: Self) -> Result<IdpfOutputShare<VI, VL>, IdpfError> {
+ match (self, other) {
+ (IdpfOutputShare::Inner(mut self_value), IdpfOutputShare::Inner(other_value)) => {
+ self_value += other_value;
+ Ok(IdpfOutputShare::Inner(self_value))
+ }
+ (IdpfOutputShare::Leaf(mut self_value), IdpfOutputShare::Leaf(other_value)) => {
+ self_value += other_value;
+ Ok(IdpfOutputShare::Leaf(self_value))
+ }
+ (_, _) => Err(IdpfError::MismatchedLevel),
+ }
+ }
+}
+
+fn extend(seed: &[u8; 16], xof_fixed_key: &XofFixedKeyAes128Key) -> ([[u8; 16]; 2], [Choice; 2]) {
+ let mut seed_stream = xof_fixed_key.with_seed(seed);
+
+ let mut seeds = [[0u8; 16], [0u8; 16]];
+ seed_stream.fill_bytes(&mut seeds[0]);
+ seed_stream.fill_bytes(&mut seeds[1]);
+
+ let mut byte = [0u8];
+ seed_stream.fill_bytes(&mut byte);
+ let control_bits = [(byte[0] & 1).into(), ((byte[0] >> 1) & 1).into()];
+
+ (seeds, control_bits)
+}
+
+fn convert<V>(
+ seed: &[u8; 16],
+ xof_fixed_key: &XofFixedKeyAes128Key,
+ parameter: &V::ValueParameter,
+) -> ([u8; 16], V)
+where
+ V: IdpfValue,
+{
+ let mut seed_stream = xof_fixed_key.with_seed(seed);
+
+ let mut next_seed = [0u8; 16];
+ seed_stream.fill_bytes(&mut next_seed);
+
+ (next_seed, V::generate(&mut seed_stream, parameter))
+}
+
+/// Helper method to update seeds, update control bits, and output the correction word for one level
+/// of the IDPF key generation process.
+fn generate_correction_word<V>(
+ input_bit: Choice,
+ value: V,
+ parameter: &V::ValueParameter,
+ keys: &mut [[u8; 16]; 2],
+ control_bits: &mut [Choice; 2],
+ extend_xof_fixed_key: &XofFixedKeyAes128Key,
+ convert_xof_fixed_key: &XofFixedKeyAes128Key,
+) -> IdpfCorrectionWord<V>
+where
+ V: IdpfValue,
+{
+ // Expand both keys into two seeds and two control bits each.
+ let (seed_0, control_bits_0) = extend(&keys[0], extend_xof_fixed_key);
+ let (seed_1, control_bits_1) = extend(&keys[1], extend_xof_fixed_key);
+
+ let (keep, lose) = (input_bit, !input_bit);
+
+ let cw_seed = xor_seeds(
+ &conditional_select_seed(lose, &seed_0),
+ &conditional_select_seed(lose, &seed_1),
+ );
+ let cw_control_bits = [
+ control_bits_0[0] ^ control_bits_1[0] ^ input_bit ^ Choice::from(1),
+ control_bits_0[1] ^ control_bits_1[1] ^ input_bit,
+ ];
+ let cw_control_bits_keep =
+ Choice::conditional_select(&cw_control_bits[0], &cw_control_bits[1], keep);
+
+ let previous_control_bits = *control_bits;
+ let control_bits_0_keep =
+ Choice::conditional_select(&control_bits_0[0], &control_bits_0[1], keep);
+ let control_bits_1_keep =
+ Choice::conditional_select(&control_bits_1[0], &control_bits_1[1], keep);
+ control_bits[0] = control_bits_0_keep ^ (cw_control_bits_keep & previous_control_bits[0]);
+ control_bits[1] = control_bits_1_keep ^ (cw_control_bits_keep & previous_control_bits[1]);
+
+ let seed_0_keep = conditional_select_seed(keep, &seed_0);
+ let seed_1_keep = conditional_select_seed(keep, &seed_1);
+ let seeds_corrected = [
+ conditional_xor_seeds(&seed_0_keep, &cw_seed, previous_control_bits[0]),
+ conditional_xor_seeds(&seed_1_keep, &cw_seed, previous_control_bits[1]),
+ ];
+
+ let (new_key_0, elements_0) =
+ convert::<V>(&seeds_corrected[0], convert_xof_fixed_key, parameter);
+ let (new_key_1, elements_1) =
+ convert::<V>(&seeds_corrected[1], convert_xof_fixed_key, parameter);
+
+ keys[0] = new_key_0;
+ keys[1] = new_key_1;
+
+ let mut cw_value = value - elements_0 + elements_1;
+ cw_value.conditional_negate(control_bits[1]);
+
+ IdpfCorrectionWord {
+ seed: cw_seed,
+ control_bits: cw_control_bits,
+ value: cw_value,
+ }
+}
+
+/// Helper function to evaluate one level of an IDPF. This updates the seed and control bit
+/// arguments that are passed in.
+#[allow(clippy::too_many_arguments)]
+fn eval_next<V>(
+ is_leader: bool,
+ parameter: &V::ValueParameter,
+ key: &mut [u8; 16],
+ control_bit: &mut Choice,
+ correction_word: &IdpfCorrectionWord<V>,
+ input_bit: Choice,
+ extend_xof_fixed_key: &XofFixedKeyAes128Key,
+ convert_xof_fixed_key: &XofFixedKeyAes128Key,
+) -> V
+where
+ V: IdpfValue,
+{
+ let (mut seeds, mut control_bits) = extend(key, extend_xof_fixed_key);
+
+ seeds[0] = conditional_xor_seeds(&seeds[0], &correction_word.seed, *control_bit);
+ control_bits[0] ^= correction_word.control_bits[0] & *control_bit;
+ seeds[1] = conditional_xor_seeds(&seeds[1], &correction_word.seed, *control_bit);
+ control_bits[1] ^= correction_word.control_bits[1] & *control_bit;
+
+ let seed_corrected = conditional_select_seed(input_bit, &seeds);
+ *control_bit = Choice::conditional_select(&control_bits[0], &control_bits[1], input_bit);
+
+ let (new_key, elements) = convert::<V>(&seed_corrected, convert_xof_fixed_key, parameter);
+ *key = new_key;
+
+ let mut out =
+ elements + V::conditional_select(&V::zero(parameter), &correction_word.value, *control_bit);
+ out.conditional_negate(Choice::from((!is_leader) as u8));
+ out
+}
+
+/// This defines a family of IDPFs (incremental distributed point functions) with certain types of
+/// values at inner tree nodes and at leaf tree nodes.
+///
+/// IDPF keys can be generated by providing an input and programmed outputs for each tree level to
+/// [`Idpf::gen`].
+pub struct Idpf<VI, VL>
+where
+ VI: IdpfValue,
+ VL: IdpfValue,
+{
+ inner_node_value_parameter: VI::ValueParameter,
+ leaf_node_value_parameter: VL::ValueParameter,
+}
+
+impl<VI, VL> Idpf<VI, VL>
+where
+ VI: IdpfValue,
+ VL: IdpfValue,
+{
+ /// Construct an [`Idpf`] instance with the given run-time parameters needed for inner and leaf
+ /// values.
+ pub fn new(
+ inner_node_value_parameter: VI::ValueParameter,
+ leaf_node_value_parameter: VL::ValueParameter,
+ ) -> Self {
+ Self {
+ inner_node_value_parameter,
+ leaf_node_value_parameter,
+ }
+ }
+
+ pub(crate) fn gen_with_random<M: IntoIterator<Item = VI>>(
+ &self,
+ input: &IdpfInput,
+ inner_values: M,
+ leaf_value: VL,
+ binder: &[u8],
+ random: &[[u8; 16]; 2],
+ ) -> Result<(IdpfPublicShare<VI, VL>, [Seed<16>; 2]), VdafError> {
+ let bits = input.len();
+
+ let initial_keys: [Seed<16>; 2] =
+ [Seed::from_bytes(random[0]), Seed::from_bytes(random[1])];
+
+ let extend_dst = [
+ VERSION, 1, /* algorithm class */
+ 0, 0, 0, 0, /* algorithm ID */
+ 0, 0, /* usage */
+ ];
+ let convert_dst = [
+ VERSION, 1, /* algorithm class */
+ 0, 0, 0, 0, /* algorithm ID */
+ 0, 1, /* usage */
+ ];
+ let extend_xof_fixed_key = XofFixedKeyAes128Key::new(&extend_dst, binder);
+ let convert_xof_fixed_key = XofFixedKeyAes128Key::new(&convert_dst, binder);
+
+ let mut keys = [initial_keys[0].0, initial_keys[1].0];
+ let mut control_bits = [Choice::from(0u8), Choice::from(1u8)];
+ let mut inner_correction_words = Vec::with_capacity(bits - 1);
+
+ for (level, value) in inner_values.into_iter().enumerate() {
+ if level >= bits - 1 {
+ return Err(IdpfError::InvalidParameter(
+ "too many values were supplied".to_string(),
+ )
+ .into());
+ }
+ inner_correction_words.push(generate_correction_word::<VI>(
+ Choice::from(input[level] as u8),
+ value,
+ &self.inner_node_value_parameter,
+ &mut keys,
+ &mut control_bits,
+ &extend_xof_fixed_key,
+ &convert_xof_fixed_key,
+ ));
+ }
+ if inner_correction_words.len() != bits - 1 {
+ return Err(
+ IdpfError::InvalidParameter("too few values were supplied".to_string()).into(),
+ );
+ }
+ let leaf_correction_word = generate_correction_word::<VL>(
+ Choice::from(input[bits - 1] as u8),
+ leaf_value,
+ &self.leaf_node_value_parameter,
+ &mut keys,
+ &mut control_bits,
+ &extend_xof_fixed_key,
+ &convert_xof_fixed_key,
+ );
+ let public_share = IdpfPublicShare {
+ inner_correction_words,
+ leaf_correction_word,
+ };
+
+ Ok((public_share, initial_keys))
+ }
+
+ /// The IDPF key generation algorithm.
+ ///
+ /// Generate and return a sequence of IDPF shares for `input`. The parameters `inner_values`
+ /// and `leaf_value` provide the output values for each successive level of the prefix tree.
+ pub fn gen<M>(
+ &self,
+ input: &IdpfInput,
+ inner_values: M,
+ leaf_value: VL,
+ binder: &[u8],
+ ) -> Result<(IdpfPublicShare<VI, VL>, [Seed<16>; 2]), VdafError>
+ where
+ M: IntoIterator<Item = VI>,
+ {
+ if input.is_empty() {
+ return Err(
+ IdpfError::InvalidParameter("invalid number of bits: 0".to_string()).into(),
+ );
+ }
+ let mut random = [[0u8; 16]; 2];
+ for random_seed in random.iter_mut() {
+ getrandom::getrandom(random_seed)?;
+ }
+ self.gen_with_random(input, inner_values, leaf_value, binder, &random)
+ }
+
+ /// Evaluate an IDPF share on `prefix`, starting from a particular tree level with known
+ /// intermediate values.
+ #[allow(clippy::too_many_arguments)]
+ fn eval_from_node(
+ &self,
+ is_leader: bool,
+ public_share: &IdpfPublicShare<VI, VL>,
+ start_level: usize,
+ mut key: [u8; 16],
+ mut control_bit: Choice,
+ prefix: &IdpfInput,
+ binder: &[u8],
+ cache: &mut dyn IdpfCache,
+ ) -> Result<IdpfOutputShare<VI, VL>, IdpfError> {
+ let bits = public_share.inner_correction_words.len() + 1;
+
+ let extend_dst = [
+ VERSION, 1, /* algorithm class */
+ 0, 0, 0, 0, /* algorithm ID */
+ 0, 0, /* usage */
+ ];
+ let convert_dst = [
+ VERSION, 1, /* algorithm class */
+ 0, 0, 0, 0, /* algorithm ID */
+ 0, 1, /* usage */
+ ];
+ let extend_xof_fixed_key = XofFixedKeyAes128Key::new(&extend_dst, binder);
+ let convert_xof_fixed_key = XofFixedKeyAes128Key::new(&convert_dst, binder);
+
+ let mut last_inner_output = None;
+ for ((correction_word, input_bit), level) in public_share.inner_correction_words
+ [start_level..]
+ .iter()
+ .zip(prefix[start_level..].iter())
+ .zip(start_level..)
+ {
+ last_inner_output = Some(eval_next(
+ is_leader,
+ &self.inner_node_value_parameter,
+ &mut key,
+ &mut control_bit,
+ correction_word,
+ Choice::from(*input_bit as u8),
+ &extend_xof_fixed_key,
+ &convert_xof_fixed_key,
+ ));
+ let cache_key = &prefix[..=level];
+ cache.insert(cache_key, &(key, control_bit.unwrap_u8()));
+ }
+
+ if prefix.len() == bits {
+ let leaf_output = eval_next(
+ is_leader,
+ &self.leaf_node_value_parameter,
+ &mut key,
+ &mut control_bit,
+ &public_share.leaf_correction_word,
+ Choice::from(prefix[bits - 1] as u8),
+ &extend_xof_fixed_key,
+ &convert_xof_fixed_key,
+ );
+ // Note: there's no point caching this node's key, because we will always run the
+ // eval_next() call for the leaf level.
+ Ok(IdpfOutputShare::Leaf(leaf_output))
+ } else {
+ Ok(IdpfOutputShare::Inner(last_inner_output.unwrap()))
+ }
+ }
+
+ /// The IDPF key evaluation algorithm.
+ ///
+ /// Evaluate an IDPF share on `prefix`.
+ pub fn eval(
+ &self,
+ agg_id: usize,
+ public_share: &IdpfPublicShare<VI, VL>,
+ key: &Seed<16>,
+ prefix: &IdpfInput,
+ binder: &[u8],
+ cache: &mut dyn IdpfCache,
+ ) -> Result<IdpfOutputShare<VI, VL>, IdpfError> {
+ let bits = public_share.inner_correction_words.len() + 1;
+ if agg_id > 1 {
+ return Err(IdpfError::InvalidParameter(format!(
+ "invalid aggregator ID {agg_id}"
+ )));
+ }
+ let is_leader = agg_id == 0;
+ if prefix.is_empty() {
+ return Err(IdpfError::InvalidParameter("empty prefix".to_string()));
+ }
+ if prefix.len() > bits {
+ return Err(IdpfError::InvalidParameter(format!(
+ "prefix length ({}) exceeds configured number of bits ({})",
+ prefix.len(),
+ bits,
+ )));
+ }
+
+ // Check for cached keys first, starting from the end of our desired path down the tree, and
+ // walking back up. If we get a hit, stop there and evaluate the remainder of the tree path
+ // going forward.
+ if prefix.len() > 1 {
+ // Skip checking for `prefix` in the cache, because we don't store field element
+ // values along with keys and control bits. Instead, start looking one node higher
+ // up, so we can recompute everything for the last level of `prefix`.
+ let mut cache_key = &prefix[..prefix.len() - 1];
+ while !cache_key.is_empty() {
+ if let Some((key, control_bit)) = cache.get(cache_key) {
+ // Evaluate the IDPF starting from the cached data at a previously-computed
+ // node, and return the result.
+ return self.eval_from_node(
+ is_leader,
+ public_share,
+ /* start_level */ cache_key.len(),
+ key,
+ Choice::from(control_bit),
+ prefix,
+ binder,
+ cache,
+ );
+ }
+ cache_key = &cache_key[..cache_key.len() - 1];
+ }
+ }
+ // Evaluate starting from the root node.
+ self.eval_from_node(
+ is_leader,
+ public_share,
+ /* start_level */ 0,
+ key.0,
+ /* control_bit */ Choice::from((!is_leader) as u8),
+ prefix,
+ binder,
+ cache,
+ )
+ }
+}
+
+/// An IDPF public share. This contains the list of correction words used by all parties when
+/// evaluating the IDPF.
+#[derive(Debug, Clone)]
+pub struct IdpfPublicShare<VI, VL> {
+ /// Correction words for each inner node level.
+ inner_correction_words: Vec<IdpfCorrectionWord<VI>>,
+ /// Correction word for the leaf node level.
+ leaf_correction_word: IdpfCorrectionWord<VL>,
+}
+
+impl<VI, VL> ConstantTimeEq for IdpfPublicShare<VI, VL>
+where
+ VI: ConstantTimeEq,
+ VL: ConstantTimeEq,
+{
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.inner_correction_words
+ .ct_eq(&other.inner_correction_words)
+ & self.leaf_correction_word.ct_eq(&other.leaf_correction_word)
+ }
+}
+
+impl<VI, VL> PartialEq for IdpfPublicShare<VI, VL>
+where
+ VI: ConstantTimeEq,
+ VL: ConstantTimeEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl<VI, VL> Eq for IdpfPublicShare<VI, VL>
+where
+ VI: ConstantTimeEq,
+ VL: ConstantTimeEq,
+{
+}
+
+impl<VI, VL> Encode for IdpfPublicShare<VI, VL>
+where
+ VI: Encode,
+ VL: Encode,
+{
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ // Control bits need to be written within each byte in LSB-to-MSB order, and assigned into
+ // bytes in big-endian order. Thus, the first four levels will have their control bits
+ // encoded in the last byte, and the last levels will have their control bits encoded in the
+ // first byte.
+ let mut control_bits: BitVec<u8, Lsb0> =
+ BitVec::with_capacity(self.inner_correction_words.len() * 2 + 2);
+ for correction_words in self.inner_correction_words.iter() {
+ control_bits.extend(correction_words.control_bits.iter().map(|x| bool::from(*x)));
+ }
+ control_bits.extend(
+ self.leaf_correction_word
+ .control_bits
+ .iter()
+ .map(|x| bool::from(*x)),
+ );
+ control_bits.set_uninitialized(false);
+ let mut packed_control = control_bits.into_vec();
+ bytes.append(&mut packed_control);
+
+ for correction_words in self.inner_correction_words.iter() {
+ Seed(correction_words.seed).encode(bytes);
+ correction_words.value.encode(bytes);
+ }
+ Seed(self.leaf_correction_word.seed).encode(bytes);
+ self.leaf_correction_word.value.encode(bytes);
+ }
+
+ fn encoded_len(&self) -> Option<usize> {
+ let control_bits_count = (self.inner_correction_words.len() + 1) * 2;
+ let mut len = (control_bits_count + 7) / 8 + (self.inner_correction_words.len() + 1) * 16;
+ for correction_words in self.inner_correction_words.iter() {
+ len += correction_words.value.encoded_len()?;
+ }
+ len += self.leaf_correction_word.value.encoded_len()?;
+ Some(len)
+ }
+}
+
+impl<VI, VL> ParameterizedDecode<usize> for IdpfPublicShare<VI, VL>
+where
+ VI: Decode,
+ VL: Decode,
+{
+ fn decode_with_param(bits: &usize, bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ let packed_control_len = (bits + 3) / 4;
+ let mut packed = vec![0u8; packed_control_len];
+ bytes.read_exact(&mut packed)?;
+ let unpacked_control_bits: BitVec<u8, Lsb0> = BitVec::from_vec(packed);
+
+ let mut inner_correction_words = Vec::with_capacity(bits - 1);
+ for chunk in unpacked_control_bits[0..(bits - 1) * 2].chunks(2) {
+ let control_bits = [(chunk[0] as u8).into(), (chunk[1] as u8).into()];
+ let seed = Seed::decode(bytes)?.0;
+ let value = VI::decode(bytes)?;
+ inner_correction_words.push(IdpfCorrectionWord {
+ seed,
+ control_bits,
+ value,
+ })
+ }
+
+ let control_bits = [
+ (unpacked_control_bits[(bits - 1) * 2] as u8).into(),
+ (unpacked_control_bits[bits * 2 - 1] as u8).into(),
+ ];
+ let seed = Seed::decode(bytes)?.0;
+ let value = VL::decode(bytes)?;
+ let leaf_correction_word = IdpfCorrectionWord {
+ seed,
+ control_bits,
+ value,
+ };
+
+ // Check that unused packed bits are zero.
+ if unpacked_control_bits[bits * 2..].any() {
+ return Err(CodecError::UnexpectedValue);
+ }
+
+ Ok(IdpfPublicShare {
+ inner_correction_words,
+ leaf_correction_word,
+ })
+ }
+}
+
+#[derive(Debug, Clone)]
+struct IdpfCorrectionWord<V> {
+ seed: [u8; 16],
+ control_bits: [Choice; 2],
+ value: V,
+}
+
+impl<V> ConstantTimeEq for IdpfCorrectionWord<V>
+where
+ V: ConstantTimeEq,
+{
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.seed.ct_eq(&other.seed)
+ & self.control_bits.ct_eq(&other.control_bits)
+ & self.value.ct_eq(&other.value)
+ }
+}
+
+impl<V> PartialEq for IdpfCorrectionWord<V>
+where
+ V: ConstantTimeEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl<V> Eq for IdpfCorrectionWord<V> where V: ConstantTimeEq {}
+
+fn xor_seeds(left: &[u8; 16], right: &[u8; 16]) -> [u8; 16] {
+ let mut seed = [0u8; 16];
+ for (a, (b, c)) in left.iter().zip(right.iter().zip(seed.iter_mut())) {
+ *c = a ^ b;
+ }
+ seed
+}
+
+fn and_seeds(left: &[u8; 16], right: &[u8; 16]) -> [u8; 16] {
+ let mut seed = [0u8; 16];
+ for (a, (b, c)) in left.iter().zip(right.iter().zip(seed.iter_mut())) {
+ *c = a & b;
+ }
+ seed
+}
+
+fn or_seeds(left: &[u8; 16], right: &[u8; 16]) -> [u8; 16] {
+ let mut seed = [0u8; 16];
+ for (a, (b, c)) in left.iter().zip(right.iter().zip(seed.iter_mut())) {
+ *c = a | b;
+ }
+ seed
+}
+
+/// Take a control bit, and fan it out into a byte array that can be used as a mask for XOF seeds,
+/// without branching. If the control bit input is 0, all bytes will be equal to 0, and if the
+/// control bit input is 1, all bytes will be equal to 255.
+fn control_bit_to_seed_mask(control: Choice) -> [u8; 16] {
+ let mask = -(control.unwrap_u8() as i8) as u8;
+ [mask; 16]
+}
+
+/// Take two seeds and a control bit, and return the first seed if the control bit is zero, or the
+/// XOR of the two seeds if the control bit is one. This does not branch on the control bit.
+fn conditional_xor_seeds(
+ normal_input: &[u8; 16],
+ switched_input: &[u8; 16],
+ control: Choice,
+) -> [u8; 16] {
+ xor_seeds(
+ normal_input,
+ &and_seeds(switched_input, &control_bit_to_seed_mask(control)),
+ )
+}
+
+/// Returns one of two seeds, depending on the value of a selector bit. Does not branch on the
+/// selector input or make selector-dependent memory accesses.
+fn conditional_select_seed(select: Choice, seeds: &[[u8; 16]; 2]) -> [u8; 16] {
+ or_seeds(
+ &and_seeds(&control_bit_to_seed_mask(!select), &seeds[0]),
+ &and_seeds(&control_bit_to_seed_mask(select), &seeds[1]),
+ )
+}
+
+/// An interface that provides memoization of IDPF computations.
+///
+/// Each instance of a type implementing `IdpfCache` should only be used with one IDPF key and
+/// public share.
+///
+/// In typical use, IDPFs will be evaluated repeatedly on inputs of increasing length, as part of a
+/// protocol executed by multiple participants. Each IDPF evaluation computes keys and control
+/// bits corresponding to tree nodes along a path determined by the input to the IDPF. Thus, the
+/// values from nodes further up in the tree may be cached and reused in evaluations of subsequent
+/// longer inputs. If one IDPF input is a prefix of another input, then the first input's path down
+/// the tree is a prefix of the other input's path.
+pub trait IdpfCache {
+ /// Fetch cached values for the node identified by the IDPF input.
+ fn get(&self, input: &BitSlice) -> Option<([u8; 16], u8)>;
+
+ /// Store values corresponding to the node identified by the IDPF input.
+ fn insert(&mut self, input: &BitSlice, values: &([u8; 16], u8));
+}
+
+/// A no-op [`IdpfCache`] implementation that always reports a cache miss.
+#[derive(Default)]
+pub struct NoCache {}
+
+impl NoCache {
+ /// Construct a `NoCache` object.
+ pub fn new() -> NoCache {
+ NoCache::default()
+ }
+}
+
+impl IdpfCache for NoCache {
+ fn get(&self, _: &BitSlice) -> Option<([u8; 16], u8)> {
+ None
+ }
+
+ fn insert(&mut self, _: &BitSlice, _: &([u8; 16], u8)) {}
+}
+
+/// A simple [`IdpfCache`] implementation that caches intermediate results in an in-memory hash map,
+/// with no eviction.
+#[derive(Default)]
+pub struct HashMapCache {
+ map: HashMap<BitBox, ([u8; 16], u8)>,
+}
+
+impl HashMapCache {
+ /// Create a new unpopulated `HashMapCache`.
+ pub fn new() -> HashMapCache {
+ HashMapCache::default()
+ }
+
+ /// Create a new unpopulated `HashMapCache`, with a set pre-allocated capacity.
+ pub fn with_capacity(capacity: usize) -> HashMapCache {
+ Self {
+ map: HashMap::with_capacity(capacity),
+ }
+ }
+}
+
+impl IdpfCache for HashMapCache {
+ fn get(&self, input: &BitSlice) -> Option<([u8; 16], u8)> {
+ self.map.get(input).cloned()
+ }
+
+ fn insert(&mut self, input: &BitSlice, values: &([u8; 16], u8)) {
+ if !self.map.contains_key(input) {
+ self.map
+ .insert(input.to_owned().into_boxed_bitslice(), *values);
+ }
+ }
+}
+
+/// A simple [`IdpfCache`] implementation that caches intermediate results in memory, with
+/// first-in-first-out eviction, and lookups via linear probing.
+pub struct RingBufferCache {
+ ring: VecDeque<(BitBox, [u8; 16], u8)>,
+}
+
+impl RingBufferCache {
+ /// Create a new unpopulated `RingBufferCache`.
+ pub fn new(capacity: usize) -> RingBufferCache {
+ Self {
+ ring: VecDeque::with_capacity(std::cmp::max(capacity, 1)),
+ }
+ }
+}
+
+impl IdpfCache for RingBufferCache {
+ fn get(&self, input: &BitSlice) -> Option<([u8; 16], u8)> {
+ // iterate back-to-front, so that we check the most recently pushed entry first.
+ for entry in self.ring.iter().rev() {
+ if input == entry.0 {
+ return Some((entry.1, entry.2));
+ }
+ }
+ None
+ }
+
+ fn insert(&mut self, input: &BitSlice, values: &([u8; 16], u8)) {
+ // evict first (to avoid growing the storage)
+ if self.ring.len() == self.ring.capacity() {
+ self.ring.pop_front();
+ }
+ self.ring
+ .push_back((input.to_owned().into_boxed_bitslice(), values.0, values.1));
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{
+ collections::HashMap,
+ convert::{TryFrom, TryInto},
+ io::Cursor,
+ ops::{Add, AddAssign, Sub},
+ str::FromStr,
+ sync::Mutex,
+ };
+
+ use assert_matches::assert_matches;
+ use bitvec::{
+ bitbox,
+ prelude::{BitBox, Lsb0},
+ slice::BitSlice,
+ vec::BitVec,
+ };
+ use num_bigint::BigUint;
+ use rand::random;
+ use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable};
+
+ use super::{
+ HashMapCache, Idpf, IdpfCache, IdpfCorrectionWord, IdpfInput, IdpfOutputShare,
+ IdpfPublicShare, NoCache, RingBufferCache,
+ };
+ use crate::{
+ codec::{
+ decode_u32_items, encode_u32_items, CodecError, Decode, Encode, ParameterizedDecode,
+ },
+ field::{Field128, Field255, Field64, FieldElement},
+ prng::Prng,
+ vdaf::{poplar1::Poplar1IdpfValue, xof::Seed},
+ };
+
+ #[test]
+ fn idpf_input_conversion() {
+ let input_1 = IdpfInput::from_bools(&[
+ false, true, false, false, false, false, false, true, false, true, false, false, false,
+ false, true, false,
+ ]);
+ let input_2 = IdpfInput::from_bytes(b"AB");
+ assert_eq!(input_1, input_2);
+ let bits = bitbox![0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0];
+ assert_eq!(input_1[..], bits);
+ }
+
+ /// A lossy IDPF cache, for testing purposes, that randomly returns cache misses.
+ #[derive(Default)]
+ struct LossyCache {
+ map: HashMap<BitBox, ([u8; 16], u8)>,
+ }
+
+ impl LossyCache {
+ /// Create a new unpopulated `LossyCache`.
+ fn new() -> LossyCache {
+ LossyCache::default()
+ }
+ }
+
+ impl IdpfCache for LossyCache {
+ fn get(&self, input: &BitSlice) -> Option<([u8; 16], u8)> {
+ if random() {
+ self.map.get(input).cloned()
+ } else {
+ None
+ }
+ }
+
+ fn insert(&mut self, input: &BitSlice, values: &([u8; 16], u8)) {
+ if !self.map.contains_key(input) {
+ self.map
+ .insert(input.to_owned().into_boxed_bitslice(), *values);
+ }
+ }
+ }
+
+ /// A wrapper [`IdpfCache`] implementation that records `get()` calls, for testing purposes.
+ struct SnoopingCache<T> {
+ inner: T,
+ get_calls: Mutex<Vec<BitBox>>,
+ insert_calls: Mutex<Vec<(BitBox, [u8; 16], u8)>>,
+ }
+
+ impl<T> SnoopingCache<T> {
+ fn new(inner: T) -> SnoopingCache<T> {
+ SnoopingCache {
+ inner,
+ get_calls: Mutex::new(Vec::new()),
+ insert_calls: Mutex::new(Vec::new()),
+ }
+ }
+ }
+
+ impl<T> IdpfCache for SnoopingCache<T>
+ where
+ T: IdpfCache,
+ {
+ fn get(&self, input: &BitSlice) -> Option<([u8; 16], u8)> {
+ self.get_calls
+ .lock()
+ .unwrap()
+ .push(input.to_owned().into_boxed_bitslice());
+ self.inner.get(input)
+ }
+
+ fn insert(&mut self, input: &BitSlice, values: &([u8; 16], u8)) {
+ self.insert_calls.lock().unwrap().push((
+ input.to_owned().into_boxed_bitslice(),
+ values.0,
+ values.1,
+ ));
+ self.inner.insert(input, values)
+ }
+ }
+
+ #[test]
+ fn test_idpf_poplar() {
+ let input = bitbox![0, 1, 1, 0, 1].into();
+ let nonce: [u8; 16] = random();
+ let idpf = Idpf::new((), ());
+ let (public_share, keys) = idpf
+ .gen(
+ &input,
+ Vec::from([Poplar1IdpfValue::new([Field64::one(), Field64::one()]); 4]),
+ Poplar1IdpfValue::new([Field255::one(), Field255::one()]),
+ &nonce,
+ )
+ .unwrap();
+
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::one(), Field64::one()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![1].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::zero(), Field64::zero()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0, 1].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::one(), Field64::one()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0, 0].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::zero(), Field64::zero()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![1, 0].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::zero(), Field64::zero()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![1, 1].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::zero(), Field64::zero()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0, 1, 1].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::one(), Field64::one()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0, 1, 1, 0].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::one(), Field64::one()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0, 1, 1, 0, 1].into(),
+ &nonce,
+ &IdpfOutputShare::Leaf(Poplar1IdpfValue::new([Field255::one(), Field255::one()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0, 1, 1, 0, 0].into(),
+ &nonce,
+ &IdpfOutputShare::Leaf(Poplar1IdpfValue::new([Field255::zero(), Field255::zero()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![1, 0, 1, 0, 0].into(),
+ &nonce,
+ &IdpfOutputShare::Leaf(Poplar1IdpfValue::new([Field255::zero(), Field255::zero()])),
+ &mut NoCache::new(),
+ &mut NoCache::new(),
+ );
+ }
+
+ fn check_idpf_poplar_evaluation(
+ public_share: &IdpfPublicShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>,
+ keys: &[Seed<16>; 2],
+ prefix: &IdpfInput,
+ binder: &[u8],
+ expected_output: &IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>,
+ cache_0: &mut dyn IdpfCache,
+ cache_1: &mut dyn IdpfCache,
+ ) {
+ let idpf = Idpf::new((), ());
+ let share_0 = idpf
+ .eval(0, public_share, &keys[0], prefix, binder, cache_0)
+ .unwrap();
+ let share_1 = idpf
+ .eval(1, public_share, &keys[1], prefix, binder, cache_1)
+ .unwrap();
+ let output = share_0.merge(share_1).unwrap();
+ assert_eq!(&output, expected_output);
+ }
+
+ #[test]
+ fn test_idpf_poplar_medium() {
+ // This test on 40 byte inputs takes about a second in debug mode. (and ten milliseconds in
+ // release mode)
+ const INPUT_LEN: usize = 320;
+ let mut bits = bitbox![0; INPUT_LEN];
+ for mut bit in bits.iter_mut() {
+ bit.set(random());
+ }
+ let input = bits.clone().into();
+
+ let mut inner_values = Vec::with_capacity(INPUT_LEN - 1);
+ let mut prng = Prng::new().unwrap();
+ for _ in 0..INPUT_LEN - 1 {
+ inner_values.push(Poplar1IdpfValue::new([
+ Field64::one(),
+ prng.next().unwrap(),
+ ]));
+ }
+ let leaf_values =
+ Poplar1IdpfValue::new([Field255::one(), Prng::new().unwrap().next().unwrap()]);
+
+ let nonce: [u8; 16] = random();
+ let idpf = Idpf::new((), ());
+ let (public_share, keys) = idpf
+ .gen(&input, inner_values.clone(), leaf_values, &nonce)
+ .unwrap();
+ let mut cache_0 = RingBufferCache::new(3);
+ let mut cache_1 = RingBufferCache::new(3);
+
+ for (level, values) in inner_values.iter().enumerate() {
+ let mut prefix = BitBox::from_bitslice(&bits[..=level]).into();
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &prefix,
+ &nonce,
+ &IdpfOutputShare::Inner(*values),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ let flipped_bit = !prefix[level];
+ prefix.index.set(level, flipped_bit);
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &prefix,
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::zero(), Field64::zero()])),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ }
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &input,
+ &nonce,
+ &IdpfOutputShare::Leaf(leaf_values),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ let mut modified_bits = bits.clone();
+ modified_bits.set(INPUT_LEN - 1, !bits[INPUT_LEN - 1]);
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &modified_bits.into(),
+ &nonce,
+ &IdpfOutputShare::Leaf(Poplar1IdpfValue::new([Field255::zero(), Field255::zero()])),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ }
+
+ #[test]
+ fn idpf_poplar_cache_behavior() {
+ let bits = bitbox![0, 1, 1, 1, 0, 1, 0, 0];
+ let input = bits.into();
+
+ let mut inner_values = Vec::with_capacity(7);
+ let mut prng = Prng::new().unwrap();
+ for _ in 0..7 {
+ inner_values.push(Poplar1IdpfValue::new([
+ Field64::one(),
+ prng.next().unwrap(),
+ ]));
+ }
+ let leaf_values =
+ Poplar1IdpfValue::new([Field255::one(), Prng::new().unwrap().next().unwrap()]);
+
+ let nonce: [u8; 16] = random();
+ let idpf = Idpf::new((), ());
+ let (public_share, keys) = idpf
+ .gen(&input, inner_values.clone(), leaf_values, &nonce)
+ .unwrap();
+ let mut cache_0 = SnoopingCache::new(HashMapCache::new());
+ let mut cache_1 = HashMapCache::new();
+
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![1, 1, 0, 0].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(Poplar1IdpfValue::new([Field64::zero(), Field64::zero()])),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ assert_eq!(
+ cache_0
+ .get_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .collect::<Vec<_>>(),
+ vec![bitbox![1, 1, 0], bitbox![1, 1], bitbox![1]],
+ );
+ assert_eq!(
+ cache_0
+ .insert_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .map(|(input, _, _)| input)
+ .collect::<Vec<_>>(),
+ vec![
+ bitbox![1],
+ bitbox![1, 1],
+ bitbox![1, 1, 0],
+ bitbox![1, 1, 0, 0]
+ ],
+ );
+
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(inner_values[0]),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ assert_eq!(
+ cache_0
+ .get_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .collect::<Vec<BitBox>>(),
+ Vec::<BitBox>::new(),
+ );
+ assert_eq!(
+ cache_0
+ .insert_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .map(|(input, _, _)| input)
+ .collect::<Vec<_>>(),
+ vec![bitbox![0]],
+ );
+
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &bitbox![0, 1].into(),
+ &nonce,
+ &IdpfOutputShare::Inner(inner_values[1]),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ assert_eq!(
+ cache_0
+ .get_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .collect::<Vec<_>>(),
+ vec![bitbox![0]],
+ );
+ assert_eq!(
+ cache_0
+ .insert_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .map(|(input, _, _)| input)
+ .collect::<Vec<_>>(),
+ vec![bitbox![0, 1]],
+ );
+
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &input,
+ &nonce,
+ &IdpfOutputShare::Leaf(leaf_values),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ assert_eq!(
+ cache_0
+ .get_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .collect::<Vec<_>>(),
+ vec![
+ bitbox![0, 1, 1, 1, 0, 1, 0],
+ bitbox![0, 1, 1, 1, 0, 1],
+ bitbox![0, 1, 1, 1, 0],
+ bitbox![0, 1, 1, 1],
+ bitbox![0, 1, 1],
+ bitbox![0, 1],
+ ],
+ );
+ assert_eq!(
+ cache_0
+ .insert_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .map(|(input, _, _)| input)
+ .collect::<Vec<_>>(),
+ vec![
+ bitbox![0, 1, 1],
+ bitbox![0, 1, 1, 1],
+ bitbox![0, 1, 1, 1, 0],
+ bitbox![0, 1, 1, 1, 0, 1],
+ bitbox![0, 1, 1, 1, 0, 1, 0],
+ ],
+ );
+
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &input,
+ &nonce,
+ &IdpfOutputShare::Leaf(leaf_values),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ assert_eq!(
+ cache_0
+ .get_calls
+ .lock()
+ .unwrap()
+ .drain(..)
+ .collect::<Vec<_>>(),
+ vec![bitbox![0, 1, 1, 1, 0, 1, 0]],
+ );
+ assert!(cache_0.insert_calls.lock().unwrap().is_empty());
+ }
+
+ #[test]
+ fn idpf_poplar_lossy_cache() {
+ let bits = bitbox![1, 0, 0, 1, 1, 0, 1, 0];
+ let input = bits.into();
+
+ let mut inner_values = Vec::with_capacity(7);
+ let mut prng = Prng::new().unwrap();
+ for _ in 0..7 {
+ inner_values.push(Poplar1IdpfValue::new([
+ Field64::one(),
+ prng.next().unwrap(),
+ ]));
+ }
+ let leaf_values =
+ Poplar1IdpfValue::new([Field255::one(), Prng::new().unwrap().next().unwrap()]);
+
+ let nonce: [u8; 16] = random();
+ let idpf = Idpf::new((), ());
+ let (public_share, keys) = idpf
+ .gen(&input, inner_values.clone(), leaf_values, &nonce)
+ .unwrap();
+ let mut cache_0 = LossyCache::new();
+ let mut cache_1 = LossyCache::new();
+
+ for (level, values) in inner_values.iter().enumerate() {
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &input[..=level].to_owned().into(),
+ &nonce,
+ &IdpfOutputShare::Inner(*values),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ }
+ check_idpf_poplar_evaluation(
+ &public_share,
+ &keys,
+ &input,
+ &nonce,
+ &IdpfOutputShare::Leaf(leaf_values),
+ &mut cache_0,
+ &mut cache_1,
+ );
+ }
+
+ #[test]
+ fn test_idpf_poplar_error_cases() {
+ let nonce: [u8; 16] = random();
+ let idpf = Idpf::new((), ());
+ // Zero bits does not make sense.
+ idpf.gen(
+ &bitbox![].into(),
+ Vec::<Poplar1IdpfValue<Field64>>::new(),
+ Poplar1IdpfValue::new([Field255::zero(); 2]),
+ &nonce,
+ )
+ .unwrap_err();
+
+ let (public_share, keys) = idpf
+ .gen(
+ &bitbox![0;10].into(),
+ Vec::from([Poplar1IdpfValue::new([Field64::zero(); 2]); 9]),
+ Poplar1IdpfValue::new([Field255::zero(); 2]),
+ &nonce,
+ )
+ .unwrap();
+
+ // Wrong number of values.
+ idpf.gen(
+ &bitbox![0; 10].into(),
+ Vec::from([Poplar1IdpfValue::new([Field64::zero(); 2]); 8]),
+ Poplar1IdpfValue::new([Field255::zero(); 2]),
+ &nonce,
+ )
+ .unwrap_err();
+ idpf.gen(
+ &bitbox![0; 10].into(),
+ Vec::from([Poplar1IdpfValue::new([Field64::zero(); 2]); 10]),
+ Poplar1IdpfValue::new([Field255::zero(); 2]),
+ &nonce,
+ )
+ .unwrap_err();
+
+ // Evaluating with empty prefix.
+ assert!(idpf
+ .eval(
+ 0,
+ &public_share,
+ &keys[0],
+ &bitbox![].into(),
+ &nonce,
+ &mut NoCache::new(),
+ )
+ .is_err());
+ // Evaluating with too-long prefix.
+ assert!(idpf
+ .eval(
+ 0,
+ &public_share,
+ &keys[0],
+ &bitbox![0; 11].into(),
+ &nonce,
+ &mut NoCache::new(),
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn idpf_poplar_public_share_round_trip() {
+ let public_share = IdpfPublicShare {
+ inner_correction_words: Vec::from([
+ IdpfCorrectionWord {
+ seed: [0xab; 16],
+ control_bits: [Choice::from(1), Choice::from(0)],
+ value: Poplar1IdpfValue::new([
+ Field64::try_from(83261u64).unwrap(),
+ Field64::try_from(125159u64).unwrap(),
+ ]),
+ },
+ IdpfCorrectionWord{
+ seed: [0xcd;16],
+ control_bits: [Choice::from(0), Choice::from(1)],
+ value: Poplar1IdpfValue::new([
+ Field64::try_from(17614120u64).unwrap(),
+ Field64::try_from(20674u64).unwrap(),
+ ]),
+ },
+ ]),
+ leaf_correction_word: IdpfCorrectionWord {
+ seed: [0xff; 16],
+ control_bits: [Choice::from(1), Choice::from(1)],
+ value: Poplar1IdpfValue::new([
+ Field255::one(),
+ Field255::get_decoded(
+ b"\xf0\xde\xbc\x9a\x78\x56\x34\x12\xf0\xde\xbc\x9a\x78\x56\x34\x12\xf0\xde\xbc\x9a\x78\x56\x34\x12\xf0\xde\xbc\x9a\x78\x56\x34\x12", // field element correction word, continued
+ ).unwrap(),
+ ]),
+ },
+ };
+ let message = hex::decode(concat!(
+ "39", // packed control bit correction words (0b00111001)
+ "abababababababababababababababab", // seed correction word, first level
+ "3d45010000000000", // field element correction word
+ "e7e8010000000000", // field element correction word, continued
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", // seed correction word, second level
+ "28c50c0100000000", // field element correction word
+ "c250000000000000", // field element correction word, continued
+ "ffffffffffffffffffffffffffffffff", // seed correction word, third level
+ "0100000000000000000000000000000000000000000000000000000000000000", // field element correction word, leaf field
+ "f0debc9a78563412f0debc9a78563412f0debc9a78563412f0debc9a78563412", // field element correction word, continued
+ ))
+ .unwrap();
+ let encoded = public_share.get_encoded();
+ let decoded = IdpfPublicShare::get_decoded_with_param(&3, &message).unwrap();
+ assert_eq!(public_share, decoded);
+ assert_eq!(message, encoded);
+ assert_eq!(public_share.encoded_len().unwrap(), encoded.len());
+
+ // check serialization of packed control bits when they span multiple bytes:
+ let public_share = IdpfPublicShare {
+ inner_correction_words: Vec::from([
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(1), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(1), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(1), Choice::from(0)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(1), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(1), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(0), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(1), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(1), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field64::zero(), Field64::zero()]),
+ },
+ ]),
+ leaf_correction_word: IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(0), Choice::from(1)],
+ value: Poplar1IdpfValue::new([Field255::zero(), Field255::zero()]),
+ },
+ };
+ let message = hex::decode(concat!(
+ "dffb02", // packed correction word control bits: 0b11011111, 0b11111011, 0b10
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000",
+ "0000000000000000",
+ "00000000000000000000000000000000",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ))
+ .unwrap();
+ let encoded = public_share.get_encoded();
+ let decoded = IdpfPublicShare::get_decoded_with_param(&9, &message).unwrap();
+ assert_eq!(public_share, decoded);
+ assert_eq!(message, encoded);
+ }
+
+ #[test]
+ fn idpf_poplar_public_share_control_bit_codec() {
+ let test_cases = [
+ (&[false, true][..], &[0b10][..]),
+ (
+ &[false, false, true, false, false, true][..],
+ &[0b10_0100u8][..],
+ ),
+ (
+ &[
+ true, true, false, true, false, false, false, false, true, true,
+ ][..],
+ &[0b0000_1011, 0b11][..],
+ ),
+ (
+ &[
+ true, true, false, true, false, true, true, true, false, true, false, true,
+ false, false, true, false,
+ ][..],
+ &[0b1110_1011, 0b0100_1010][..],
+ ),
+ (
+ &[
+ true, true, true, true, true, false, true, true, false, true, true, true,
+ false, true, false, true, false, false, true, false, true, true,
+ ][..],
+ &[0b1101_1111, 0b1010_1110, 0b11_0100][..],
+ ),
+ ];
+
+ for (control_bits, serialized_control_bits) in test_cases {
+ let public_share = IdpfPublicShare::<
+ Poplar1IdpfValue<Field64>,
+ Poplar1IdpfValue<Field255>,
+ > {
+ inner_correction_words: control_bits[..control_bits.len() - 2]
+ .chunks(2)
+ .map(|chunk| IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [Choice::from(chunk[0] as u8), Choice::from(chunk[1] as u8)],
+ value: Poplar1IdpfValue::new([Field64::zero(); 2]),
+ })
+ .collect(),
+ leaf_correction_word: IdpfCorrectionWord {
+ seed: [0; 16],
+ control_bits: [
+ Choice::from(control_bits[control_bits.len() - 2] as u8),
+ Choice::from(control_bits[control_bits.len() - 1] as u8),
+ ],
+ value: Poplar1IdpfValue::new([Field255::zero(); 2]),
+ },
+ };
+
+ let mut serialized_public_share = serialized_control_bits.to_owned();
+ let idpf_bits = control_bits.len() / 2;
+ let size_seeds = 16 * idpf_bits;
+ let size_field_vecs =
+ Field64::ENCODED_SIZE * 2 * (idpf_bits - 1) + Field255::ENCODED_SIZE * 2;
+ serialized_public_share.resize(
+ serialized_control_bits.len() + size_seeds + size_field_vecs,
+ 0,
+ );
+
+ assert_eq!(public_share.get_encoded(), serialized_public_share);
+ assert_eq!(
+ IdpfPublicShare::get_decoded_with_param(&idpf_bits, &serialized_public_share)
+ .unwrap(),
+ public_share
+ );
+ }
+ }
+
+ #[test]
+ fn idpf_poplar_public_share_unused_bits() {
+ let mut buf = vec![0u8; 4096];
+
+ buf[0] = 1 << 2;
+ let err =
+ IdpfPublicShare::<Field64, Field255>::decode_with_param(&1, &mut Cursor::new(&buf))
+ .unwrap_err();
+ assert_matches!(err, CodecError::UnexpectedValue);
+
+ buf[0] = 1 << 4;
+ let err =
+ IdpfPublicShare::<Field64, Field255>::decode_with_param(&2, &mut Cursor::new(&buf))
+ .unwrap_err();
+ assert_matches!(err, CodecError::UnexpectedValue);
+
+ buf[0] = 1 << 6;
+ let err =
+ IdpfPublicShare::<Field64, Field255>::decode_with_param(&3, &mut Cursor::new(&buf))
+ .unwrap_err();
+ assert_matches!(err, CodecError::UnexpectedValue);
+
+ buf[0] = 0;
+ buf[1] = 1 << 2;
+ let err =
+ IdpfPublicShare::<Field64, Field255>::decode_with_param(&5, &mut Cursor::new(&buf))
+ .unwrap_err();
+ assert_matches!(err, CodecError::UnexpectedValue);
+ }
+
+ /// Stores a test vector for the IDPF key generation algorithm.
+ struct IdpfTestVector {
+ /// The number of bits in IDPF inputs.
+ bits: usize,
+ /// The binder string used when generating and evaluating keys.
+ binder: Vec<u8>,
+ /// The IDPF input provided to the key generation algorithm.
+ alpha: IdpfInput,
+ /// The IDPF output values, at each inner level, provided to the key generation algorithm.
+ beta_inner: Vec<Poplar1IdpfValue<Field64>>,
+ /// The IDPF output values for the leaf level, provided to the key generation algorithm.
+ beta_leaf: Poplar1IdpfValue<Field255>,
+ /// The two keys returned by the key generation algorithm.
+ keys: [[u8; 16]; 2],
+ /// The public share returned by the key generation algorithm.
+ public_share: Vec<u8>,
+ }
+
+ /// Load a test vector for Idpf key generation.
+ fn load_idpfpoplar_test_vector() -> IdpfTestVector {
+ let test_vec: serde_json::Value =
+ serde_json::from_str(include_str!("vdaf/test_vec/07/IdpfPoplar_0.json")).unwrap();
+ let test_vec_obj = test_vec.as_object().unwrap();
+
+ let bits = test_vec_obj
+ .get("bits")
+ .unwrap()
+ .as_u64()
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let alpha_str = test_vec_obj.get("alpha").unwrap().as_str().unwrap();
+ let alpha_bignum = BigUint::from_str(alpha_str).unwrap();
+ let zero_bignum = BigUint::from(0u8);
+ let one_bignum = BigUint::from(1u8);
+ let alpha_bits = (0..bits)
+ .map(|level| (&alpha_bignum >> (bits - level - 1)) & &one_bignum != zero_bignum)
+ .collect::<BitVec>();
+ let alpha = alpha_bits.into();
+
+ let beta_inner_level_array = test_vec_obj.get("beta_inner").unwrap().as_array().unwrap();
+ let beta_inner = beta_inner_level_array
+ .iter()
+ .map(|array| {
+ Poplar1IdpfValue::new([
+ Field64::from(array[0].as_str().unwrap().parse::<u64>().unwrap()),
+ Field64::from(array[1].as_str().unwrap().parse::<u64>().unwrap()),
+ ])
+ })
+ .collect::<Vec<_>>();
+
+ let beta_leaf_array = test_vec_obj.get("beta_leaf").unwrap().as_array().unwrap();
+ let beta_leaf = Poplar1IdpfValue::new([
+ Field255::from(
+ beta_leaf_array[0]
+ .as_str()
+ .unwrap()
+ .parse::<BigUint>()
+ .unwrap(),
+ ),
+ Field255::from(
+ beta_leaf_array[1]
+ .as_str()
+ .unwrap()
+ .parse::<BigUint>()
+ .unwrap(),
+ ),
+ ]);
+
+ let keys_array = test_vec_obj.get("keys").unwrap().as_array().unwrap();
+ let keys = [
+ hex::decode(keys_array[0].as_str().unwrap())
+ .unwrap()
+ .try_into()
+ .unwrap(),
+ hex::decode(keys_array[1].as_str().unwrap())
+ .unwrap()
+ .try_into()
+ .unwrap(),
+ ];
+
+ let public_share_hex = test_vec_obj.get("public_share").unwrap();
+ let public_share = hex::decode(public_share_hex.as_str().unwrap()).unwrap();
+
+ let binder_hex = test_vec_obj.get("binder").unwrap();
+ let binder = hex::decode(binder_hex.as_str().unwrap()).unwrap();
+
+ IdpfTestVector {
+ bits,
+ binder,
+ alpha,
+ beta_inner,
+ beta_leaf,
+ keys,
+ public_share,
+ }
+ }
+
+ #[test]
+ fn idpf_poplar_generate_test_vector() {
+ let test_vector = load_idpfpoplar_test_vector();
+ let idpf = Idpf::new((), ());
+ let (public_share, keys) = idpf
+ .gen_with_random(
+ &test_vector.alpha,
+ test_vector.beta_inner,
+ test_vector.beta_leaf,
+ &test_vector.binder,
+ &test_vector.keys,
+ )
+ .unwrap();
+
+ assert_eq!(keys[0].0, test_vector.keys[0]);
+ assert_eq!(keys[1].0, test_vector.keys[1]);
+
+ let expected_public_share =
+ IdpfPublicShare::get_decoded_with_param(&test_vector.bits, &test_vector.public_share)
+ .unwrap();
+ for (level, (correction_words, expected_correction_words)) in public_share
+ .inner_correction_words
+ .iter()
+ .zip(expected_public_share.inner_correction_words.iter())
+ .enumerate()
+ {
+ assert_eq!(
+ correction_words, expected_correction_words,
+ "layer {level} did not match\n{correction_words:#x?}\n{expected_correction_words:#x?}"
+ );
+ }
+ assert_eq!(
+ public_share.leaf_correction_word,
+ expected_public_share.leaf_correction_word
+ );
+
+ assert_eq!(
+ public_share, expected_public_share,
+ "public share did not match\n{public_share:#x?}\n{expected_public_share:#x?}"
+ );
+ let encoded_public_share = public_share.get_encoded();
+ assert_eq!(encoded_public_share, test_vector.public_share);
+ }
+
+ #[test]
+ fn idpf_input_from_bytes_to_bytes() {
+ let test_cases: &[&[u8]] = &[b"hello", b"banana", &[1], &[127], &[1, 2, 3, 4], &[]];
+ for test_case in test_cases {
+ assert_eq!(&IdpfInput::from_bytes(test_case).to_bytes(), test_case);
+ }
+ }
+
+ #[test]
+ fn idpf_input_from_bools_to_bytes() {
+ let input = IdpfInput::from_bools(&[true; 7]);
+ assert_eq!(input.to_bytes(), &[254]);
+ let input = IdpfInput::from_bools(&[true; 9]);
+ assert_eq!(input.to_bytes(), &[255, 128]);
+ }
+
+ /// Demonstrate use of an IDPF with values that need run-time parameters for random generation.
+ #[test]
+ fn idpf_with_value_parameters() {
+ use super::IdpfValue;
+
+ /// A test-only type for use as an [`IdpfValue`].
+ #[derive(Debug, Clone, Copy)]
+ struct MyUnit;
+
+ impl IdpfValue for MyUnit {
+ type ValueParameter = ();
+
+ fn generate<S>(_: &mut S, _: &Self::ValueParameter) -> Self
+ where
+ S: rand_core::RngCore,
+ {
+ MyUnit
+ }
+
+ fn zero(_: &()) -> Self {
+ MyUnit
+ }
+
+ fn conditional_select(_: &Self, _: &Self, _: Choice) -> Self {
+ MyUnit
+ }
+ }
+
+ impl Encode for MyUnit {
+ fn encode(&self, _: &mut Vec<u8>) {}
+ }
+
+ impl Decode for MyUnit {
+ fn decode(_: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ Ok(MyUnit)
+ }
+ }
+
+ impl ConditionallySelectable for MyUnit {
+ fn conditional_select(_: &Self, _: &Self, _: Choice) -> Self {
+ MyUnit
+ }
+ }
+
+ impl ConditionallyNegatable for MyUnit {
+ fn conditional_negate(&mut self, _: Choice) {}
+ }
+
+ impl Add for MyUnit {
+ type Output = Self;
+
+ fn add(self, _: Self) -> Self::Output {
+ MyUnit
+ }
+ }
+
+ impl AddAssign for MyUnit {
+ fn add_assign(&mut self, _: Self) {}
+ }
+
+ impl Sub for MyUnit {
+ type Output = Self;
+
+ fn sub(self, _: Self) -> Self::Output {
+ MyUnit
+ }
+ }
+
+ /// A test-only type for use as an [`IdpfValue`], representing a variable-length vector of
+ /// field elements. The length must be fixed before generating IDPF keys, but we assume it
+ /// is not known at compile time.
+ #[derive(Debug, Clone)]
+ struct MyVector(Vec<Field128>);
+
+ impl IdpfValue for MyVector {
+ type ValueParameter = usize;
+
+ fn generate<S>(seed_stream: &mut S, length: &Self::ValueParameter) -> Self
+ where
+ S: rand_core::RngCore,
+ {
+ let mut output = vec![<Field128 as FieldElement>::zero(); *length];
+ for element in output.iter_mut() {
+ *element = <Field128 as IdpfValue>::generate(seed_stream, &());
+ }
+ MyVector(output)
+ }
+
+ fn zero(length: &usize) -> Self {
+ MyVector(vec![<Field128 as FieldElement>::zero(); *length])
+ }
+
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ debug_assert_eq!(a.0.len(), b.0.len());
+ let mut output = vec![<Field128 as FieldElement>::zero(); a.0.len()];
+ for ((a_elem, b_elem), output_elem) in
+ a.0.iter().zip(b.0.iter()).zip(output.iter_mut())
+ {
+ *output_elem = <Field128 as ConditionallySelectable>::conditional_select(
+ a_elem, b_elem, choice,
+ );
+ }
+ MyVector(output)
+ }
+ }
+
+ impl Encode for MyVector {
+ fn encode(&self, bytes: &mut Vec<u8>) {
+ encode_u32_items(bytes, &(), &self.0);
+ }
+ }
+
+ impl Decode for MyVector {
+ fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
+ decode_u32_items(&(), bytes).map(MyVector)
+ }
+ }
+
+ impl ConditionallyNegatable for MyVector {
+ fn conditional_negate(&mut self, choice: Choice) {
+ for element in self.0.iter_mut() {
+ element.conditional_negate(choice);
+ }
+ }
+ }
+
+ impl Add for MyVector {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ debug_assert_eq!(self.0.len(), rhs.0.len());
+ let mut output = vec![<Field128 as FieldElement>::zero(); self.0.len()];
+ for ((left_elem, right_elem), output_elem) in
+ self.0.iter().zip(rhs.0.iter()).zip(output.iter_mut())
+ {
+ *output_elem = left_elem + right_elem;
+ }
+ MyVector(output)
+ }
+ }
+
+ impl AddAssign for MyVector {
+ fn add_assign(&mut self, rhs: Self) {
+ debug_assert_eq!(self.0.len(), rhs.0.len());
+ for (self_elem, right_elem) in self.0.iter_mut().zip(rhs.0.iter()) {
+ *self_elem += *right_elem;
+ }
+ }
+ }
+
+ impl Sub for MyVector {
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ debug_assert_eq!(self.0.len(), rhs.0.len());
+ let mut output = vec![<Field128 as FieldElement>::zero(); self.0.len()];
+ for ((left_elem, right_elem), output_elem) in
+ self.0.iter().zip(rhs.0.iter()).zip(output.iter_mut())
+ {
+ *output_elem = left_elem - right_elem;
+ }
+ MyVector(output)
+ }
+ }
+
+ // Use a unit type for inner nodes, thus emulating a DPF. Use a newtype around a `Vec` for
+ // the leaf nodes, to test out values that require runtime parameters.
+ let idpf = Idpf::new((), 3);
+ let binder = b"binder";
+ let (public_share, [key_0, key_1]) = idpf
+ .gen(
+ &IdpfInput::from_bytes(b"ae"),
+ [MyUnit; 15],
+ MyVector(Vec::from([
+ Field128::from(1),
+ Field128::from(2),
+ Field128::from(3),
+ ])),
+ binder,
+ )
+ .unwrap();
+
+ let zero_share_0 = idpf
+ .eval(
+ 0,
+ &public_share,
+ &key_0,
+ &IdpfInput::from_bytes(b"ou"),
+ binder,
+ &mut NoCache::new(),
+ )
+ .unwrap();
+ let zero_share_1 = idpf
+ .eval(
+ 1,
+ &public_share,
+ &key_1,
+ &IdpfInput::from_bytes(b"ou"),
+ binder,
+ &mut NoCache::new(),
+ )
+ .unwrap();
+ let zero_output = zero_share_0.merge(zero_share_1).unwrap();
+ assert_matches!(zero_output, IdpfOutputShare::Leaf(value) => {
+ assert_eq!(value.0.len(), 3);
+ assert_eq!(value.0[0], <Field128 as FieldElement>::zero());
+ assert_eq!(value.0[1], <Field128 as FieldElement>::zero());
+ assert_eq!(value.0[2], <Field128 as FieldElement>::zero());
+ });
+
+ let programmed_share_0 = idpf
+ .eval(
+ 0,
+ &public_share,
+ &key_0,
+ &IdpfInput::from_bytes(b"ae"),
+ binder,
+ &mut NoCache::new(),
+ )
+ .unwrap();
+ let programmed_share_1 = idpf
+ .eval(
+ 1,
+ &public_share,
+ &key_1,
+ &IdpfInput::from_bytes(b"ae"),
+ binder,
+ &mut NoCache::new(),
+ )
+ .unwrap();
+ let programmed_output = programmed_share_0.merge(programmed_share_1).unwrap();
+ assert_matches!(programmed_output, IdpfOutputShare::Leaf(value) => {
+ assert_eq!(value.0.len(), 3);
+ assert_eq!(value.0[0], Field128::from(1));
+ assert_eq!(value.0[1], Field128::from(2));
+ assert_eq!(value.0[2], Field128::from(3));
+ });
+ }
+}