Skip to content

Commit 6bfc338

Browse files
committed
mastic: Let beta be the counter plus the encoded, truncated weigh
This required shifting the SZK abstraction boundary to include FLP input shares as well as the proof shares.
1 parent 1c217a0 commit 6bfc338

File tree

12 files changed

+1187
-1274
lines changed

12 files changed

+1187
-1274
lines changed

src/flp/szk.rs

Lines changed: 265 additions & 309 deletions
Large diffs are not rendered by default.

src/vdaf/mastic.rs

Lines changed: 83 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
codec::{CodecError, Decode, Encode, ParameterizedDecode},
1010
field::{decode_fieldvec, FieldElement, FieldElementWithInteger},
1111
flp::{
12-
szk::{Szk, SzkJointShare, SzkProofShare, SzkQueryShare, SzkQueryState},
12+
szk::{Szk, SzkInputShare, SzkJointShare, SzkQueryShare, SzkQueryState},
1313
Type,
1414
},
1515
vdaf::{
@@ -20,7 +20,7 @@ use crate::{
2020
},
2121
vidpf::{
2222
Vidpf, VidpfError, VidpfInput, VidpfKey, VidpfPublicShare, VidpfServerId, VidpfWeight,
23-
VIDPF_PROOF_SIZE,
23+
VIDPF_PROOF_SIZE, VIDPF_SEED_SIZE,
2424
},
2525
};
2626

@@ -35,17 +35,18 @@ use super::xof::XofTurboShake128;
3535
const NONCE_SIZE: usize = 16;
3636

3737
pub(crate) const USAGE_PROVE_RAND: u8 = 0;
38-
pub(crate) const USAGE_PROOF_SHARE: u8 = 1;
39-
pub(crate) const USAGE_QUERY_RAND: u8 = 2;
40-
pub(crate) const USAGE_JOINT_RAND_SEED: u8 = 3;
41-
pub(crate) const USAGE_JOINT_RAND_PART: u8 = 4;
42-
pub(crate) const USAGE_JOINT_RAND: u8 = 5;
43-
pub(crate) const USAGE_ONEHOT_CHECK: u8 = 6;
44-
pub(crate) const USAGE_PAYLOAD_CHECK: u8 = 7;
45-
pub(crate) const USAGE_EVAL_PROOF: u8 = 8;
46-
pub(crate) const USAGE_NODE_PROOF: u8 = 9;
47-
pub(crate) const USAGE_EXTEND: u8 = 10;
48-
pub(crate) const USAGE_CONVERT: u8 = 11;
38+
pub(crate) const USAGE_WEIGHT_SHARE: u8 = 1;
39+
pub(crate) const USAGE_PROOF_SHARE: u8 = 2;
40+
pub(crate) const USAGE_QUERY_RAND: u8 = 3;
41+
pub(crate) const USAGE_JOINT_RAND_SEED: u8 = 4;
42+
pub(crate) const USAGE_JOINT_RAND_PART: u8 = 5;
43+
pub(crate) const USAGE_JOINT_RAND: u8 = 6;
44+
pub(crate) const USAGE_ONEHOT_CHECK: u8 = 7;
45+
pub(crate) const USAGE_PAYLOAD_CHECK: u8 = 8;
46+
pub(crate) const USAGE_EVAL_PROOF: u8 = 9;
47+
pub(crate) const USAGE_NODE_PROOF: u8 = 10;
48+
pub(crate) const USAGE_EXTEND: u8 = 11;
49+
pub(crate) const USAGE_CONVERT: u8 = 12;
4950

5051
pub(crate) fn dst_usage(usage: u8) -> [u8; 8] {
5152
const VERSION: u8 = 0;
@@ -67,7 +68,7 @@ pub struct Mastic<T: Type> {
6768
impl<T: Type> Mastic<T> {
6869
/// Creates a new instance of Mastic, with a specific attribute length and weight type.
6970
pub fn new(algorithm_id: u32, typ: T, bits: usize) -> Result<Self, VdafError> {
70-
let vidpf = Vidpf::new(bits, typ.input_len() + 1)?;
71+
let vidpf = Vidpf::new(bits, 1 + typ.output_len())?;
7172
let szk = Szk::new(typ, algorithm_id);
7273
Ok(Self {
7374
id: algorithm_id.to_le_bytes(),
@@ -152,19 +153,19 @@ pub struct MasticInputShare<F: FieldElement> {
152153
/// VIDPF key share.
153154
vidpf_key: VidpfKey,
154155

155-
/// The proof share.
156-
proof_share: SzkProofShare<F>,
156+
/// SZK input share.
157+
szk_input_share: SzkInputShare<F>,
157158
}
158159

159160
impl<F: FieldElement> Encode for MasticInputShare<F> {
160161
fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> {
161162
bytes.extend_from_slice(&self.vidpf_key.0[..]);
162-
self.proof_share.encode(bytes)?;
163+
self.szk_input_share.encode(bytes)?;
163164
Ok(())
164165
}
165166

166167
fn encoded_len(&self) -> Option<usize> {
167-
Some(16 + self.proof_share.encoded_len()?)
168+
Some(VIDPF_SEED_SIZE + self.szk_input_share.encoded_len()?)
168169
}
169170
}
170171

@@ -177,17 +178,11 @@ impl<'a, T: Type> ParameterizedDecode<(&'a Mastic<T>, usize)> for MasticInputSha
177178
return Err(CodecError::UnexpectedValue);
178179
}
179180
let vidpf_key = VidpfKey::decode(bytes)?;
180-
let proof_share = SzkProofShare::decode_with_param(
181-
&(
182-
*agg_id == 0,
183-
mastic.szk.typ.proof_len(),
184-
mastic.szk.typ.joint_rand_len() != 0,
185-
),
186-
bytes,
187-
)?;
181+
let szk_input_share =
182+
SzkInputShare::decode_with_param(&(&mastic.szk, *agg_id == 0), bytes)?;
188183
Ok(Self {
189184
vidpf_key,
190-
proof_share,
185+
szk_input_share,
191186
})
192187
}
193188
}
@@ -202,7 +197,7 @@ impl<F: FieldElement> ConstantTimeEq for MasticInputShare<F> {
202197
fn ct_eq(&self, other: &MasticInputShare<F>) -> Choice {
203198
self.vidpf_key
204199
.ct_eq(&other.vidpf_key)
205-
.bitand(self.proof_share.ct_eq(&other.proof_share))
200+
.bitand(self.szk_input_share.ct_eq(&other.szk_input_share))
206201
}
207202
}
208203

@@ -270,47 +265,28 @@ impl<T: Type> Mastic<T> {
270265
}
271266

272267
// The output with which we program the VIDPF is a counter and the encoded measurement.
273-
let mut beta = VidpfWeight(self.szk.typ.encode_measurement(weight)?);
274-
beta.0.insert(0, T::Field::one());
268+
let encoded_weight = self.szk.typ.encode_measurement(weight)?;
269+
let mut beta = Vec::with_capacity(1 + self.szk.typ.output_len());
270+
beta.push(T::Field::one());
271+
beta.append(&mut self.szk.typ.truncate(encoded_weight.clone())?);
275272

276273
// Compute the measurement shares for each aggregator by generating VIDPF
277274
// keys for the measurement and evaluating each of them.
278-
let public_share = self
279-
.vidpf
280-
.gen_with_keys(ctx, &vidpf_keys, alpha, &beta, nonce)?;
275+
let public_share =
276+
self.vidpf
277+
.gen_with_keys(ctx, &vidpf_keys, alpha, &VidpfWeight(beta), nonce)?;
281278

282-
let leader_beta_share = self.vidpf.get_beta_share(
283-
ctx,
284-
VidpfServerId::S0,
285-
&public_share,
286-
&vidpf_keys[0],
287-
nonce,
288-
)?;
289-
let helper_beta_share = self.vidpf.get_beta_share(
290-
ctx,
291-
VidpfServerId::S1,
292-
&public_share,
293-
&vidpf_keys[1],
294-
nonce,
295-
)?;
296-
297-
let [leader_szk_proof_share, helper_szk_proof_share] = self.szk.prove(
298-
ctx,
299-
&leader_beta_share.as_ref()[1..],
300-
&helper_beta_share.as_ref()[1..],
301-
&beta.as_ref()[1..],
302-
szk_random,
303-
joint_random_opt,
304-
nonce,
305-
)?;
279+
let [leader_szk_input_share, helper_szk_input_share] =
280+
self.szk
281+
.prove(ctx, &encoded_weight, szk_random, joint_random_opt, nonce)?;
306282
let [leader_vidpf_key, helper_vidpf_key] = vidpf_keys;
307283
let leader_share = MasticInputShare {
308284
vidpf_key: leader_vidpf_key,
309-
proof_share: leader_szk_proof_share,
285+
szk_input_share: leader_szk_input_share,
310286
};
311287
let helper_share = MasticInputShare {
312288
vidpf_key: helper_vidpf_key,
313-
proof_share: helper_szk_proof_share,
289+
szk_input_share: helper_szk_input_share,
314290
};
315291
Ok((public_share, vec![leader_share, helper_share]))
316292
}
@@ -405,15 +381,15 @@ impl<'a, T: Type> ParameterizedDecode<(&'a Mastic<T>, &'a MasticAggregationParam
405381
#[derive(Clone, Debug, PartialEq)]
406382
pub struct MasticPrepareShare<F: FieldElement> {
407383
/// [`Vidpf`] evaluation proof, which guarantees one-hotness and payload consistency.
408-
eval_proof: [u8; VIDPF_PROOF_SIZE],
384+
vidpf_eval_proof: [u8; VIDPF_PROOF_SIZE],
409385

410386
/// If [`Szk`]` verification of the root weight is needed, a verification message.
411387
szk_query_share_opt: Option<SzkQueryShare<F>>,
412388
}
413389

414390
impl<F: FieldElement> Encode for MasticPrepareShare<F> {
415391
fn encode(&self, bytes: &mut Vec<u8>) -> Result<(), CodecError> {
416-
bytes.extend_from_slice(&self.eval_proof);
392+
bytes.extend_from_slice(&self.vidpf_eval_proof);
417393
match &self.szk_query_share_opt {
418394
Some(query_share) => query_share.encode(bytes),
419395
None => Ok(()),
@@ -436,8 +412,8 @@ impl<F: FieldElement> ParameterizedDecode<MasticPrepareState<F>> for MasticPrepa
436412
prep_state: &MasticPrepareState<F>,
437413
bytes: &mut Cursor<&[u8]>,
438414
) -> Result<Self, CodecError> {
439-
let mut eval_proof = [0; VIDPF_PROOF_SIZE];
440-
bytes.read_exact(&mut eval_proof[..])?;
415+
let mut vidpf_eval_proof = [0; VIDPF_PROOF_SIZE];
416+
bytes.read_exact(&mut vidpf_eval_proof[..])?;
441417
let requires_joint_rand = prep_state.szk_query_state.is_some();
442418
let szk_query_share_opt = prep_state
443419
.verifier_len
@@ -446,7 +422,7 @@ impl<F: FieldElement> ParameterizedDecode<MasticPrepareState<F>> for MasticPrepa
446422
})
447423
.transpose()?;
448424
Ok(Self {
449-
eval_proof,
425+
vidpf_eval_proof,
450426
szk_query_share_opt,
451427
})
452428
}
@@ -523,6 +499,32 @@ impl<T: Type> Aggregator<32, NONCE_SIZE> for Mastic<T> {
523499
&mut prefix_tree,
524500
)?;
525501

502+
// Set the weight of the root node to the weight we expect for each level of the three.
503+
{
504+
let encoded_weight_share = match input_share.szk_input_share {
505+
SzkInputShare::Leader {
506+
ref uncompressed_meas_share,
507+
..
508+
} => uncompressed_meas_share.clone(),
509+
SzkInputShare::Helper {
510+
ref share_seed_and_blind,
511+
..
512+
} => self.szk.derive_helper_meas_share(share_seed_and_blind, ctx),
513+
};
514+
515+
let mut beta_share = Vec::with_capacity(1 + self.szk.typ.output_len());
516+
beta_share.push(T::Field::zero());
517+
beta_share.append(&mut self.szk.typ.truncate(encoded_weight_share)?);
518+
if agg_id == 1 {
519+
beta_share[0] = T::Field::one();
520+
for x in beta_share.iter_mut() {
521+
*x = -*x;
522+
}
523+
}
524+
525+
prefix_tree.root.as_mut().unwrap().value.share = VidpfWeight(beta_share);
526+
}
527+
526528
let root = prefix_tree.root.as_ref().unwrap();
527529

528530
// Onehot and payload checks
@@ -539,11 +541,12 @@ impl<T: Type> Aggregator<32, NONCE_SIZE> for Mastic<T> {
539541

540542
// Traverse the prefix tree breadth-first.
541543
let mut q = VecDeque::with_capacity(100);
542-
q.push_back(root.left.as_ref().unwrap());
543-
q.push_back(root.right.as_ref().unwrap());
544+
q.push_back(root);
544545
while let Some(node) = q.pop_front() {
545546
// Update onehot proof.
546-
onehot_check_xof.update(&node.value.state.node_proof);
547+
if let Some(ref node_proof) = node.value.state.node_proof {
548+
onehot_check_xof.update(node_proof);
549+
}
547550

548551
// Update payload check.
549552
if let (Some(left), Some(right)) = (node.left.as_ref(), node.right.as_ref()) {
@@ -583,7 +586,7 @@ impl<T: Type> Aggregator<32, NONCE_SIZE> for Mastic<T> {
583586
c.get_encoded().unwrap()
584587
};
585588

586-
let eval_proof = {
589+
let vidpf_eval_proof = {
587590
let mut eval_proof_xof =
588591
XofTurboShake128::init(verify_key, &[&dst_usage(USAGE_EVAL_PROOF), &self.id, ctx]);
589592
eval_proof_xof.update(&onehot_check);
@@ -592,53 +595,48 @@ impl<T: Type> Aggregator<32, NONCE_SIZE> for Mastic<T> {
592595
eval_proof_xof.into_seed().0
593596
};
594597

595-
let mut truncated_out_shares =
596-
Vec::with_capacity(self.szk.typ.output_len() * prefixes.len());
597-
for VidpfWeight(mut out_share) in out_shares.into_iter() {
598-
let mut truncated_out_share = self.szk.typ.truncate(out_share.drain(1..).collect())?;
599-
truncated_out_shares.append(&mut out_share);
600-
truncated_out_shares.append(&mut truncated_out_share);
601-
}
598+
let output_shares = MasticOutputShare::from(
599+
out_shares
600+
.into_iter()
601+
.flat_map(|VidpfWeight(out_share)| out_share)
602+
.collect::<Vec<_>>(),
603+
);
602604

603605
Ok(if agg_param.require_weight_check {
604606
// Range check.
605-
let VidpfWeight(beta_share) =
606-
self.vidpf
607-
.get_beta_share(ctx, id, public_share, &input_share.vidpf_key, nonce)?;
608607
let (szk_query_share, szk_query_state) = self.szk.query(
609608
ctx,
610609
agg_param
611610
.level_and_prefixes
612611
.level()
613612
.try_into()
614613
.map_err(|_| VdafError::Vidpf(VidpfError::InvalidInputLength))?,
615-
&beta_share[1..],
616-
&input_share.proof_share,
614+
&input_share.szk_input_share,
617615
verify_key,
618616
nonce,
619617
)?;
620618

621619
let verifier_len = szk_query_share.flp_verifier.len();
622620
(
623621
MasticPrepareState {
624-
output_shares: MasticOutputShare::from(truncated_out_shares),
622+
output_shares,
625623
szk_query_state,
626624
verifier_len: Some(verifier_len),
627625
},
628626
MasticPrepareShare {
629-
eval_proof,
627+
vidpf_eval_proof,
630628
szk_query_share_opt: Some(szk_query_share),
631629
},
632630
)
633631
} else {
634632
(
635633
MasticPrepareState {
636-
output_shares: MasticOutputShare::from(truncated_out_shares),
634+
output_shares,
637635
szk_query_state: None,
638636
verifier_len: None,
639637
},
640638
MasticPrepareShare {
641-
eval_proof,
639+
vidpf_eval_proof,
642640
szk_query_share_opt: None,
643641
},
644642
)
@@ -663,7 +661,7 @@ impl<T: Type> Aggregator<32, NONCE_SIZE> for Mastic<T> {
663661
"Received more than two prepare shares".to_string(),
664662
));
665663
};
666-
if leader_share.eval_proof != helper_share.eval_proof {
664+
if leader_share.vidpf_eval_proof != helper_share.vidpf_eval_proof {
667665
return Err(VdafError::Uncategorized(
668666
"Vidpf proof verification failed".to_string(),
669667
));

src/vdaf/test_vec/mastic/04/MasticCount_0.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
1
66
],
77
"agg_shares": [
8-
"3e9d16bcf3364f0b855cecb7b6c54efdb5f87e0adad194cb9a4b489d3f726b60",
9-
"c362e9430bc9b0f47ca31348483ab1024d0781f5242e6b3468b4b762bf8d949f"
8+
"5af9ab8b843705149280f1ccca8f6fd27b2a8e20fffc93386561e426cb661abe",
9+
"a70654747ac8faeb6f7f0e333470902d87d571dfff026cc79d9e1bd93399e541"
1010
],
1111
"ctx": "736f6d65206170706c69636174696f6e",
1212
"prep": [
1313
{
1414
"input_shares": [
15-
"000102030405060708090a0b0c0d0e0f2712998769f26b6277b32c9f3a40c040110fbc523feaddcea2f553d5770bb55232e052d8e61616f7",
15+
"000102030405060708090a0b0c0d0e0f3a5ce17b0f2a658b62d021849c8078cc153daebc91baff47cafe696f8a727550eabecb1d9d526dbcb160fe0688ff228e",
1616
"101112131415161718191a1b1c1d1e1f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
1717
],
1818
"measurement": [
@@ -25,28 +25,28 @@
2525
"nonce": "000102030405060708090a0b0c0d0e0f",
2626
"out_shares": [
2727
[
28-
"3e9d16bcf3364f0b",
29-
"855cecb7b6c54efd",
30-
"b5f87e0adad194cb",
31-
"9a4b489d3f726b60"
28+
"5af9ab8b84370514",
29+
"9280f1ccca8f6fd2",
30+
"7b2a8e20fffc9338",
31+
"6561e426cb661abe"
3232
],
3333
[
34-
"c362e9430bc9b0f4",
35-
"7ca31348483ab102",
36-
"4d0781f5242e6b34",
37-
"68b4b762bf8d949f"
34+
"a70654747ac8faeb",
35+
"6f7f0e333470902d",
36+
"87d571dfff026cc7",
37+
"9d9e1bd93399e541"
3838
]
3939
],
4040
"prep_messages": [
4141
""
4242
],
4343
"prep_shares": [
4444
[
45-
"e5633260ecd70036482bbf2c93887bf4347c130f587247d63fe537250bb3c49382518600b8bd84150c9df3154968eb00fd6949610bbaa838f528e845e9b134bf",
46-
"e5633260ecd70036482bbf2c93887bf4347c130f587247d63fe537250bb3c4937fae79ff46427bea6814d366e7d3bd95781ac4b7ab90e6a99f6399ebb8914e6d"
45+
"dd752fe3b326ead90c6c15b9e88eb68d66bc6c3027ffe47cd30adc6a676768bc5844bbdc64f5c59673f8175353ab579a89d7292b5633f55789cefa7a75c23285",
46+
"dd752fe3b326ead90c6c15b9e88eb68d66bc6c3027ffe47cd30adc6a676768bca9bb44239a0a3a69f4717d9b89198300c1b3deaa8e061c1f41e575da38079978"
4747
]
4848
],
49-
"public_share": "0e30118b55bf77ff34817d80025c5c736b1ee2188425350e71cc1dc5a779de3e6ff0676312c362381937b2713cf2d65c6bfd5e3a8400fe1b3f71da0726d672f55f893f521233758b42d37212b7a0c5d64e450af652ace0e50986026a123de440e13432801b02eeda044017d8cc739b4f93ea6360a4887c1b1f4e688c4a6b02a111",
49+
"public_share": "069034cf44d5ccc95a21fe1c957e4cb148fa1e3550974a7e201b5836e2e3a63986e03b75ef8ad65e277199e80a0654e867d0fa4377137a8dafd9937992c6f5cac40610ec917a421217c881c9e01c372d5cf72aa01238da8c11d770bdc1a43eb88140d93fa618f9481f8b99db46e5b989a85713fa72c327c92f65754d1ee4269e40",
5050
"rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
5151
}
5252
],

0 commit comments

Comments
 (0)