From f7aa316a9a81ab3b53fdc1110f5fa005d11aa938 Mon Sep 17 00:00:00 2001 From: Taiki Yamaguchi Date: Thu, 16 Nov 2023 14:51:42 +0800 Subject: [PATCH 1/3] initialize the first row with the actual timestamp --- benches/oneshot/ipa.rs | 4 ++ src/protocol/ipa_prf/prf_sharding/mod.rs | 70 +++++++++++++++++++++++- src/test_fixture/ipa.rs | 1 + 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/benches/oneshot/ipa.rs b/benches/oneshot/ipa.rs index dd9ba3659..3997fb795 100644 --- a/benches/oneshot/ipa.rs +++ b/benches/oneshot/ipa.rs @@ -151,6 +151,10 @@ async fn run(args: Args) -> Result<(), Error> { } else { CappingOrder::CapOldestFirst }; + + // TODO: Note that how we apply the attribution window to IPA and OPRF IPA is different. + // For IPA, trigger events are considered within the window if [0..window] (upper bound inclusive). + // For OPRF, trigger events are considered within the window if [0..window) (upper bound exclusive). let expected_results = ipa_in_the_clear( &raw_data, args.per_user_cap, diff --git a/src/protocol/ipa_prf/prf_sharding/mod.rs b/src/protocol/ipa_prf/prf_sharding/mod.rs index 5e0ae01d5..ab2619150 100644 --- a/src/protocol/ipa_prf/prf_sharding/mod.rs +++ b/src/protocol/ipa_prf/prf_sharding/mod.rs @@ -589,7 +589,9 @@ where // This is incorrect in the case that the CAP is less than the maximum value of "trigger value" for a single row // Not a problem if you assume that's an invalid input difference_to_cap: BitDecomposed::new(vec![Replicated::ZERO; TV::BITS as usize]), - source_event_timestamp: BitDecomposed::new(vec![Replicated::ZERO; TS::BITS as usize]), + source_event_timestamp: BitDecomposed::decompose(TS::BITS, |i| { + input_row.timestamp.map(|v| Gf2::truncate_from(v[i])) + }), } } @@ -834,6 +836,8 @@ where #[cfg(all(test, unit_test))] pub mod tests { + use std::num::NonZeroU32; + use super::{CappedAttributionOutputs, PrfShardedIpaInputRow}; use crate::{ ff::{Field, Fp32BitPrime, GaloisField, Gf2, Gf20Bit, Gf3Bit, Gf5Bit}, @@ -1062,4 +1066,68 @@ pub mod tests { assert_eq!(result, &expected); }); } + + #[test] + fn semi_honest_aggregation_capping_attribution_with_attribution_window() { + // In OPRF, the attribution window's upper bound is excluded - i.e., [0..ATTRIBUTION_WINDOW_SECONDS). + // Ex. If attribution window is 200s and the time difference from the nearest source event + // to the trigger event is 200s, then the trigger event is NOT attributed. + const ATTRIBUTION_WINDOW_SECONDS: u32 = 200; + + run(|| async move { + let world = TestWorld::default(); + + let records: Vec> = vec![ + /* First User */ + oprf_test_input_with_timestamp(123, false, 17, 0, 1), + oprf_test_input_with_timestamp(123, true, 0, 7, 200), // tsΔ = 199, attributed to 17 + oprf_test_input_with_timestamp(123, false, 20, 0, 200), + oprf_test_input_with_timestamp(123, true, 0, 3, 300), // tsΔ = 100, attributed to 20 + /* Second User */ + oprf_test_input_with_timestamp(234, false, 12, 0, 0), + oprf_test_input_with_timestamp(234, true, 0, 5, 199), // tsΔ = 199, attributed to 12 + /* Third User */ + oprf_test_input_with_timestamp(345, false, 20, 0, 0), + oprf_test_input_with_timestamp(345, true, 0, 7, 100), // tsΔ = 100, attributed to 20 + oprf_test_input_with_timestamp(345, false, 18, 0, 200), + oprf_test_input_with_timestamp(345, false, 12, 0, 300), + oprf_test_input_with_timestamp(345, true, 0, 7, 400), // tsΔ = 100, attributed to 12 + oprf_test_input_with_timestamp(345, true, 0, 7, 499), // tsΔ = 199, attributed to 12 + oprf_test_input_with_timestamp(345, true, 0, 7, 500), // tsΔ = 200, not attributed + oprf_test_input_with_timestamp(345, true, 0, 7, 700), // tsΔ = 400, not attributed + ]; + + let mut expected = [0_u128; 32]; + expected[12] = 19; + expected[17] = 7; + expected[20] = 10; + + let num_saturating_bits: usize = 5; + + let histogram = [3, 3, 2, 2, 1, 1, 1, 1]; + + let result: Vec<_> = world + .semi_honest(records.into_iter(), |ctx, input_rows| async move { + attribution_and_capping_and_aggregation::< + _, + Gf5Bit, + Gf3Bit, + Gf20Bit, + Replicated, + Fp32BitPrime, + >( + ctx, + input_rows, + num_saturating_bits, + NonZeroU32::new(ATTRIBUTION_WINDOW_SECONDS), + &histogram, + ) + .await + .unwrap() + }) + .await + .reconstruct(); + assert_eq!(result, &expected); + }); + } } diff --git a/src/test_fixture/ipa.rs b/src/test_fixture/ipa.rs index 5529079ee..f5ffcb9df 100644 --- a/src/test_fixture/ipa.rs +++ b/src/test_fixture/ipa.rs @@ -116,6 +116,7 @@ fn update_expected_output_for_user<'a, I: IntoIterator, order: &CappingOrder, ) { + // TODO: change how we apply the window to IPA and OPRF IPA let within_window = |value: u64| -> bool { if let Some(window) = attribution_window_seconds { value <= u64::from(window.get()) From 5b215b968062aee9b7e08756413c459d4d31357a Mon Sep 17 00:00:00 2001 From: Taiki Yamaguchi Date: Thu, 16 Nov 2023 15:27:55 +0800 Subject: [PATCH 2/3] better test case --- benches/oneshot/ipa.rs | 3 ++- src/protocol/ipa_prf/prf_sharding/mod.rs | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/benches/oneshot/ipa.rs b/benches/oneshot/ipa.rs index 3997fb795..ee7320452 100644 --- a/benches/oneshot/ipa.rs +++ b/benches/oneshot/ipa.rs @@ -152,7 +152,8 @@ async fn run(args: Args) -> Result<(), Error> { CappingOrder::CapOldestFirst }; - // TODO: Note that how we apply the attribution window to IPA and OPRF IPA is different. + // TODO: Remove the below comment once #844 is landed. + // Note that how we apply the attribution window to IPA and OPRF IPA is different. // For IPA, trigger events are considered within the window if [0..window] (upper bound inclusive). // For OPRF, trigger events are considered within the window if [0..window) (upper bound exclusive). let expected_results = ipa_in_the_clear( diff --git a/src/protocol/ipa_prf/prf_sharding/mod.rs b/src/protocol/ipa_prf/prf_sharding/mod.rs index ab2619150..76faa68dc 100644 --- a/src/protocol/ipa_prf/prf_sharding/mod.rs +++ b/src/protocol/ipa_prf/prf_sharding/mod.rs @@ -759,6 +759,7 @@ where // The result is true if the time delta is `[0, attribution_window_seconds)` // If we want to include the upper bound, we need to use `bitwise_greater_than_constant()` // and negate the result. + // TODO: Change to `lte` protocol once #844 is landed. bitwise_less_than_constant( ctx.narrow(&Step::CompareTimeDeltaToAttributionWindow), record_id, @@ -1072,6 +1073,7 @@ pub mod tests { // In OPRF, the attribution window's upper bound is excluded - i.e., [0..ATTRIBUTION_WINDOW_SECONDS). // Ex. If attribution window is 200s and the time difference from the nearest source event // to the trigger event is 200s, then the trigger event is NOT attributed. + // TODO: Modify the test case once #844 is landed. i.e., change the second test input row ts to `201`. const ATTRIBUTION_WINDOW_SECONDS: u32 = 200; run(|| async move { @@ -1088,19 +1090,19 @@ pub mod tests { oprf_test_input_with_timestamp(234, true, 0, 5, 199), // tsΔ = 199, attributed to 12 /* Third User */ oprf_test_input_with_timestamp(345, false, 20, 0, 0), - oprf_test_input_with_timestamp(345, true, 0, 7, 100), // tsΔ = 100, attributed to 20 + oprf_test_input_with_timestamp(345, true, 0, 3, 100), // tsΔ = 100, attributed to 20 oprf_test_input_with_timestamp(345, false, 18, 0, 200), oprf_test_input_with_timestamp(345, false, 12, 0, 300), - oprf_test_input_with_timestamp(345, true, 0, 7, 400), // tsΔ = 100, attributed to 12 - oprf_test_input_with_timestamp(345, true, 0, 7, 499), // tsΔ = 199, attributed to 12 - oprf_test_input_with_timestamp(345, true, 0, 7, 500), // tsΔ = 200, not attributed - oprf_test_input_with_timestamp(345, true, 0, 7, 700), // tsΔ = 400, not attributed + oprf_test_input_with_timestamp(345, true, 0, 3, 400), // tsΔ = 100, attributed to 12 + oprf_test_input_with_timestamp(345, true, 0, 3, 499), // tsΔ = 199, attributed to 12 + oprf_test_input_with_timestamp(345, true, 0, 3, 500), // tsΔ = 200, not attributed + oprf_test_input_with_timestamp(345, true, 0, 3, 700), // tsΔ = 400, not attributed ]; let mut expected = [0_u128; 32]; - expected[12] = 19; + expected[12] = 11; expected[17] = 7; - expected[20] = 10; + expected[20] = 6; let num_saturating_bits: usize = 5; From a05bd81b3d4e998c0fc696b4ed320b902dcb2a6e Mon Sep 17 00:00:00 2001 From: Taiki Yamaguchi Date: Thu, 16 Nov 2023 15:35:10 +0800 Subject: [PATCH 3/3] remove comment --- src/test_fixture/ipa.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test_fixture/ipa.rs b/src/test_fixture/ipa.rs index f5ffcb9df..5529079ee 100644 --- a/src/test_fixture/ipa.rs +++ b/src/test_fixture/ipa.rs @@ -116,7 +116,6 @@ fn update_expected_output_for_user<'a, I: IntoIterator, order: &CappingOrder, ) { - // TODO: change how we apply the window to IPA and OPRF IPA let within_window = |value: u64| -> bool { if let Some(window) = attribution_window_seconds { value <= u64::from(window.get())