use crate::{
bt::BinaryTree,
codec::{CodecError, Decode, Encode, ParameterizedDecode},
field::{decode_fieldvec, Field64, FieldElement, FieldElementWithInteger},
flp::{types::Count, Type},
vdaf::{
poplar1::{Poplar1, Poplar1AggregationParam},
xof::{Seed, Xof},
Aggregatable, AggregateShare, Aggregator, Client, Collector, OutputShare,
PrepareTransition, Vdaf, VdafError,
},
vidpf::{
Vidpf, VidpfError, VidpfInput, VidpfKey, VidpfPublicShare, VidpfServerId, VidpfWeight,
VIDPF_PROOF_SIZE,
},
};
use szk::{Szk, SzkJointShare, SzkProofShare, SzkQueryShare, SzkQueryState};
use rand::{rng, Rng};
use std::io::{Cursor, Read};
use std::ops::BitAnd;
use std::slice::from_ref;
use std::{collections::VecDeque, fmt::Debug};
use subtle::{Choice, ConstantTimeEq};
use super::xof::XofTurboShake128;
pub(crate) mod szk;
pub(crate) const SEED_SIZE: usize = 32;
pub(crate) const NONCE_SIZE: usize = 16;
pub(crate) const USAGE_PROVE_RAND: u8 = 0;
pub(crate) const USAGE_PROOF_SHARE: u8 = 1;
pub(crate) const USAGE_QUERY_RAND: u8 = 2;
pub(crate) const USAGE_JOINT_RAND_SEED: u8 = 3;
pub(crate) const USAGE_JOINT_RAND_PART: u8 = 4;
pub(crate) const USAGE_JOINT_RAND: u8 = 5;
pub(crate) const USAGE_ONEHOT_CHECK: u8 = 6;
pub(crate) const USAGE_PAYLOAD_CHECK: u8 = 7;
pub(crate) const USAGE_EVAL_PROOF: u8 = 8;
pub(crate) const USAGE_NODE_PROOF: u8 = 9;
pub(crate) const USAGE_EXTEND: u8 = 10;
pub(crate) const USAGE_CONVERT: u8 = 11;
pub(crate) fn dst_usage(usage: u8) -> [u8; 8] {
const VERSION: u8 = 0;
[b'm', b'a', b's', b't', b'i', b'c', VERSION, usage]
}
pub type MasticCount = Mastic<Count<Field64>>;
#[derive(Clone, Debug)]
pub struct Mastic<T: Type> {
id: [u8; 4],
pub(crate) szk: Szk<T>,
pub(crate) vidpf: Vidpf<VidpfWeight<T::Field>>,
}
impl<T: Type> Mastic<T> {
pub fn new(algorithm_id: u32, weight_type: T, bits: usize) -> Result<Self, VdafError> {
let vidpf = Vidpf::new(bits, weight_type.input_len() + 1)?;
let szk = Szk::new(weight_type, algorithm_id);
Ok(Self {
id: algorithm_id.to_be_bytes(),
szk,
vidpf,
})
}
fn agg_share_len(&self, agg_param: &MasticAggregationParam) -> usize {
(1 + self.szk.typ.output_len()) * agg_param.level_and_prefixes.prefixes().len()
}
pub fn weight_type(&self) -> &T {
&self.szk.typ
}
pub fn bits(&self) -> usize {
self.vidpf.bits.into()
}
}
impl Mastic<Count<Field64>> {
pub fn new_count(bits: usize) -> Result<Self, VdafError> {
Self::new(0xFFFF0001, Count::new(), bits)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MasticAggregationParam {
level_and_prefixes: Poplar1AggregationParam,
require_weight_check: bool,
}
impl MasticAggregationParam {
pub fn new(prefixes: Vec<VidpfInput>, require_weight_check: bool) -> Result<Self, VdafError> {
Ok(Self {
level_and_prefixes: Poplar1AggregationParam::try_from_prefixes(prefixes)?,
require_weight_check,
})
}
}
impl Encode for MasticAggregationParam {
fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> {
self.level_and_prefixes.encode(bytes)?;
let require_weight_check = if self.require_weight_check { 1u8 } else { 0u8 };
require_weight_check.encode(bytes)?;
Ok(())
}
fn encoded_len(&self) -> Option<usize> {
Some(self.level_and_prefixes.encoded_len()? + 1usize)
}
}
impl Decode for MasticAggregationParam {
fn decode(bytes: &mut Cursor<&[u8]>) -> Result<Self, CodecError> {
let level_and_prefixes = Poplar1AggregationParam::decode(bytes)?;
let require_weight_check_u8 = u8::decode(bytes)?;
let require_weight_check = require_weight_check_u8 != 0;
Ok(Self {
level_and_prefixes,
require_weight_check,
})
}
}
pub type MasticPublicShare<V> = VidpfPublicShare<V>;
impl<T: Type> ParameterizedDecode<Mastic<T>> for MasticPublicShare<VidpfWeight<T::Field>> {
fn decode_with_param(
mastic: &Mastic<T>,
bytes: &mut Cursor<&[u8]>,
) -> Result<Self, CodecError> {
VidpfPublicShare::decode_with_param(&mastic.vidpf, bytes)
}
}
#[derive(Clone, Debug)]
pub struct MasticInputShare<F: FieldElement> {
vidpf_key: VidpfKey,
proof_share: SzkProofShare<F>,
}
impl<F: FieldElement> Encode for MasticInputShare<F> {
fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> {
bytes.extend_from_slice(&self.vidpf_key.0[..]);
self.proof_share.encode(bytes)?;
Ok(())
}
fn encoded_len(&self) -> Option<usize> {
Some(16 + self.proof_share.encoded_len()?)
}
}
impl<'a, T: Type> ParameterizedDecode<(&'a Mastic<T>, usize)> for MasticInputShare<T::Field> {
fn decode_with_param(
(mastic, agg_id): &(&'a Mastic<T>, usize),
bytes: &mut Cursor<&[u8]>,
) -> Result<Self, CodecError> {
if *agg_id > 1 {
return Err(CodecError::UnexpectedValue);
}
let vidpf_key = VidpfKey::decode(bytes)?;
let proof_share = SzkProofShare::decode_with_param(
&(
*agg_id == 0,
mastic.szk.typ.proof_len(),
mastic.szk.typ.joint_rand_len() != 0,
),
bytes,
)?;
Ok(Self {
vidpf_key,
proof_share,
})
}
}
impl<F: FieldElement> PartialEq for MasticInputShare<F> {
fn eq(&self, other: &MasticInputShare<F>) -> bool {
self.ct_eq(other).into()
}
}
impl<F: FieldElement> ConstantTimeEq for MasticInputShare<F> {
fn ct_eq(&self, other: &MasticInputShare<F>) -> Choice {
self.vidpf_key
.ct_eq(&other.vidpf_key)
.bitand(self.proof_share.ct_eq(&other.proof_share))
}
}
pub type MasticOutputShare<V> = OutputShare<V>;
pub type MasticAggregateShare<V> = AggregateShare<V>;
impl<'a, T: Type> ParameterizedDecode<(&'a Mastic<T>, &'a MasticAggregationParam)>
for MasticAggregateShare<T::Field>
{
fn decode_with_param(
(mastic, agg_param): &(&Mastic<T>, &MasticAggregationParam),
bytes: &mut Cursor<&[u8]>,
) -> Result<Self, CodecError> {
decode_fieldvec(mastic.agg_share_len(agg_param), bytes).map(AggregateShare)
}
}
impl<'a, T: Type> ParameterizedDecode<(&'a Mastic<T>, &'a MasticAggregationParam)>
for MasticOutputShare<T::Field>
{
fn decode_with_param(
(mastic, agg_param): &(&Mastic<T>, &MasticAggregationParam),
bytes: &mut Cursor<&[u8]>,
) -> Result<Self, CodecError> {
decode_fieldvec(mastic.agg_share_len(agg_param), bytes).map(OutputShare)
}
}
impl<T: Type> Vdaf for Mastic<T> {
type Measurement = (VidpfInput, T::Measurement);
type AggregateResult = Vec<T::AggregateResult>;
type AggregationParam = MasticAggregationParam;
type PublicShare = MasticPublicShare<VidpfWeight<T::Field>>;
type InputShare = MasticInputShare<T::Field>;
type OutputShare = MasticOutputShare<T::Field>;
type AggregateShare = MasticAggregateShare<T::Field>;
fn algorithm_id(&self) -> u32 {
u32::from_be_bytes(self.id)
}
fn num_aggregators(&self) -> usize {
2
}
}
impl<T: Type> Mastic<T> {
fn shard_with_random(
&self,
ctx: &[u8],
(alpha, weight): &(VidpfInput, T::Measurement),
nonce: &[u8; NONCE_SIZE],
vidpf_keys: [VidpfKey; 2],
szk_random: [Seed<SEED_SIZE>; 2],
joint_random_opt: Option<Seed<SEED_SIZE>>,
) -> Result<(<Self as Vdaf>::PublicShare, Vec<<Self as Vdaf>::InputShare>), VdafError> {
if alpha.len() != usize::from(self.vidpf.bits) {
return Err(VdafError::Vidpf(VidpfError::InvalidInputLength));
}
let mut beta = VidpfWeight(self.szk.typ.encode_measurement(weight)?);
beta.0.insert(0, T::Field::one());
let public_share = self
.vidpf
.gen_with_keys(ctx, &vidpf_keys, alpha, &beta, nonce)?;
let leader_beta_share = self.vidpf.get_beta_share(
ctx,
VidpfServerId::S0,
&public_share,
&vidpf_keys[0],
nonce,
)?;
let helper_beta_share = self.vidpf.get_beta_share(
ctx,
VidpfServerId::S1,
&public_share,
&vidpf_keys[1],
nonce,
)?;
let [leader_szk_proof_share, helper_szk_proof_share] = self.szk.prove(
ctx,
&leader_beta_share.as_ref()[1..],
&helper_beta_share.as_ref()[1..],
&beta.as_ref()[1..],
szk_random,
joint_random_opt,
nonce,
)?;
let [leader_vidpf_key, helper_vidpf_key] = vidpf_keys;
let leader_share = MasticInputShare {
vidpf_key: leader_vidpf_key,
proof_share: leader_szk_proof_share,
};
let helper_share = MasticInputShare {
vidpf_key: helper_vidpf_key,
proof_share: helper_szk_proof_share,
};
Ok((public_share, vec![leader_share, helper_share]))
}
}
impl<T: Type> Client<16> for Mastic<T> {
fn shard(
&self,
ctx: &[u8],
measurement: &(VidpfInput, T::Measurement),
nonce: &[u8; 16],
) -> Result<(Self::PublicShare, Vec<Self::InputShare>), VdafError> {
let mut rng = rng();
let vidpf_keys = rng.random();
let joint_random_opt = if self.szk.requires_joint_rand() {
Some(rng.random())
} else {
None
};
let szk_random = rng.random();
self.shard_with_random(
ctx,
measurement,
nonce,
vidpf_keys,
szk_random,
joint_random_opt,
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MasticPrepareState<F: FieldElement> {
output_shares: MasticOutputShare<F>,
szk_query_state: SzkQueryState,
verifier_len: Option<usize>,
}
impl<F: FieldElement> Encode for MasticPrepareState<F> {
fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> {
self.output_shares.encode(bytes)?;
if let Some(joint_rand_seed) = &self.szk_query_state {
joint_rand_seed.encode(bytes)?;
}
Ok(())
}
fn encoded_len(&self) -> Option<usize> {
Some(
self.output_shares.as_ref().len() * F::ENCODED_SIZE
+ self.szk_query_state.as_ref().map_or(0, |_| SEED_SIZE),
)
}
}
impl<'a, T: Type> ParameterizedDecode<(&'a Mastic<T>, &'a MasticAggregationParam)>
for MasticPrepareState<T::Field>
{
fn decode_with_param(
decoder @ (mastic, agg_param): &(&Mastic<T>, &MasticAggregationParam),
bytes: &mut Cursor<&[u8]>,
) -> Result<Self, CodecError> {
let output_shares = MasticOutputShare::decode_with_param(decoder, bytes)?;
let szk_query_state = (mastic.szk.typ.joint_rand_len() > 0
&& agg_param.require_weight_check)
.then(|| Seed::decode(bytes))
.transpose()?;
let verifier_len = agg_param
.require_weight_check
.then_some(mastic.szk.typ.verifier_len());
Ok(Self {
output_shares,
szk_query_state,
verifier_len,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MasticPrepareShare<F: FieldElement> {
vidpf_eval_proof: [u8; VIDPF_PROOF_SIZE],
szk_query_share_opt: Option<SzkQueryShare<F>>,
}
impl<F: FieldElement> Encode for MasticPrepareShare<F> {
fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> {
bytes.extend_from_slice(&self.vidpf_eval_proof);
match &self.szk_query_share_opt {
Some(query_share) => query_share.encode(bytes),
None => Ok(()),
}
}
fn encoded_len(&self) -> Option<usize> {
Some(
VIDPF_PROOF_SIZE
+ match &self.szk_query_share_opt {
Some(query_share) => query_share.encoded_len()?,
None => 0,
},
)
}
}
impl<F: FieldElement> ParameterizedDecode<MasticPrepareState<F>> for MasticPrepareShare<F> {
fn decode_with_param(
prep_state: &MasticPrepareState<F>,
bytes: &mut Cursor<&[u8]>,
) -> Result<Self, CodecError> {
let mut vidpf_eval_proof = [0; VIDPF_PROOF_SIZE];
bytes.read_exact(&mut vidpf_eval_proof[..])?;
let requires_joint_rand = prep_state.szk_query_state.is_some();
let szk_query_share_opt = prep_state
.verifier_len
.map(|verifier_len| {
SzkQueryShare::decode_with_param(&(requires_joint_rand, verifier_len), bytes)
})
.transpose()?;
Ok(Self {
vidpf_eval_proof,
szk_query_share_opt,
})
}
}
pub type MasticPrepareMessage = SzkJointShare;
impl<F: FieldElement> ParameterizedDecode<MasticPrepareState<F>> for MasticPrepareMessage {
fn decode_with_param(
prep_state: &MasticPrepareState<F>,
bytes: &mut Cursor<&[u8]>,
) -> Result<Self, CodecError> {
match prep_state.szk_query_state {
Some(_) => SzkJointShare::decode_with_param(&true, bytes),
None => SzkJointShare::decode_with_param(&false, bytes),
}
}
}
impl<T: Type> Aggregator<SEED_SIZE, NONCE_SIZE> for Mastic<T> {
type PrepareState = MasticPrepareState<T::Field>;
type PrepareShare = MasticPrepareShare<T::Field>;
type PrepareMessage = MasticPrepareMessage;
fn is_agg_param_valid(cur: &MasticAggregationParam, prev: &[MasticAggregationParam]) -> bool {
if cur.require_weight_check != prev.is_empty() {
return false;
};
if prev.is_empty() {
return true;
}
let cur_poplar_agg_param = &cur.level_and_prefixes;
let prev_poplar_agg_param = from_ref(&prev.last().as_ref().unwrap().level_and_prefixes);
Poplar1::<XofTurboShake128, SEED_SIZE>::is_agg_param_valid(
cur_poplar_agg_param,
prev_poplar_agg_param,
)
}
fn prepare_init(
&self,
verify_key: &[u8; SEED_SIZE],
ctx: &[u8],
agg_id: usize,
agg_param: &MasticAggregationParam,
nonce: &[u8; NONCE_SIZE],
public_share: &MasticPublicShare<VidpfWeight<T::Field>>,
input_share: &MasticInputShare<T::Field>,
) -> Result<(MasticPrepareState<T::Field>, MasticPrepareShare<T::Field>), VdafError> {
let id = match agg_id {
0 => Ok(VidpfServerId::S0),
1 => Ok(VidpfServerId::S1),
_ => Err(VdafError::Uncategorized(
"Invalid aggregator ID".to_string(),
)),
}?;
let prefixes = agg_param.level_and_prefixes.prefixes();
let mut prefix_tree = BinaryTree::default();
let out_shares = self.vidpf.eval_prefix_tree_with_siblings(
ctx,
id,
public_share,
&input_share.vidpf_key,
nonce,
prefixes,
&mut prefix_tree,
)?;
let root = prefix_tree.root.as_ref().unwrap();
let (onehot_check, payload_check) = {
let mut onehot_check_xof = XofTurboShake128::from_seed_slice(
&[],
&[&dst_usage(USAGE_ONEHOT_CHECK), &self.id, ctx],
);
let mut payload_check_xof = XofTurboShake128::from_seed_slice(
&[],
&[&dst_usage(USAGE_PAYLOAD_CHECK), &self.id, ctx],
);
let mut payload_check_buf = Vec::with_capacity(T::Field::ENCODED_SIZE);
let mut q = VecDeque::with_capacity(100);
q.push_back(root.left.as_ref().unwrap());
q.push_back(root.right.as_ref().unwrap());
while let Some(node) = q.pop_front() {
onehot_check_xof.update(&node.value.state.node_proof);
if let (Some(left), Some(right)) = (node.left.as_ref(), node.right.as_ref()) {
for (w, (w_left, w_right)) in node
.value
.share
.0
.iter()
.zip(left.value.share.0.iter().zip(right.value.share.0.iter()))
{
(*w - (*w_left + *w_right))
.encode(&mut payload_check_buf)
.unwrap();
payload_check_xof.update(&payload_check_buf);
payload_check_buf.clear();
}
q.push_back(left);
q.push_back(right);
}
}
let onehot_check = onehot_check_xof.into_seed().0;
let payload_check = payload_check_xof.into_seed().0;
(onehot_check, payload_check)
};
let counter_check = {
let c_left = &root.left.as_ref().unwrap().value.share.0[0];
let c_right = &root.right.as_ref().unwrap().value.share.0[0];
let mut c = *c_left + *c_right;
if id == VidpfServerId::S1 {
c += T::Field::one();
}
c.get_encoded().unwrap()
};
let vidpf_eval_proof = {
let mut vidpf_eval_proof = [0; VIDPF_PROOF_SIZE];
XofTurboShake128::seed_stream(
verify_key,
&[&dst_usage(USAGE_EVAL_PROOF), &self.id, ctx],
&[&onehot_check, &counter_check, &payload_check],
)
.fill(&mut vidpf_eval_proof);
vidpf_eval_proof
};
let mut truncated_out_shares =
Vec::with_capacity(self.szk.typ.output_len() * prefixes.len());
for VidpfWeight(mut out_share) in out_shares.into_iter() {
let mut truncated_out_share = self.szk.typ.truncate(out_share.drain(1..).collect())?;
truncated_out_shares.append(&mut out_share);
truncated_out_shares.append(&mut truncated_out_share);
}
Ok(if agg_param.require_weight_check {
let VidpfWeight(beta_share) =
self.vidpf
.get_beta_share(ctx, id, public_share, &input_share.vidpf_key, nonce)?;
let (szk_query_share, szk_query_state) = self.szk.query(
ctx,
agg_param
.level_and_prefixes
.level()
.try_into()
.map_err(|_| VdafError::Vidpf(VidpfError::InvalidInputLength))?,
&beta_share[1..],
&input_share.proof_share,
verify_key,
nonce,
)?;
let verifier_len = szk_query_share.flp_verifier.len();
(
MasticPrepareState {
output_shares: MasticOutputShare::from(truncated_out_shares),
szk_query_state,
verifier_len: Some(verifier_len),
},
MasticPrepareShare {
vidpf_eval_proof,
szk_query_share_opt: Some(szk_query_share),
},
)
} else {
(
MasticPrepareState {
output_shares: MasticOutputShare::from(truncated_out_shares),
szk_query_state: None,
verifier_len: None,
},
MasticPrepareShare {
vidpf_eval_proof,
szk_query_share_opt: None,
},
)
})
}
fn prepare_shares_to_prepare_message<M: IntoIterator<Item = MasticPrepareShare<T::Field>>>(
&self,
ctx: &[u8],
_agg_param: &MasticAggregationParam,
inputs: M,
) -> Result<MasticPrepareMessage, VdafError> {
let mut inputs_iter = inputs.into_iter();
let leader_share = inputs_iter.next().ok_or(VdafError::Uncategorized(
"No leader share received".to_string(),
))?;
let helper_share = inputs_iter.next().ok_or(VdafError::Uncategorized(
"No helper share received".to_string(),
))?;
if inputs_iter.next().is_some() {
return Err(VdafError::Uncategorized(
"Received more than two prepare shares".to_string(),
));
};
if leader_share.vidpf_eval_proof != helper_share.vidpf_eval_proof {
return Err(VdafError::Uncategorized(
"Vidpf proof verification failed".to_string(),
));
};
match (
leader_share.szk_query_share_opt,
helper_share.szk_query_share_opt,
) {
(Some(leader_query_share), Some(helper_query_share)) => Ok(self
.szk
.merge_query_shares(ctx, leader_query_share, helper_query_share)?),
(None, None) => Ok(SzkJointShare::default()),
(_, _) => Err(VdafError::Uncategorized(
"Only one of leader and helper query shares is present".to_string(),
)),
}
}
fn prepare_next(
&self,
_ctx: &[u8],
state: MasticPrepareState<T::Field>,
input: MasticPrepareMessage,
) -> Result<PrepareTransition<Self, SEED_SIZE, NONCE_SIZE>, VdafError> {
let MasticPrepareState {
output_shares,
szk_query_state,
verifier_len: _,
} = state;
self.szk.decide(szk_query_state, input)?;
Ok(PrepareTransition::Finish(output_shares))
}
fn aggregate_init(&self, agg_param: &Self::AggregationParam) -> Self::AggregateShare {
MasticAggregateShare::<T::Field>::from(vec![
T::Field::zero();
(1 + self.szk.typ.output_len())
* agg_param
.level_and_prefixes
.prefixes()
.len()
])
}
fn aggregate<M: IntoIterator<Item = MasticOutputShare<T::Field>>>(
&self,
agg_param: &MasticAggregationParam,
output_shares: M,
) -> Result<MasticAggregateShare<T::Field>, VdafError> {
let mut agg_share =
MasticAggregateShare::from(vec![T::Field::zero(); self.agg_share_len(agg_param)]);
for output_share in output_shares.into_iter() {
agg_share.accumulate(&output_share)?;
}
Ok(agg_share)
}
}
impl<T: Type> Collector for Mastic<T> {
fn unshard<M: IntoIterator<Item = Self::AggregateShare>>(
&self,
agg_param: &MasticAggregationParam,
agg_shares: M,
_num_measurements: usize,
) -> Result<Self::AggregateResult, VdafError> {
let num_prefixes = agg_param.level_and_prefixes.prefixes().len();
let AggregateShare(agg) = agg_shares.into_iter().try_fold(
AggregateShare(vec![T::Field::zero(); self.agg_share_len(agg_param)]),
|mut agg, agg_share| {
agg.merge(&agg_share)?;
Result::<_, VdafError>::Ok(agg)
},
)?;
let mut result = Vec::with_capacity(num_prefixes);
for agg_for_prefix in agg.chunks(1 + self.szk.typ.output_len()) {
let num_measurements = agg_for_prefix[0];
let num_measurements =
<T::Field as FieldElementWithInteger>::Integer::from(num_measurements);
let num_measurements: u64 = num_measurements.try_into().map_err(|e| {
VdafError::Uncategorized(format!("failed to convert num_measurements to u64: {e}"))
})?;
let num_measurements = usize::try_from(num_measurements).map_err(|e| {
VdafError::Uncategorized(format!(
"failed to convert num_measurements to usize: {e}"
))
})?;
let encoded_agg_result = &agg_for_prefix[1..];
result.push(
self.szk
.typ
.decode_result(encoded_agg_result, num_measurements)?,
);
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::{Field128, Field64};
use crate::flp::gadgets::{Mul, ParallelSum};
use crate::flp::types::{Count, Histogram, Sum, SumVec};
use crate::vdaf::test_utils::run_vdaf;
use rand::{rng, Rng};
const CTX_STR: &[u8] = b"mastic ctx";
#[test]
fn test_mastic_sum() {
let algorithm_id = 6;
let max_measurement = 29;
let sum_typ = Sum::<Field128>::new(max_measurement).unwrap();
let mastic = Mastic::new(algorithm_id, sum_typ, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let inputs = [
VidpfInput::from_bytes(&[240u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[112u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[48u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[32u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[0u8, 0u8, 1u8, 4u8][..]),
];
let three_prefixes = vec![VidpfInput::from_bools(&[false, false, true])];
let individual_prefixes = vec![
VidpfInput::from_bools(&[false]),
VidpfInput::from_bools(&[true]),
];
let first_agg_param = MasticAggregationParam::new(three_prefixes.clone(), true).unwrap();
let second_agg_param = MasticAggregationParam::new(individual_prefixes, true).unwrap();
let third_agg_param = MasticAggregationParam::new(three_prefixes, false).unwrap();
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&first_agg_param,
[
(inputs[0].clone(), 24),
(inputs[1].clone(), 0),
(inputs[2].clone(), 0),
(inputs[3].clone(), 3),
(inputs[4].clone(), 28)
]
)
.unwrap(),
vec![3]
);
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&second_agg_param,
[
(inputs[0].clone(), 24),
(inputs[1].clone(), 0),
(inputs[2].clone(), 0),
(inputs[3].clone(), 3),
(inputs[4].clone(), 28)
]
)
.unwrap(),
vec![31, 24]
);
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&third_agg_param,
[
(inputs[0].clone(), 24),
(inputs[1].clone(), 0),
(inputs[2].clone(), 0),
(inputs[3].clone(), 3),
(inputs[4].clone(), 28)
]
)
.unwrap(),
vec![3]
);
}
#[test]
fn test_input_share_encode_sum() {
let algorithm_id = 6;
let max_measurement = 29;
let sum_typ = Sum::<Field128>::new(max_measurement).unwrap();
let mastic = Mastic::new(algorithm_id, sum_typ, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (_, input_shares) = mastic
.shard(CTX_STR, &(first_input, 26u128), &nonce)
.unwrap();
let [leader_input_share, helper_input_share] = [&input_shares[0], &input_shares[1]];
assert_eq!(
leader_input_share.encoded_len().unwrap(),
leader_input_share.get_encoded().unwrap().len()
);
assert_eq!(
helper_input_share.encoded_len().unwrap(),
helper_input_share.get_encoded().unwrap().len()
);
}
#[test]
fn test_agg_param_roundtrip() {
let three_prefixes = vec![VidpfInput::from_bools(&[false, false, true])];
let individual_prefixes = vec![
VidpfInput::from_bools(&[false]),
VidpfInput::from_bools(&[true]),
];
let agg_params = [
MasticAggregationParam::new(three_prefixes.clone(), true).unwrap(),
MasticAggregationParam::new(individual_prefixes, true).unwrap(),
MasticAggregationParam::new(three_prefixes, false).unwrap(),
];
let encoded_agg_params = agg_params
.iter()
.map(|agg_param| agg_param.get_encoded().unwrap());
let decoded_agg_params = encoded_agg_params
.map(|encoded_ap| MasticAggregationParam::get_decoded(&encoded_ap).unwrap());
agg_params
.iter()
.zip(decoded_agg_params)
.for_each(|(agg_param, decoded_agg_param)| assert_eq!(*agg_param, decoded_agg_param));
}
#[test]
fn test_public_share_roundtrip_sum() {
let algorithm_id = 6;
let max_measurement = 29;
let sum_typ = Sum::<Field128>::new(max_measurement).unwrap();
let mastic = Mastic::new(algorithm_id, sum_typ, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (public, _) = mastic
.shard(CTX_STR, &(first_input, 4u128), &nonce)
.unwrap();
let encoded_public = public.get_encoded().unwrap();
let decoded_public =
MasticPublicShare::get_decoded_with_param(&mastic, &encoded_public[..]).unwrap();
assert_eq!(public, decoded_public);
}
#[test]
fn test_mastic_count() {
let algorithm_id = 6;
let count = Count::<Field128>::new();
let mastic = Mastic::new(algorithm_id, count, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let inputs = [
VidpfInput::from_bytes(&[240u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[112u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[48u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[32u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[0u8, 0u8, 1u8, 4u8][..]),
];
let three_prefixes = vec![VidpfInput::from_bools(&[false, false, true])];
let individual_prefixes = vec![
VidpfInput::from_bools(&[false]),
VidpfInput::from_bools(&[true]),
];
let first_agg_param = MasticAggregationParam::new(three_prefixes.clone(), true).unwrap();
let second_agg_param = MasticAggregationParam::new(individual_prefixes, true).unwrap();
let third_agg_param = MasticAggregationParam::new(three_prefixes, false).unwrap();
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&first_agg_param,
[
(inputs[0].clone(), true),
(inputs[1].clone(), false),
(inputs[2].clone(), false),
(inputs[3].clone(), true),
(inputs[4].clone(), true)
]
)
.unwrap(),
vec![1]
);
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&second_agg_param,
[
(inputs[0].clone(), true),
(inputs[1].clone(), false),
(inputs[2].clone(), false),
(inputs[3].clone(), true),
(inputs[4].clone(), true)
]
)
.unwrap(),
vec![2, 1]
);
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&third_agg_param,
[
(inputs[0].clone(), true),
(inputs[1].clone(), false),
(inputs[2].clone(), false),
(inputs[3].clone(), true),
(inputs[4].clone(), true)
]
)
.unwrap(),
vec![1]
);
}
#[test]
fn test_public_share_encoded_len() {
let algorithm_id = 6;
let count = Count::<Field64>::new();
let mastic = Mastic::new(algorithm_id, count, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (public, _) = mastic.shard(CTX_STR, &(first_input, true), &nonce).unwrap();
assert_eq!(
public.encoded_len().unwrap(),
public.get_encoded().unwrap().len()
);
}
#[test]
fn test_public_share_roundtrip_count() {
let algorithm_id = 6;
let count = Count::<Field64>::new();
let mastic = Mastic::new(algorithm_id, count, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (public, _) = mastic.shard(CTX_STR, &(first_input, true), &nonce).unwrap();
let encoded_public = public.get_encoded().unwrap();
let decoded_public =
MasticPublicShare::get_decoded_with_param(&mastic, &encoded_public[..]).unwrap();
assert_eq!(public, decoded_public);
}
#[test]
fn test_mastic_sumvec() {
let algorithm_id = 6;
let sumvec =
SumVec::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(5, 3, 3).unwrap();
let mastic = Mastic::new(algorithm_id, sumvec, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let inputs = [
VidpfInput::from_bytes(&[240u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[112u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[48u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[32u8, 0u8, 1u8, 4u8][..]),
VidpfInput::from_bytes(&[0u8, 0u8, 1u8, 4u8][..]),
];
let measurements = [
vec![1u128, 16u128, 0u128],
vec![0u128, 0u128, 0u128],
vec![0u128, 0u128, 0u128],
vec![1u128, 17u128, 31u128],
vec![6u128, 4u128, 11u128],
];
let three_prefixes = vec![VidpfInput::from_bools(&[false, false, true])];
let individual_prefixes = vec![
VidpfInput::from_bools(&[false]),
VidpfInput::from_bools(&[true]),
];
let first_agg_param = MasticAggregationParam::new(three_prefixes.clone(), true).unwrap();
let second_agg_param = MasticAggregationParam::new(individual_prefixes, true).unwrap();
let third_agg_param = MasticAggregationParam::new(three_prefixes, false).unwrap();
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&first_agg_param,
[
(inputs[0].clone(), measurements[0].clone()),
(inputs[1].clone(), measurements[1].clone()),
(inputs[2].clone(), measurements[2].clone()),
(inputs[3].clone(), measurements[3].clone()),
(inputs[4].clone(), measurements[4].clone()),
]
)
.unwrap(),
vec![vec![1, 17, 31]]
);
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&second_agg_param,
[
(inputs[0].clone(), measurements[0].clone()),
(inputs[1].clone(), measurements[1].clone()),
(inputs[2].clone(), measurements[2].clone()),
(inputs[3].clone(), measurements[3].clone()),
(inputs[4].clone(), measurements[4].clone()),
]
)
.unwrap(),
vec![vec![7, 21, 42], vec![1, 16, 0]]
);
assert_eq!(
run_vdaf(
CTX_STR,
&mastic,
&third_agg_param,
[
(inputs[0].clone(), measurements[0].clone()),
(inputs[1].clone(), measurements[1].clone()),
(inputs[2].clone(), measurements[2].clone()),
(inputs[3].clone(), measurements[3].clone()),
(inputs[4].clone(), measurements[4].clone()),
]
)
.unwrap(),
vec![vec![1, 17, 31]]
);
}
#[test]
fn test_input_share_encode_sumvec() {
let algorithm_id = 6;
let sumvec =
SumVec::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(5, 3, 3).unwrap();
let measurement = vec![1, 16, 0];
let mastic = Mastic::new(algorithm_id, sumvec, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (_public, input_shares) = mastic
.shard(CTX_STR, &(first_input, measurement), &nonce)
.unwrap();
let leader_input_share = &input_shares[0];
let helper_input_share = &input_shares[1];
assert_eq!(
leader_input_share.encoded_len().unwrap(),
leader_input_share.get_encoded().unwrap().len()
);
assert_eq!(
helper_input_share.encoded_len().unwrap(),
helper_input_share.get_encoded().unwrap().len()
);
}
#[test]
fn test_input_share_roundtrip_sumvec() {
let algorithm_id = 6;
let sumvec =
SumVec::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(5, 3, 3).unwrap();
let measurement = vec![1, 16, 0];
let mastic = Mastic::new(algorithm_id, sumvec, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (_public, input_shares) = mastic
.shard(CTX_STR, &(first_input, measurement), &nonce)
.unwrap();
let leader_input_share = &input_shares[0];
let helper_input_share = &input_shares[1];
let encoded_input_share = leader_input_share.get_encoded().unwrap();
let decoded_leader_input_share =
MasticInputShare::get_decoded_with_param(&(&mastic, 0), &encoded_input_share[..])
.unwrap();
assert_eq!(leader_input_share, &decoded_leader_input_share);
let encoded_input_share = helper_input_share.get_encoded().unwrap();
let decoded_helper_input_share =
MasticInputShare::get_decoded_with_param(&(&mastic, 1), &encoded_input_share[..])
.unwrap();
assert_eq!(helper_input_share, &decoded_helper_input_share);
}
#[test]
fn test_public_share_encode_sumvec() {
let algorithm_id = 6;
let sumvec =
SumVec::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(5, 3, 3).unwrap();
let measurement = vec![1, 16, 0];
let mastic = Mastic::new(algorithm_id, sumvec, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (public, _) = mastic
.shard(CTX_STR, &(first_input, measurement), &nonce)
.unwrap();
assert_eq!(
public.encoded_len().unwrap(),
public.get_encoded().unwrap().len()
);
}
#[test]
fn test_public_share_roundtrip_sumvec() {
let algorithm_id = 6;
let sumvec =
SumVec::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(5, 3, 3).unwrap();
let measurement = vec![1, 16, 0];
let mastic = Mastic::new(algorithm_id, sumvec, 32).unwrap();
let mut nonce = [0u8; 16];
rng().fill(&mut nonce[..]);
let first_input = VidpfInput::from_bytes(&[15u8, 0u8, 1u8, 4u8][..]);
let (public, _) = mastic
.shard(CTX_STR, &(first_input, measurement), &nonce)
.unwrap();
let encoded_public_share = public.get_encoded().unwrap();
let decoded_public_share =
MasticPublicShare::get_decoded_with_param(&mastic, &encoded_public_share[..]).unwrap();
assert_eq!(public, decoded_public_share);
}
mod prep_state {
use super::*;
fn test_prep_state_roundtrip<T: Type>(
typ: T,
weight: T::Measurement,
require_weight_check: bool,
) {
let mastic = Mastic::new(0, typ, 256).unwrap();
let ctx = b"some application";
let verify_key = [0u8; 32];
let nonce = [0u8; 16];
let alpha = VidpfInput::from_bools(&[false; 256][..]);
let (public_share, input_shares) =
mastic.shard(ctx, &(alpha.clone(), weight), &nonce).unwrap();
let agg_param = MasticAggregationParam::new(
vec![alpha, VidpfInput::from_bools(&[true; 256][..])],
require_weight_check,
)
.unwrap();
for agg_id in [0, 1] {
let (prep_state, _prep_share) = mastic
.prepare_init(
&verify_key,
ctx,
agg_id,
&agg_param,
&nonce,
&public_share,
&input_shares[agg_id],
)
.unwrap();
let encoded = prep_state.get_encoded().unwrap();
assert_eq!(Some(encoded.len()), prep_state.encoded_len());
assert_eq!(
MasticPrepareState::get_decoded_with_param(&(&mastic, &agg_param), &encoded)
.unwrap(),
prep_state
);
}
}
#[test]
fn without_joint_rand() {
test_prep_state_roundtrip(Count::<Field64>::new(), true, true);
}
#[test]
fn without_weight_check() {
let histogram: Histogram<Field128, ParallelSum<_, Mul<_>>> =
Histogram::new(10, 3).unwrap();
test_prep_state_roundtrip(histogram, 0, false);
}
#[test]
fn with_weight_check_and_joint_rand() {
let histogram: Histogram<Field128, ParallelSum<_, Mul<_>>> =
Histogram::new(10, 3).unwrap();
test_prep_state_roundtrip(histogram, 0, true);
}
}
mod test_vec {
use serde::Deserialize;
use std::collections::HashMap;
use super::*;
use crate::{
flp::{
types::{Histogram, MultihotCountVec},
Type,
},
idpf::IdpfInput,
};
#[derive(Debug, Deserialize)]
struct HexEncoded(#[serde(with = "hex")] Vec<u8>);
fn check_test_vec<T: Type>(
algorithm_id: u32,
new_typ: impl Fn(&HashMap<String, serde_json::Value>) -> T,
test_vec_str: &str,
) where
T::Measurement: for<'a> Deserialize<'a>,
T::AggregateResult: for<'a> Deserialize<'a> + PartialEq,
{
#[derive(Debug, Deserialize)]
struct TestVector<T>
where
T: Type,
T::Measurement: for<'a> Deserialize<'a>,
T::AggregateResult: for<'a> Deserialize<'a>,
{
agg_param: HexEncoded,
agg_result: Vec<T::AggregateResult>,
agg_shares: [HexEncoded; 2],
vidpf_bits: usize,
ctx: HexEncoded,
prep: Vec<PrepTestVector<T::Measurement>>,
#[serde(flatten)]
type_params: HashMap<String, serde_json::Value>,
shares: usize,
verify_key: HexEncoded,
}
#[derive(Debug, Deserialize)]
struct PrepTestVector<M> {
input_shares: [HexEncoded; 2],
measurement: (Vec<bool>, M),
nonce: HexEncoded,
out_shares: [Vec<HexEncoded>; 2],
prep_messages: [HexEncoded; 1],
prep_shares: [[HexEncoded; 2]; 1],
public_share: HexEncoded,
rand: HexEncoded,
}
let test_vec: TestVector<T> = serde_json::from_str(test_vec_str).unwrap();
let mastic = Mastic::new(
algorithm_id,
new_typ(&test_vec.type_params),
test_vec.vidpf_bits,
)
.unwrap();
let agg_param = MasticAggregationParam::get_decoded(&test_vec.agg_param.0).unwrap();
let verify_key = <[u8; 32]>::try_from(&test_vec.verify_key.0[..]).unwrap();
let HexEncoded(ctx) = &test_vec.ctx;
let mut out_shares_0 = Vec::new();
let mut out_shares_1 = Vec::new();
for prep in &test_vec.prep {
let measurement = (
IdpfInput::from_bools(&prep.measurement.0),
prep.measurement.1.clone(),
);
let nonce = <[u8; NONCE_SIZE]>::try_from(&prep.nonce.0[..]).unwrap();
let (vidpf_keys, szk_random, joint_random_opt) = {
let mut r = Cursor::new(prep.rand.0.as_ref());
let vidpf_keys = [Seed::decode(&mut r).unwrap(), Seed::decode(&mut r).unwrap()];
let szk_random = [Seed::decode(&mut r).unwrap(), Seed::decode(&mut r).unwrap()];
let joint_random_opt = mastic
.szk
.requires_joint_rand()
.then(|| Seed::decode(&mut r).unwrap());
(vidpf_keys, szk_random, joint_random_opt)
};
let (public_share, input_shares) = mastic
.shard_with_random(
ctx,
&measurement,
&nonce,
vidpf_keys,
szk_random,
joint_random_opt,
)
.unwrap();
{
let expected_public_share =
MasticPublicShare::get_decoded_with_param(&mastic, &prep.public_share.0)
.unwrap();
assert_eq!(public_share, expected_public_share);
assert_eq!(public_share.get_encoded().unwrap(), prep.public_share.0);
let expected_input_shares = prep
.input_shares
.iter()
.enumerate()
.map(|(agg_id, HexEncoded(bytes))| {
MasticInputShare::get_decoded_with_param(&(&mastic, agg_id), bytes)
.unwrap()
})
.collect::<Vec<_>>();
assert_eq!(input_shares, expected_input_shares);
assert_eq!(
input_shares[0].get_encoded().unwrap(),
prep.input_shares[0].0
);
assert_eq!(
input_shares[1].get_encoded().unwrap(),
prep.input_shares[1].0
);
}
let (prep_state_0, prep_share_0) = mastic
.prepare_init(
&verify_key,
ctx,
0,
&agg_param,
&nonce,
&public_share,
&input_shares[0],
)
.unwrap();
let (prep_state_1, prep_share_1) = mastic
.prepare_init(
&verify_key,
ctx,
1,
&agg_param,
&nonce,
&public_share,
&input_shares[1],
)
.unwrap();
{
let expected_prep_share_0 = MasticPrepareShare::get_decoded_with_param(
&prep_state_0,
&prep.prep_shares[0][0].0,
)
.unwrap();
assert_eq!(prep_share_0, expected_prep_share_0);
assert_eq!(
prep_share_0.get_encoded().unwrap(),
prep.prep_shares[0][0].0
);
let expected_prep_share_1 = MasticPrepareShare::get_decoded_with_param(
&prep_state_1,
&prep.prep_shares[0][1].0,
)
.unwrap();
assert_eq!(prep_share_1, expected_prep_share_1);
assert_eq!(
prep_share_1.get_encoded().unwrap(),
prep.prep_shares[0][1].0
);
}
let prep_msg = mastic
.prepare_shares_to_prepare_message(
ctx,
&agg_param,
[prep_share_0, prep_share_1],
)
.unwrap();
{
let expected_prep_msg = MasticPrepareMessage::get_decoded_with_param(
&prep_state_0,
&prep.prep_messages[0].0,
)
.unwrap();
assert_eq!(prep_msg, expected_prep_msg);
assert_eq!(prep_msg.get_encoded().unwrap(), prep.prep_messages[0].0);
}
let PrepareTransition::Finish(out_share_0) = mastic
.prepare_next(ctx, prep_state_0, prep_msg.clone())
.unwrap()
else {
panic!("unexpected transition");
};
let PrepareTransition::Finish(out_share_1) =
mastic.prepare_next(ctx, prep_state_1, prep_msg).unwrap()
else {
panic!("unexpected transition");
};
{
let expected_out_shares = prep
.out_shares
.iter()
.map(|out_share| {
MasticOutputShare::from(
out_share
.iter()
.map(|HexEncoded(bytes)| T::Field::get_decoded(bytes).unwrap())
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>();
assert_eq!(out_share_0, expected_out_shares[0]);
assert_eq!(out_share_1, expected_out_shares[1]);
}
out_shares_0.push(out_share_0);
out_shares_1.push(out_share_1);
}
let agg_share_0 = mastic.aggregate(&agg_param, out_shares_0).unwrap();
let agg_share_1 = mastic.aggregate(&agg_param, out_shares_1).unwrap();
{
let expected_agg_shares = test_vec
.agg_shares
.iter()
.map(|HexEncoded(bytes)| {
MasticAggregateShare::get_decoded_with_param(&(&mastic, &agg_param), bytes)
.unwrap()
})
.collect::<Vec<_>>();
assert_eq!(agg_share_0, expected_agg_shares[0]);
assert_eq!(agg_share_1, expected_agg_shares[1]);
}
let agg_result = mastic
.unshard(&agg_param, [agg_share_0, agg_share_1], test_vec.prep.len())
.unwrap();
assert_eq!(agg_result, test_vec.agg_result);
assert_eq!(test_vec.shares, 2);
}
#[test]
fn count_0() {
check_test_vec(
0xFFFF0001,
|_type_params| Count::<Field64>::new(),
include_str!("test_vec/mastic/04/MasticCount_0.json"),
);
}
#[test]
fn count_1() {
check_test_vec(
0xFFFF0001,
|_type_params| Count::<Field64>::new(),
include_str!("test_vec/mastic/04/MasticCount_1.json"),
);
}
#[test]
fn count_2() {
check_test_vec(
0xFFFF0001,
|_type_params| Count::<Field64>::new(),
include_str!("test_vec/mastic/04/MasticCount_2.json"),
);
}
#[test]
fn count_3() {
check_test_vec(
0xFFFF0001,
|_type_params| Count::<Field64>::new(),
include_str!("test_vec/mastic/04/MasticCount_3.json"),
);
}
#[test]
fn sum_0() {
check_test_vec(
0xFFFF0002,
|type_params| {
let max_measurement = type_params["max_measurement"].as_u64().unwrap();
Sum::<Field64>::new(max_measurement).unwrap()
},
include_str!("test_vec/mastic/04/MasticSum_0.json"),
);
}
#[test]
fn sum_1() {
check_test_vec(
0xFFFF0002,
|type_params| {
let max_measurement = type_params["max_measurement"].as_u64().unwrap();
Sum::<Field64>::new(max_measurement).unwrap()
},
include_str!("test_vec/mastic/04/MasticSum_1.json"),
);
}
#[test]
fn sum_vec_0() {
check_test_vec(
0xFFFF0003,
|type_params| {
let bits = type_params["bits"].as_u64().unwrap() as usize;
let length = type_params["length"].as_u64().unwrap() as usize;
let chunk_length = type_params["chunk_length"].as_u64().unwrap() as usize;
SumVec::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(
bits,
length,
chunk_length,
)
.unwrap()
},
include_str!("test_vec/mastic/04/MasticSumVec_0.json"),
);
}
#[test]
fn histogram_0() {
check_test_vec(
0xFFFF0004,
|type_params| {
let length = type_params["length"].as_u64().unwrap() as usize;
let chunk_length = type_params["chunk_length"].as_u64().unwrap() as usize;
Histogram::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(
length,
chunk_length,
)
.unwrap()
},
include_str!("test_vec/mastic/04/MasticHistogram_0.json"),
);
}
#[test]
fn multihot_count_vec_0() {
check_test_vec(
0xFFFF0005,
|type_params| {
let length = type_params["length"].as_u64().unwrap() as usize;
let max_weight = type_params["max_weight"].as_u64().unwrap() as usize;
let chunk_length = type_params["chunk_length"].as_u64().unwrap() as usize;
MultihotCountVec::<Field128, ParallelSum<Field128, Mul<Field128>>>::new(
length,
max_weight,
chunk_length,
)
.unwrap()
},
include_str!("test_vec/mastic/04/MasticMultihotCountVec_0.json"),
);
}
}
}