//! This module implements the incremental distributed point function (IDPF) described in //! [[draft-irtf-cfrg-vdaf-08]]. //! //! [draft-irtf-cfrg-vdaf-08]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/08/ 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}, iter::zip, ops::{Add, AddAssign, ControlFlow, Index, Sub}, }; use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq}; /// IDPF-related errors. #[derive(Debug, thiserror::Error)] #[non_exhaustive] 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::(); 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::(); 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 + '_ { 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 { let mut vec = BitVec::::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(), } } /// Return the bit at the specified level if the level is in bounds. pub fn get(&self, level: usize) -> Option { self.index.get(level).as_deref().copied() } } impl From> for IdpfInput { fn from(bit_vec: BitVec) -> Self { IdpfInput { index: bit_vec.into_boxed_bitslice(), } } } impl From> for IdpfInput { fn from(bit_box: BitBox) -> Self { IdpfInput { index: bit_box } } } impl Index for IdpfInput where BitSlice: Index, { type Output = >::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 + AddAssign + Sub + ConditionallyNegatable + Encode + ParameterizedDecode + Sized { /// Any run-time parameters needed to produce a value. type ValueParameter; /// Generate a pseudorandom value from a seed stream. fn generate(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 IdpfValue for F where F: FieldElement, { type ValueParameter = (); fn generate(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 { ::zero() } fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { ::conditional_select(a, b, choice) } } /// An output from evaluation of an IDPF at some level and index. #[derive(Debug, PartialEq, Eq)] pub enum IdpfOutputShare { /// 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 IdpfOutputShare where VI: IdpfValue, VL: IdpfValue, { /// Combine two output share values into one. pub fn merge(self, other: Self) -> Result, 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]); // "Steal" the control bits from the seeds. let control_bits_0 = seeds[0].as_ref()[0] & 1; let control_bits_1 = seeds[1].as_ref()[0] & 1; seeds[0].as_mut()[0] &= 0xfe; seeds[1].as_mut()[0] &= 0xfe; (seeds, [control_bits_0.into(), control_bits_1.into()]) } fn convert( 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( 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 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::(&seeds_corrected[0], convert_xof_fixed_key, parameter); let (new_key_1, elements_1) = convert::(&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( is_leader: bool, parameter: &V::ValueParameter, key: &mut [u8; 16], control_bit: &mut Choice, correction_word: &IdpfCorrectionWord, 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::(&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 where VI: IdpfValue, VL: IdpfValue, { inner_node_value_parameter: VI::ValueParameter, leaf_node_value_parameter: VL::ValueParameter, } impl Idpf 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>( &self, input: &IdpfInput, inner_values: M, leaf_value: VL, binder: &[u8], random: &[[u8; 16]; 2], ) -> Result<(IdpfPublicShare, [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::( 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::( 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( &self, input: &IdpfInput, inner_values: M, leaf_value: VL, binder: &[u8], ) -> Result<(IdpfPublicShare, [Seed<16>; 2]), VdafError> where M: IntoIterator, { 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, start_level: usize, mut key: [u8; 16], mut control_bit: Choice, prefix: &IdpfInput, binder: &[u8], cache: &mut dyn IdpfCache, ) -> Result, 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, key: &Seed<16>, prefix: &IdpfInput, binder: &[u8], cache: &mut dyn IdpfCache, ) -> Result, 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 { /// Correction words for each inner node level. inner_correction_words: Vec>, /// Correction word for the leaf node level. leaf_correction_word: IdpfCorrectionWord, } impl ConstantTimeEq for IdpfPublicShare 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 PartialEq for IdpfPublicShare where VI: ConstantTimeEq, VL: ConstantTimeEq, { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).into() } } impl Eq for IdpfPublicShare where VI: ConstantTimeEq, VL: ConstantTimeEq, { } impl Encode for IdpfPublicShare where VI: Encode, VL: Encode, { fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { // 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 = 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 { 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 ParameterizedDecode for IdpfPublicShare where VI: Decode, VL: Decode, { fn decode_with_param(bits: &usize, bytes: &mut Cursor<&[u8]>) -> Result { 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 = 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 { seed: [u8; 16], control_bits: [Choice; 2], value: V, } impl ConstantTimeEq for IdpfCorrectionWord 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 PartialEq for IdpfCorrectionWord where V: ConstantTimeEq, { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).into() } } impl Eq for IdpfCorrectionWord where V: ConstantTimeEq {} pub(crate) 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. pub(crate) 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. pub(crate) 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]), ) } /// Interchange the contents of seeds if the choice is 1, otherwise seeds remain unchanged. pub(crate) fn conditional_swap_seed(lhs: &mut [u8; 16], rhs: &mut [u8; 16], choice: Choice) { zip(lhs, rhs).for_each(|(a, b)| u8::conditional_swap(a, b, choice)); } /// 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, } 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)); } } /// Utilities for testing IDPFs. #[cfg(feature = "test-util")] #[cfg_attr(docsrs, doc(cfg(feature = "test-util")))] pub mod test_utils { use super::*; use rand::prelude::*; use zipf::ZipfDistribution; /// Generate a set of IDPF inputs with the given bit length `bits`. They are sampled according /// to the Zipf distribution with parameters `zipf_support` and `zipf_exponent`. Return the /// measurements, along with the prefixes traversed during the heavy hitters computation for /// the given threshold. /// /// The prefix tree consists of a sequence of candidate prefixes for each level. For a given level, /// the candidate prefixes are computed from the hit counts of the prefixes at the previous level: /// For any prefix `p` whose hit count is at least the desired threshold, add `p || 0` and `p || 1` /// to the list. pub fn generate_zipf_distributed_batch( rng: &mut impl Rng, bits: usize, threshold: usize, measurement_count: usize, zipf_support: usize, zipf_exponent: f64, ) -> (Vec, Vec>) { // Generate random inputs. let mut inputs = Vec::with_capacity(zipf_support); for _ in 0..zipf_support { let bools: Vec = (0..bits).map(|_| rng.gen()).collect(); inputs.push(IdpfInput::from_bools(&bools)); } // Sample a number of inputs according to the Zipf distribution. let mut samples = Vec::with_capacity(measurement_count); let zipf = ZipfDistribution::new(zipf_support, zipf_exponent).unwrap(); for _ in 0..measurement_count { samples.push(inputs[zipf.sample(rng) - 1].clone()); } // Compute the prefix tree for the desired threshold. let mut prefix_tree = Vec::with_capacity(bits); prefix_tree.push(vec![ IdpfInput::from_bools(&[false]), IdpfInput::from_bools(&[true]), ]); for level in 0..bits - 1 { // Compute the hit count of each prefix from the previous level. let mut hit_counts = vec![0; prefix_tree[level].len()]; for (hit_count, prefix) in hit_counts.iter_mut().zip(prefix_tree[level].iter()) { for sample in samples.iter() { let mut is_prefix = true; for j in 0..prefix.len() { if prefix[j] != sample[j] { is_prefix = false; break; } } if is_prefix { *hit_count += 1; } } } // Compute the next set of candidate prefixes. let mut next_prefixes = Vec::with_capacity(prefix_tree.last().unwrap().len()); for (hit_count, prefix) in hit_counts.iter().zip(prefix_tree[level].iter()) { if *hit_count >= threshold { next_prefixes.push(prefix.clone_with_suffix(&[false])); next_prefixes.push(prefix.clone_with_suffix(&[true])); } } prefix_tree.push(next_prefixes); } (samples, prefix_tree) } } #[cfg(test)] mod tests { use std::{ collections::HashMap, convert::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, } 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 { inner: T, get_calls: Mutex>, insert_calls: Mutex>, } impl SnoopingCache { fn new(inner: T) -> SnoopingCache { SnoopingCache { inner, get_calls: Mutex::new(Vec::new()), insert_calls: Mutex::new(Vec::new()), } } } impl IdpfCache for SnoopingCache 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>, keys: &[Seed<16>; 2], prefix: &IdpfInput, binder: &[u8], expected_output: &IdpfOutputShare, Poplar1IdpfValue>, 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![bitbox![1, 1, 0], bitbox![1, 1], bitbox![1]], ); assert_eq!( cache_0 .insert_calls .lock() .unwrap() .drain(..) .map(|(input, _, _)| input) .collect::>(), 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::::new(), ); assert_eq!( cache_0 .insert_calls .lock() .unwrap() .drain(..) .map(|(input, _, _)| input) .collect::>(), 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![bitbox![0]], ); assert_eq!( cache_0 .insert_calls .lock() .unwrap() .drain(..) .map(|(input, _, _)| input) .collect::>(), 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![ 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![ 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![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::>::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::from(83261u64), Field64::from(125159u64), ]), }, IdpfCorrectionWord{ seed: [0xcd;16], control_bits: [Choice::from(0), Choice::from(1)], value: Poplar1IdpfValue::new([ Field64::from(17614120u64), Field64::from(20674u64), ]), }, ]), 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().unwrap(); 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().unwrap(); 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, Poplar1IdpfValue, > { 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().unwrap(), 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::::decode_with_param(&1, &mut Cursor::new(&buf)) .unwrap_err(); assert_matches!(err, CodecError::UnexpectedValue); buf[0] = 1 << 4; let err = IdpfPublicShare::::decode_with_param(&2, &mut Cursor::new(&buf)) .unwrap_err(); assert_matches!(err, CodecError::UnexpectedValue); buf[0] = 1 << 6; let err = IdpfPublicShare::::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::::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, /// 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>, /// The IDPF output values for the leaf level, provided to the key generation algorithm. beta_leaf: Poplar1IdpfValue, /// 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, } /// 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/08/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::(); 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::().unwrap()), Field64::from(array[1].as_str().unwrap().parse::().unwrap()), ]) }) .collect::>(); 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::() .unwrap(), ), Field255::from( beta_leaf_array[1] .as_str() .unwrap() .parse::() .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().unwrap(); 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(_: &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) -> Result<(), CodecError> { Ok(()) } } impl Decode for MyUnit { fn decode(_: &mut Cursor<&[u8]>) -> Result { 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); impl IdpfValue for MyVector { type ValueParameter = usize; fn generate(seed_stream: &mut S, length: &Self::ValueParameter) -> Self where S: rand_core::RngCore, { let mut output = vec![::zero(); *length]; for element in output.iter_mut() { *element = ::generate(seed_stream, &()); } MyVector(output) } fn zero(length: &usize) -> Self { MyVector(vec![::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![::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 = ::conditional_select( a_elem, b_elem, choice, ); } MyVector(output) } } impl Encode for MyVector { fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { encode_u32_items(bytes, &(), &self.0) } } impl Decode for MyVector { fn decode(bytes: &mut Cursor<&[u8]>) -> Result { 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![::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![::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], ::zero()); assert_eq!(value.0[1], ::zero()); assert_eq!(value.0[2], ::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)); }); } }