Skip to content

Commit

Permalink
Spec: filtering IDs (#123)
Browse files Browse the repository at this point in the history
Specs the ability to set a filtering ID (and modify the default ID space). See https://github.com/patcg-individual-drafts/private-aggregation-api/blob/main/flexible_filtering.md#proposal-filtering-id-in-the-encrypted-payload and issue #92.

To support this new functionality, we increase the report version. Note that this also requires aggregation service versions to support the new version.
  • Loading branch information
alexmturner committed May 6, 2024
1 parent e03bedb commit a857522
Showing 1 changed file with 90 additions and 16 deletions.
106 changes: 90 additions & 16 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ interface PrivateAggregation {
dictionary PAHistogramContribution {
required bigint bucket;
required long value;
bigint filteringId = 0;
};

dictionary PADebugModeOptions {
Expand Down Expand Up @@ -168,12 +169,22 @@ are:
1. If |contribution|["{{PAHistogramContribution/value}}"] is negative,
[=exception/throw=] a {{RangeError}}.
1. Let |scopingDetails| be [=this=]'s [=PrivateAggregation/scoping details=].
1. Let |batchingScope| be the result of running |scopingDetails|' [=scoping
details/get batching scope steps=].
1. Let |filteringIdMaxBytes| be the [=default filtering ID max bytes=].
1. If [=pre-specified report parameters map=][|batchingScope|] [=map/exists=]:
1. Set |filteringIdMaxBytes| to [=pre-specified report parameters
map=][|batchingScope|]'s [=pre-specified report parameters/filtering ID
max bytes=].
1. If |contribution|["{{PAHistogramContribution/filteringId}}"] is not [=set/
contained=] in [=the exclusive range|the range=] 0 to
256<sup>|filteringIdMaxBytes|</sup>, exclusive, [=exception/throw=] a
{{RangeError}}.
1. Let |entry| be a new [=contribution cache entry=] with the items:
: [=contribution cache entry/contribution=]
:: |contribution|
: [=contribution cache entry/batching scope=]
:: The result of running |scopingDetails|' [=scoping details/get batching
scope steps=].
:: |batchingScope|
: [=contribution cache entry/debug scope=]
:: The result of running |scopingDetails|' [=scoping details/get debug scope
steps=].
Expand Down Expand Up @@ -349,6 +360,8 @@ An aggregatable report is a [=struct=] with the following items:
:: An [=aggregation coordinator=]
: <dfn>context ID</dfn>
:: A [=string=] or null
: <dfn>filtering ID max bytes</dfn>
:: A positive integer
: <dfn>queued</dfn>
:: A [=boolean=]

Expand Down Expand Up @@ -379,6 +392,8 @@ items:
<dl dfn-for="pre-specified report parameters">
: <dfn>context ID</dfn> (default: null)
:: A [=string=] or null
: <dfn>filtering ID max bytes</dfn> (default: [=default filtering ID max bytes=])
:: A positive integer

</dl>

Expand Down Expand Up @@ -413,6 +428,18 @@ The user agent may expose controls that allow the user to delete data from the
[=contribution cache=], the [=debug scope map=] and the [=pre-specified report
parameters map=].

Constants {#constants}
======================

<dfn>Default filtering ID max bytes</dfn> is a positive integer controlling the
max bytes used if none is explicitly chosen. Its value is 1.

<dfn>Valid filtering ID max bytes range</dfn> is a [=set=] of positive integers
controlling the allowable values of max bytes. Its value is [=the inclusive
range|the range=] 1 to 8, inclusive.

Issue: Consider adding more constants.

[=Implementation-defined=] values {#implementation-defined-values}
==================================================================

Expand Down Expand Up @@ -510,12 +537,14 @@ To <dfn>determine if a report should be sent deterministically</dfn> given a
steps. They return a [=boolean=]:
1. If |preSpecifiedParams|' [=pre-specified report parameters/context ID=] is
not null, return true.
1. If |preSpecifiedParams|' [=pre-specified report parameters/filtering ID max
bytes=] is not the [=default filtering ID max bytes=], return true.
1. Return false.

Note: If a context ID was specified, a report is sent, even if there are no
contributions or there is insufficent budget for the requested
contributions. See [Protecting against leaks via the number of
reports](#protecting-against-leaks-via-the-number-of-reports).
Note: If a context ID or non-default filtering ID max bytes was specified, a
report is sent, even if there are no contributions or there is insufficent
budget for the requested contributions. See [Protecting against leaks via
the number of reports](#protecting-against-leaks-via-the-number-of-reports).

To <dfn algorithm export>process contributions for a batching scope</dfn> given
a [=batching scope=] |batchingScope|, an [=origin=] |reportingOrigin|, a
Expand Down Expand Up @@ -598,6 +627,10 @@ scope</dfn> given a [=pre-specified report parameters=] |params| and a
1. Let |contextId| be |params|' [=pre-specified report parameters/context ID=].
1. [=Assert=]: |contextId| is null or |contextId|'s [=string/length=] is not
larger than 64.
1. Let |filteringIdMaxBytes| be |params|' [=pre-specified report parameters/
filtering ID max bytes=].
1. [=Assert=]: |filteringIdMaxBytes| is [=set/contained=] in the [=valid
filtering ID max bytes range=]
1. [=map/Set=] [=pre-specified report parameters map=][|batchingScope|] to
|params|.

Expand Down Expand Up @@ -681,6 +714,9 @@ perform the following steps. They return an [=aggregatable report=].
:: |aggregationCoordinator|
: [=aggregatable report/context ID=]
:: |preSpecifiedParams|' [=pre-specified report parameters/context ID=]
: [=aggregatable report/filtering ID max bytes=]
:: |preSpecifiedParams|' [=pre-specified report parameters/filtering ID max
bytes=]
: [=aggregatable report/queued=]
:: false
1. Return |report|.
Expand Down Expand Up @@ -909,21 +945,32 @@ To <dfn>obtain the plaintext payload</dfn> given an [=aggregatable report=]
:: 0
: {{PAHistogramContribution/value}}
:: 0
: {{PAHistogramContribution/filteringId}}
:: 0
1. [=list/Append=] |nullContribution| to |contributions|.

Note: This padding protects against the number of contributions being leaked
through the encrypted payload size, see discussion
[below](#protecting-against-leaks-via-payload-size).
1. [=list/iterate|For each=] |contribution| of |report|'s [=aggregatable report/
contributions=]:
1. Let |filteringIdMaxBytes| be |report|'s [=aggregatable report/filtering
id max bytes=].
1. [=Assert=]: |contribution|["{{PAHistogramContribution/filteringId}}"]
is [=set/contained=] in [=the exclusive range|the range=] 0 to
256<sup>|filteringIdMaxBytes|</sup>, exclusive.
1. Let |contributionData| be an [=ordered map=] of the following key/value
pairs:
: "`bucket`"
:: The result of [=encoding an integer for the payload=] given
|contribution|["{{PAHistogramContribution/bucket}}"] and 128.
|contribution|["{{PAHistogramContribution/bucket}}"] and 16.
: "`value`"
:: The result of [=encoding an integer for the payload=] given
|contribution|["{{PAHistogramContribution/value}}"] and 32.
|contribution|["{{PAHistogramContribution/value}}"] and 4.
: "`id`"
:: The result of [=encoding an integer for the payload=] given
|contribution|[="{{PAHistogramContribution/filteringId}}"] and
|filteringIdMaxBytes|.
1. [=list/Append=] |contributionData| to |payloadData|.
1. Let |payload| be an [=ordered map=] of the following key/value pairs:
: "`data`"
Expand Down Expand Up @@ -953,9 +1000,9 @@ They return a [=byte sequence=] or an error.
with |hpkeContext| and |aad|.

To <dfn>encode an integer for the payload</dfn> given an integer |intToEncode|
and an integer |bitLength|, return the representation of |intToEncode| as a
big-endian [=byte sequence=] of length |bitLength| / 8, left padding with zeroes
as necessary.
and an integer |byteLength|, return the representation of |intToEncode| as a
big-endian [=byte sequence=] of length |byteLength|, left padding with zeroes as
necessary.

To <dfn>obtain a report's shared info</dfn> given an [=aggregatable report=]
|report|, perform the following steps. They return a [=string=].
Expand All @@ -973,7 +1020,7 @@ To <dfn>obtain a report's shared info</dfn> given an [=aggregatable report=]
:: The number of seconds in |scheduledReportTime|, rounded down to the
nearest number of whole seconds and [=serialize an integer|serialized=]
: "`version`"
:: "`0.1`"
:: "`1.0`"
1. Return the result of [=serializing an infra value to a json string=] given
|sharedInfo|.

Expand Down Expand Up @@ -1038,6 +1085,7 @@ partial interface SharedStorageWorkletGlobalScope {
dictionary SharedStoragePrivateAggregationConfig {
USVString aggregationCoordinatorOrigin;
USVString contextId;
[EnforceRange] unsigned long long filteringIdMaxBytes;
};

partial dictionary SharedStorageRunOperationMethodOptions {
Expand Down Expand Up @@ -1078,15 +1126,25 @@ steps. They return a [=pre-specified report parameters=], null, or a
{{DOMException}}:
1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]
does not [=map/exist=], return null.
1. Let |privateAggregationConfig| be
|options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"].
1. Let |contextId| be null.
1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]["{{SharedStoragePrivateAggregationConfig/contextId}}"]
1. If |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/contextId}}"]
[=map/exists=], set |contextId| to
|options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]["{{SharedStoragePrivateAggregationConfig/contextId}}"].
|privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/contextId}}"].
1. If |contextId|'s [=string/length=] is greater than 64, return a new
{{DOMException}} with name "`DataError`".
1. Let |filteringIdMaxBytes| be the [=default filtering ID max bytes=].
1. If |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/filteringIdMaxBytes}}"]
[=map/exists=], set |filteringIdMaxBytes| to
|privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/filteringIdMaxBytes}}"].
1. If |filteringIdMaxBytes| is not [=set/contained=] in the [=valid filtering ID
max bytes range=], return a new {{DOMException}} with name "`DataError`".
1. Return a new [=pre-specified report parameters=] with the items:
: [=pre-specified report parameters/context ID=]
:: |contextId|
: [=pre-specified report parameters/filtering ID max bytes=]
:: |filteringIdMaxBytes|

The {{WindowSharedStorage}}'s {{WindowSharedStorage/run()}} method steps are
modified in four ways. First, add the following steps just after step 2 ("If
Expand Down Expand Up @@ -1282,6 +1340,7 @@ dictionary PASignalValue {
dictionary PAExtendedHistogramContribution {
required (PASignalValue or bigint) bucket;
required (PASignalValue or long) value;
bigint filteringId = 0;
};

[Exposed=InterestGroupScriptRunnerGlobalScope, SecureContext]
Expand Down Expand Up @@ -1349,9 +1408,16 @@ event, PAExtendedHistogramContribution contribution)</dfn> method steps are:
throw=] a {{TypeError}}.
1. Otherwise, if |contribution|["{{PAHistogramContribution/value}}"] is
negative, [=exception/throw=] a {{TypeError}}.
1. If |contribution|["{{PAExtendedHistogramContribution/filteringId}}"] is
not [=set/contained=] in [=the exclusive range|the range=] 0 to
256<sup>[=default filtering ID max bytes=]</sup>, exclusive, [=exception/
throw=] a {{TypeError}}.

Issue: Make the error types on validation issues here and above consistent
with {{PrivateAggregation/contributeToHistogram(contribution)}}.

Note: It is not currently possible to set a non-default filtering ID max
bytes for Protected Audience.
1. Let |batchingScope| be null.
1. If |event| [=string/starts with=] "`reserved.`", set |batchingScope| to the
result of running |scopingDetails|' [=scoping details/get batching scope
Expand Down Expand Up @@ -1974,7 +2040,10 @@ an <a spec="turtledove">auction config</a> |auctionConfig| and a
:: |bucket|
: {{PAHistogramContribution/value}}
:: |value|
: {{PAHistogramContribution/filteringId}}
:: 0

Issue: Consider allowing the filtering ID to be set here.
1. [=map/For each=] |ig| of the [=user agent=]'s <a spec="turtledove">
interest group set</a> whose
<a spec="turtledove" for="interest group">owner</a> is
Expand Down Expand Up @@ -2029,11 +2098,15 @@ following steps. They return a {{PAHistogramContribution}}.
1. Let |value| be |contribution|["{{PAExtendedHistogramContribution/value}}"].
1. If |value| is a {{PASignalValue}}, set |value| to the result of [=filling in
the signal value=] given |value|, 2<sup>31</sup>−1 and |leadingBidInfo|.
1. Return a new {{PAHistogramContribution}} with the items:
1. Let |filledInContribution| be a new {{PAHistogramContribution}} with the
items:
: {{PAHistogramContribution/bucket}}
:: |bucket|
: {{PAHistogramContribution/value}}
:: |value|
: {{PAHistogramContribution/filteringId}}
:: |contribution|["{{PAExtendedHistogramContribution/filteringId}}"]
1. Return |filledInContribution|.

To <dfn>fill in the signal value</dfn> given a {{PASignalValue}} |value|, an
integer |maxAllowed| and a <a spec="turtledove">leading bid info</a>
Expand Down Expand Up @@ -2218,7 +2291,8 @@ However, the number of reports with the given metadata could expose some
cross-site information. To protect against this, the API delays sending reports
by a randomized amount of time to make it difficult to determine whether a
report was sent or not from any particular event. In the case that a
[=aggregatable report/context ID=] is supplied, the API makes the number of
[=aggregatable report/context ID=] is supplied or a non-default [=aggregatable
report/filtering ID max bytes=] is specified, the API makes the number of
reports sent deterministic (sending 'null reports' if necessary -- each
containing only a contribution with a value of 0 in the payload). Additional
mitigations may also be possible in the future, e.g. adding noise to the report
Expand Down

0 comments on commit a857522

Please sign in to comment.