coding: utf-8
title: Bidding and Auction Services docname: draft-ietf-bidding-and-auction-services-latest category: std
area: TBD workgroup: TBD keyword: Internet-Draft
ipr: trust200902
stand_alone: yes pi: [toc, sortrefs, symrefs]
name: Daniel Kocoj
organization: Google
email: dankocoj@google.com
- ins: B. R. Hamilton name: Benjamin "Russ" Hamilton organization: Google email: behamilton@google.com
normative: CBOR: RFC8949 CDDL: RFC8610 JSON: RFC8259 OHTTP: RFC9458 HPKE: RFC9180 GZIP: RFC1952 UUID: RFC9562 ISO4217: target: https://www.iso.org/iso-4217-currency-codes.html title: ISO 4217 Currency codes date: 2024 ORIGIN: target: https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin title: HTML Living Standard date: 2024 URL: target: https://url.spec.whatwg.org/#concept-url title: URL Living Standard date: 2024 ADRENDERID: target: https://wicg.github.io/turtledove/#server-auction-previous-win-ad-render-id title: Protected Audience date: 2024 IGOWNER: target: https://wicg.github.io/turtledove/#server-auction-response-interest-group-owner title: Protected Audience date: 2024
informative:
--- abstract
The Bidding and Auction Services provide a way for advertising auctions to execute in remote environments while preserving user privacy.
--- middle
Today, real-time bidding and ad auctions are executed on servers that may not provide technical guarantees of security. Some users have concerns about how their data is handled to generate relevant ads and in how that data is shared. Protected Audience API (Android, Chrome) provides ways to preserve privacy and limit third-party data sharing by serving personalized ads based on previous mobile app or web engagement.
This Bidding and Auction Services proposal outlines a way to allow Protected Audience computation to take place on cloud servers in a Trusted Execution Environment (TEE), rather than running locally on a user's device. Running workloads in a TEE in cloud has the following benefits:
- Scalable ad auctions.
- A scalable ad auction may include several buyers and sellers and that can demand more compute resources and network bandwidth.
- Lower latency of ad auctions.
- Server to server communication on the cloud is faster than multiple device to server calls.
- Adtech code can execute faster on servers with higher computing power.
- Higher utility of ad auctions.
- Servers have better processing power, therefore adtechs can run compute intensive workloads on a server for better utility.
- Lower latency of ad auctions also positively impact utility.
- Security protection
- TEEs can protect confidentiality of adtech code and signals.
- System health of the user's device.
- Ensure better system health of user's device by freeing up computational cycles and network bandwidth.
Standardized protocols for interacting with Bidding and Auction Services are essential to creating a diverse and healthy ecosystem for such services.
This document provides a specification for the request and response message format that a client can use to communicate with remote services that allows the client to offload much of the work involved in running an advertisement selection auction as part of the client's implementation of the Protected Audience API.
This document does not describe distribution of private keys to the Bidding and Auction services.
The key word "client" is to be interpreted as an implementation of this document that creates Requests ({{client-to-services}}) and consumes Responses ({{services-to-client}}). The key phrase "Bidding and Auction Services" is to be interpreted as an implementation of this document that consumes Requests and creates Responses.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 {{!RFC2119}} {{!RFC8174}} when, and only when, they appear in all capitals, as shown here.
To understand this document, it is important to know that the communication between the client and the remote services uses a request-response message exchange pattern. The request will first reach a seller service, after which the seller will forward parts of the request to buyer service. It is then up to the seller service to gather buyer responses and form a final response for the client. More detail about the seller and buyer services can be found in the server-side system design documentation.
{{format}} makes frequent use of the following definitions.
Term with CDDL Definition | Detailed Reference |
---|---|
json = tstr |
[JSON] |
uuid = tstr .regexp "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}" |
[UUID] |
origin = tstr .regexp "https://([^/:](:[0-9]+)?/" |
[ORIGIN] |
currency = tstr .size 3 .regexp /^[A-Z]{3}$/ |
[ISO4217] |
adRenderUrl = tstr |
[URL] |
adRenderId = tstr |
[ADRENDERID] |
interestGroupOwner = origin |
[IGOWNER] |
Bidding and Auction Services requests and responses have the following framing:
Byte | 0 | 0 | 1 to 4 | 5 to Size+4 | Size+5 to end |
---|---|---|---|---|---|
Bits | 7-5 | 4-0 | * | * | * |
-------- | --------- | ------------- | -------- | ----------------- | --------------- |
Contents | Version | Compression | Size | Request Payload | Padding |
where the the first 3 bits of the frame header specify the payload version and the following 5 bits specify the compression algorithm. The format described in this document corresponds to version 0.
The compression method's value in bits 4-0 in {{request-framing}} corresponds to the below table:
Compression | Description |
---|---|
0 | No Compression |
1 | Brotli {{!RFC7932}} |
2 | GZIP {{!RFC1952}} |
3-31 | Reserved |
The amount of padding depends on the type of message and will be discussed for each message type separately.
GZIP MUST be implemented by the Client and the Bidding and Auction Services, while Brotli MAY be.
This section discusses the request message sent from the client to the Bidding and Auction Services endpoint.
The request from the Client to Bidding and Auction Services consists of an [HPKE] encrypted payload with attached header (see {{request-encryption}}). The plaintext payload contains a framing header, outer request, and padding (see {{request-framing}}). The request message {{request-message}} is a [CBOR] encoded message that contains one or more compressed interest group lists {{request-groups}}.
The request is encrypted with [HPKE]. The request uses a similar encapsulated message format to that used by [OHTTP], with only an additional version field.
Encapsulated Request {
Version (8),
Key Identifier (8),
HPKE KEM ID (16),
HPKE KDF ID (16),
HPKE AEAD ID (16),
Encapsulated KEM Shared Secret (8 * Nenc),
HPKE-Protected Request (..),
}
The Version
for this message SHOULD be 0. The HPKE KEM ID
, HPKE KDF ID
,
and HPKE AEAD ID
are the key encapsulation mechanism, key derivation function
and authenticated encryption with associated data function parameters for
[HPKE]. For this protocol, a compliant implementation MUST support
DHKEM(X25519, HKDF-SHA256) (0x0020) for HPKE KEM ID
, HKDF-SHA256 (0x0001) for
HPKE KDF ID
, and AES-256-GCM (0x0002) for HPKE AEAD ID
.
Encryption of the request is similar to in [OHTTP]
Section 4.3,
only with a different media type and the Version
prepended to the encrypted
message:
- Construct a message header (
hdr
) by concatenating the values of theKey Identifier
,HPKE KEM ID
,HPKE KDF ID
, andHPKE AEAD ID
in network byte order. - Build a sequence of bytes (
info
) by concatenating the ASCII-encoded string "message/auction request", a zero byte, andhdr
. - Create a sending HPKE context by invoking
SetupBaseS()
(Section 5.1.1 of [HPKE]) with the public key of the receiverpkR
andinfo
. This yields the contextsctxt
and an encapsulation keyenc
. - Encrypt
request
by invoking theSeal()
method onsctxt
(Section 5.2 of [HPKE]) with empty associated dataaad
, yielding ciphertextct
. - Concatenate the values of
Version
,hdr
,enc
, andct
.
In pseudocode, this procedure is as follows:
hdr = concat(encode(1, key_id),
encode(2, kem_id),
encode(2, kdf_id),
encode(2, aead_id))
info = concat(encode_str("message/auction request"),
encode(1, 0),
hdr)
enc, sctxt = SetupBaseS(pkR, info)
ct = sctxt.Seal("", request)
enc_request = concat(encode(1, version), hdr, enc, ct)
A Bidding and Auction Services endpoint decrypts this encapsulated message in a similar manner to [OHTTP] Section 4.3, or more explicitly as follows:
- Parse
enc_request
intoversion
,key_id
,kem_id
,kdf_id
,aead_id
,enc
, andct
. - If
version
is not 0, return an error. - Find the matching HPKE private key,
skR
, corresponding tokey_id
. If there is no matching key, return an error. - Build a sequence of bytes (
info
) by concatenating the ASCII-encoded string "message/auction request"; a zero byte;key_id
as an 8-bit integer; pluskem_id
,kdf_id
, andaead_id
as three 16-bit integers. - Create a receiving HPKE context,
rctxt
, by invokingSetupBaseR()
(Section 5.1.1 of [HPKE]) withskR
,enc
, andinfo
. - Decrypt
ct
by invoking theOpen()
method onrctxt
(Section 5.2 of [HPKE]), with an empty associated dataaad
, yieldingrequest
and returning an error on failure.
In pseudocode, this procedure is as follows:
version, key_id, kem_id, kdf_id, aead_id, enc, ct = parse(enc_request)
if version != 0 then return error
info = concat(encode_str("message/auction request"),
encode(1, 0),
encode(1, key_id),
encode(2, kem_id),
encode(2, kdf_id),
encode(2, aead_id))
rctxt = SetupBaseR(enc, skR, info)
request, error = rctxt.Open("", ct)
Bidding and Auction Services retains the HPKE context, rctxt
, so that it can
encapsulate a response.
The plaintext message uses the framing described in {{framing}}.
Messages MAY be zero padded so that the encrypted request is one of the following bin sizes: 0KiB, 5KiB, 10KiB, 20KiB, 30KiB, 40KiB, 55KiB. An implementation MAY need to remove some data from the payload to fit inside the largest bucket.
A compatible implementation processing requests SHOULD NOT rely on a specific padding scheme for requests.
The request message is a [CBOR] encoded message with the following [CDDL] schema:
request = {
; Current version of the protocol.
; In this document, it must be 0.
version: int,
; Used by the Bidding and Auction Services to
; keep track of a request (and corresponding response)
; over its lifetime.
; Must be a [UUID][Version 4].
generationId: uuid,
; Represents the publisher initiating the request.
publisher: origin,
interestGroups: {
; Map of interest group owner to CBOR encoded list of interest
; groups compressed as described in § Generating a Request.
* interestGroupOwner => bstr
},
? enableDebugReporting: bool
}
The version
field SHOULD be set to 0 for this version of the protocol. The
interestGroups
field is a map from interest group owner to a compressed,
[CBOR]-encoded list of interest groups for that owner.
A list of interest group is encoded in [CBOR] with the following [CDDL] schema:
interestGroups = [ * interestGroup ]
interestGroup = {
; This interest group's name, see
; https://wicg.github.io/turtledove/#interest-group-name.
name: tstr,
; Keys used to look up real-time bidding signals, see
; https://wicg.github.io/turtledove/#interest-group-trusted-bidding-signals-keys.
? biddingSignalsKeys: [* tstr],
; Data about the user that the bidder can use during bid calculation, see
; https://wicg.github.io/turtledove/#interest-group-user-bidding-signals.
? userBiddingSignals: json,
; Contains various ads that the interest group might show. See
; https://wicg.github.io/turtledove/#interest-group-ads.
? ads: [* adRenderId],
; Contains various ad components (or "products") that can be used to
; construct ads composed of multiple pieces — a top-level ad template
; "container" which includes some slots that can be filled in with
; specific "products". See
; https://wicg.github.io/turtledove/#interest-group-ad-components.
? components: [* adRenderId],
? browserSignals: {
; Number of times the group was joined in the last 30 days.
? joinCount: int,
; Number of times the group bid in an auction in the last 30
; days.
? bidCount: int,
; Tuple of time-ad pairs for a previous win for this interest
; group that occurred in the last 30 days.
; The time is specified in seconds before the containing
; auctionBlob was requested.
? prevWins: [* [int, adRenderId]],
; The most recent join time for this group expressed
; in milli seconds before the containing auctionBlob
; was requested. This field will be used by newer client
; versions. For older devices, the precison will be in seconds.
; If recencyMs is present, this value will be used to offer
; higher precision. If not, recency will be used. Only
; one of the recency or recencyMs is expected to present in
; the request.
? recencyMs: int
}
}
Each list is separately compressed with the compression method indicated in the {{framing}} header.
This section describes how the client MAY form and serialize request messages in order to communicate with the Bidding and Auction services.
This algorithm takes as input all of the relevant interest groups
, a config
consisting of the publisher
, a map of from (origin, string) tuple to origin
ig pagg coordinators
,
an optional desired total size
, an optional boolean debugging report locked out
defaults to false, an optional list of interest group owners
to
include each with an optional desired size
, and the [HPKE] public key
with
its associated key ID
. It returns an encrypted request
and a request context
tuple.
- Let
included_groups
be an empty map. - If
desired total size
is not specified, but the list ofinterest group owners
includes at least one entry with a specifieddesired size
:- Set
desired total size
to the sum of all specifieddesired size
in the list ofinterest group owners
.
- Set
- Group the list of
relevant interest groups
by owner into a map of from interest group owner to a list of interest groups sorted by decreasing priority,interest group map
. - If the list of
interest group owners
is specified, remove interest groups whose owner is not on the list. - Construct a request,
request
withrequest["publisher"]
set topublisher
,request["version"]
set to 0,request["generationId"]
set to a new [UUID] Version 4, andrequest["enableDebugReporting"]
set todebugging report locked out
. - Set
current_size
to be the serialized size of the encrypted request created fromrequest
without padding. - Set
remaining_allocated_size
to 0. - Set
remaining_unsized_owners
to 0. - For each
interest group owner
,interest group list
ininterest group map
:- If there is a
desired size
forinterest group owner
:- Increment
remaining_allocated_size
bydesired size
.
- Increment
- Otherwise
- Increment
remaining_unsized_owners
by 1.
- Increment
- If there is a
- For each
interest group owner
,interest group list
ininterest group map
where there is adesired size
specified forinterest group owner
:- If the number of
unsized_owners
is not 0:- Set the
allowed_interest_group_size
to thedesired size
for thisinterest group owner
. This is a fixed size allocation.
- Set the
- Otherwise:
- Let
remaining_size
be equal to thedesired total size
-current_size
. - Set the
allowed_interest_group_size
toremaining_size
*desired_size
/remaining_allocated_size
. This is a proportional allocation.
- Let
- Set
remaining_allocated_size
=remaining_allocated_size
-current_size
. - [CBOR] encode the
interest group list
intoserialized list
. - [GZIP] the
serialized list
intocompressed list
. - If setting
request["interestGroups"][interest group owner]
tocompressed list
would make it's serialized size more thanallowed_interest_group_size
larger than the current size, then remove the lowest priority interest group and repeat from the previous step. - Set
request["interestGroups"][interest group owner]
tocompressed list
. - Set
included_groups[interest group owner]
tointerest group list
. - Set
current_size
to be the serialized size of the encrypted request created fromrequest
without padding.
- If the number of
- For each
interest group owner
,interest group list
ininterest group map
where there is notdesired size
specified forinterest group owner
:- Let
remaining_size
be equal to thedesired total size
-current_size
. - Set the
allowed_interest_group_size
toremaining_size
*/remaining_unsized_owners
. This is a equal size allocation. - Decrement
remaining_unsized_owners
by 1. - [CBOR] encode the
interest group list
intoserialized list
. - [GZIP] the
serialized list
intocompressed list
. - If adding the
compressed list
torequest
would make it more thanallowed_interest_group_size
larger than the current size, then remove the lowest priority interest group and repeat from the previous step. - Set
request["interestGroups"][interest group owner]
tocompressed list
. - Set
included_groups[interest group owner]
tointerest group list
. - Set
current_size
to be the serialized size of the encrypted request created fromrequest
without padding.
- Let
- If there are no interest groups in the request, discard the
request
and return failure. - Prepend the framing header to
request
withCompression
set to 2. - If
desired total size
is set then zero padrequest
todesired total size
. Otherwise zero padrequest
up to the smallest bin size in {{request-framing}} larger than request. - Encrypt
request
using thepublic key
and itskey id
as in {{request-encryption}} to get theencrypted message
andhpke context
. - Let the
request context
be the tuple (included_groups
,hpke context
,ig pagg coordinators
). - Return the
encrypted message
and therequest context
.
This section describes how the Bidding and Auction Services MUST deserialize request messages from the client.
The algorithm takes as input a serialized request message from the client ({{request-generate}}) and a list of HPKE private keys (along with their corresponding key IDs).
The output is either an error sent back to the client, an empty message sent back to the client, or a request message the Bidding and Auction services can consume along with an HPKE context.
- Let
encrypted request
be the request received from the client. - Let
error_msg
be an empty string. - De-encapsulate and decrypt
encrypted request
by using the input private key corresponding tokey_id
, as described in {{request-encryption}}, to get the decrypted message andrctxt
.- If decapsulation or decryption fails, return failure.
- Else, save the decrypted output as
framed request
and saverctxt
.
- Remove and extract the first 5 bytes from
framed request
as theframing header
(described in {{framing}}), removing them fromframed request
. - If the
framing header
'sVersion
field is not 0, return failure. - If the
framing header
'sCompression
field is not supported, return failure. Otherwise, save theCompression
field value ascompression type
. - Let
length
be equal to theframing header
'sSize
field. - If
length
is greater than the length of the remaining bytes inframed request
, return failure. - Take the first
length
remaining bytes inframed response
asdecodable request
, discarding the rest. - [CBOR] decode
decodable request
into the message represented in {{request-message}}. Let this berequest
. - If [CBOR] decoding fails, return failure.
- Let
processed request
be an empty struct. - If
request
is not a map, return failure. - If
request["version"]
does not exist or is not 0, return failure. - If
request["publisher"]
does not exist or is not a string, return failure. - Set
processed request["publisher"]
torequest["publisher"]
. - If
request["generationId"]
does not exist or is not a string, return failure. - Set
processed request["generationId"]
torequest["generationId"]
. - If
request["enableDebugReporting]
exists:- If
request["enableDebugReporting"]
is not a boolean, return failure. - Set
processed request["enableDebugReporting"]
torequest["enableDebugReporting"]
.
- If
- If
request["interestGroups]
does not exist or is not a map, return failure. - Set
processed request["interestGroups"]
to an empty map. - For each
key
,value
map entry ofrequest["interestGroups"]
:- If
key
is not a string, append an error message toerror_msg
. Proceed to {{request-parse-error}}. - Set
processed request["interestGroups"]``[key]
to an empty list. - Decompress
value
according tocompression type
and set asbuyer input cbor
. If decompression fails, return failure. - [CBOR] decode
buyer input cbor
intobuyer input
. If decoding fails, return failure. - If
buyer input
is not an array, return failure. - For each
interest group
inbuyer input
:- If the
interest groups
is not a map, append an error message toerror_msg
. Proceed to {{request-parse-error}}. - Let
ig
be an empty struct similar to {{request-groups}}. - If
interest group["name"]
does not exist or is not a string, return failure. - Set
ig["name"]
tointerest group["name"]
. - If
interest group["userBiddingSignals"]
exists:- If
interest group["userBiddingSignals"]
is not a string, return failure. - Set
ig["userBiddingSignals"]
tointerest group["userBiddingSignals"]
.
- If
- If
interest group["biddingSignalsKeys"]
exists:- If
interest group["biddingSignalsKeys"]
is not an array of strings, return failure. - Set
ig["biddingSignalsKeys"]
tointerest group["biddingSignalsKeys"]
.
- If
- If
interest group["ads"]
exists:- If
interest group["ads"]
is not an array of strings, return failure. - Set
ig["ads"]
tointerest group["ads"]
.
- If
- If
interest group["component"]
exists:- If
interest group["component"]
is not an array of strings, return failure. - Set
ig["component"]
tointerest group["component"]
.
- If
- If
interest group["browserSignals"]
exists:- If
interest group["browserSignals"]
is not a map, return failure. - Let
igbs
be an empty struct similar tobrowserSignals
as defined in {{request-groups}}. - Let
signals
beinterest group["browserSignals"]
. - If
signals["bidCount"]
exists:- If
signals["bidCount"]
is not a valid 64-bit unsigned integer, return failure. - Set
igbs["bidCount"]
tosignals["bidCount"]
.
- If
- If
signals["joinCount"]
exists:- If
signals["joinCount"]
is not a valid 64-bit unsigned integer, return failure. - Set
igbs["joinCount"]
tosignals["joinCount"]
.
- If
- If
signals["recencyMs"]
exists:- If
signals["recencyMs"]
is not a valid 64-bit unsigned integer, return failure. - Set
igbs["recencyMs"]
tosignals["recencyMs"]
.
- If
- If
signals["prevWins"]
exists:- Let
pw
be an empty array. - If
signals["prevWins"]
is not an array, return failure. - For each
prevWinTuple
insignals["prevWins"]
:- Let
pwt
be an empty array. - If
prevWinTuple
is not an array of size 2, return failure. - If
prevWinTuple[0]
is not a valid 64-bit unsigned integer, return failure. - If
prevWinTuple[1]
is not a string, return failure. - Set
pwt
toprevWinTuple
. - Append
pwt
topw
.
- Let
- Set
igbs["prevWins"]
topw
.
- Let
- Set
ig["browserSignals"]
toigbs
.
- If
- Append
ig
toprocessed request["interestGroups"]``[ key ]
.
- If the
- If
- Return
processed request
andrctxt
to the Bidding and Auction Services.
If {{request-parsing}} returns with failure, the following algorithm describes how the Bidding and Auction Services MUST respond.
The input to this algorithm is error_msg
, which MAY be returned from the point
of failure in {{request-parsing}}.
The output is a response to the client.
- If the failure happens before or during decryption, respond with an empty message.
- Otherwise abort processing the request.
- Let
error
be a new map with key-value pairs:[("code", 400), ("error", error_msg)]
. - Let
response
be a new {{response-message}}. - Set
response["error"]
toerror
. - Serialize and send
response
to the client per {{services-to-client}}.
This section discusses the request message sent from the Bidding and Auction Services endpoint to the client in reply to a request.
The response from the Bidding and Auction Services endpoint consists of an [HPKE] encrypted payload with attached header (see {{response-encryption}}). The plaintext payload contains a framing header, response message, and padding (see {{response-framing}}). The response message {{response-message}} is a compressed [CBOR] encoded message.
The response uses a similar encapsulated response format to that used by [OHTTP].
Encapsulated Response {
Nonce (8 * max(Nn, Nk)),
AEAD-Protected Response (..),
}
Encryption of the response is similar to in [OHTTP] Section 4.4, only with a different media type, repeated below for clarity:
- Export a secret (
secret
) fromcontext
, using the string "message/auction response" as theexporter_context
parameter tocontext.Export
; see Section 5.3 of [HPKE]. The length of this secret ismax(Nn, Nk)
, whereNn
andNk
are the length of the AEAD key and nonce that are associated withcontext
. - Generate a random value of length
max(Nn, Nk)
bytes, calledresponse_nonce
. - Extract a pseudorandom key (
prk
) using theExtract
function provided by the KDF algorithm associated with context. Theikm
input to this function issecret
; thesalt
input is the concatenation ofenc
(fromenc_request
) andresponse_nonce
. - Use the
Expand
function provided by the same KDF to create an AEAD key,key
, of lengthNk
-- the length of the keys used by the AEAD associated withcontext
. Generatingaead_key
uses a label of "key". - Use the same
Expand
function to create a nonce,nonce
, of lengthNn
-- the length of the nonce used by the AEAD. Generatingaead_nonce
uses a label of "nonce". - Encrypt
response
, passing the AEAD functionSeal
the values ofaead_key
,aead_nonce
, an emptyaad
, and apt
input ofresponse
. This yieldsct
. - Concatenate
response_nonce
andct
, yielding an Encapsulated Response,enc_response
. Note thatresponse_nonce
is of fixed length, so there is no ambiguity in parsing eitherresponse_nonce
orct
.
In pseudocode, this procedure is as follows:
secret = context.Export("message/auction response", max(Nn, Nk))
response_nonce = random(max(Nn, Nk))
salt = concat(enc, response_nonce)
prk = Extract(salt, secret)
aead_key = Expand(prk, "key", Nk)
aead_nonce = Expand(prk, "nonce", Nn)
ct = Seal(aead_key, aead_nonce, "", response)
enc_response = concat(response_nonce, ct)
Clients decrypt an Encapsulated Response by reversing this process. That is,
Clients first parse enc_response
into response_nonce
and ct
. Then, they
follow the same process to derive values for aead_key
and aead_nonce
, using
their sending HPKE context, sctxt
, as the HPKE context, context
.
The Client uses these values to decrypt ct
using the AEAD function Open
.
Decrypting might produce an error, as follows:
response, error = Open(aead_key, aead_nonce, "", ct)
The plaintext message uses the framing described in {{framing}}.
Messages MAY be exponentially padded so that the encrypted response is a power of 2 in length.
A compatible implementation processing requests SHOULD NOT rely on a specific padding scheme for requests.
The response message is a [CBOR] encoded message, compressed using the method indicated in {#request-framing}. The [CBOR] encoded message SHOULD be serialized into deterministically encoded [CBOR] (as defined in Section 4.2) and follows the following [CDDL] schema:
response = {
; The ad to render.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-ad-render-url.
adRenderURL: adRenderUrl,
; List of URLs for component ads displayed as part of this
; ad.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-ad-components.
; If not present, map as an empty list.
? components: [* adRenderUrl],
; Name of the interest group to which the ad belongs.
; See https://wicg.github.io/turtledove/#interest-group-name.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-interest-group-name.
; If not present, map as Null.
? interestGroupName: tstr,
; Origin of the Buyer who owns the interest group.
; The original request for this response MUST contain this
; interestGroupOwner, which additionally MUST provide an interest
; group with interestGroupName.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-interest-group-owner.
; If not present, map as Null.
? interestGroupOwner: interestGroupOwner,
; Indices of interest groups in the original request for this owner
; that submitted a bid.
; Maps to https://wicg.github.io/turtledove/#server-auction-response-bidding-groups.
; If not present, map as an empty list.
; Else,
; 1. create an empty list
; 2. for each interest group owner key in the biddingGroups map
; 3. for each index in biddingGroups[interest group owner]
; 4. interest group name equals the string at the index from (3)
; in Encryption Context's Interest Group Map (Section 2.2.4.1.2)
; 5. add a tuple to the list in (1) of [interest group owner (2),
interest group name (4)]
; 6. return the list in (1)
? biddingGroups: {
* interestGroupOwner => [* int]
},
; Indices and update-if-older-than times of interest groups in the original
; request for this owner for interest groups where an update-if-older-than
; time (in milliseconds) was specified.
; Maps to https://wicg.github.io/turtledove/#server-auction-response-update-groups.
? updateGroups: {
* interestGroupOwner => [
* {
index: int,
updateIfOlderThanMs: int
}]
},
; Score of the ad determined during the auction.
; Any value that is zero or negative indicates that the ad cannot
; win the auction.
; The winner of the auction would be the ad that was given the
; highest score.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-score.
; If not present, map as Null.
? score: float,
; Bid price corresponding to an ad
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-bid.
; If not present, map as Null.
? bid: float,
; Optional currency of the bid.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-bid-currency.
; If not present, map as Null.
? bidCurrency: currency,
; Optional BuyerReportingId of the winning Ad
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-buyer-reporting-id.
; If not present, map as Null.
? buyerReportingId: tstr,
; Optional BuyerAndSellerReportingId of the winning Ad
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-buyer-and-seller-reporting-id.
; If not present, map as Null.
? buyerAndSellerReportingId: tstr,
; Optional SelectedBuyerAndSellerReportingId of the winning Ad
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-selected-buyer-and-seller-reporting-id
; If not present, map as Null.
? selectedBuyerAndSellerReportingId: tstr,
; The auction result may be ignored if set to true.
; Maps to https://wicg.github.io/turtledove/#server-auction-response-is-chaff.
; If not present, map as false.
? isChaff: bool,
; Optional wrapper for various reporting URLs.
? winReportingUrls: {
; Maps to https://wicg.github.io/turtledove/#server-auction-response-buyer-reporting.
; If not present, map as 'Null'.
? buyerReportingUrls: reportingUrls,
; Maps to https://wicg.github.io/turtledove/#server-auction-response-component-seller-reporting.
; If not present, map as 'Null'.
? componentSellerReportingUrls: reportingUrls,
; Maps to https://wicg.github.io/turtledove/#server-auction-response-top-level-seller-reporting.
; If not present, map as 'Null'.
? topLevelSellerReportingUrls: reportingUrls
},
; Contains an error message from the auction executed on the trusted auction
; server.
; May be used to provide additional context for the result of an auction.
; Maps to https://wicg.github.io/turtledove/#server-auction-response-error.
; If not present, map as Null.
; Else, ignore the `code` field and use the `message` field directly
; for the server auction response error field.
? error: {
code: int,
message: tstr
},
; Arbitrary metadata to pass to the top-level seller.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-ad-metadata.
; If not present, map as Null.
? adMetadata: json,
; Optional name/domain for the top-level seller in case this is a
; component auction.
; Maps directly to https://wicg.github.io/turtledove/#server-auction-response-top-level-seller.
; If not present, map as Null.
? topLevelSeller: origin,
; Optional list of forDebuggingOnly reports.
; If not present, map as an empty list.
; Maps to https://wicg.github.io/turtledove/#server-auction-response-component-win-debugging-only-reports
; and https://wicg.github.io/turtledove/#server-auction-response-server-filtered-debugging-only-reports.
? debugReports: [
* {
origin => [
* {
url tstr,
? bool isWinReport,
? bool isSellerReport,
? bool componentWin
; Optional list of private aggregation contributions.
; If not present, map as an empty list.
? paggResponse: [
* {
origin => [
* {
? igIndex: int,
? coordinator: origin,
? componentWin: bool,
eventContributions: [
tstr => [
* {
bucket: blob,
value: int
}
]
]
}
]
}
],
}
; Defines the structure for reporting URLs.
reportingUrls = {
; Maps directly to https://wicg.github.io/turtledove/#server-auction-reporting-info-reporting-url.
; If not present, map as Null.
? reportingUrl: tstr,
; Maps directly to https://wicg.github.io/turtledove/#server-auction-reporting-info-beacon-urls.
; If not present, map as an empty ordered map (https://infra.spec.whatwg.org/#ordered-map).
? interactionReportingUrls: { * tstr => tstr }
}
This algorithm describes how conforming Bidding and Auction Services MAY generate a response to a request.
The input is a payload
corresponding to {{response-message}} and the HPKE
receiver context saved in {{request-parsing}}, rctxt
.
The output is a response
to be sent to a Client.
- Let
cbor payload
equal the deterministically encoded CBORpayload
. Return an emptyresponse
on CBOR encoding failure. - Let
compressed payload
equal the [GZIP] compressedcbor payload
, returning an emptyresponse
on compression failure. - Create a framed payload, as described in {{response-framing}}:
- Create a
framing header
. - Set the
framing header
Compression
to 2. - Set the
framing header
Version
to 0. - Set the
framing header
Size
to the size ofcompressed payload
. - Let
framed payload
equal the result of prepend the framing header tocompressed payload
. - Padding MAY be added to
framing header
, as described in {{response-framing}}. - Return an empty
response
on failure of any of the previous steps.
- Create a
- Let
response
equal the result of the encryption and encapsulation offramed payload
withrctxt
, as described in {{response-encryption}}. Return an emptyresponse
on failure. - Return
response
.
This algorithm describes how a conforming Client MUST parse and validate a
response from Bidding and Auction Services. It takes as input the
request context
tuple returned from {{request-generate}} in addition to the
encrypted response
.
- Use
request context
'shpke context
as thecontext
for decryption and follow the decryption steps in {{response-encryption}} to decryptencrypted response
and obtainframed response
anderror
. - If
error
is not null, return failure. - Remove and extract the first 5 bytes from
framed response
as theframing header
(described in {{framing}}), removing them fromframed response
. - If the
framing header
'sVersion
field is not 0, return failure. - Let
length
be equal to theframing header
'sSize
field. - If
length
is greater than the length of the remaining bytes inframed response
, return failure. - Take the first
length
remaining bytes inframed response
ascompressed response
, discarding the rest. - Decompress the
compressed response
intoserialized response
using the method indicated byframing header
'sCompression
field, returning failure if decompression fails. - [CBOR] decode the
serialized response
intoresponse
, returning failure if decompression fails. - If
response
is not a map, return failure. - If
response["error"]
exists, return failure. - If
response["isChaff"]
exists and is either not a boolean or is true, return failure. - Let
processed response
be a new structure analogous to server auction response. - If
response["adRenderURL"]
does not exist, return failure. - Set
processed response["ad render url"]
toresponse["adRenderURL"]
parsed as a [URL], returning failure if there is an error. - If
response["components"]
exists:- If
response["components"]
is not an array, return failure. - For each
component
inresponse["components"]
:- Append
component
parsed as a [URL] toprocessed response["ad components"]
, returning failure if there is an error.
- Append
- If
- If
response["interestGroupName"]
does not exist or is not a string, return failure. - Set
processed response["interest group name"]
toresponse["interestGroupName"]
. - If
response["interestGroupOwner"]
does not exist or is not a string, return failure. - Set
processed response["interest group owner"]
toresponse["interestGroupOwner"]
parsed as an [ORIGIN], returning failure if there is an error. - If
response["biddingGroups"]
does not exist or is not a map, return failure. - For each
key
,value
inresponse["biddingGroups"]
:- Let
owner
be equal tokey
parsed as an [ORIGIN], returning failure if there is an error. - If
request context
'sincluded_groups
does not containowner
as a key, return failure. - If
value
is not a list, return failure. - For each
element
invalue
:- If
element
is not an integer orelement < 0
, return failure. - If
element
is greater than or equal to the length ofincluded_groups[owner]
, return failure. - Let
name
be theinterest group name
forincluded_groups[owner][element]
. - Append the tuple (
owner
,name
) toprocessed response["bidding groups"]
.
- If
- Let
- If
response["updateGroups"]
exists and is a map:- For each
key
,value
inresponse["updateGroups"]
:- Let
owner
be equal tokey
parsed as an [ORIGIN], continuing the next iteration of this loop if there is an error. - If
request context
'sincluded_groups
does not containowner
as a key, continue the next iteration of this loop. - If
value
is not a list, return failure. - For each
element
invalue
:- If
element
is not a map, continue the next iteration of this loop. - If
element["index"]
does not exist or is not an integer orelement["updateIfOlderThanMs"]
does not exist or is not an integer, continue the next iteration of this loop. - If
element["index"]
is not an integer orelement["index"] < 0
, continue the next iteration of this loop. - If
element["index"]
is greater than or equal to the length ofincluded_groups[owner]
, continue the next iteration of this loop. - Let
name
be theinterest group name
forincluded_groups[owner][element]
. - Let
interest group key
be the tuple (owner
,name
). - Let
update duration
beelement["updateIfOlderThanMs"]
, parsed into a time duration as integer milliseconds. - Set
processed response["update groups"][intereset group key]
toupdate duration
.
- If
- Let
- For each
- If
response["score"]
exists:- If
response["score"]
is not a floating point value, return failure. - Set
processed response["score"]
toresponse["score"]
.
- If
- If
response["bid"]
exists:- If
response["bid"]
is not a floating point value, return failure. - Let
bid
be a new structure analogous to bid with currency. - Set
bid
svalue
field toresponse["bid"]
. - If
response["bidCurrency"]
exists:- If
response["bidCurrency"]
is not a string, return failure. - If
response["bidCUrrency"]
is not 3 bytes long or contains characters other than upper case ASCII letters, return failure. - Set
bid
'scurrency
field toresponse["bidCurrency"]
.
- If
- Set
processed response["bid"]
tobid
.
- If
- If
response["winReportingURLs"]
exists and is a map:- If
response["winReportingURLs"]["buyerReportingURLs"]
exists:- Let
buyer reporting
be the result of {{response-parsing-reporting}} onresponse["winReportingURLs"]["buyerReportingURLs"]
. - Set
processed response["buyer reporting"]
tobuyer reporting
.
- Let
- If
response["winReportingURLs"]["topLevelSellerReportingURLs"]
exists:- Let
top level seller reporting
be the result of {{response-parsing-reporting}} onresponse["winReportingURLs"]["topLevelSellerReportingURLs"]
. - Set
processed response["top level seller reporting"]
totop level seller reporting
.
- Let
- If
response["winReportingURLs"]["componentSellerReportingURLs"]
exists:- Let
component seller reporting
be the result of {{response-parsing-reporting}} onresponse["winReportingURLs"]["componentSellerReportingURLs"]
. - Set
processed response["component seller reporting"
tocomponent seller reporting
.
- Let
- If
- If
response["topLevelSeller"]
exists:- If
response["topLevelSeller"]
is not a string, return failure. - Set
processed response["top level seller"]
toresponse["topLevelSeller"]
parsed as a [URL], returning failure if there is an error.
- If
- If
response["adMetadata"]
exists and is a string setprocessed response["ad metadata"]
toresponse["adMetadata"]
. - If
response["buyerReportingId"]
exists and is a string, setprocessed response["buyer reporting id"]
toresponse["buyerReportingId"]
. - If
response["buyerAndSellerReportingId"]
exists and is a string, setprocessed response["buyer and seller reporting id"]
toresponse["buyerAndSellerReportingId"]
. - If
response["selectedBuyerAndSellerReportingId"]
exists and is a string, setprocessed response["selected buyer and seller reporting id"]
toresponse["selectedBuyerAndSellerReportingId"]
. - If
response["debugReports"]
exists and is an array:- For each
per origin debug reports
inresponse["debugReports"]
:- If
per origin debug reports["adTechOrigin"]
does not exist or is not a string, continue with the next iteration. - Let
ad tech origin
beper origin debug reports[ "adTechOrigin"]
parsed as an [ORIGIN], continue with the next iteration if there is an error. - If
per origin debug reports["reports"]
does not exist or is not an array, continue with the next iteration. - For each
report
inper origin debug reports["reports"]
:- If
report
is not a map, continue with the next iteration. - Let
component win
bereport["componentWin"]
if it exists and is a bool, otherwise false. - If
report["url"]
exists and is a string:- Let
url
bereport["url"]
parsed as a [URL], or continue with the next iteration if there is an error. - If
component win
is false, setprocessed response["server filtered debugging only reports"][ad tech origin]
tourl
, and continue with the next iteration. - Let
debug report key
be a new structure analogous to server auction debug report key. - Set
debug report key["from seller"]
toreport["isSellerReport"]
if it exists and is a bool, otherwise false. - Set
debug report key["is debug win"]
toreport["isWinReport"]
if it exists and is a bool, otherwise false. - Set
processed response[ "component win debugging only reports"][ debug report key]
tourl
.
- Let
- Otherwise:
- If
component win
is false andprocessed response[ "server filtered debugging only reports"]
does not containad tech origin
, setprocessed response[ "server filtered debugging only reports"][ ad tech origin]
to an empty list.
- If
- If
- If
- For each
- If
response["paggResponse"]
exists and is an array:- For each
per origin response
inpagg response
:- If
per origin response
is not a map, continue with the next iteration. - If
per origin response["reportingOrigin"]
does not exist or is not a string, continue with the next iteration. - Let
reporting origin
beper origin response["reportingOrigin"]
parsed as an [ORIGIN], continue with the next iteration if there is an error. - If
per origin response["igContributions"]
does not exist or is not an array, continue with the next iteration. - Let
names
be an empty array. - If
request context
'sincluded_groups
containsowner
as a key, setnames
to its value. - For each
ig contribution
inper origin response["igContributions"]
:- If
ig contribution
is not a map, continue with the next iteration. - Let
coordinator
be null. - If
ig contribution["coordinator"]
exists and is a string, setcoordinator
toper origin response["reportingOrigin"]
parsed as an [ORIGIN], continue with the next iteration if there is an error. - Otherwise if
ig contribution["igIndex"]
exists and is an integer:- If
ig contribution["igIndex"] < 0
or is greater than or equal to the length ofnames
, continue with the next iteration. - Let
ig key
be the tuple (owner
,names[igIndex]
). - If
request context
'sig pagg coordinators
containsig key
, setcoordinator
torequest context
'sig pagg coordinators[ig key]
.
- If
- Let
is component win
be false. - If
ig contribution["componentWin"]
exists and is a boolean, setis component win
to it. - If
ig contribution["eventContributions"]
exists and is an array:- For each
event contribution
inig contribution["eventContributions"]
:- Continue with the next iteration if any of the following conditions hold:
event contribution
is not a map;event contribution["event"]
does not exist or is not a string;event contribution["event"]
starts with "reserved.", but is not one of "reserved.win", "reserved.loss", or "reserved.always", continue with the next iteration.
- Let
event
beevent contribution["event"]
. - If
event contribution["contributions"]
exists and is an array, for eachcontribution
in it:- Continue with the next iteration if any of the following conditions hold:
contribution
is not a map;contribution["bucket"]
does not exist or is not a byte array or its size is greater than 16.contribution["value"]
does not exist or is not an integer.
- Let
private aggregation contribution
be a new structure analogous to [PAExtendedHistogramContribution] (https://wicg.github.io/turtledove/#dictdef-paextendedhistogramcontribution). - Set
private aggregation contribution["bucket"]
tocontribution["bucket"]
parsed as a big endian integer. - Set
private aggregation contribution["value"]
tocontribution["value"]
. - If
is component win
is true:- Let
key
be a new structure analogous to [server auction private aggregation contribution key] (https://wicg.github.io/turtledove/#server-auction-private-aggregation-contribution-key). - Set
key
["reporting origin"] toreporting origin
. - Set
key
["coordinator"] tocoordinator
. - Set
key
["event"] toevent
. - If
processed response["component win private aggregation contributions"]
does not containkey
, setprocessed response["component win private aggregation contributions"][key]
to a new array. - Append
private aggregation contribution
toprocessed response["component win private aggregation contributions"][key]
.
- Let
- Otherwise if
event contribution["event"]
starts with "reserved.", appendprivate aggregation contribution
toprocessed response["server filtered private aggregation contributions reserved"][key]
. - Otherwise, append
private aggregation contribution
toprocessed response["server filtered private aggregation contributions non reserved"][key]
.
- Continue with the next iteration if any of the following conditions hold:
- Continue with the next iteration if any of the following conditions hold:
- For each
- If
- If
- For each
- Return
processed response
.
To parse reporting URLs on a [CBOR] map reporting URLs
with a schema like
reportingUrls
from {{response-message}}:
- Let
processed reporting URLs
be a new structure analogous to server auction reporting info. - If
reporting URLs["reportingURL"]
exists and is a string: - Let
reporting URL
bereporting URLs["reportingURL"]
parsed as a [URL], or null if there is an error. - If
reporting URL
is not null, setprocessed reporting URLs["reporting url"]
toreporting URL
. - If
reporting URLs["interactionReportingURLs"]
exists and is a map: - For each
key
,value
inreporting URLs["interactionReportingURLs"]
: 1. Ifkey
is not a string, continue with the next iteration. 1. Letreporting URL
bevalue
parsed as a [URL]. If there is an error, continue with the next iteration. 1. Setprocessed reporting URLs["beacon urls"][key]
toreporting URL
. - Return
processed reporting URLs
.
TODO
This document introduces no additional considerations for IANA.
--- back
{:numbered="false"}
TODO