[attribution] Feedback on Attribution Level 1 draft

Hello PATWG,

This posting is in response to a request for review of Attribution Level 1
(AL1) on the PPM list [2]. I have been involved in the PPM WG at IETF for
some time, and in particular am a co-author of the Distributed Aggregation
Protocol [1].

I must confess to not having spent much or any time working at the W3C
layer before, and so while I hope my input is useful, I will almost
certainly ask questions or make observations that touch on settled
questions at the web standards layer. Please forgive any such trespass.
There's also some areas that I'm not confident in my ability to review,
such as the holistic differential privacy analysis of section 8 or numerous
web platform details.

With that sandbagging out of the way: I've organized my comments in this
response in decreasing order of significance. I'm starting with what I
think are the most substantial questions I want to raise and then after
that enumerating more mundane spelling errors or clarifications.

The headline here is that I think AL1's `dap-18-histogram` aggregation
method is a sound usage of DAP, and that while this document isn't
finished, it is on the right path. If there's some specific sentence W3C
needs me to say akin to "I support adoption" in IETF land, then I guess I'm
saying that.

Now, let's get into some complaining.

## Discovering aggregation services

3.3. Finding a Supported Aggregation Service [3] describes asking the
browser for a list of aggregation services with
`navigator.attribution?.aggregationServices`. How does an aggregation
service get in that list? My expectation would be that ad networks are the
ones who are arranging contractual relationships with aggregator operators,
not the browsers. When they do that, is there some out-of-band process by
which they register their aggregation services with browsers? Is there
something akin to web PKI root store programs where browsers vet and
monitor aggregation services?

Next, it appears that an aggregation service is represented as a key-value
pair in a dictionary, where the key is a URL like
https://aggregator.example/dap and the value is an
`AttributionAggregationProtocol` value like `dap-18-histogram`. But in DAP,
you need at the very least two URLs, one for each aggregator. In
particular, it's important that the DAP client (which in this case is the
user-agent) fetches each aggregator's HPKE configuration from a trusted
source. Privacy won't hold up if the leader provides the helper's HPKE
configuration. So: what exactly is available at
https://aggregator.example/dap? Is it maybe a JSON document which in turn
contains both aggregator URLs and other config and metadata? If so, what's
the schema of that document, and who is responsible for constructing that
document and serving it?

10.4. Unconfigured Browsers [5] discusses "prioritiz[ing] the acquisition
of aggregation service configuration in a way that is not observable to
sites" to avoid introducing a fingerprinting signal. This would include
fetching HPKE configurations for DAP aggregators. But in DAP, those HPKE
configs have a lifetime indicated by a `Cache-Control` header. So what if
HPKE config expiration races with a site measuring a conversion? Is the
user-agent required to avoid yielding control to conversion site JavaScript
unless the user-agent has satisfied itself that it has an aggregation
service config that will remain valid long enough for the site JS to run?

## DAP integration

7.1.2. DAP Extensions [7] discusses extensions AL1 needs. Having clicked
through to draft-thomson-ppm-dap-attribution-01, I'm not sure I understand
the purpose of the `budget_source` extension. It's described as being
opaque, so much so that the contents of the extension aren't even
specified. But more to the point, it's not clear what, if anything, DAP
aggregators are meant to do with this extension. If the idea is to
introduce some kind of domain separation between one task and the next,
couldn't that be achieved without an extension, for instance by using
`task_info` or the task ID? [8] That being said, I don't object to a task
extension that does nothing but indicate that all participants agree that
some mechanism is in use.

Also, 7.1.2. says the extensions are "necessary for this application". Is
"necessary" enough or should this be an RFC 2119 MUST to ensure
applications do the right thing w.r.t. constructing task extensions and
HPKE AAD?

Much of 7.1.3. Report Encryption for DAP [9] restates things already
explicitly laid out in documents like draft-ietf-ppm-dap or
draft-ietf-ppm-l1-bound-sum. For example, there's no need to dictate the
use of Field128, because Prio3L1BoundSum is already defined to use that
field and no other. Or in step 11, you say to construct report IDs by
sampling 16 bytes from a CSPRNG, which duplicates the prescription in DAP
4.4.2 [10].

I'm concerned about the risk of this document and PPM documents
accidentally disagreeing, which is a headache for implementers and requires
errata to clean up. Could AL1 say approximately "do the upload interaction
per DAP section 4.4, using the Prio3L1BoundSum VDAF"?

`min_batch_size` is fixed to 20. Can you comment on this choice? Why is it
fixed, and why 20? Same goes for the `time_precision` of 5.

`batch_mode` is TBD. OK, fine, the document isn't finished, but I'm
curious: draft-thomson-ppm-dap-attribution-01 defines a "collector chosen"
batch mode. Is the intent that AL1 will require that batch mode
unconditionally or will it allow multiple batch modes? I think either is
fine.

## Histogram layout consensus

I'm going to guess this has been argued about a bunch already. But here I
go anyway.

In 3.4. Saving Impressions [4], we learn that the `saveImpression` side
indicates the `histogramIndex` into which the impression should be
recorded. 4.1. Impression Store [18] tells us that the user-agent is meant
to store both that index and the impression site where `saveImpression` was
called.

IIUC, the histogram buckets are labeled by site. So a particular
`saveImpression` call creates an association between a histogram index and
a site, right?

The measurement site doesn't need to agree on the histogram layout, but it
does have to indicate the size of the histogram [12].

All this means that every impression site has to share a consistent view of
the histogram layout, and conversion sites at least have to agree with
other sites on the size.

What if `a.example` and `b.example` both use the same index? Or what if two
`saveImpression` calls from `a.example` use different indices? What if the
conversion site comes along and indicates a histogram size smaller than
what the impression sites expected? Do impressions whose indices are too
large just get invalidated and discarded?

It seems to me that having the impression sites indicate both index and
impression site is redundant, allowing the representation of impossible
state. And I worry that it could be challenging to get arbitarily many
impression and conversion sites to use a consistent histogram layout.

My first thought is that the user-agent could just construct the histogram
based on the observed impression site values in the impressions store,
labeling buckets as it pleases, but of course you can't do that, because
you need the collector to know what the histogram buckets represent when it
receives aggregates. This makes me wonder if the user-agent could be handed
the histogram layout explicitly and all at once instead of building it
incrementally across potentially many calls to `saveImpression` and
`measureConversion`.

I'm concerned about cases where impression sites disagreeing on histogram
layout could undetectably corrupt aggregate output. What if the
`saveImpression` and `measureConversion` API calls included a byte string
identifying a particular histogram layout? Off the top of my head, it could
be the digest of the bucket labels, ordered by histogram index. You could
then also fold that histogram layout hash into the DAP task configuration,
so that some site using stale JavaScript for whatever reason doesn't
contaminate aggregations.

## Saving impressions

3.4. Saving Impressions [4] discusses the `options.priority` parameter,
 which is set by the impression site and later used to allocate credit at
conversion measurement time (4.4.2.1). Aren't impression sites incentivized
to exaggerate their priority to get more credit?

This goes to a bigger question: what's the security model here? Are the
impression and conversion sites adversaries, or is there an assumption of
control by a common actor?

It makes intuitive sense that impression sites may wish to constrain which
sites can measure conversions via `options.{conversionSites,
conversionCallers}`. But since the impression and conversion can be
something like a week apart, does this pose an operational problem in
deployments? What if the set of legitimate conversion sites has changed
since the impression occurred?

## Measuring conversions

2. Overview of Operation [11] says:

> If the query found no impressions, or the privacy budget for the site is
exhausted, a histogram consisting entirely of zeros (0) is constructed.

We've done this in other DAP deployments, and it worked as far as hiding
the fact that some event hadn't occurred. But the extra workload from
summing zeroes was not insignificant, so much so that it wound up being a
good tradeoff to deploy Oblivious HTTP to conceal the absence of an event
instead of synthesizing empty contributions. That wouldn't work here,
because you're trying to hide the absence of an event from the local
JavaScript rather than some remote server. Anyway -- do you have a sense of
what the proportion of zero to nonzero reports will be? i.e. how much work
will be wasted? If the contributions are overwhelmingly zeroes, then it
might be worth doing something about that.

3.5. Requesting Attribution for a Conversion [12] describes the
`measureConversion` API. What happens if a conversion site calls
`measureConversion` multiple times --

- if the parameters are exactly the same both times?
  - Do both calls succeed, yielding identical histograms or does the second
call fail?
  - Or does the second call yield (unbeknownst to the caller) an all-zeroes
histogram because the first call consumed the impressions?
- if the two calls have distinct `options.aggregationService` values?
  - Is it allowed to measure the same conversions in two different
aggregation services, e.g. to check that a TEE and an MPC compute the same
results?
  - If you do allow that, does each call consume some epsilon?

## Document scope

1.5. Collective Privacy Effect [13] discusses the use of central
differential privacy, calling it "the mathematical basis for the privacy
design used in this specification". The nuts and bolts of central DP are
out of scope of the document, instead being described in the privacy budget
report extension report extension [14]. That's fair enough. But does that
mean implementations can't or shouldn't do local DP?

Are there any collector<->aggregator interactions that need to be
described? I suppose those are out of this document's scope, but where do
they get written down?

## Ticky-tacky gripes and typos

4.4.2.1. Last-N-Touch Attribution [15]:

> Let sumCredit be the sum of credit’s items.

Does this mean `sumCredit` is not necessarily 1.0f? What are the upper and
lower bounds on `sumCredit`?

1.5. Collective Privacy Effect [13]:

> If attribution is justified, both these factors motivate the enablement
of attribution for all users.

To be clear, this means this feature is on by default in user-agents?

3.2. Navigator Interface [16]: Possible typo. In the first bullet, should
"aggregation functions" be "aggregation services"? Is there a notion of
"aggregation function" distinct from a service?

3.3. Finding a Supported Aggregation Service [3] says "[t]his document
defines two protocols" but only dap-18-histogram is defined. Maybe there
used to be a TEE mechanism there?

3.5. Requesting Attribution for a Conversion [12]:

- `matchValues` is described as "A set of match values that can be used to
select this impression." That seems wrong, as this section is discussing
conversions rather than impressions.
- `credit` is defined as just "a list of numbers" which IMO is terse
compared to other definitions in this section.

4.2. State For Privacy Budget Management [17] discusses the boolean
attribution budget lock. I'm surprised the spec has to get this specific,
and indeed there's a note saying "yeah you might not actually use a
boolean". The relevant APIs return `Promise<Whatever>` anyway, so can't you
just say "accessing the store must be threadsafe" and let implementations
do the right thing? This is probably a W3C/web platform norm I'm unfamiliar
with.

In 7.1.1. Prio and DAP [6], `dap-18-histogram` is described as "aggregation
method us[ing] the Prio3L1BoundSum instantiation [PRIO-L1] of the Prio3
Verifiable Distributed Aggregation Function (VDAF) [VDAF]." Nomenclature
nitpick: Prio3L1BoundSum itself is a VDAF, which can in turn be
instantiated with parameters like length or maximum value. Prio3 is a
family of VDAFs, but there's no formal notion of VDAF families or groups.

7.1.2. DAP Extensions [7] links to the wrong version of
draft-thomsom-ppm-dap-attribution. -00 has no mention of "budget source".
You want -01. Actually, what you want is for PPM to get off our butts and
adopt that document...

In various places, the specification requires implementations to "attempt
to mitigate side channels". This is good advice. What's explicitly
discussed is timing side channels, but I wonder whether this prescription
also includes keeping encryption keys or plaintext data out of the address
space where impression or conversion site code runs? Perhaps that kind of
separation is assumed in the web platform.

That's it for now. I look forward to further discussion.
Tim

[1]: https://datatracker.ietf.org/doc/html/draft-ietf-ppm-dap-18
[2]: https://mailarchive.ietf.org/arch/msg/ppm/FS6PdCnNJOYMNshzA7lTsO13y8o/
[3]: https://w3c.github.io/attribution/#find-aggregation-service
[4]: https://w3c.github.io/attribution/#save-impression-api
[5]: https://w3c.github.io/attribution/#unconfigured
[6]: https://w3c.github.io/attribution/#prio
[7]: https://w3c.github.io/attribution/#dap-extensions
[8]: https://datatracker.ietf.org/doc/html/draft-ietf-ppm-dap-18#section-4.2
[9]: https://w3c.github.io/attribution/#encrypt-dap
[10]:
https://datatracker.ietf.org/doc/html/draft-ietf-ppm-dap-18#section-4.4.2
[11]: https://w3c.github.io/attribution/#overview
[12]: https://w3c.github.io/attribution/#measure-conversion-api
[13]: https://w3c.github.io/attribution/#collective
[14]:
https://datatracker.ietf.org/doc/html/draft-thomson-ppm-dap-attribution-01#section-3
[15]: https://w3c.github.io/attribution/#last-n-touch-attribution
[16]: https://w3c.github.io/attribution/#navigator-interface
[17]: https://w3c.github.io/attribution/#privacy-state
[18]: https://w3c.github.io/attribution/#s-impression-store

Received on Friday, 22 May 2026 16:13:37 UTC