Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spec: filtering IDs #123

Merged
merged 3 commits into from
May 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link

@linnan-github linnan-github May 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use unsigned long long to match `size_t in the implementation? Do we know whether there's similar API behavior?

Copy link

@arichiv arichiv May 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, doesn't JS max out at 53 bits for ints because that's the non-exponent part of a double (and all JS numbers are doubles)? Is an unsigned long enough?

If you really need an unsigned 64 bit int then you might need to accept it as a string and have the algo convert it internally.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just following the guidance in the Web Platform Design Principles here. We unfortunately didn't follow this for some of the other existing fields -- we may want to change those at some point.

};

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the third time we do something similar, it might be worth making an algo but up to you

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
Loading