impl<P, const SEED_SIZE: usize> Poplar1<P, SEED_SIZE> { /// Create an instance of [`Poplar1`]. The caller provides the bit length of each /// measurement (`BITS` as defined in [[draft-irtf-cfrg-vdaf-08]]). /// /// [draft-irtf-cfrg-vdaf-08]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/08/ pubfn new(bits: usize) -> Self { Self {
bits,
phantom: PhantomData,
}
}
}
impl Poplar1<XofTurboShake128, 16> { /// Create an instance of [`Poplar1`] using [`XofTurboShake128`]. The caller provides the bit length of /// each measurement (`BITS` as defined in [[draft-irtf-cfrg-vdaf-08]]). /// /// [draft-irtf-cfrg-vdaf-08]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vdaf/08/ pubfn new_turboshake128(bits: usize) -> Self {
Poplar1::new(bits)
}
}
/// Poplar1 public share. /// /// This is comprised of the correction words generated for the IDPF. pubtype Poplar1PublicShare =
IdpfPublicShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>;
/// Poplar1 input share. /// /// This is comprised of an IDPF key share and the correlated randomness used to compute the sketch /// during preparation. #[derive(Debug, Clone)] pubstruct Poplar1InputShare<const SEED_SIZE: usize> { /// IDPF key share.
idpf_key: Seed<16>,
/// Seed used to generate the Aggregator's share of the correlated randomness used in the first /// part of the sketch.
corr_seed: Seed<SEED_SIZE>,
/// Aggregator's share of the correlated randomness used in the second part of the sketch. Used /// for inner nodes of the IDPF tree.
corr_inner: Vec<[Field64; 2]>,
/// Aggregator's share of the correlated randomness used in the second part of the sketch. Used /// for leaf nodes of the IDPF tree.
corr_leaf: [Field255; 2],
}
impl<const SEED_SIZE: usize> Eq for Poplar1InputShare<SEED_SIZE> {}
impl<const SEED_SIZE: usize> ConstantTimeEq for Poplar1InputShare<SEED_SIZE> { fn ct_eq(&self, other: &Self) -> Choice { // We short-circuit on the length of corr_inner being different. Only the content is // protected. ifself.corr_inner.len() != other.corr_inner.len() { return Choice::from(0);
}
letmut res = self.idpf_key.ct_eq(&other.idpf_key)
& self.corr_seed.ct_eq(&other.corr_seed)
& self.corr_leaf.ct_eq(&other.corr_leaf); for (x, y) inself.corr_inner.iter().zip(other.corr_inner.iter()) {
res &= x.ct_eq(y);
}
res
}
}
impl<F: FieldElement> Encode for PrepareState<F> { fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> { self.sketch.encode(bytes)?; // `expect` safety: output_share's length is the same as the number of prefixes; the number // of prefixes is capped at 2^32-1.
u32::try_from(self.output_share.len())
.expect("Couldn't convert output_share length to u32")
.encode(bytes)?; for elem in &self.output_share {
elem.encode(bytes)?;
}
Ok(())
}
impl<F: FieldElement> SketchState<F> { fn decode_sketch_share(&self, bytes: &mut Cursor<&[u8]>) -> Result<Vec<F>, CodecError> { matchself { // The sketch share is three field elements. Self::RoundOne { .. } => Ok(vec![
F::decode(bytes)?,
F::decode(bytes)?,
F::decode(bytes)?,
]), // The sketch verifier share is one field element. Self::RoundTwo => Ok(vec![F::decode(bytes)?]),
}
}
fn decode_sketch(&self, bytes: &mut Cursor<&[u8]>) -> Result<Option<[F; 3]>, CodecError> { matchself { // The sketch is three field elements. Self::RoundOne { .. } => Ok(Some([
F::decode(bytes)?,
F::decode(bytes)?,
F::decode(bytes)?,
])), // The sketch verifier should be zero if the sketch if valid. Instead of transmitting // this zero over the wire, we just expect an empty message. Self::RoundTwo => Ok(None),
}
}
}
/// A vector of field elements transmitted while evaluating Poplar1. #[derive(Clone, Debug)] pubenum Poplar1FieldVec { /// Field type for inner nodes of the IDPF tree.
Inner(Vec<Field64>),
/// Field type for leaf nodes of the IDPF tree.
Leaf(Vec<Field255>),
}
impl Poplar1FieldVec { fn zero(is_leaf: bool, len: usize) -> Self { if is_leaf { Self::Leaf(vec![<Field255 as FieldElement>::zero(); len])
} else { Self::Inner(vec![<Field64 as FieldElement>::zero(); len])
}
}
}
/// Poplar1 aggregation parameter. /// /// This includes an indication of what level of the IDPF tree is being evaluated and the set of /// prefixes to evaluate at that level. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pubstruct Poplar1AggregationParam {
level: u16,
prefixes: Vec<IdpfInput>,
}
impl Poplar1AggregationParam { /// Construct an aggregation parameter from a set of candidate prefixes. /// /// # Errors /// /// * The list of prefixes is empty. /// * The prefixes have different lengths (they must all be the same). /// * The prefixes have length 0, or length longer than 2^16 bits. /// * There are more than 2^32 - 1 prefixes. /// * The prefixes are not unique. /// * The prefixes are not in lexicographic order. pubfn try_from_prefixes(prefixes: Vec<IdpfInput>) -> Result<Self, VdafError> { if prefixes.is_empty() { return Err(VdafError::Uncategorized( "at least one prefix is required".into(),
));
} if u32::try_from(prefixes.len()).is_err() { return Err(VdafError::Uncategorized("too many prefixes".into()));
}
let len = prefixes[0].len(); letmut last_prefix = None; for prefix in prefixes.iter() { if prefix.len() != len { return Err(VdafError::Uncategorized( "all prefixes must have the same length".into(),
));
} iflet Some(last_prefix) = last_prefix { if prefix <= last_prefix { if prefix == last_prefix { return Err(VdafError::Uncategorized( "prefixes must be nonrepeating".into(),
));
} else { return Err(VdafError::Uncategorized( "prefixes must be in lexicographic order".into(),
));
}
}
}
last_prefix = Some(prefix);
}
let level = len
.checked_sub(1)
.ok_or_else(|| VdafError::Uncategorized("prefixes are too short".into()))?; let level = u16::try_from(level)
.map_err(|_| VdafError::Uncategorized("prefixes are too long".into()))?;
Ok(Self { level, prefixes })
}
/// Return the level of the IDPF tree. pubfn level(&self) -> usize {
usize::from(self.level)
}
impl Encode for Poplar1AggregationParam { fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> { // Okay to unwrap because `try_from_prefixes()` checks this conversion succeeds. let prefix_count = u32::try_from(self.prefixes.len()).unwrap(); self.level.encode(bytes)?;
prefix_count.encode(bytes)?;
// The encoding of the prefixes is defined by treating the IDPF indices as integers, // shifting and ORing them together, and encoding the resulting arbitrary precision integer // in big endian byte order. Thus, the first prefix will appear in the last encoded byte, // aligned to its least significant bit. The last prefix will appear in the first encoded // byte, not necessarily aligned to a byte boundary. If the highest bits in the first byte // are unused, they will be set to zero.
// When an IDPF index is treated as an integer, the first bit is the integer's most // significant bit, and bits are subsequently processed in order of decreasing significance. // Thus, setting aside the order of bytes, bits within each byte are ordered with the // [`Msb0`](bitvec::prelude::Msb0) convention, not [`Lsb0`](bitvec::prelude::Msb0). Yet, // the entire integer is aligned to the least significant bit of the last byte, so we // could not use `Msb0` directly without padding adjustments. Instead, we use `Lsb0` // throughout and reverse the bit order of each prefix.
fn encoded_len(&self) -> Option<usize> { let packed_bit_count = (usize::from(self.level) + 1) * self.prefixes.len(); // 4 bytes for the number of prefixes, 2 bytes for the level, and a variable number of bytes // for the packed prefixes themselves.
Some(6 + (packed_bit_count + 7) / 8)
}
}
impl Decode for Poplar1AggregationParam { fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> { let level = u16::decode(bytes)?; let prefix_count =
usize::try_from(u32::decode(bytes)?).map_err(|e| CodecError::Other(e.into()))?;
let packed_bit_count = (usize::from(level) + 1) * prefix_count; letmut packed = vec![0u8; (packed_bit_count + 7) / 8];
bytes.read_exact(&mut packed)?; if packed_bit_count % 8 != 0 { let unused_bits = packed[0] >> (packed_bit_count % 8); if unused_bits != 0 { return Err(CodecError::UnexpectedValue);
}
}
packed.reverse(); let bits = BitVec::<u8, Lsb0>::from_vec(packed);
impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Vdaf for Poplar1<P, SEED_SIZE> { type Measurement = IdpfInput; type AggregateResult = Vec<u64>; type AggregationParam = Poplar1AggregationParam; type PublicShare = Poplar1PublicShare; type InputShare = Poplar1InputShare<SEED_SIZE>; type OutputShare = Poplar1FieldVec; type AggregateShare = Poplar1FieldVec;
// Generate the authenticator for each inner level of the IDPF tree. letmut prng = self.init_prng::<_, _, Field64>(&poplar_random[2], DST_SHARD_RANDOMNESS, [nonce]); let auth_inner: Vec<Field64> = (0..self.bits - 1).map(|_| prng.get()).collect();
// Generate the authenticator for the last level of the IDPF tree (i.e., the leaves). // // TODO(cjpatton) spec: Consider using a different XOF for the leaf and inner nodes. // "Switching" the XOF between field types is awkward. letmut prng = prng.into_new_field::<Field255>(); let auth_leaf = prng.get();
// Generate the IDPF shares. let idpf = Idpf::new((), ()); let (public_share, [idpf_key_0, idpf_key_1]) = idpf.gen_with_random(
input,
auth_inner
.iter()
.map(|auth| Poplar1IdpfValue([Field64::one(), *auth])),
Poplar1IdpfValue([Field255::one(), auth_leaf]),
nonce,
idpf_random,
)?;
// Generate the correlated randomness for the inner nodes. This includes additive shares of // the random offsets `a, b, c` and additive shares of `A := -2*a + auth` and `B := a^2 + b // - a*auth + c`, where `auth` is the authenticator for the level of the tree. These values // are used, respectively, to compute and verify the sketch during the preparation phase. // (See Section 4.2 of [BBCG+21].) let corr_seed_0 = &poplar_random[0]; let corr_seed_1 = &poplar_random[1]; letmut prng = prng.into_new_field::<Field64>(); letmut corr_prng_0 = self.init_prng::<_, _, Field64>(
corr_seed_0,
DST_CORR_INNER,
[[0].as_slice(), nonce.as_slice()],
); letmut corr_prng_1 = self.init_prng::<_, _, Field64>(
corr_seed_1,
DST_CORR_INNER,
[[1].as_slice(), nonce.as_slice()],
); letmut corr_inner_0 = Vec::with_capacity(self.bits - 1); letmut corr_inner_1 = Vec::with_capacity(self.bits - 1); for auth in auth_inner.into_iter() { let (next_corr_inner_0, next_corr_inner_1) =
compute_next_corr_shares(&mut prng, &mut corr_prng_0, &mut corr_prng_1, auth);
corr_inner_0.push(next_corr_inner_0);
corr_inner_1.push(next_corr_inner_1);
}
if usize::from(agg_param.level) < self.bits - 1 { letmut corr_prng = self.init_prng::<_, _, Field64>(
input_share.corr_seed.as_ref(),
DST_CORR_INNER,
[[agg_id as u8].as_slice(), nonce.as_slice()],
); // Fast-forward the correlated randomness XOF to the level of the tree that we are // aggregating. for _ in0..3 * agg_param.level {
corr_prng.get();
}
impl<P: Xof<SEED_SIZE>, const SEED_SIZE: usize> Collector for Poplar1<P, SEED_SIZE> { fn unshard<M: IntoIterator<Item = Poplar1FieldVec>>(
&self,
agg_param: &Poplar1AggregationParam,
agg_shares: M,
_num_measurements: usize,
) -> Result<Vec<u64>, VdafError> { let result = aggregate(
usize::from(agg_param.level) == self.bits - 1,
agg_param.prefixes.len(),
agg_shares,
)?;
match result {
Poplar1FieldVec::Inner(vec) => Ok(vec.into_iter().map(u64::from).collect()),
Poplar1FieldVec::Leaf(vec) => Ok(vec
.into_iter()
.map(u64::try_from)
.collect::<Result<Vec<_>, _>>()?),
}
}
}
impl From<IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>> for Poplar1IdpfValue<Field64>
{ fn from(
out_share: IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>,
) -> Poplar1IdpfValue<Field64> { match out_share {
IdpfOutputShare::Inner(array) => array,
IdpfOutputShare::Leaf(..) => panic!("tried to convert leaf share into inner field"),
}
}
}
impl From<IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>> for Poplar1IdpfValue<Field255>
{ fn from(
out_share: IdpfOutputShare<Poplar1IdpfValue<Field64>, Poplar1IdpfValue<Field255>>,
) -> Poplar1IdpfValue<Field255> { match out_share {
IdpfOutputShare::Inner(..) => panic!("tried to convert inner share into leaf field"),
IdpfOutputShare::Leaf(array) => array,
}
}
}
/// Derive shares of the correlated randomness for the next level of the IDPF tree. // // TODO(cjpatton) spec: Consider deriving the shares of a, b, c for each level directly from the // seed, rather than iteratively, as we do in Doplar. This would be more efficient for the // Aggregators. As long as the Client isn't significantly slower, this should be a win. #[allow(non_snake_case)] fn compute_next_corr_shares<F: FieldElement + From<u64>, S: RngCore>(
prng: &mut Prng<F, S>,
corr_prng_0: &mut Prng<F, S>,
corr_prng_1: &mut Prng<F, S>,
auth: F,
) -> ([F; 2], [F; 2]) { let two = F::from(2); let a = corr_prng_0.get() + corr_prng_1.get(); let b = corr_prng_0.get() + corr_prng_1.get(); let c = corr_prng_0.get() + corr_prng_1.get(); let A = -two * a + auth; let B = a * a + b - a * auth + c; let corr_1 = [prng.get(), prng.get()]; let corr_0 = [A - corr_1[0], B - corr_1[1]];
(corr_0, corr_1)
}
/// Compute the Aggregator's share of the sketch verifier. The shares should sum to zero. #[allow(non_snake_case)] fn finish_sketch<F: FieldElement>(
sketch: [F; 3],
A_share: F,
B_share: F,
is_leader: bool,
) -> Vec<F> { letmut next_sketch_share = A_share * sketch[0] + B_share; if !is_leader {
next_sketch_share += sketch[0] * sketch[0] - sketch[1] - sketch[2];
}
vec![next_sketch_share]
}
fn aggregate<M: IntoIterator<Item = Poplar1FieldVec>>(
is_leaf: bool,
len: usize,
shares: M,
) -> Result<Poplar1FieldVec, VdafError> { letmut result = Poplar1FieldVec::zero(is_leaf, len); for share in shares.into_iter() {
result.accumulate(&share)?;
}
Ok(result)
}
/// A vector of two field elements. /// /// This represents the values that Poplar1 programs into IDPFs while sharding. #[derive(Debug, Clone, Copy)] pubstruct Poplar1IdpfValue<F>([F; 2]);
impl<F> Poplar1IdpfValue<F> { /// Create a new value from a pair of field elements. pubfn new(array: [F; 2]) -> Self { Self(array)
}
}
impl<F> IdpfValue for Poplar1IdpfValue<F> where
F: FieldElement,
{ type ValueParameter = ();
impl<F> ConditionallyNegatable for Poplar1IdpfValue<F> where
F: ConditionallyNegatable,
{ fn conditional_negate(&mutself, choice: subtle::Choice) {
F::conditional_negate(&mutself.0[0], choice);
F::conditional_negate(&mutself.0[1], choice);
}
}
#[cfg(test)] mod tests { usesuper::*; usecrate::vdaf::{equality_comparison_test, test_utils::run_vdaf_prepare}; use assert_matches::assert_matches; use rand::prelude::*; use serde::Deserialize; use std::collections::HashSet;
// Unless this is the last level of the tree, construct the next set of candidate // prefixes. if level < vdaf.bits - 1 { letmut next_prefixes = Vec::new(); for (prefix, count) in agg_param.prefixes.into_iter().zip(agg_result.iter()) { if *count >= threshold as u64 {
next_prefixes.push(prefix.clone_with_suffix(&[false]));
next_prefixes.push(prefix.clone_with_suffix(&[true]));
}
}
agg_param.prefixes = next_prefixes;
}
}
let got: HashSet<IdpfInput> = agg_param
.prefixes
.into_iter()
.zip(agg_result.iter())
.filter(|(_prefix, count)| **count >= threshold as u64)
.map(|(prefix, _count)| prefix)
.collect();
let want: HashSet<IdpfInput> = expected_result
.into_iter()
.map(|bytes| IdpfInput::from_bytes(bytes.as_ref()))
.collect();
assert_eq!(got, want);
}
#[test] fn shard_prepare() { letmut rng = thread_rng(); let vdaf = Poplar1::new_turboshake128(64); let verify_key = rng.gen(); let input = IdpfInput::from_bytes(b"12341324"); let nonce = rng.gen(); let (public_share, input_shares) = vdaf.shard(&input, &nonce).unwrap();
let r2_prep_msg = poplar
.prepare_shares_to_prepare_message(
&agg_param,
[r1_prep_share_0.clone(), r1_prep_share_1.clone()],
)
.unwrap();
let out_share_0 = assert_matches!(
poplar
.prepare_next(r1_prep_state_0.clone(), r2_prep_msg.clone())
.unwrap(),
PrepareTransition::Finish(out) => out
); let out_share_1 = assert_matches!(
poplar
.prepare_next(r1_prep_state_1, r2_prep_msg.clone())
.unwrap(),
PrepareTransition::Finish(out) => out
);
let agg_share_0 = poplar.aggregate(&agg_param, [out_share_0.clone()]).unwrap(); let agg_share_1 = poplar.aggregate(&agg_param, [out_share_1.clone()]).unwrap();
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.