Skip to content

Commit

Permalink
Restrict the number of report states per source (WICG#1203)
Browse files Browse the repository at this point in the history
Estimated channel capacity when the number of states is sufficiently high is effectively zero, and a randomised source response is effectively guaranteed. Additionally, a very high number of states coupled with the complexity of the flexible event feature can present challenges to performance. Altogether, user-agents may have cause to restrict the total number of report states per source.

Co-authored-by: Andrew Paseltiner <[email protected]>
Co-authored-by: Nan Lin <[email protected]>
  • Loading branch information
3 people authored Mar 22, 2024
1 parent 99113cc commit bc66cc3
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 6 deletions.
7 changes: 5 additions & 2 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,8 @@ 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=].

<dfn>Max trigger-state cardinality</dfn> is a positive integer that controls the maximum [=set/size=] of [=obtain a set of possible trigger states|the set of possible trigger states=] for any one [=attribution source=].

<dfn>Randomized null report rate excluding source registration time</dfn> is a
double between 0 and 1 (both inclusive) that controls the randomized number of null reports
generated for an [=attribution trigger=] whose [=attribution trigger/aggregatable source registration time configuration=]
Expand Down Expand Up @@ -1969,6 +1971,7 @@ To <dfn>compute the channel capacity of a source</dfn> given a [=randomized resp
1. Let |pickRate| be the [=obtain a randomized source response pick rate|randomized response pick rate=] with |config| and |epsilon|.
1. Let |states| be the [=obtain a set of possible trigger states|number of possible trigger states=] with |config|.
1. If |states| is 1, return 0.
1. If |states| is greater than the user agent's [=max trigger-state cardinality=], return an error.
1. Let |p| be |pickRate| * (|states| - 1) / |states|.
1. Return log2(|states|) - h(|p|) - |p| * log2(|states| - 1) where h is the binary entropy function [[BIN-ENT]].

Expand Down Expand Up @@ -2295,8 +2298,8 @@ To <dfn noexport>parse source-registration JSON</dfn> given a [=byte sequence=]
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 |channelCapacity| be the result of [=computing the channel capacity of a source=] with |randomizedResponseConfig| and |epsilon|.
1. If |channelCapacity| is an error or is greater than [=max event-level channel capacity per source=][|sourceType|], return null.
1. Let |source| be a new [=attribution source=] struct whose items are:

: [=attribution source/source identifier=]
Expand Down
33 changes: 33 additions & 0 deletions ts/src/header-validator/source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,39 @@ const testCases: TestCase[] = [
],
},

{
name: 'trigger-state-cardinality-valid',
json: `{"destination": "https://a.test"}`,
sourceType: SourceType.event,
vsv: {
maxEventLevelChannelCapacityPerSource: {
[SourceType.event]: Infinity,
[SourceType.navigation]: 0,
},
maxSettableEventLevelEpsilon: 14,
maxTriggerStateCardinality: 3,
},
},
{
name: 'trigger-state-cardinality-invalid',
json: `{"destination": "https://a.test"}`,
sourceType: SourceType.event,
vsv: {
maxEventLevelChannelCapacityPerSource: {
[SourceType.event]: Infinity,
[SourceType.navigation]: 0,
},
maxSettableEventLevelEpsilon: 14,
maxTriggerStateCardinality: 2,
},
expectedErrors: [
{
path: [],
msg: 'number of possible output states (3) exceeds max cardinality (2)',
},
],
},

{
name: 'event-level-epsilon-valid',
json: `{
Expand Down
19 changes: 15 additions & 4 deletions ts/src/header-validator/validate-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,8 @@ function eventLevelEpsilon(ctx: RegistrationContext, j: Json): Maybe<number> {
}

function channelCapacity(ctx: SourceContext, s: Source): void {
const numStatesWords = 'number of possible output states'

const perTriggerDataConfigs = s.triggerSpecs.flatMap((spec) =>
Array(spec.triggerData.size).fill(
new privacy.PerTriggerDataConfig(
Expand All @@ -838,21 +840,30 @@ function channelCapacity(ctx: SourceContext, s: Source): void {
ctx.vsv.maxEventLevelChannelCapacityPerSource[ctx.sourceType]
)

const max = ctx.vsv.maxEventLevelChannelCapacityPerSource[ctx.sourceType]
const maxTriggerStates = ctx.vsv.maxTriggerStateCardinality

if (out.numStates > maxTriggerStates) {
ctx.error(
`${numStatesWords} (${out.numStates}) exceeds max cardinality (${maxTriggerStates})`
)
}

const maxInfoGain =
ctx.vsv.maxEventLevelChannelCapacityPerSource[ctx.sourceType]
const infoGainMsg = `information gain: ${out.infoGain.toFixed(2)}`

if (out.infoGain > max) {
if (out.infoGain > maxInfoGain) {
ctx.error(
`${infoGainMsg} exceeds max event-level channel capacity per ${
ctx.sourceType
} source (${max.toFixed(2)})`
} source (${maxInfoGain.toFixed(2)})`
)
} else if (ctx.noteInfoGain) {
ctx.note(infoGainMsg)
}

if (ctx.noteInfoGain) {
ctx.note(`number of possible output states: ${out.numStates}`)
ctx.note(`${numStatesWords}: ${out.numStates}`)
ctx.note(`randomized trigger rate: ${out.flipProb.toFixed(7)}`)
}
}
Expand Down
2 changes: 2 additions & 0 deletions ts/src/vendor-specific-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SourceType } from './source-type'
export type VendorSpecificValues = {
maxEventLevelChannelCapacityPerSource: Record<SourceType, number>
maxSettableEventLevelEpsilon: number
maxTriggerStateCardinality: number
}

export const Chromium: Readonly<VendorSpecificValues> = {
Expand All @@ -11,4 +12,5 @@ export const Chromium: Readonly<VendorSpecificValues> = {
[SourceType.navigation]: 11.5,
},
maxSettableEventLevelEpsilon: 14,
maxTriggerStateCardinality: Infinity,
}

0 comments on commit bc66cc3

Please sign in to comment.