From c7ae28b91adf235c4957f0e092b5886ee26cf231 Mon Sep 17 00:00:00 2001
From: "Benjamin M. Case" <35273659+bmcase@users.noreply.github.com>
Date: Mon, 5 Jan 2026 09:59:25 -0500
Subject: [PATCH 1/4] anti-replay requirements for level 1
anti-replay requirements -- just the basic ones from https://github.com/w3c/attribution/issues/43 not including partitioning and requerying.
I have a separate branch with addition of associated data in the AEAD to partition reports but requires more changes and probably can wait till level 2 as @csharrison mentioned on the issue.
---
api.bs | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/api.bs b/api.bs
index a5622b9..96ea413 100644
--- a/api.bs
+++ b/api.bs
@@ -2555,6 +2555,20 @@ that was expended by the site that requested the report.
An [=aggregation service=] MUST guarantee
that it does not accept the same report more than once.
+To support anti-replay verification,
+each [=conversion report=] includes associated data
+that is authenticated as part of the AEAD encryption.
+This associated data includes:
+
+* The [=origin=] of the caller invoking the {{Attribution/measureConversion()}} API.
+* A timestamp, as determined by the device at the time of the API call.
+
+The [=aggregation service=] uses this associated data
+to shard its anti-replay mechanism.
+This sharding enables efficient verification
+that each report is processed only once,
+while allowing the service to scale.
+
# Differential Privacy # {#dp}
From aa8bf3af9dc9bf841853281501f6f9baf78d1673 Mon Sep 17 00:00:00 2001
From: "Benjamin M. Case" <35273659+bmcase@users.noreply.github.com>
Date: Mon, 5 Jan 2026 10:24:54 -0500
Subject: [PATCH 2/4] add topLevelSite in reportMetadata
---
api.bs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/api.bs b/api.bs
index 96ea413..c1450dd 100644
--- a/api.bs
+++ b/api.bs
@@ -2498,7 +2498,7 @@ and a [=list=] of [=integers=] |histogram|:
mapped to the [=isomorphic encode|encoded=] value of |topLevelSite|[1].
1. Let |reportMetadata| be encoded DAP [`ReportMetadata`](https://datatracker.ietf.org/doc/html/draft-ietf-ppm-dap-15#section-4.5.2)
- generated from |reportID|, |time|, and |extensions|.
+ generated from |reportID|, |topLevelSite|, |time|, and |extensions|.
1. Let |encryptedInputShares| be an [=list/is empty|empty=] [=list=].
From 8261113485fe062f6162902a07d4575489af2514 Mon Sep 17 00:00:00 2001
From: "Benjamin M. Case" <35273659+bmcase@users.noreply.github.com>
Date: Mon, 5 Jan 2026 10:55:12 -0500
Subject: [PATCH 3/4] use `site` consistently for anti-replay
also makes it consistent with the privacy unit section https://w3c.github.io/attribution/#dp-unit
---
api.bs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/api.bs b/api.bs
index c1450dd..cc48ad4 100644
--- a/api.bs
+++ b/api.bs
@@ -2560,7 +2560,7 @@ each [=conversion report=] includes associated data
that is authenticated as part of the AEAD encryption.
This associated data includes:
-* The [=origin=] of the caller invoking the {{Attribution/measureConversion()}} API.
+* The [=site=] of the caller invoking the {{Attribution/measureConversion()}} API.
* A timestamp, as determined by the device at the time of the API call.
The [=aggregation service=] uses this associated data
From 3ade7feeee5783816baeb17166d48847b72bed68 Mon Sep 17 00:00:00 2001
From: "Benjamin M. Case" <35273659+bmcase@users.noreply.github.com>
Date: Mon, 5 Jan 2026 11:49:19 -0500
Subject: [PATCH 4/4] use `conversionCaller` in anti-replay
---
api.bs | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/api.bs b/api.bs
index cc48ad4..1f0dd51 100644
--- a/api.bs
+++ b/api.bs
@@ -1560,6 +1560,7 @@ and [=implicit API inputs=] |implicitInputs|:
1. Let |encryptedReport| be the result of invoking construct a DAP report,
given |validatedOptions|,
|implicitInputs|' [=implicit API inputs/top-level site=],
+ |implicitInputs|' [=implicit API inputs/intermediary site=],
|implicitInputs|' [=implicit API inputs/timestamp=], and |report|.
1. Let |result| be a {{AttributionConversionResult}} with the following items:
@@ -2435,6 +2436,7 @@ To construct a DAP report,
producing a [=byte sequence=] |report|,
given [=validated conversion options=] |options|,
[=site=] |topLevelSite|,
+[=site=] or `undefined` |intermediarySite|,
[=moment=] |now|,
and a [=list=] of [=integers=] |histogram|:
@@ -2497,8 +2499,11 @@ and a [=list=] of [=integers=] |histogram|:
* The extension codepoint for [[DAP-EXT#name-requester-website-identity|requester identity]],
mapped to the [=isomorphic encode|encoded=] value of |topLevelSite|[1].
+1. Let |conversionCaller| be |intermediarySite| if |intermediarySite| is not `undefined`,
+ |topLevelSite| otherwise.
+
1. Let |reportMetadata| be encoded DAP [`ReportMetadata`](https://datatracker.ietf.org/doc/html/draft-ietf-ppm-dap-15#section-4.5.2)
- generated from |reportID|, |topLevelSite|, |time|, and |extensions|.
+ generated from |reportID|, |conversionCaller|, |time|, and |extensions|.
1. Let |encryptedInputShares| be an [=list/is empty|empty=] [=list=].
@@ -2560,7 +2565,9 @@ each [=conversion report=] includes associated data
that is authenticated as part of the AEAD encryption.
This associated data includes:
-* The [=site=] of the caller invoking the {{Attribution/measureConversion()}} API.
+* The [=site=] that invoked the {{Attribution/measureConversion()}} API.
+ This is the [=intermediary site=] if the API was called from a cross-site frame,
+ or the [=conversion site=] otherwise.
* A timestamp, as determined by the device at the time of the API call.
The [=aggregation service=] uses this associated data