diff --git a/index.bs b/index.bs index 6c153f603..61ea82b46 100644 --- a/index.bs +++ b/index.bs @@ -1178,9 +1178,10 @@ controls the maximum [=map/size=] of a [=trigger spec map=] for an controls how many [=attribution sources=] can be in the [=attribution source cache=] per [=attribution source/source origin=]. -Randomized response epsilon is a non-negative double that controls -the randomized response probability of an [=attribution source=]. If [=automation local testing mode=] is true, -this is `∞`. +Max settable event-level epsilon is a non-negative double that +controls the default and maximum values that a source registration can specify +for the epsilon parameter used by [=compute the channel capacity of a source=] +and [=obtain a randomized source response=]. Randomized null report rate excluding source registration time is a double between 0 and 1 (both inclusive) that controls the randomized number of null reports @@ -1986,7 +1987,10 @@ To parse source-registration JSON given a [=byte sequence=] : [=randomized response output configuration/trigger specs=] :: |triggerSpecs| -1. Let |epsilon| be the user agent's [=randomized response epsilon=]. +1. Let |epsilon| be the user agent's [=max settable event-level epsilon=]. +1. Set |epsilon| to |value|["`event_level_epsilon`"] if it [=map/exists=]: +1. If |epsilon| is not a double, is less than 0, or is greater than the user agent's [=max settable event-level epsilon=], return null. +1. If [=automation local testing mode=] is true, set |epsilon| to `∞`. 1. If the result of [=computing the channel capacity of a source=] with |randomizedResponseConfig| and |epsilon| is greater than [=max event-level channel capacity per source=][|sourceType|], return null. 1. Let |triggerDataMatchingMode| be "[=trigger-data matching mode/modulus=]". @@ -3904,7 +3908,8 @@ Any given [=attribution source=] has a [=obtain a set of possible trigger states The choice of trigger state may encode cross-site information. To protect the cross-site information disclosure, each [=attribution source=] is subject to a [=obtain a randomized response|randomized response mechanism=] [[RR]], which will choose a state at random with [=obtain a randomized source response pick rate|pick rate=] -dependent on the user agent's [=randomized response epsilon=]. +dependent on the source's event-level epsilon, which has an upper bound of the +user agent's [=max settable event-level epsilon=]. This introduces some level of plausible deniability into the resulting [=event-level reports=] (or lack thereof), as there is always a chance that the output was generated from a random process. We can reason about the diff --git a/params/chromium-params.md b/params/chromium-params.md index 2f2891f17..984700bad 100644 --- a/params/chromium-params.md +++ b/params/chromium-params.md @@ -8,7 +8,7 @@ Chromium's implementation assigns the following values: | Name | Value | | ---- | ----- | | [Max pending sources per source origin][] | [4096][max pending sources per source origin value] | -| [Randomized response epsilon][] | [14][randomized response epsilon value] | +| [Max settable event-level epsilon][] | [14][max settable event-level epsilon value] | | [Randomized null report rate excluding source registration time][] | [0.05][randomized null report rate excluding source registration time value] | | [Randomized null report rate including source registration time][] | [0.008][randomized null report rate including source registration time value] | | [Max event-level reports per attribution destination][] | [1024][max event-level reports per attribution destination value] | @@ -28,8 +28,8 @@ Chromium's implementation assigns the following values: [Max pending sources per source origin]: https://wicg.github.io/attribution-reporting-api/#max-pending-sources-per-source-origin [max pending sources per source origin value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=151;drc=3be0e68c5ed56aba7c321cbaea22558eee61fc50 -[Randomized response epsilon]: https://wicg.github.io/attribution-reporting-api/#randomized-response-epsilon -[randomized response epsilon value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=57;drc=3733a639d724a4353463a872605119d11a1e4d37 +[Max settable event-level epsilon]: https://wicg.github.io/attribution-reporting-api/#max-settable-event-level-epsilon +[max settable event-level epsilon value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=57;drc=3733a639d724a4353463a872605119d11a1e4d37 [Randomized null report rate excluding source registration time]: https://wicg.github.io/attribution-reporting-api/#randomized-null-report-rate-excluding-source-registration-time [randomized null report rate excluding source registration time value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=109;drc=3733a639d724a4353463a872605119d11a1e4d37 [Randomized null report rate including source registration time]: https://wicg.github.io/attribution-reporting-api/#randomized-null-report-rate-including-source-registration-time diff --git a/ts/src/header-validator/source.test.ts b/ts/src/header-validator/source.test.ts index bfe4e9398..d7ebcb0b1 100644 --- a/ts/src/header-validator/source.test.ts +++ b/ts/src/header-validator/source.test.ts @@ -45,6 +45,7 @@ const testCases: TestCase[] = [ debugKey: 1n, debugReporting: true, destination: new Set(['https://a.test']), + eventLevelEpsilon: 14, eventReportWindows: { startTime: 0, endTimes: [3601], @@ -1186,7 +1187,7 @@ const testCases: TestCase[] = [ [SourceType.event]: 0, [SourceType.navigation]: Infinity, }, - randomizedResponseEpsilon: 14, + maxSettableEventLevelEpsilon: 14, }, expectedErrors: [ { @@ -1204,7 +1205,7 @@ const testCases: TestCase[] = [ [SourceType.event]: Infinity, [SourceType.navigation]: 0, }, - randomizedResponseEpsilon: 14, + maxSettableEventLevelEpsilon: 14, }, expectedErrors: [ { @@ -1214,6 +1215,53 @@ const testCases: TestCase[] = [ ], }, + { + name: 'event-level-epsilon-valid', + json: `{ + "destination": "https://a.test", + "event_level_epsilon": 13.5 + }`, + }, + { + name: 'event-level-epsilon-wrong-type', + json: `{ + "destination": "https://a.test", + "event_level_epsilon": "1" + }`, + expectedErrors: [ + { + path: ['event_level_epsilon'], + msg: 'must be a number', + }, + ], + }, + { + name: 'event-level-epsilon-negative', + json: `{ + "destination": "https://a.test", + "event_level_epsilon": -1 + }`, + expectedErrors: [ + { + path: ['event_level_epsilon'], + msg: 'must be in the range [0, 14]', + }, + ], + }, + { + name: 'event-level-epsilon-above-max', + json: `{ + "destination": "https://a.test", + "event_level_epsilon": 14.1 + }`, + expectedErrors: [ + { + path: ['event_level_epsilon'], + msg: 'must be in the range [0, 14]', + }, + ], + }, + // Full Flex // TODO: compare returned trigger specs against expected values diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index cc088c971..7215acb2f 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -792,6 +792,12 @@ function eventReportWindow( ) } +function eventLevelEpsilon(ctx: Context, j: Json): Maybe { + return number(ctx, j).filter((n) => + isInRange(ctx, n, 0, ctx.vsv.maxSettableEventLevelEpsilon) + ) +} + function channelCapacity(ctx: Context, s: Source): void { const perTriggerDataConfigs = s.triggerSpecs.flatMap((spec) => Array(spec.triggerData.size).fill( @@ -808,7 +814,7 @@ function channelCapacity(ctx: Context, s: Source): void { ) const { infoGain } = config.computeConfigData( - ctx.vsv.randomizedResponseEpsilon, + s.eventLevelEpsilon, ctx.vsv.maxEventLevelChannelCapacityPerSource[ctx.sourceType] ) @@ -1057,6 +1063,8 @@ export type Source = CommonDebug & triggerSpecs: TriggerSpec[] triggerDataMatching: TriggerDataMatching + + eventLevelEpsilon: number } function source(ctx: Context, j: Json): Maybe { @@ -1110,6 +1118,11 @@ function source(ctx: Context, j: Json): Maybe { ), aggregationKeys: field('aggregation_keys', aggregationKeys, new Map()), destination: field('destination', destination), + eventLevelEpsilon: field( + 'event_level_epsilon', + eventLevelEpsilon, + ctx.vsv.maxSettableEventLevelEpsilon + ), eventReportWindows: () => eventReportWindowsVal, expiry: () => expiryVal, filterData: field('filter_data', filterData, new Map()), diff --git a/ts/src/vendor-specific-values.ts b/ts/src/vendor-specific-values.ts index 2cfaefe47..af9cdae64 100644 --- a/ts/src/vendor-specific-values.ts +++ b/ts/src/vendor-specific-values.ts @@ -2,7 +2,7 @@ import { SourceType } from './source-type' export type VendorSpecificValues = { maxEventLevelChannelCapacityPerSource: Record - randomizedResponseEpsilon: number + maxSettableEventLevelEpsilon: number } export const Chromium: Readonly = { @@ -10,5 +10,5 @@ export const Chromium: Readonly = { [SourceType.event]: 6.5, [SourceType.navigation]: 11.46173, }, - randomizedResponseEpsilon: 14, + maxSettableEventLevelEpsilon: 14, }