Internet-Draft Bidding and Auction Services October 2024
Kocoj & Hamilton Expires 28 April 2025 [Page]
Workgroup:
TBD
Internet-Draft:
draft-ietf-bidding-and-auction-services-latest
Published:
Intended Status:
Standards Track
Expires:
Authors:
D. Kocoj
Google
B. R. Hamilton
Google

Bidding and Auction Services

Abstract

The Bidding and Auction Services provide a way for advertising auctions to execute in remote environments while preserving user privacy.

Discussion Venues

This note is to be removed before publishing as an RFC.

Source for this draft and an issue tracker can be found at https://github.com/privacysandbox/draft-ietf-bidding-and-auction-services.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 28 April 2025.

Table of Contents

1. Introduction

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:

Standardized protocols for interacting with Bidding and Auction Services are essential to creating a diverse and healthy ecosystem for such services.

1.1. Scope

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.

1.2. Terminology

The key word "client" is to be interpreted as an implementation of this document that creates Requests (Section 2.2) and consumes Responses (Section 2.3). 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.

2. Message Format Specifications

2.1. Overview

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.

2.1.1. Common Definitions

Section 2 makes frequent use of the following definitions.

Table 1
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]

2.1.2. Message Framing and Padding

Bidding and Auction Services requests and responses have the following framing:

Table 2
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 Section 2.2.2 corresponds to the below table:

Table 3
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.

2.2. Request Format

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 Section 2.2.1). The plaintext payload contains a framing header, outer request, and padding (see Section 2.2.2). The request message Section 2.2.3 is a [CBOR] encoded message that contains one or more compressed interest group lists Section 2.2.3.1.

2.2.1. Encryption

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:

  1. Construct a message header (hdr) by concatenating the values of the Key Identifier, HPKE KEM ID, HPKE KDF ID, and HPKE AEAD ID in network byte order.

  2. Build a sequence of bytes (info) by concatenating the ASCII-encoded string "message/auction request", a zero byte, and hdr.

  3. Create a sending HPKE context by invoking SetupBaseS() (Section 5.1.1 of [HPKE]) with the public key of the receiver pkR and info. This yields the context sctxt and an encapsulation key enc.

  4. Encrypt request by invoking the Seal() method on sctxt (Section 5.2 of [HPKE]) with empty associated data aad, yielding ciphertext ct.

  5. Concatenate the values of Version, hdr, enc, and ct.

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:

  1. Parse enc_request into version, key_id, kem_id, kdf_id, aead_id, enc, and ct.

  2. If version is not 0, return an error.

  3. Find the matching HPKE private key, skR, corresponding to key_id. If there is no matching key, return an error.

  4. 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; plus kem_id, kdf_id, and aead_id as three 16-bit integers.

  5. Create a receiving HPKE context, rctxt, by invoking SetupBaseR() (Section 5.1.1 of [HPKE]) with skR, enc, and info.

  6. Decrypt ct by invoking the Open() method on rctxt (Section 5.2 of [HPKE]), with an empty associated data aad, yielding request 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.

2.2.2. Framing and Padding

The plaintext message uses the framing described in Section 2.1.2.

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.

2.2.3. Request Message

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.

2.2.3.1. List of Interest Groups

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 Section 2.1.2 header.

2.2.4. Generating a Request

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, and a config consisting of the publisher, an optional desired total size, an optional list of interest group owners to include each with an optional desired size, and the [HPKE] public key and its associated key ID. It returns an encrypted request and a request context tuple.

  1. Let included_groups be an empty map.

  2. If desired total size is not specified, but the list of interest group owners includes at least one entry with a specified desired size:

    1. Set desired total size to the sum of all specified desired size in the list of interest group owners.

  3. 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.

  4. If the list of interest group owners is specified, remove interest groups whose owner is not on the list.

  5. Construct a request, request with request["publisher"] set to publisher, request["version"] set to 0, request["generationId"] set to a new [UUID] Version 4, and request["enableDebugReporting"] set to true.

  6. Set current_size to be the serialized size of the encrypted request created from request without padding.

  7. Set remaining_allocated_size to 0.

  8. Set remaining_unsized_owners to 0.

  9. For each interest group owner, interest group list in interest group map:

    1. If there is a desired size for interest group owner:

      1. Increment remaining_allocated_size by desired size.

    2. Otherwise

      1. Increment remaining_unsized_owners by 1.

  10. For each interest group owner, interest group list in interest group map where there is a desired size specified for interest group owner:

    1. If the number of unsized_owners is not 0:

      1. Set the allowed_interest_group_size to the desired size for this interest group owner. This is a fixed size allocation.

    2. Otherwise:

      1. Let remaining_size be equal to the desired total size-current_size.

      2. Set the allowed_interest_group_size to remaining_size*desired_size/remaining_allocated_size. This is a proportional allocation.

    3. Set remaining_allocated_size = remaining_allocated_size-current_size.

    4. [CBOR] encode the interest group list into serialized list.

    5. [GZIP] the serialized list into compressed list.

    6. If setting request["interestGroups"][interest group owner] to compressed list would make it's serialized size more than allowed_interest_group_size larger than the current size, then remove the lowest priority interest group and repeat from the previous step.

    7. Set request["interestGroups"][interest group owner] to compressed list.

    8. Set included_groups[interest group owner] to interest group list.

    9. Set current_size to be the serialized size of the encrypted request created from request without padding.

  11. For each interest group owner, interest group list in interest group map where there is not desired size specified for interest group owner:

    1. Let remaining_size be equal to the desired total size-current_size.

    2. Set the allowed_interest_group_size to remaining_size*/remaining_unsized_owners. This is a equal size allocation.

    3. Decrement remaining_unsized_owners by 1.

    4. [CBOR] encode the interest group list into serialized list.

    5. [GZIP] the serialized list into compressed list.

    6. If adding the compressed list to request would make it more than allowed_interest_group_size larger than the current size, then remove the lowest priority interest group and repeat from the previous step.

    7. Set request["interestGroups"][interest group owner] to compressed list.

    8. Set included_groups[interest group owner] to interest group list.

    9. Set current_size to be the serialized size of the encrypted request created from request without padding.

  12. If there are no interest groups in the request, discard the request and return failure.

  13. Prepend the framing header to request with Compression set to 2.

  14. If desired total size is set then zero pad request to desired total size. Otherwise zero pad request up to the smallest bin size in Section 2.2.2 larger than request.

  15. Encrypt request using the public key and its key id as in Section 2.2.1 to get the encrypted message and hpke context.

  16. Let the request context be the tuple (included_groups, hpke context)

  17. Return the request and the request context.

2.2.5. Parsing a Request

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 (Section 2.2.4) 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.

  1. Let encrypted request be the request received from the client.

  2. Let error_msg be an empty string.

  3. De-encapsulate and decrypt encrypted request by using the input private key corresponding to key_id, as described in Section 2.2.1, to get the decrypted message and rctxt.

    1. If decapsulation or decryption fails, return failure.

    2. Else, save the decrypted output as framed request and save rctxt.

  4. Remove and extract the first 5 bytes from framed request as the framing header (described in Section 2.1.2), removing them from framed request.

  5. If the framing header's Version field is not 0, return failure.

  6. If the framing header's Compression field is not supported, return failure. Otherwise, save the Compression field value as compression type.

  7. Let length be equal to the framing header's Size field.

  8. If length is greater than the length of the remaining bytes in framed request, return failure.

  9. Take the first length remaining bytes in framed response as decodable request, discarding the rest.

  10. [CBOR] decode decodable request into the message represented in Section 2.2.3. Let this be request.

  11. If [CBOR] decoding fails, return failure.

  12. Let processed request be an empty struct.

  13. If request is not a map, return failure.

  14. If request["version"] does not exist or is not 0, return failure.

  15. If request["publisher"] does not exist or is not a string, return failure.

  16. Set processed request["publisher"] to request["publisher"].

  17. If request["generationId"] does not exist or is not a string, return failure.

  18. Set processed request["generationId"] to request["generationId"].

  19. If request["enableDebugReporting] exists:

    1. If request["enableDebugReporting"] is not a boolean, return failure.

    2. Set processed request["enableDebugReporting"] to request["enableDebugReporting"].

  20. If request["interestGroups] does not exist or is not a map, return failure.

  21. Set processed request["interestGroups"] to an empty map.

  22. For each key, value map entry of request["interestGroups"]:

    1. If key is not a string, append an error message to error_msg. Proceed to Section 2.2.5.1.

    2. Set processed request["interestGroups"][key] to an empty list.

    3. Decompress value according to compression type and set as buyer input cbor. If decompression fails, return failure.

    4. [CBOR] decode buyer input cbor into buyer input. If decoding fails, return failure.

    5. If buyer input is not an array, return failure.

    6. For each interest group in buyer input:

      1. If the interest groups is not a map, append an error message to error_msg. Proceed to Section 2.2.5.1.

      2. Let ig be an empty struct similar to Section 2.2.3.1.

      3. If interest group["name"] does not exist or is not a string, return failure.

      4. Set ig["name"] to interest group["name"].

      5. If interest group["userBiddingSignals"] exists:

        1. If interest group["userBiddingSignals"] is not a string, return failure.

        2. Set ig["userBiddingSignals"] to interest group["userBiddingSignals"].

      6. If interest group["biddingSignalsKeys"] exists:

        1. If interest group["biddingSignalsKeys"] is not an array of strings, return failure.

        2. Set ig["biddingSignalsKeys"] to interest group["biddingSignalsKeys"].

      7. If interest group["ads"] exists:

        1. If interest group["ads"] is not an array of strings, return failure.

        2. Set ig["ads"] to interest group["ads"].

      8. If interest group["component"] exists:

        1. If interest group["component"] is not an array of strings, return failure.

        2. Set ig["component"] to interest group["component"].

      9. If interest group["browserSignals"] exists:

        1. If interest group["browserSignals"] is not a map, return failure.

        2. Let igbs be an empty struct similar to browserSignals as defined in Section 2.2.3.1.

        3. Let signals be interest group["browserSignals"].

        4. If signals["bidCount"] exists:

          1. If signals["bidCount"] is not a valid 64-bit unsigned integer, return failure.

          2. Set igbs["bidCount"] to signals["bidCount"].

        5. If signals["joinCount"] exists:

          1. If signals["joinCount"] is not a valid 64-bit unsigned integer, return failure.

          2. Set igbs["joinCount"] to signals["joinCount"].

        6. If signals["recencyMs"] exists:

          1. If signals["recencyMs"] is not a valid 64-bit unsigned integer, return failure.

          2. Set igbs["recencyMs"] to signals["recencyMs"].

        7. If signals["prevWins"] exists:

          1. Let pw be an empty array.

          2. If signals["prevWins"] is not an array, return failure.

          3. For each prevWinTuple in signals["prevWins"]:

            1. Let pwt be an empty array.

            2. If prevWinTuple is not an array of size 2, return failure.

            3. If prevWinTuple[0] is not a valid 64-bit unsigned integer, return failure.

            4. If prevWinTuple[1] is not a string, return failure.

            5. Set pwt to prevWinTuple.

            6. Append pwt to pw.

          4. Set igbs["prevWins"] to pw.

        8. Set ig["browserSignals"] to igbs.

      10. Append ig to processed request["interestGroups"][ key ].

  23. Return processed request and rctxt to the Bidding and Auction Services.

2.2.5.1. Request Parse Error Handling

If Section 2.2.5 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 Section 2.2.5.

The output is a response to the client.

  1. If the failure happens before or during decryption, respond with an empty message.

  2. Otherwise abort processing the request.

  3. Let error be a new map with key-value pairs: [("code", 400), ("error", error_msg)].

  4. Let response be a new Section 2.3.3.

  5. Set response["error"] to error.

  6. Serialize and send response to the client per Section 2.3.

2.3. Response Format

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 Section 2.3.1). The plaintext payload contains a framing header, response message, and padding (see Section 2.3.2). The response message Section 2.3.3 is a compressed [CBOR] encoded message.

2.3.1. Encryption

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:

  1. Export a secret (secret) from context, using the string "message/auction response" as the exporter_context parameter to context.Export; see Section 5.3 of [HPKE]. The length of this secret is max(Nn, Nk), where Nn and Nk are the length of the AEAD key and nonce that are associated with context.

  2. Generate a random value of length max(Nn, Nk) bytes, called response_nonce.

  3. Extract a pseudorandom key (prk) using the Extract function provided by the KDF algorithm associated with context. The ikm input to this function is secret; the salt input is the concatenation of enc (from enc_request) and response_nonce.

  4. Use the Expand function provided by the same KDF to create an AEAD key, key, of length Nk -- the length of the keys used by the AEAD associated with context. Generating aead_key uses a label of "key".

  5. Use the same Expand function to create a nonce, nonce, of length Nn -- the length of the nonce used by the AEAD. Generating aead_nonce uses a label of "nonce".

  6. Encrypt response, passing the AEAD function Seal the values of aead_key, aead_nonce, an empty aad, and a pt input of response. This yields ct.

  7. Concatenate response_nonce and ct, yielding an Encapsulated Response, enc_response. Note that response_nonce is of fixed length, so there is no ambiguity in parsing either response_nonce or ct.

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)

2.3.2. Framing and Padding

The plaintext message uses the framing described in Section 2.1.2.

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.

2.3.3. Response Message

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,

  ; 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,
}

; 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 }
}

2.3.4. Generating a Response

This algorithm describes how conforming Bidding and Auction Services MAY generate a response to a request.

The input is a payload corresponding to Section 2.3.3 and the HPKE receiver context saved in Section 2.2.5, rctxt.

The output is a response to be sent to a Client.

  1. Let cbor payload equal the deterministically encoded CBOR payload. Return an empty response on CBOR encoding failure.

  2. Let compressed payload equal the [GZIP] compressed cbor payload, returning an empty response on compression failure.

  3. Create a framed payload, as described in Section 2.3.2:

    1. Create a framing header.

    2. Set the framing header Compression to 2.

    3. Set the framing header Version to 0.

    4. Set the framing header Size to the size of compressed payload.

    5. Let framed payload equal the result of prepend the framing header to compressed payload.

    6. Padding MAY be added to framing header, as described in Section 2.3.2.

    7. Return an empty response on failure of any of the previous steps.

  4. Let response equal the result of the encryption and encapsulation of framed payload with rctxt, as described in Section 2.3.1. Return an empty response on failure.

  5. Return response.

2.3.5. Parsing a 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 Section 2.2.4 in addition to the encrypted response.

  1. Use request context's hpke context as the context for decryption and follow the decryption steps in Section 2.3.1 to decrypt encrypted response and obtain framed response and error.

  2. If error is not null, return failure.

  3. Remove and extract the first 5 bytes from framed response as the framing header (described in Section 2.1.2), removing them from framed response.

  4. If the framing header's Version field is not 0, return failure.

  5. Let length be equal to the framing header's Size field.

  6. If length is greater than the length of the remaining bytes in framed response, return failure.

  7. Take the first length remaining bytes in framed response as compressed response, discarding the rest.

  8. Decompress the compressed response into serialized response using the method indicated by framing header's Compression field, returning failure if decompression fails.

  9. [CBOR] decode the serialized response into response, returning failure if decompression fails.

  10. If response is not a map, return failure.

  11. If response["error"] exists, return failure.

  12. If response["isChaff"] exists and is either not a boolean or is true, return failure.

  13. Let processed response be a new structure analogous to server auction response.

  14. If response["adRenderURL"] does not exist, return failure.

  15. Set processed response["ad render url"] to response["adRenderURL"] parsed as a [URL], returning failure if there is an error.

  16. If response["components"] exists:

  17. If response["components"] is not an array, return failure.

  18. For each component in response["components"]: 1. Append component parsed as a [URL] to processed response["ad components"], returning failure if there is an error.

  19. If response["interestGroupName"] does not exist or is not a string, return failure.

  20. Set processed response["interest group name"] to response["interestGroupName"].

  21. If response["interestGroupOwner"] does not exist or is not a string, return failure.

  22. Set processed response["interest group owner"] to response["interestGroupOwner"] parsed as an [ORIGIN], returning failure if there is an error.

  23. If response["biddingGroups"] does not exist or is not a map, return failure.

  24. For each key, value in response["biddingGroups"]:

  25. Let owner be equal to key parsed as an [ORIGIN], returning failure if there is an error.

  26. request context's included_groups does not contain owner as a key, return failure.

  27. If value is not a list, return failure.

  28. For each element in value: 1. If element is not an integer or element < 0, return failure. 1. If element is greater than or equal to the length of included_groups[owner], return failure. 1. Let name be the interest group name for included_groups[owner][element]. 1. Append the tuple (owner, name) to processed response["bidding groups"].

  29. If response["updateGroups"] exists and is a map:

  30. For each key, value in response["updateGroups"]: 1. Let owner be equal to key parsed as an [ORIGIN], continuing the next iteration of this loop if there is an error. 1. If request context's included_groups does not contain owner as a key, continue the next iteration of this loop. 1. If value is not a list, return failure. 1. For each element in value:

    1. If element is not a map, continue the next iteration of this loop.

    2. If element["index"] does not exist or is not an integer or element["updateIfOlderThanMs"] does not exist or is not an integer, continue the next iteration of this loop.

    3. If element["index"] is not an integer or element["index"] < 0, continue the next iteration of this loop.

    4. If element["index"] is greater than or equal to the length of included_groups[owner], continue the next iteration of this loop.

    5. Let name be the interest group name for included_groups[owner][element].

    6. Let interest group key be the tuple (owner, name).

    7. Let update duration be element["updateIfOlderThanMs"], parsed into a time duration as integer milliseconds.

    8. Set processed response["update groups"][intereset group key] to update duration.

  31. If response["score"] exists:

  32. If response["score"] is not a floating point value, return failure.

  33. Set processed response["score"] to response["score"].

  34. If response["bid"] exists:

  35. If response["bid"] is not a floating point value, return failure.

  36. Let bid be a new structure analogous to bid with currency.

  37. Set bids value field to response["bid"].

  38. If response["bidCurrency"] exists: 1. If response["bidCurrency"] is not a string, return failure. 1. If response["bidCUrrency"] is not 3 bytes long or contains characters other than upper case ASCII letters, return failure. 1. Set bid's currency field to response["bidCurrency"].

  39. Set processed response["bid"] to bid.

  40. If response["winReportingURLs"] exists and is a map:

  41. If response["winReportingURLs"]["buyerReportingURLs"] exists: 1. Let buyer reporting be the result of Section 2.3.5.1 on response["winReportingURLs"]["buyerReportingURLs"]. 1. Set processed response["buyer reporting"] to buyer reporting.

  42. If response["winReportingURLs"]["topLevelSellerReportingURLs"] exists: 1. Let top level seller reporting be the result of Section 2.3.5.1 on response["winReportingURLs"]["topLevelSellerReportingURLs"]. 1. Set processed response["top level seller reporting"] to top level seller reporting.

  43. If response["winReportingURLs"]["componentSellerReportingURLs"] exists: 1. Let component seller reporting be the result of Section 2.3.5.1 on response["winReportingURLs"]["componentSellerReportingURLs"]. 1. Set processed response["component seller reporting" to component seller reporting.

  44. If response["topLevelSeller"] exists:

  45. If response["topLevelSeller"] is not a string, return failure.

  46. Set processed response["top level seller"] to response["topLevelSeller"] parsed as a [URL], returning failure if there is an error.

  47. If response["adMetadata"] exists and is a string set processed response["ad metadata"] to response["adMetadata"].

  48. If response["buyerReportingId"] exists and is a string set processed response["buyer reporting id"] to response["buyerReportingId"].

  49. If response["buyerAndSellerReportingId"] exists and is a string set processed response["buyer and seller reporting id"] to response["buyerAndSellerReportingId"].

  50. Return processed response.

2.3.5.1. Parsing reporting URLs

To parse reporting URLs on a [CBOR] map reporting URLs with a schema like reportingUrls from Section 2.3.3:

  1. Let processed reporting URLs be a new structure analogous to server auction reporting info.

  2. If reporting URLs["reportingURL"] exists and is a string:

  3. Let reporting URL be reporting URLs["reportingURL"] parsed as a [URL], or null if there is an error.

  4. If reporting URL is not null, set processed reporting URLs["reporting url"] to reporting URL.

  5. If reporting URLs["interactionReportingURLs"] exists and is a map:

  6. For each key, value in reporting URLs["interactionReportingURLs"]: 1. If key is not a string, continue with the next iteration. 1. Let reporting URL be value parsed as a [URL]. If there is an error, continue with the next iteration. 1. Set processed reporting URLs["beacon urls"][key] to reporting URL.

  7. Return processed reporting URLs.

3. Security Considerations

TODO

4. IANA Considerations

This document introduces no additional considerations for IANA.

5. Normative References

[ADRENDERID]
"Protected Audience", , <https://wicg.github.io/turtledove/#server-auction-previous-win-ad-render-id>.
[CBOR]
Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, , <https://www.rfc-editor.org/rfc/rfc8949>.
[CDDL]
Birkholz, H., Vigano, C., and C. Bormann, "Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures", RFC 8610, DOI 10.17487/RFC8610, , <https://www.rfc-editor.org/rfc/rfc8610>.
[GZIP]
Deutsch, P., "GZIP file format specification version 4.3", RFC 1952, DOI 10.17487/RFC1952, , <https://www.rfc-editor.org/rfc/rfc1952>.
[HPKE]
Barnes, R., Bhargavan, K., Lipp, B., and C. Wood, "Hybrid Public Key Encryption", RFC 9180, DOI 10.17487/RFC9180, , <https://www.rfc-editor.org/rfc/rfc9180>.
[IGOWNER]
"Protected Audience", , <https://wicg.github.io/turtledove/#server-auction-response-interest-group-owner>.
[ISO4217]
"ISO 4217 Currency codes", , <https://www.iso.org/iso-4217-currency-codes.html>.
[JSON]
Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/rfc/rfc8259>.
[OHTTP]
Thomson, M. and C. A. Wood, "Oblivious HTTP", RFC 9458, DOI 10.17487/RFC9458, , <https://www.rfc-editor.org/rfc/rfc9458>.
[ORIGIN]
"HTML Living Standard", , <https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin>.
[RFC1952]
Deutsch, P., "GZIP file format specification version 4.3", RFC 1952, DOI 10.17487/RFC1952, , <https://www.rfc-editor.org/rfc/rfc1952>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC7932]
Alakuijala, J. and Z. Szabadka, "Brotli Compressed Data Format", RFC 7932, DOI 10.17487/RFC7932, , <https://www.rfc-editor.org/rfc/rfc7932>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.
[URL]
"URL Living Standard", , <https://url.spec.whatwg.org/#concept-url>.
[UUID]
Davis, K., Peabody, B., and P. Leach, "Universally Unique IDentifiers (UUIDs)", RFC 9562, DOI 10.17487/RFC9562, , <https://www.rfc-editor.org/rfc/rfc9562>.

Acknowledgments

TODO

Authors' Addresses

Daniel Kocoj
Google
Benjamin "Russ" Hamilton
Google