diff --git a/Cargo.lock b/Cargo.lock index d5222a185..37f5ee2df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3505,9 +3505,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -3560,11 +3560,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -4193,9 +4192,9 @@ dependencies = [ [[package]] name = "prio" -version = "0.16.5" +version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d81f366140fb9dfdaf6f3be9b937bf598e5dbf8aafb9dbd182b1f3be196bc2" +checksum = "4f86ecfd264aa77241b69044a91e7627b7cfdf88fb771835a7663eeec4543f75" dependencies = [ "aes 0.8.4", "bitvec", @@ -5648,9 +5647,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" diff --git a/Cargo.toml b/Cargo.toml index e0e095034..08166a360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ postgres-types = "0.2.6" pretty_assertions = "1.4.0" # Disable default features so that individual workspace crates can choose to # re-enable them -prio = { version = "0.16.5", default-features = false, features = ["experimental"] } +prio = { version = "0.16.6", default-features = false, features = ["experimental"] } prometheus = "0.13.4" querystring = "1.1.0" quickcheck = { version = "1.0.3", default-features = false } diff --git a/aggregator/src/aggregator.rs b/aggregator/src/aggregator.rs index 50bcaab2f..9f0f50cbd 100644 --- a/aggregator/src/aggregator.rs +++ b/aggregator/src/aggregator.rs @@ -925,10 +925,15 @@ impl TaskAggregator { bits, length, chunk_length, + dp_strategy, } => { let vdaf = Prio3::new_sum_vec(2, *bits, *length, *chunk_length)?; let verify_key = task.vdaf_verify_key()?; - VdafOps::Prio3SumVec(Arc::new(vdaf), verify_key) + VdafOps::Prio3SumVec( + Arc::new(vdaf), + verify_key, + vdaf_ops_strategies::Prio3SumVec::from_vdaf_dp_strategy(dp_strategy.clone()), + ) } VdafInstance::Prio3SumVecField64MultiproofHmacSha256Aes128 { @@ -936,21 +941,31 @@ impl TaskAggregator { bits, length, chunk_length, + dp_strategy, } => { let vdaf = new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128::< ParallelSum>, >(*proofs, *bits, *length, *chunk_length)?; let verify_key = task.vdaf_verify_key()?; - VdafOps::Prio3SumVecField64MultiproofHmacSha256Aes128(Arc::new(vdaf), verify_key) + VdafOps::Prio3SumVecField64MultiproofHmacSha256Aes128( + Arc::new(vdaf), + verify_key, + vdaf_ops_strategies::Prio3SumVec::from_vdaf_dp_strategy(dp_strategy.clone()), + ) } VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy, } => { let vdaf = Prio3::new_histogram(2, *length, *chunk_length)?; let verify_key = task.vdaf_verify_key()?; - VdafOps::Prio3Histogram(Arc::new(vdaf), verify_key) + VdafOps::Prio3Histogram( + Arc::new(vdaf), + verify_key, + vdaf_ops_strategies::Prio3Histogram::from_vdaf_dp_strategy(dp_strategy.clone()), + ) } #[cfg(feature = "fpvec_bounded_l2")] @@ -958,22 +973,32 @@ impl TaskAggregator { bitsize, dp_strategy, length, - } => { - match bitsize { - Prio3FixedPointBoundedL2VecSumBitSize::BitSize16 => { - let vdaf: Prio3FixedPointBoundedL2VecSum> = - Prio3::new_fixedpoint_boundedl2_vec_sum(2, *length)?; - let verify_key = task.vdaf_verify_key()?; - VdafOps::Prio3FixedPoint16BitBoundedL2VecSum(Arc::new(vdaf), verify_key, vdaf_ops_strategies::Prio3FixedPointBoundedL2VecSum::from_vdaf_dp_strategy(dp_strategy.clone())) - } - Prio3FixedPointBoundedL2VecSumBitSize::BitSize32 => { - let vdaf: Prio3FixedPointBoundedL2VecSum> = - Prio3::new_fixedpoint_boundedl2_vec_sum(2, *length)?; - let verify_key = task.vdaf_verify_key()?; - VdafOps::Prio3FixedPoint32BitBoundedL2VecSum(Arc::new(vdaf), verify_key, vdaf_ops_strategies::Prio3FixedPointBoundedL2VecSum::from_vdaf_dp_strategy(dp_strategy.clone())) - } + } => match bitsize { + Prio3FixedPointBoundedL2VecSumBitSize::BitSize16 => { + let vdaf: Prio3FixedPointBoundedL2VecSum> = + Prio3::new_fixedpoint_boundedl2_vec_sum(2, *length)?; + let verify_key = task.vdaf_verify_key()?; + VdafOps::Prio3FixedPoint16BitBoundedL2VecSum( + Arc::new(vdaf), + verify_key, + vdaf_ops_strategies::Prio3FixedPointBoundedL2VecSum::from_vdaf_dp_strategy( + dp_strategy.clone(), + ), + ) } - } + Prio3FixedPointBoundedL2VecSumBitSize::BitSize32 => { + let vdaf: Prio3FixedPointBoundedL2VecSum> = + Prio3::new_fixedpoint_boundedl2_vec_sum(2, *length)?; + let verify_key = task.vdaf_verify_key()?; + VdafOps::Prio3FixedPoint32BitBoundedL2VecSum( + Arc::new(vdaf), + verify_key, + vdaf_ops_strategies::Prio3FixedPointBoundedL2VecSum::from_vdaf_dp_strategy( + dp_strategy.clone(), + ), + ) + } + }, VdafInstance::Poplar1 { bits } => { let vdaf = Poplar1::new_turboshake128(*bits); @@ -1164,19 +1189,60 @@ impl TaskAggregator { } } -#[cfg(feature = "fpvec_bounded_l2")] mod vdaf_ops_strategies { use std::sync::Arc; use janus_core::vdaf::vdaf_dp_strategies; + use prio::dp::distributions::PureDpDiscreteLaplace; + #[cfg(feature = "fpvec_bounded_l2")] use prio::dp::distributions::ZCdpDiscreteGaussian; + #[derive(Debug)] + pub enum Prio3Histogram { + NoDifferentialPrivacy, + PureDpDiscreteLaplace(Arc), + } + + impl Prio3Histogram { + pub fn from_vdaf_dp_strategy(dp_strategy: vdaf_dp_strategies::Prio3Histogram) -> Self { + match dp_strategy { + vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy => { + Prio3Histogram::NoDifferentialPrivacy + } + vdaf_dp_strategies::Prio3Histogram::PureDpDiscreteLaplace(s) => { + Prio3Histogram::PureDpDiscreteLaplace(Arc::new(s)) + } + } + } + } + + #[derive(Debug)] + pub enum Prio3SumVec { + NoDifferentialPrivacy, + PureDpDiscreteLaplace(Arc), + } + + impl Prio3SumVec { + pub fn from_vdaf_dp_strategy(dp_strategy: vdaf_dp_strategies::Prio3SumVec) -> Self { + match dp_strategy { + vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy => { + Prio3SumVec::NoDifferentialPrivacy + } + vdaf_dp_strategies::Prio3SumVec::PureDpDiscreteLaplace(s) => { + Prio3SumVec::PureDpDiscreteLaplace(Arc::new(s)) + } + } + } + } + + #[cfg(feature = "fpvec_bounded_l2")] #[derive(Debug)] pub enum Prio3FixedPointBoundedL2VecSum { NoDifferentialPrivacy, ZCdpDiscreteGaussian(Arc), } + #[cfg(feature = "fpvec_bounded_l2")] impl Prio3FixedPointBoundedL2VecSum { pub fn from_vdaf_dp_strategy( dp_strategy: vdaf_dp_strategies::Prio3FixedPointBoundedL2VecSum, @@ -1199,12 +1265,21 @@ mod vdaf_ops_strategies { enum VdafOps { Prio3Count(Arc, VerifyKey), Prio3Sum(Arc, VerifyKey), - Prio3SumVec(Arc, VerifyKey), + Prio3SumVec( + Arc, + VerifyKey, + vdaf_ops_strategies::Prio3SumVec, + ), Prio3SumVecField64MultiproofHmacSha256Aes128( Arc>>>, VerifyKey<32>, + vdaf_ops_strategies::Prio3SumVec, + ), + Prio3Histogram( + Arc, + VerifyKey, + vdaf_ops_strategies::Prio3Histogram, ), - Prio3Histogram(Arc, VerifyKey), #[cfg(feature = "fpvec_bounded_l2")] Prio3FixedPoint16BitBoundedL2VecSum( Arc>>, @@ -1255,18 +1330,28 @@ macro_rules! vdaf_ops_dispatch { body } - crate::aggregator::VdafOps::Prio3SumVec(vdaf, verify_key) => { + crate::aggregator::VdafOps::Prio3SumVec(vdaf, verify_key, _dp_strategy) => { let $vdaf = vdaf; let $verify_key = verify_key; type $Vdaf = ::prio::vdaf::prio3::Prio3SumVec; const $VERIFY_KEY_LENGTH: usize = ::janus_core::vdaf::VERIFY_KEY_LENGTH; - type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; - let $dp_strategy = &Arc::new(janus_core::dp::NoDifferentialPrivacy); - let body = $body; - body + match _dp_strategy { + vdaf_ops_strategies::Prio3SumVec::NoDifferentialPrivacy => { + type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; + let $dp_strategy = &Arc::new(janus_core::dp::NoDifferentialPrivacy); + let body = $body; + body + } + vdaf_ops_strategies::Prio3SumVec::PureDpDiscreteLaplace(_strategy) => { + type $DpStrategy = ::prio::dp::distributions::PureDpDiscreteLaplace; + let $dp_strategy = &_strategy; + let body = $body; + body + } + } } - crate::aggregator::VdafOps::Prio3SumVecField64MultiproofHmacSha256Aes128(vdaf, verify_key) => { + crate::aggregator::VdafOps::Prio3SumVecField64MultiproofHmacSha256Aes128(vdaf, verify_key, _dp_strategy) => { let $vdaf = vdaf; let $verify_key = verify_key; type $Vdaf = ::janus_core::vdaf::Prio3SumVecField64MultiproofHmacSha256Aes128< @@ -1276,21 +1361,41 @@ macro_rules! vdaf_ops_dispatch { >, >; const $VERIFY_KEY_LENGTH: usize = 32; - type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; - let $dp_strategy = &Arc::new(janus_core::dp::NoDifferentialPrivacy); - let body = $body; - body + match _dp_strategy { + vdaf_ops_strategies::Prio3SumVec::NoDifferentialPrivacy => { + type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; + let $dp_strategy = &Arc::new(janus_core::dp::NoDifferentialPrivacy); + let body = $body; + body + } + vdaf_ops_strategies::Prio3SumVec::PureDpDiscreteLaplace(_strategy) => { + type $DpStrategy = ::prio::dp::distributions::PureDpDiscreteLaplace; + let $dp_strategy = &_strategy; + let body = $body; + body + } + } } - crate::aggregator::VdafOps::Prio3Histogram(vdaf, verify_key) => { + crate::aggregator::VdafOps::Prio3Histogram(vdaf, verify_key, _dp_strategy) => { let $vdaf = vdaf; let $verify_key = verify_key; type $Vdaf = ::prio::vdaf::prio3::Prio3Histogram; const $VERIFY_KEY_LENGTH: usize = ::janus_core::vdaf::VERIFY_KEY_LENGTH; - type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; - let $dp_strategy = &Arc::new(janus_core::dp::NoDifferentialPrivacy); - let body = $body; - body + match _dp_strategy { + vdaf_ops_strategies::Prio3Histogram::NoDifferentialPrivacy => { + type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; + let $dp_strategy = &Arc::new(janus_core::dp::NoDifferentialPrivacy); + let body = $body; + body + } + vdaf_ops_strategies::Prio3Histogram::PureDpDiscreteLaplace(_strategy) => { + type $DpStrategy = ::prio::dp::distributions::PureDpDiscreteLaplace; + let $dp_strategy = &_strategy; + let body = $body; + body + } + } } #[cfg(feature = "fpvec_bounded_l2")] diff --git a/aggregator/src/aggregator/aggregation_job_creator.rs b/aggregator/src/aggregator/aggregation_job_creator.rs index f392fdb22..efe8aa6b2 100644 --- a/aggregator/src/aggregator/aggregation_job_creator.rs +++ b/aggregator/src/aggregator/aggregation_job_creator.rs @@ -321,6 +321,7 @@ impl AggregationJobCreator { bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Arc::new(Prio3::new_sum_vec(2, *bits, *length, *chunk_length)?); @@ -335,6 +336,7 @@ impl AggregationJobCreator { bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Arc::new(new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128::< @@ -351,6 +353,7 @@ impl AggregationJobCreator { VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Arc::new(Prio3::new_histogram(2, *length, *chunk_length)?); @@ -431,6 +434,7 @@ impl AggregationJobCreator { bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Arc::new(Prio3::new_sum_vec(2, *bits, *length, *chunk_length)?); @@ -452,6 +456,7 @@ impl AggregationJobCreator { bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Arc::new(new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128::< @@ -473,6 +478,7 @@ impl AggregationJobCreator { VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Arc::new(Prio3::new_histogram(2, *length, *chunk_length)?); diff --git a/aggregator/src/aggregator/aggregation_job_writer.rs b/aggregator/src/aggregator/aggregation_job_writer.rs index 14192c3e9..454562996 100644 --- a/aggregator/src/aggregator/aggregation_job_writer.rs +++ b/aggregator/src/aggregator/aggregation_job_writer.rs @@ -699,6 +699,7 @@ where bits, length, chunk_length: _, + dp_strategy: _, } => { metrics.aggregated_report_share_dimension_histogram.record( u64::try_from(*bits) @@ -715,6 +716,7 @@ where bits, length, chunk_length: _, + dp_strategy: _, } => { metrics.aggregated_report_share_dimension_histogram.record( u64::try_from(*bits) @@ -732,6 +734,7 @@ where Prio3Histogram { length, chunk_length: _, + dp_strategy: _, } => { metrics.aggregated_report_share_dimension_histogram.record( u64::try_from(*length).unwrap_or(u64::MAX), diff --git a/aggregator/src/binaries/janus_cli.rs b/aggregator/src/binaries/janus_cli.rs index 585c76002..48bb71d0e 100644 --- a/aggregator/src/binaries/janus_cli.rs +++ b/aggregator/src/binaries/janus_cli.rs @@ -759,7 +759,7 @@ mod tests { hpke::HpkeKeypair, test_util::{kubernetes, roundtrip_encoding}, time::RealClock, - vdaf::VdafInstance, + vdaf::{vdaf_dp_strategies, VdafInstance}, }; use janus_messages::{ codec::Encode, Duration, HpkeAeadId, HpkeConfig, HpkeConfigId, HpkeKdfId, HpkeKemId, Role, @@ -1295,6 +1295,7 @@ mod tests { bits: 1, length: 4, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, ) .with_id(*tasks[0].id()) diff --git a/aggregator_api/src/routes.rs b/aggregator_api/src/routes.rs index 4f28a10bf..6515cefd7 100644 --- a/aggregator_api/src/routes.rs +++ b/aggregator_api/src/routes.rs @@ -45,7 +45,12 @@ pub(super) async fn get_config( SupportedQueryType::TimeInterval, SupportedQueryType::FixedSize, ], - features: &["TokenHash", "UploadMetrics", "TimeBucketedFixedSize"], + features: &[ + "TokenHash", + "UploadMetrics", + "TimeBucketedFixedSize", + "PureDpDiscreteLaplace", + ], }) } diff --git a/aggregator_api/src/tests.rs b/aggregator_api/src/tests.rs index 09eb23f11..ed4827080 100644 --- a/aggregator_api/src/tests.rs +++ b/aggregator_api/src/tests.rs @@ -26,7 +26,7 @@ use janus_core::{ hpke::{HpkeKeypair, HpkePrivateKey}, test_util::install_test_trace_subscriber, time::MockClock, - vdaf::VdafInstance, + vdaf::{vdaf_dp_strategies, VdafInstance}, }; use janus_messages::{ Duration, HpkeAeadId, HpkeConfig, HpkeConfigId, HpkeKdfId, HpkeKemId, HpkePublicKey, Role, @@ -76,7 +76,7 @@ async fn get_config() { r#"{"protocol":"DAP-09","dap_url":"https://dap.url/","role":"Either","vdafs":"#, r#"["Prio3Count","Prio3Sum","Prio3Histogram","Prio3SumVec"],"#, r#""query_types":["TimeInterval","FixedSize"],"#, - r#""features":["TokenHash","UploadMetrics","TimeBucketedFixedSize"]}"#, + r#""features":["TokenHash","UploadMetrics","TimeBucketedFixedSize","PureDpDiscreteLaplace"]}"#, ) ); } @@ -1575,6 +1575,7 @@ fn post_task_req_serialization() { bits: 1, length: 5, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, role: Role::Helper, vdaf_verify_key: "encoded".to_owned(), @@ -1615,7 +1616,7 @@ fn post_task_req_serialization() { Token::StructVariant { name: "VdafInstance", variant: "Prio3SumVec", - len: 3, + len: 4, }, Token::Str("bits"), Token::U64(1), @@ -1623,6 +1624,14 @@ fn post_task_req_serialization() { Token::U64(5), Token::Str("chunk_length"), Token::U64(2), + Token::Str("dp_strategy"), + Token::Struct { + name: "Prio3SumVec", + len: 1, + }, + Token::Str("dp_strategy"), + Token::Str("NoDifferentialPrivacy"), + Token::StructEnd, Token::StructVariantEnd, Token::Str("role"), Token::UnitVariant { @@ -1688,6 +1697,7 @@ fn post_task_req_serialization() { bits: 1, length: 5, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, role: Role::Leader, vdaf_verify_key: "encoded".to_owned(), @@ -1732,7 +1742,7 @@ fn post_task_req_serialization() { Token::StructVariant { name: "VdafInstance", variant: "Prio3SumVec", - len: 3, + len: 4, }, Token::Str("bits"), Token::U64(1), @@ -1740,6 +1750,14 @@ fn post_task_req_serialization() { Token::U64(5), Token::Str("chunk_length"), Token::U64(2), + Token::Str("dp_strategy"), + Token::Struct { + name: "Prio3SumVec", + len: 1, + }, + Token::Str("dp_strategy"), + Token::Str("NoDifferentialPrivacy"), + Token::StructEnd, Token::StructVariantEnd, Token::Str("role"), Token::UnitVariant { @@ -1833,6 +1851,7 @@ fn task_resp_serialization() { bits: 1, length: 5, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, SecretBytes::new(b"vdaf verify key!".to_vec()), 1, @@ -1897,7 +1916,7 @@ fn task_resp_serialization() { Token::StructVariant { name: "VdafInstance", variant: "Prio3SumVec", - len: 3, + len: 4, }, Token::Str("bits"), Token::U64(1), @@ -1905,6 +1924,14 @@ fn task_resp_serialization() { Token::U64(5), Token::Str("chunk_length"), Token::U64(2), + Token::Str("dp_strategy"), + Token::Struct { + name: "Prio3SumVec", + len: 1, + }, + Token::Str("dp_strategy"), + Token::Str("NoDifferentialPrivacy"), + Token::StructEnd, Token::StructVariantEnd, Token::Str("role"), Token::UnitVariant { diff --git a/aggregator_core/src/datastore/tests.rs b/aggregator_core/src/datastore/tests.rs index ffd081c18..354873490 100644 --- a/aggregator_core/src/datastore/tests.rs +++ b/aggregator_core/src/datastore/tests.rs @@ -28,7 +28,7 @@ use janus_core::{ hpke::{self, HpkeApplicationInfo, Label}, test_util::{install_test_trace_subscriber, run_vdaf}, time::{Clock, DurationExt, IntervalExt, MockClock, TimeExt}, - vdaf::{VdafInstance, VERIFY_KEY_LENGTH}, + vdaf::{vdaf_dp_strategies, VdafInstance, VERIFY_KEY_LENGTH}, }; use janus_messages::{ query_type::{FixedSize, QueryType, TimeInterval}, @@ -39,6 +39,9 @@ use janus_messages::{ }; use prio::{ codec::{Decode, Encode}, + dp::{ + distributions::PureDpDiscreteLaplace, DifferentialPrivacyStrategy, PureDpBudget, Rational, + }, idpf::IdpfInput, topology::ping_pong::PingPongMessage, vdaf::{ @@ -153,6 +156,20 @@ async fn roundtrip_task(ephemeral_datastore: EphemeralDatastore) { bits: 1, length: 8, chunk_length: 3, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, + }, + Role::Leader, + ), + ( + VdafInstance::Prio3SumVec { + bits: 1, + length: 8, + chunk_length: 3, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::PureDpDiscreteLaplace( + PureDpDiscreteLaplace::from_budget( + PureDpBudget::new(Rational::from_unsigned(1u128, 4u128).unwrap()).unwrap(), + ), + ), }, Role::Leader, ), @@ -161,6 +178,7 @@ async fn roundtrip_task(ephemeral_datastore: EphemeralDatastore) { bits: 1, length: 64, chunk_length: 10, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, Role::Helper, ), @@ -170,6 +188,7 @@ async fn roundtrip_task(ephemeral_datastore: EphemeralDatastore) { VdafInstance::Prio3Histogram { length: 4, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, Role::Leader, ), @@ -177,6 +196,7 @@ async fn roundtrip_task(ephemeral_datastore: EphemeralDatastore) { VdafInstance::Prio3Histogram { length: 5, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, Role::Leader, ), diff --git a/aggregator_core/src/task.rs b/aggregator_core/src/task.rs index 9cbe5466a..0af1f4dbd 100644 --- a/aggregator_core/src/task.rs +++ b/aggregator_core/src/task.rs @@ -1333,6 +1333,7 @@ mod tests { hpke::{HpkeKeypair, HpkePrivateKey}, test_util::roundtrip_encoding, time::DurationExt, + vdaf::vdaf_dp_strategies, }; use janus_messages::{ Duration, HpkeAeadId, HpkeConfig, HpkeConfigId, HpkeKdfId, HpkeKemId, HpkePublicKey, TaskId, @@ -1644,6 +1645,7 @@ mod tests { bits: 1, length: 8, chunk_length: 3, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, SecretBytes::new(b"1234567812345678".to_vec()), 1, @@ -1705,7 +1707,7 @@ mod tests { Token::StructVariant { name: "VdafInstance", variant: "Prio3SumVec", - len: 3, + len: 4, }, Token::Str("bits"), Token::U64(1), @@ -1713,6 +1715,14 @@ mod tests { Token::U64(8), Token::Str("chunk_length"), Token::U64(3), + Token::Str("dp_strategy"), + Token::Struct { + name: "Prio3SumVec", + len: 1, + }, + Token::Str("dp_strategy"), + Token::Str("NoDifferentialPrivacy"), + Token::StructEnd, Token::StructVariantEnd, Token::Str("role"), Token::UnitVariant { diff --git a/core/src/vdaf.rs b/core/src/vdaf.rs index 2881fdbe2..4521d6b6d 100644 --- a/core/src/vdaf.rs +++ b/core/src/vdaf.rs @@ -34,20 +34,50 @@ pub enum Prio3FixedPointBoundedL2VecSumBitSize { /// Contains dedicated enums which describe the differential privacy strategies /// of a given VDAF. If a VDAF only supports a single strategy, such as for example /// `NoDifferentialPrivacy`, then no enum is required. -#[cfg(feature = "fpvec_bounded_l2")] pub mod vdaf_dp_strategies { - use derivative::Derivative; + use prio::dp::distributions::PureDpDiscreteLaplace; + #[cfg(feature = "fpvec_bounded_l2")] use prio::dp::distributions::ZCdpDiscreteGaussian; use serde::{Deserialize, Serialize}; + /// Differential privacy strategies supported by `Prio3Histogram`. + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[serde(tag = "dp_strategy")] + pub enum Prio3Histogram { + NoDifferentialPrivacy, + PureDpDiscreteLaplace(PureDpDiscreteLaplace), + } + + impl Default for Prio3Histogram { + fn default() -> Self { + Self::NoDifferentialPrivacy + } + } + + /// Differential privacy strategies supported by `Prio3SumVec`. + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[serde(tag = "dp_strategy")] + pub enum Prio3SumVec { + NoDifferentialPrivacy, + PureDpDiscreteLaplace(PureDpDiscreteLaplace), + } + + impl Default for Prio3SumVec { + fn default() -> Self { + Self::NoDifferentialPrivacy + } + } + /// Differential privacy strategies supported by `Prio3FixedPointBoundedL2VecSum`. - #[derive(Debug, Derivative, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg(feature = "fpvec_bounded_l2")] + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(tag = "dp_strategy")] pub enum Prio3FixedPointBoundedL2VecSum { NoDifferentialPrivacy, ZCdpDiscreteGaussian(ZCdpDiscreteGaussian), } + #[cfg(feature = "fpvec_bounded_l2")] impl Default for Prio3FixedPointBoundedL2VecSum { fn default() -> Self { Self::NoDifferentialPrivacy @@ -72,6 +102,8 @@ pub enum VdafInstance { bits: usize, length: usize, chunk_length: usize, + #[serde(default)] + dp_strategy: vdaf_dp_strategies::Prio3SumVec, }, /// Prio3SumVec with additional customizations: a smaller field, multiple proofs, and a /// different XOF. @@ -80,9 +112,16 @@ pub enum VdafInstance { bits: usize, length: usize, chunk_length: usize, + #[serde(default)] + dp_strategy: vdaf_dp_strategies::Prio3SumVec, }, /// A `Prio3` histogram with `length` buckets in it. - Prio3Histogram { length: usize, chunk_length: usize }, + Prio3Histogram { + length: usize, + chunk_length: usize, + #[serde(default)] + dp_strategy: vdaf_dp_strategies::Prio3Histogram, + }, /// A `Prio3` fixed point vector sum with bounded L2 norm. #[cfg(feature = "fpvec_bounded_l2")] Prio3FixedPointBoundedL2VecSum { @@ -143,6 +182,7 @@ impl TryFrom<&taskprov::VdafType> for VdafInstance { bits: *bits as usize, length: *length as usize, chunk_length: *chunk_length as usize, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }), taskprov::VdafType::Prio3SumVecField64MultiproofHmacSha256Aes128 { bits, @@ -154,6 +194,7 @@ impl TryFrom<&taskprov::VdafType> for VdafInstance { bits: *bits as usize, length: *length as usize, chunk_length: *chunk_length as usize, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }), taskprov::VdafType::Prio3Histogram { length, @@ -161,6 +202,7 @@ impl TryFrom<&taskprov::VdafType> for VdafInstance { } => Ok(Self::Prio3Histogram { length: *length as usize, chunk_length: *chunk_length as usize, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }), taskprov::VdafType::Poplar1 { bits } => Ok(Self::Poplar1 { bits: *bits as usize, @@ -223,14 +265,26 @@ macro_rules! vdaf_dispatch_impl_base { bits, length, chunk_length, + dp_strategy, } => { let $vdaf = ::prio::vdaf::prio3::Prio3::new_sum_vec(2, *bits, *length, *chunk_length)?; type $Vdaf = ::prio::vdaf::prio3::Prio3SumVec; const $VERIFY_KEY_LEN: usize = ::janus_core::vdaf::VERIFY_KEY_LENGTH; - type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; - let $dp_strategy = janus_core::dp::NoDifferentialPrivacy; - $body + match dp_strategy.clone() { + ::janus_core::vdaf::vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy => { + type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; + let $dp_strategy = janus_core::dp::NoDifferentialPrivacy; + $body + } + ::janus_core::vdaf::vdaf_dp_strategies::Prio3SumVec::PureDpDiscreteLaplace( + _strategy, + ) => { + type $DpStrategy = ::prio::dp::distributions::PureDpDiscreteLaplace; + let $dp_strategy = _strategy; + $body + } + } } ::janus_core::vdaf::VdafInstance::Prio3SumVecField64MultiproofHmacSha256Aes128 { @@ -238,6 +292,7 @@ macro_rules! vdaf_dispatch_impl_base { bits, length, chunk_length, + dp_strategy, } => { let $vdaf = janus_core::vdaf::new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128( @@ -254,21 +309,42 @@ macro_rules! vdaf_dispatch_impl_base { >; const $VERIFY_KEY_LEN: usize = ::janus_core::vdaf::VERIFY_KEY_LENGTH_HMACSHA256_AES128; - type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; - let $dp_strategy = janus_core::dp::NoDifferentialPrivacy; - $body + match dp_strategy.clone() { + ::janus_core::vdaf::vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy => { + type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; + let $dp_strategy = janus_core::dp::NoDifferentialPrivacy; + $body + } + ::janus_core::vdaf::vdaf_dp_strategies::Prio3SumVec::PureDpDiscreteLaplace( + _strategy, + ) => { + type $DpStrategy = ::prio::dp::distributions::PureDpDiscreteLaplace; + let $dp_strategy = _strategy; + $body + } + } } ::janus_core::vdaf::VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy, } => { let $vdaf = ::prio::vdaf::prio3::Prio3::new_histogram(2, *length, *chunk_length)?; type $Vdaf = ::prio::vdaf::prio3::Prio3Histogram; const $VERIFY_KEY_LEN: usize = ::janus_core::vdaf::VERIFY_KEY_LENGTH; - type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; - let $dp_strategy = janus_core::dp::NoDifferentialPrivacy; - $body + match dp_strategy.clone() { + ::janus_core::vdaf::vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy => { + type $DpStrategy = janus_core::dp::NoDifferentialPrivacy; + let $dp_strategy = janus_core::dp::NoDifferentialPrivacy; + $body + } + ::janus_core::vdaf::vdaf_dp_strategies::Prio3Histogram::PureDpDiscreteLaplace(_strategy) => { + type $DpStrategy = ::prio::dp::distributions::PureDpDiscreteLaplace; + let $dp_strategy = _strategy; + $body + } + } } ::janus_core::vdaf::VdafInstance::Poplar1 { bits } => { @@ -536,8 +612,16 @@ macro_rules! vdaf_dispatch { #[cfg(test)] mod tests { - use super::VdafInstance; + #[cfg(feature = "fpvec_bounded_l2")] + use crate::vdaf::Prio3FixedPointBoundedL2VecSumBitSize; + use crate::vdaf::{vdaf_dp_strategies, VdafInstance}; use assert_matches::assert_matches; + #[cfg(feature = "fpvec_bounded_l2")] + use prio::dp::{distributions::ZCdpDiscreteGaussian, ZCdpBudget}; + use prio::dp::{ + distributions::{DiscreteLaplaceDpStrategy, PureDpDiscreteLaplace}, + DifferentialPrivacyStrategy, PureDpBudget, Rational, + }; use serde_test::{assert_tokens, Token}; #[test] @@ -569,12 +653,47 @@ mod tests { bits: 1, length: 8, chunk_length: 3, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, &[ Token::StructVariant { name: "VdafInstance", variant: "Prio3SumVec", - len: 3, + len: 4, + }, + Token::Str("bits"), + Token::U64(1), + Token::Str("length"), + Token::U64(8), + Token::Str("chunk_length"), + Token::U64(3), + Token::Str("dp_strategy"), + Token::Struct { + name: "Prio3SumVec", + len: 1, + }, + Token::Str("dp_strategy"), + Token::Str("NoDifferentialPrivacy"), + Token::StructEnd, + Token::StructVariantEnd, + ], + ); + assert_tokens( + &VdafInstance::Prio3SumVec { + bits: 1, + length: 8, + chunk_length: 3, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::PureDpDiscreteLaplace( + PureDpDiscreteLaplace::from_budget( + PureDpBudget::new(Rational::from_unsigned(2u128, 1u128).unwrap()).unwrap(), + ), + ), + }, + &[ + Token::StructVariant { + name: "VdafInstance", + variant: "Prio3SumVec", + len: 4, }, Token::Str("bits"), Token::U64(1), @@ -582,6 +701,29 @@ mod tests { Token::U64(8), Token::Str("chunk_length"), Token::U64(3), + Token::Str("dp_strategy"), + Token::Struct { + name: "DiscreteLaplaceDpStrategy", + len: 2, + }, + Token::Str("dp_strategy"), + Token::Str("PureDpDiscreteLaplace"), + Token::Str("budget"), + Token::Struct { + name: "PureDpBudget", + len: 1, + }, + Token::Str("epsilon"), + Token::Tuple { len: 2 }, + Token::Seq { len: Some(1) }, + Token::U32(2), + Token::SeqEnd, + Token::Seq { len: Some(1) }, + Token::U32(1), + Token::SeqEnd, + Token::TupleEnd, + Token::StructEnd, + Token::StructEnd, Token::StructVariantEnd, ], ); @@ -591,12 +733,13 @@ mod tests { bits: 1, length: 8, chunk_length: 3, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, &[ Token::StructVariant { name: "VdafInstance", variant: "Prio3SumVecField64MultiproofHmacSha256Aes128", - len: 4, + len: 5, }, Token::Str("proofs"), Token::U8(2), @@ -606,6 +749,14 @@ mod tests { Token::U64(8), Token::Str("chunk_length"), Token::U64(3), + Token::Str("dp_strategy"), + Token::Struct { + name: "Prio3SumVec", + len: 1, + }, + Token::Str("dp_strategy"), + Token::Str("NoDifferentialPrivacy"), + Token::StructEnd, Token::StructVariantEnd, ], ); @@ -613,17 +764,26 @@ mod tests { &VdafInstance::Prio3Histogram { length: 6, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, &[ Token::StructVariant { name: "VdafInstance", variant: "Prio3Histogram", - len: 2, + len: 3, }, Token::Str("length"), Token::U64(6), Token::Str("chunk_length"), Token::U64(2), + Token::Str("dp_strategy"), + Token::Struct { + name: "Prio3Histogram", + len: 1, + }, + Token::Str("dp_strategy"), + Token::Str("NoDifferentialPrivacy"), + Token::StructEnd, Token::StructVariantEnd, ], ); @@ -667,15 +827,84 @@ mod tests { variant: "FakeFailsPrepStep", }], ); + } + + #[cfg(feature = "fpvec_bounded_l2")] + #[test] + fn vdaf_deserialization_backwards_compatibility_fpvec_bounded_l2() { + assert_eq!( + serde_yaml::from_str::( + "--- +!Prio3FixedPointBoundedL2VecSum +bitsize: BitSize16 +dp_strategy: + dp_strategy: ZCdpDiscreteGaussian + budget: + epsilon: + - - 1 + - - 2 +length: 10" + ) + .unwrap(), + VdafInstance::Prio3FixedPointBoundedL2VecSum { + bitsize: Prio3FixedPointBoundedL2VecSumBitSize::BitSize16, + dp_strategy: + vdaf_dp_strategies::Prio3FixedPointBoundedL2VecSum::ZCdpDiscreteGaussian( + ZCdpDiscreteGaussian::from_budget(ZCdpBudget::new( + Rational::from_unsigned(1u128, 2u128).unwrap(), + )), + ), + length: 10, + } + ); + } - // Backwards compatibility + #[test] + fn vdaf_deserialization_backwards_compatibility() { assert_matches!( serde_yaml::from_str( "--- !Prio3Sum - bits: 12" +bits: 12" ), Ok(VdafInstance::Prio3Sum { bits: 12 }) ); + assert_matches!( + serde_yaml::from_str( + "--- +!Prio3Histogram +length: 4 +chunk_length: 2" + ), + Ok(VdafInstance::Prio3Histogram { + length: 4, + chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, + }) + ); + assert_eq!( + serde_yaml::from_str::( + "--- +!Prio3SumVec +bits: 2 +length: 2 +chunk_length: 2 +dp_strategy: + dp_strategy: PureDpDiscreteLaplace + budget: + epsilon: [[1], [1]]" + ) + .unwrap(), + VdafInstance::Prio3SumVec { + bits: 2, + length: 2, + chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::PureDpDiscreteLaplace( + DiscreteLaplaceDpStrategy::from_budget( + PureDpBudget::new(Rational::from_unsigned(1u128, 1u128).unwrap()).unwrap() + ), + ), + } + ); } } diff --git a/integration_tests/src/client.rs b/integration_tests/src/client.rs index 48eaaf0f3..c3d3c87af 100644 --- a/integration_tests/src/client.rs +++ b/integration_tests/src/client.rs @@ -96,6 +96,7 @@ fn json_encode_vdaf(vdaf: &VdafInstance) -> Value { bits, length, chunk_length, + dp_strategy: _, } => json!({ "type": "Prio3SumVec", "bits": format!("{bits}"), @@ -107,6 +108,7 @@ fn json_encode_vdaf(vdaf: &VdafInstance) -> Value { bits, length, chunk_length, + dp_strategy: _, } => json!({ "type": "Prio3SumVecField64MultiproofHmacSha256Aes128", "proofs": format!("{proofs}"), @@ -117,6 +119,7 @@ fn json_encode_vdaf(vdaf: &VdafInstance) -> Value { VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, } => { json!({ "type": "Prio3Histogram", diff --git a/integration_tests/tests/integration/common.rs b/integration_tests/tests/integration/common.rs index 7d4a15971..2b10e60ca 100644 --- a/integration_tests/tests/integration/common.rs +++ b/integration_tests/tests/integration/common.rs @@ -224,6 +224,33 @@ pub async fn verify_aggregate_generic( ) where V: vdaf::Client<16> + vdaf::Collector + InteropClientEncoding, V::AggregateResult: PartialEq, +{ + let (report_count, aggregate_result) = collect_aggregate_result_generic( + task_parameters, + leader_port, + vdaf, + before_timestamp, + &test_case.aggregation_parameter, + ) + .await; + + assert_eq!( + report_count, + u64::try_from(test_case.measurements.len()).unwrap() + ); + assert_eq!(aggregate_result, test_case.aggregate_result); +} + +pub async fn collect_aggregate_result_generic( + task_parameters: &TaskParameters, + leader_port: u16, + vdaf: V, + before_timestamp: Time, + aggregation_parameter: &V::AggregationParam, +) -> (u64, V::AggregateResult) +where + V: vdaf::Client<16> + vdaf::Collector + InteropClientEncoding, + V::AggregateResult: PartialEq, { let leader_endpoint = task_parameters .endpoint_fragments @@ -247,7 +274,7 @@ pub async fn verify_aggregate_generic( .unwrap(); // Send a collect request and verify that we got the correct result. - match &task_parameters.query_type { + let (report_count, aggregate_result) = match &task_parameters.query_type { QueryType::TimeInterval => { let batch_interval = Interval::new( before_timestamp @@ -259,43 +286,42 @@ pub async fn verify_aggregate_generic( ) .unwrap(); - let collection = collect_generic( + let collection_1 = collect_generic( &collector, Query::new_time_interval(batch_interval), - &test_case.aggregation_parameter, + aggregation_parameter, ) .await .unwrap(); - assert_eq!( - collection.report_count(), - u64::try_from(test_case.measurements.len()).unwrap() - ); - assert_eq!(collection.aggregate_result(), &test_case.aggregate_result); - // Collect again to verify that collections can be repeated. - let collection = collect_generic( + let collection_2 = collect_generic( &collector, Query::new_time_interval(batch_interval), - &test_case.aggregation_parameter, + aggregation_parameter, ) .await .unwrap(); + assert_eq!(collection_1.report_count(), collection_2.report_count()); assert_eq!( - collection.report_count(), - u64::try_from(test_case.measurements.len()).unwrap() + collection_1.aggregate_result(), + collection_2.aggregate_result() ); - assert_eq!(collection.aggregate_result(), &test_case.aggregate_result); + + ( + collection_2.report_count(), + collection_2.aggregate_result().clone(), + ) } QueryType::FixedSize { .. } => { let mut requests = 0; - let collection = loop { + let collection_1 = loop { requests += 1; let collection_res = collect_generic::<_, FixedSize>( &collector, Query::new_fixed_size(FixedSizeQuery::CurrentBatch), - &test_case.aggregation_parameter, + aggregation_parameter, ) .await; match collection_res { @@ -313,29 +339,30 @@ pub async fn verify_aggregate_generic( } }; - let batch_id = *collection.partial_batch_selector().batch_id(); - assert_eq!( - collection.report_count(), - u64::try_from(test_case.measurements.len()).unwrap() - ); - assert_eq!(collection.aggregate_result(), &test_case.aggregate_result); + let batch_id = *collection_1.partial_batch_selector().batch_id(); // Collect again to verify that collections can be repeated. - let collection = collect_generic( + let collection_2 = collect_generic( &collector, Query::new_fixed_size(FixedSizeQuery::ByBatchId { batch_id }), - &test_case.aggregation_parameter, + aggregation_parameter, ) .await .unwrap(); + assert_eq!(collection_1.report_count(), collection_2.report_count()); assert_eq!( - collection.report_count(), - u64::try_from(test_case.measurements.len()).unwrap() + collection_1.aggregate_result(), + collection_2.aggregate_result() ); - assert_eq!(collection.aggregate_result(), &test_case.aggregate_result); + + ( + collection_2.report_count(), + collection_2.aggregate_result().clone(), + ) } }; + (report_count, aggregate_result) } pub async fn submit_measurements_and_verify_aggregate( @@ -420,6 +447,7 @@ pub async fn submit_measurements_and_verify_aggregate( bits, length, chunk_length, + dp_strategy: _, } => { let vdaf = Prio3::new_sum_vec_multithreaded(2, *bits, *length, *chunk_length).unwrap(); @@ -469,6 +497,7 @@ pub async fn submit_measurements_and_verify_aggregate( bits, length, chunk_length, + dp_strategy: _, } => { let vdaf = new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128::< ParallelSumMultithreaded<_, _>, @@ -519,6 +548,7 @@ pub async fn submit_measurements_and_verify_aggregate( VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, } => { let vdaf = Prio3::new_histogram_multithreaded(2, *length, *chunk_length).unwrap(); diff --git a/integration_tests/tests/integration/divviup_ts.rs b/integration_tests/tests/integration/divviup_ts.rs index f8fc465d8..3add71f70 100644 --- a/integration_tests/tests/integration/divviup_ts.rs +++ b/integration_tests/tests/integration/divviup_ts.rs @@ -6,7 +6,10 @@ use crate::{ initialize_rustls, }; use janus_aggregator_core::task::{test_util::TaskBuilder, QueryType}; -use janus_core::{test_util::install_test_trace_subscriber, vdaf::VdafInstance}; +use janus_core::{ + test_util::install_test_trace_subscriber, + vdaf::{vdaf_dp_strategies, VdafInstance}, +}; use janus_integration_tests::{ client::{ClientBackend, InteropClient}, janus::JanusContainer, @@ -67,6 +70,7 @@ async fn janus_divviup_ts_histogram() { VdafInstance::Prio3Histogram { length: 4, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, ) .await; @@ -83,6 +87,7 @@ async fn janus_divviup_ts_sumvec() { bits: 16, length: 15, chunk_length: 16, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, ) .await; diff --git a/integration_tests/tests/integration/in_cluster.rs b/integration_tests/tests/integration/in_cluster.rs index 45a851d79..4a5e80cfc 100644 --- a/integration_tests/tests/integration/in_cluster.rs +++ b/integration_tests/tests/integration/in_cluster.rs @@ -21,7 +21,7 @@ use janus_core::{ kubernetes::{Cluster, PortForward}, }, time::DurationExt, - vdaf::VdafInstance, + vdaf::{vdaf_dp_strategies, VdafInstance}, }; use janus_integration_tests::{client::ClientBackend, TaskParameters}; use janus_messages::{Duration as JanusDuration, TaskId}; @@ -408,6 +408,7 @@ impl InClusterJanusPair { bits, length, chunk_length, + dp_strategy: _, } => Vdaf::SumVec { bits: bits.try_into().unwrap(), length: length.try_into().unwrap(), @@ -416,6 +417,7 @@ impl InClusterJanusPair { VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, } => Vdaf::Histogram(Histogram::Length { length: length.try_into().unwrap(), chunk_length: Some(chunk_length.try_into().unwrap()), @@ -566,6 +568,7 @@ async fn in_cluster_histogram() { VdafInstance::Prio3Histogram { length: 4, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, QueryType::TimeInterval, ) diff --git a/integration_tests/tests/integration/janus.rs b/integration_tests/tests/integration/janus.rs index ddd0a37f1..82be09a84 100644 --- a/integration_tests/tests/integration/janus.rs +++ b/integration_tests/tests/integration/janus.rs @@ -1,20 +1,30 @@ use crate::{ common::{ - build_test_task, submit_measurements_and_verify_aggregate, - submit_measurements_and_verify_aggregate_varying_aggregation_parameter, TestContext, + build_test_task, collect_aggregate_result_generic, + submit_measurements_and_verify_aggregate, + submit_measurements_and_verify_aggregate_varying_aggregation_parameter, + submit_measurements_generic, TestContext, }, initialize_rustls, }; use janus_aggregator_core::task::{test_util::TaskBuilder, QueryType}; -use janus_core::{test_util::install_test_trace_subscriber, vdaf::VdafInstance}; +use janus_core::{ + test_util::install_test_trace_subscriber, + vdaf::{vdaf_dp_strategies, VdafInstance}, +}; #[cfg(feature = "testcontainer")] use janus_integration_tests::janus::JanusContainer; use janus_integration_tests::{client::ClientBackend, janus::JanusInProcess, TaskParameters}; #[cfg(feature = "testcontainer")] use janus_interop_binaries::test_util::generate_network_name; use janus_messages::Role; -use prio::vdaf::dummy; -use std::time::Duration; +use prio::{ + dp::{ + distributions::PureDpDiscreteLaplace, DifferentialPrivacyStrategy, PureDpBudget, Rational, + }, + vdaf::{dummy, prio3::Prio3}, +}; +use std::{iter, time::Duration}; /// A pair of Janus instances, running in containers, against which integration tests may be run. #[cfg(feature = "testcontainer")] @@ -205,6 +215,7 @@ async fn janus_janus_histogram_4_buckets() { VdafInstance::Prio3Histogram { length: 4, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, QueryType::TimeInterval, ) @@ -220,7 +231,7 @@ async fn janus_janus_histogram_4_buckets() { .await; } -/// This test exercises Prio3Sum with Janus as both the leader and the helper. +/// This test exercises Prio3Histogram with Janus as both the leader and the helper. #[tokio::test(flavor = "multi_thread")] async fn janus_in_process_histogram_4_buckets() { install_test_trace_subscriber(); @@ -232,6 +243,7 @@ async fn janus_in_process_histogram_4_buckets() { VdafInstance::Prio3Histogram { length: 4, chunk_length: 2, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, )) .await; @@ -315,6 +327,7 @@ async fn janus_janus_sum_vec() { bits: 16, length: 15, chunk_length: 16, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, QueryType::TimeInterval, ) @@ -341,6 +354,7 @@ async fn janus_in_process_sum_vec() { bits: 16, length: 15, chunk_length: 16, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, )) .await; @@ -368,6 +382,7 @@ async fn janus_in_process_customized_sum_vec() { bits: 16, length: 15, chunk_length: 16, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, )) .await; @@ -443,3 +458,120 @@ async fn janus_in_process_one_round_with_agg_param_time_interval() { ) .await; } + +#[tokio::test(flavor = "multi_thread")] +async fn janus_in_process_histogram_dp_noise() { + static TEST_NAME: &str = "janus_in_process_histogram_dp_noise"; + const HISTOGRAM_LENGTH: usize = 100; + const CHUNK_LENGTH: usize = 10; + + install_test_trace_subscriber(); + initialize_rustls(); + + let epsilon = Rational::from_unsigned(1u128, 10u128).unwrap(); + let janus_pair = JanusInProcessPair::new(TaskBuilder::new( + QueryType::TimeInterval, + VdafInstance::Prio3Histogram { + length: HISTOGRAM_LENGTH, + chunk_length: CHUNK_LENGTH, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::PureDpDiscreteLaplace( + PureDpDiscreteLaplace::from_budget(PureDpBudget::new(epsilon).unwrap()), + ), + }, + )) + .await; + let vdaf = Prio3::new_histogram_multithreaded(2, HISTOGRAM_LENGTH, CHUNK_LENGTH).unwrap(); + + let total_measurements: usize = janus_pair + .task_parameters + .min_batch_size + .try_into() + .unwrap(); + let measurements = iter::repeat(0).take(total_measurements).collect::>(); + let client_implementation = ClientBackend::InProcess + .build( + TEST_NAME, + &janus_pair.task_parameters, + (janus_pair.leader.port(), janus_pair.helper.port()), + vdaf.clone(), + ) + .await + .unwrap(); + let before_timestamp = submit_measurements_generic(&measurements, &client_implementation).await; + let (report_count, aggregate_result) = collect_aggregate_result_generic( + &janus_pair.task_parameters, + janus_pair.leader.port(), + vdaf, + before_timestamp, + &(), + ) + .await; + assert_eq!(report_count, janus_pair.task_parameters.min_batch_size); + + let mut un_noised_result = [0u128; HISTOGRAM_LENGTH]; + un_noised_result[0] = report_count.into(); + // Smoke test: Just confirm that some noise was added. Since epsilon is small, the noise will be + // large (drawn from Laplace_Z(20) + Laplace_Z(20)), and it is highly unlikely that all 100 + // noise values will be zero simultaneously. + assert_ne!(aggregate_result, un_noised_result); +} + +#[tokio::test(flavor = "multi_thread")] +async fn janus_in_process_sumvec_dp_noise() { + static TEST_NAME: &str = "janus_in_process_sumvec_dp_noise"; + const VECTOR_LENGTH: usize = 50; + const BITS: usize = 2; + const CHUNK_LENGTH: usize = 10; + + install_test_trace_subscriber(); + initialize_rustls(); + + let epsilon = Rational::from_unsigned(1u128, 10u128).unwrap(); + let janus_pair = JanusInProcessPair::new(TaskBuilder::new( + QueryType::TimeInterval, + VdafInstance::Prio3SumVec { + bits: BITS, + length: VECTOR_LENGTH, + chunk_length: CHUNK_LENGTH, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::PureDpDiscreteLaplace( + PureDpDiscreteLaplace::from_budget(PureDpBudget::new(epsilon).unwrap()), + ), + }, + )) + .await; + let vdaf = Prio3::new_sum_vec_multithreaded(2, BITS, VECTOR_LENGTH, CHUNK_LENGTH).unwrap(); + + let total_measurements: usize = janus_pair + .task_parameters + .min_batch_size + .try_into() + .unwrap(); + let measurements = iter::repeat(vec![0; VECTOR_LENGTH]) + .take(total_measurements) + .collect::>(); + let client_implementation = ClientBackend::InProcess + .build( + TEST_NAME, + &janus_pair.task_parameters, + (janus_pair.leader.port(), janus_pair.helper.port()), + vdaf.clone(), + ) + .await + .unwrap(); + let before_timestamp = submit_measurements_generic(&measurements, &client_implementation).await; + let (report_count, aggregate_result) = collect_aggregate_result_generic( + &janus_pair.task_parameters, + janus_pair.leader.port(), + vdaf, + before_timestamp, + &(), + ) + .await; + assert_eq!(report_count, janus_pair.task_parameters.min_batch_size); + + let un_noised_result = [0u128; VECTOR_LENGTH]; + // Smoke test: Just confirm that some noise was added. Since epsilon is small, the noise will be + // large (drawn from Laplace_Z(150) + Laplace_Z(150)), and it is highly unlikely that all 50 + // noise values will be zero simultaneously. + assert_ne!(aggregate_result, un_noised_result); +} diff --git a/interop_binaries/src/commands/janus_interop_client.rs b/interop_binaries/src/commands/janus_interop_client.rs index a16bea42e..7d5fb6371 100644 --- a/interop_binaries/src/commands/janus_interop_client.rs +++ b/interop_binaries/src/commands/janus_interop_client.rs @@ -133,6 +133,7 @@ async fn handle_upload( bits, length, chunk_length, + dp_strategy: _, } => { let measurement = parse_vector_measurement::(request.measurement.clone())?; let vdaf = Prio3::new_sum_vec_multithreaded(2, bits, length, chunk_length) @@ -145,6 +146,7 @@ async fn handle_upload( bits, length, chunk_length, + dp_strategy: _, } => { let measurement = parse_vector_measurement::(request.measurement.clone())?; let vdaf = new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128::< @@ -157,6 +159,7 @@ async fn handle_upload( VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, } => { let measurement = parse_primitive_measurement::(request.measurement.clone())?; let vdaf = Prio3::new_histogram_multithreaded(2, length, chunk_length) diff --git a/interop_binaries/src/commands/janus_interop_collector.rs b/interop_binaries/src/commands/janus_interop_collector.rs index 3224daf5a..3afb3fd81 100644 --- a/interop_binaries/src/commands/janus_interop_collector.rs +++ b/interop_binaries/src/commands/janus_interop_collector.rs @@ -338,6 +338,7 @@ async fn handle_collection_start( bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Prio3::new_sum_vec(2, bits, length, chunk_length) @@ -364,6 +365,7 @@ async fn handle_collection_start( bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128::< @@ -395,6 +397,7 @@ async fn handle_collection_start( VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Prio3::new_histogram(2, length, chunk_length) @@ -546,6 +549,7 @@ async fn handle_collection_start( bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Prio3::new_sum_vec(2, bits, length, chunk_length) @@ -572,6 +576,7 @@ async fn handle_collection_start( bits, length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128::< @@ -603,6 +608,7 @@ async fn handle_collection_start( VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, }, ) => { let vdaf = Prio3::new_histogram(2, length, chunk_length) diff --git a/interop_binaries/src/lib.rs b/interop_binaries/src/lib.rs index 7252bb1ce..402da4b24 100644 --- a/interop_binaries/src/lib.rs +++ b/interop_binaries/src/lib.rs @@ -2,8 +2,11 @@ use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use derivative::Derivative; use janus_aggregator_core::task::{test_util::Task, QueryType}; #[cfg(feature = "fpvec_bounded_l2")] -use janus_core::vdaf::{vdaf_dp_strategies, Prio3FixedPointBoundedL2VecSumBitSize}; -use janus_core::{hpke::HpkeKeypair, vdaf::VdafInstance}; +use janus_core::vdaf::Prio3FixedPointBoundedL2VecSumBitSize; +use janus_core::{ + hpke::HpkeKeypair, + vdaf::{vdaf_dp_strategies, VdafInstance}, +}; use janus_messages::{ query_type::{FixedSize, QueryType as _, TimeInterval}, HpkeAeadId, HpkeConfigId, HpkeKdfId, HpkeKemId, Role, TaskId, Time, @@ -147,6 +150,7 @@ impl From for VdafObject { bits, length, chunk_length, + dp_strategy: _, } => VdafObject::Prio3SumVec { bits: NumberAsString(bits), length: NumberAsString(length), @@ -158,6 +162,7 @@ impl From for VdafObject { bits, length, chunk_length, + dp_strategy: _, } => VdafObject::Prio3SumVecField64MultiproofHmacSha256Aes128 { proofs: NumberAsString(proofs), bits: NumberAsString(bits), @@ -168,6 +173,7 @@ impl From for VdafObject { VdafInstance::Prio3Histogram { length, chunk_length, + dp_strategy: _, } => VdafObject::Prio3Histogram { length: NumberAsString(length), chunk_length: NumberAsString(chunk_length), @@ -204,6 +210,7 @@ impl From for VdafInstance { bits: bits.0, length: length.0, chunk_length: chunk_length.0, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, VdafObject::Prio3SumVecField64MultiproofHmacSha256Aes128 { @@ -216,6 +223,7 @@ impl From for VdafInstance { bits: bits.0, length: length.0, chunk_length: chunk_length.0, + dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }, VdafObject::Prio3Histogram { @@ -224,6 +232,7 @@ impl From for VdafInstance { } => VdafInstance::Prio3Histogram { length: length.0, chunk_length: chunk_length.0, + dp_strategy: vdaf_dp_strategies::Prio3Histogram::NoDifferentialPrivacy, }, #[cfg(feature = "fpvec_bounded_l2")]