/// IDPF-related errors. #[derive(Debug, thiserror::Error)] #[non_exhaustive] pubenum 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)] pubstruct 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.) pubfn from_bytes(bytes: &[u8]) -> IdpfInput { let bit_slice_u8_storage = bytes.view_bits::<Msb0>(); letmut 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. pubfn 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. pubfn clone_with_suffix(&self, suffix: &[bool]) -> IdpfInput { letmut 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. pubfn len(&self) -> usize { self.index.len()
}
/// Check if the input is empty, i.e. it does not contain any bits. pubfn is_empty(&self) -> bool { self.index.is_empty()
}
/// Get an iterator over the bits that make up this input. pubfn 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. pubfn to_bytes(&self) -> Vec<u8> { letmut 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. pubfn 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. pubfn get(&self, level: usize) -> Option<bool> { self.index.get(level).as_deref().copied()
}
}
/// 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. pubtrait IdpfValue:
Add<Output = Self>
+ AddAssign
+ Sub<Output = Self>
+ ConditionallyNegatable
+ Encode
+ ParameterizedDecode<Self::ValueParameter>
+ 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. letmut 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,
}
}
}
/// An output from evaluation of an IDPF at some level and index. #[derive(Debug, PartialEq, Eq)] pubenum 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),
}
// "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;
/// 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);
/// 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);
let (new_key, elements) = convert::<V>(&seed_corrected, convert_xof_fixed_key, parameter);
*key = new_key;
letmut 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`]. pubstruct 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. pubfn new(
inner_node_value_parameter: VI::ValueParameter,
leaf_node_value_parameter: VL::ValueParameter,
) -> Self { Self {
inner_node_value_parameter,
leaf_node_value_parameter,
}
}
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. pubfngen<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(),
);
} letmut 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: &mutdyn 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);
letmut 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`. pubfn eval(
&self,
agg_id: usize,
public_share: &IdpfPublicShare<VI, VL>,
key: &Seed<16>,
prefix: &IdpfInput,
binder: &[u8],
cache: &mutdyn 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`. letmut cache_key = &prefix[..prefix.len() - 1]; while !cache_key.is_empty() { iflet 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. returnself.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)] pubstruct 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> 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>) -> 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. letmut control_bits: BitVec<u8, Lsb0> =
BitVec::with_capacity(self.inner_correction_words.len() * 2 + 2); for correction_words inself.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); letmut packed_control = control_bits.into_vec();
bytes.append(&mut packed_control);
for correction_words inself.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; letmut len = (control_bits_count + 7) / 8 + (self.inner_correction_words.len() + 1) * 16; for correction_words inself.inner_correction_words.iter() {
len += correction_words.value.encoded_len()?;
}
len += self.leaf_correction_word.value.encoded_len()?;
Some(len)
}
}
letmut 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);
}
/// 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: & style='color:red'>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. pubtrait 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(&mutself, input: &BitSlice, values: &([u8; 16], u8));
}
/// A no-op [`IdpfCache`] implementation that always reports a cache miss. #[derive(Default)] pubstruct NoCache {}
/// A simple [`IdpfCache`] implementation that caches intermediate results in an in-memory hash map, /// with no eviction. #[derive(Default)] pubstruct HashMapCache {
map: HashMap<BitBox, ([u8; 16], u8)>,
}
impl HashMapCache { /// Create a new unpopulated `HashMapCache`. pubfn new() -> HashMapCache {
HashMapCache::default()
}
/// Create a new unpopulated `HashMapCache`, with a set pre-allocated capacity. pubfn with_capacity(capacity: usize) -> HashMapCache { Self {
map: HashMap::with_capacity(capacity),
}
}
}
/// A simple [`IdpfCache`] implementation that caches intermediate results in memory, with /// first-in-first-out eviction, and lookups via linear probing. pubstruct RingBufferCache {
ring: VecDeque<(BitBox, [u8; 16], u8)>,
}
/// 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. pubfn generate_zipf_distributed_batch(
rng: &mutimpl Rng,
bits: usize,
threshold: usize,
measurement_count: usize,
zipf_support: usize,
zipf_exponent: f64,
) -> (Vec<IdpfInput>, Vec<Vec<IdpfInput>>) { // Generate random inputs. letmut inputs = Vec::with_capacity(zipf_support); for _ in0..zipf_support { let bools: Vec<bool> = (0..bits).map(|_| rng.gen()).collect();
inputs.push(IdpfInput::from_bools(&bools));
}
// Sample a number of inputs according to the Zipf distribution. letmut samples = Vec::with_capacity(measurement_count); let zipf = ZipfDistribution::new(zipf_support, zipf_exponent).unwrap(); for _ in0..measurement_count {
samples.push(inputs[zipf.sample(rng) - 1].clone());
}
// Compute the prefix tree for the desired threshold. letmut prefix_tree = Vec::with_capacity(bits);
prefix_tree.push(vec![
IdpfInput::from_bools(&[false]),
IdpfInput::from_bools(&[true]),
]);
for level in0..bits - 1 { // Compute the hit count of each prefix from the previous level. letmut 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() { letmut is_prefix = true; for j in0..prefix.len() { if prefix[j] != sample[j] {
is_prefix = false; break;
}
} if is_prefix {
*hit_count += 1;
}
}
}
// Compute the next set of candidate prefixes. letmut 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};
#[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; letmut bits = bitbox![0; INPUT_LEN]; formut bit in bits.iter_mut() {
bit.set(random());
} let input = bits.clone().into();
#[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();
#[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());
/// 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/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::<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 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() { usesuper::IdpfValue;
/// A test-only type for use as an [`IdpfValue`]. #[derive(Debug, Clone, Copy)] struct MyUnit;
impl IdpfValue for MyUnit { type ValueParameter = ();
/// 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,
{ letmut output = vec![<Field128 as FieldElement>::zero(); *length]; for element in output.iter_mut() {
*element = <Field128 as IdpfValue>::generate(seed_stream, &());
}
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());
});
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.