diff --git a/spec.bs b/spec.bs
index 218b27a..bb7b119 100644
--- a/spec.bs
+++ b/spec.bs
@@ -131,6 +131,7 @@ interface PrivateAggregation {
dictionary PAHistogramContribution {
required bigint bucket;
required long value;
+ bigint filteringId = 0;
};
dictionary PADebugModeOptions {
@@ -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|filteringIdMaxBytes|, 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=].
@@ -349,6 +360,8 @@ An aggregatable report is a [=struct=] with the following items:
:: An [=aggregation coordinator=]
: context ID
:: A [=string=] or null
+: filtering ID max bytes
+:: A positive integer
: queued
:: A [=boolean=]
@@ -379,6 +392,8 @@ items:
: context ID (default: null)
:: A [=string=] or null
+: filtering ID max bytes (default: [=default filtering ID max bytes=])
+:: A positive integer
@@ -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}
+======================
+
+Default filtering ID max bytes is a positive integer controlling the
+max bytes used if none is explicitly chosen. Its value is 1.
+
+Valid filtering ID max bytes range 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}
==================================================================
@@ -510,12 +537,14 @@ To determine if a report should be sent deterministically 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 process contributions for a batching scope given
a [=batching scope=] |batchingScope|, an [=origin=] |reportingOrigin|, a
@@ -598,6 +627,10 @@ scope 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|.
@@ -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|.
@@ -909,6 +945,8 @@ To obtain the plaintext payload 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
@@ -916,14 +954,23 @@ To obtain the plaintext payload given an [=aggregatable report=]
[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|filteringIdMaxBytes|, 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`"
@@ -953,9 +1000,9 @@ They return a [=byte sequence=] or an error.
with |hpkeContext| and |aad|.
To encode an integer for the payload 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 obtain a report's shared info given an [=aggregatable report=]
|report|, perform the following steps. They return a [=string=].
@@ -973,7 +1020,7 @@ To obtain a report's shared info 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|.
@@ -1038,6 +1085,7 @@ partial interface SharedStorageWorkletGlobalScope {
dictionary SharedStoragePrivateAggregationConfig {
USVString aggregationCoordinatorOrigin;
USVString contextId;
+ [EnforceRange] unsigned long long filteringIdMaxBytes;
};
partial dictionary SharedStorageRunOperationMethodOptions {
@@ -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
@@ -1282,6 +1340,7 @@ dictionary PASignalValue {
dictionary PAExtendedHistogramContribution {
required (PASignalValue or bigint) bucket;
required (PASignalValue or long) value;
+ bigint filteringId = 0;
};
[Exposed=InterestGroupScriptRunnerGlobalScope, SecureContext]
@@ -1349,9 +1408,16 @@ event, PAExtendedHistogramContribution contribution) 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[=default filtering ID max bytes=], 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
@@ -1974,7 +2040,10 @@ an auction config |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
interest group set whose
owner is
@@ -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|, 231−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 fill in the signal value given a {{PASignalValue}} |value|, an
integer |maxAllowed| and a leading bid info
@@ -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