Internet-Draft Privacy Pass Issuance Protocol for ACT October 2025
Schlesinger & Katz Expires 9 April 2026 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-schlesinger-privacypass-act-latest
Published:
Intended Status:
Standards Track
Expires:
Authors:
S. Schlesinger
Google
J. Katz
Google

Privacy Pass Issuance Protocol for Anonymous Credit Tokens

Abstract

This document specifies the issuance and redemption protocols for tokens based on the Anonymous Credit Tokens (ACT) protocol.

About This Document

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

The latest revision of this draft can be found at https://example.com/LATEST. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-schlesinger-privacypass-act/.

Discussion of this document takes place on the PRIVACYPASS Privacy Pass mailing list (mailto:WG@example.com), which is archived at https://example.com/WG.

Source for this draft and an issue tracker can be found at https://github.com/USER/REPO.

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 9 April 2026.

Table of Contents

1. Introduction

[ARCHITECTURE] describes the Privacy Pass architecture, and [ISSUANCE] and [AUTHSCHEME] describe the issuance and redemption protocols for basic Privacy Pass tokens, i.e., those computed using blind RSA signatures as specified in Section 6 of [ISSUANCE] or verifiable oblivious pseudorandom functions as specified in Section 5 of [ISSUANCE]. Further, [ARC] scheme, and its associated integration in [ARCHITECTURE] [ARC_PP], extends these approaches to multi-use tokens.

The Anonymous Credit Tokens (ACT) protocol, as specified in [ACT], offers a differentiated approach to rate limiting from [ARC]. In particular, ACT credentials can be presented up-to N times. When they spend a certain number of credits from their token, their old token is invalidated and they redeem a new token with the new balance.

This document specifies the issuance and redemption protocols for ACT. Section 2 describes motivation for this new type of token, Section 4 presents an overview of the protocols, and the remainder of the document specifies the protocols themselves.

2. Motivation

To demonstrate how ACT is useful, one can use a similar example to the the one presented in Section 2 of [ARC_PP]: a client that wishes to keep its IP address private while accessing a service. [ARC_PP] offers the origin to limit the number of requests a client can make to N. This is enforced by each origin getting its own presentation context, and limiting the number of presentations per context to N. This means that, from a single token, we can produce N presentations and access the system N times, unlinkably. These presentations can be generated in parallel.

On the other hand, consider the case of an ACT with N credits in it. A client willing to redeem N different credits has to spend 1, then get a refund, spend 1, then get a refund, and so on. Because the client can't spend 1 until they get a refund for their previous credit, a single live session is enforced per ACT. This provides concurrency control. A client is also able to spend more than 1, allowing for a more efficient redemption of multipe tokens. Finally, as new presentation requires the obtention of a previous refund, the origin gains the ability to invalidate a session by declining said refund. This creates the ability to shed harmful future traffic or redirect it in a favorable way.

One such use case for this is a privacy proxy, another is privately accessing web APIs like the artificial intelligence models, and finally zero trust networks which act as forward proxies for their user traffic.

Therefore, ACT provides the following properties

  1. Concurrency control: Preventing multiple simultaneous uses of the same credential, mitigating abuse from token sharing or replay.

  2. Dynamic Revocation: Enabling immediate invalidation of tokens in response to origin policy, without waiting for token expiry.

  3. Per-Session Rate Limiting: Enforcing access policies that adapt to user, device, or risk context, rather than static per-token limits. This creates incentives for platform to deploy such methods.

3. Terminology

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.

This document uses the terms Origin, Client, Issuer, and Token as defined in Section 2 of [ARCHITECTURE]. Moreover, the following additional terms are used throughout this document.

Unless otherwise specified, this document encodes protocol messages in TLS notation from Section 3 of [TLS13]. Moreover, all constants are in network byte order.

4. Protocol Overview

The issuance and redemption protocols defined in this document are built on the Anonymous Credit Tokens (ACT) protocol. ACT tokens can be thought of as single use tokens, similar to the RSA Blind Signatures protocol. However, by another viewpoint, they might be thought of as stateful, multi-use tokens.

With ACT, Clients receive TokenChallenge inputs from the redemption protocol ([AUTHSCHEME], Section 2.1). If they have a valid ACT for the designated Issuer, Clients can use the TokenChallenge to produce a single token for presentation. Otherwise, Clients invoke the issuance protocol to obtain an ACT. This interaction is shown below.

Client Attester Issuer Origin Request TokenChallenge Attestation CredentialRequest CredentialResponse | Request + Token | |
Figure 1: Issuance and Redemption Overview

Similar to the core Privacy Pass protocols, the TokenChallenge can be interactive or non-interactive, and per-origin or cross-origin.

ACT is only compatible with deployment models where the Issuer and Origin are operated by the same entity (see Section 4 of [ARCHITECTURE]), as tokens produced from a credential are not publicly verifiable. The details of attestation are outside the scope of the issuance protocol; see Section 4 of [ARCHITECTURE] for information about how attestation can be implemented in each of the relevant deployment models.

The issuance and redemption protocols in this document are built on [ACT].

5. Configuration

ACT Issuers are configured with key material used for issuance and token verification. Concretely, Issuers run the KeyGen function from [ACT] to produce a private and public key, denoted skI and pkI, respectively.

skI, pkI = SetupServer()

The Issuer Public Key ID, denoted issuer_key_id, is computed as the SHA-256 hash of the Issuer Public Key, i.e., issuer_key_id = SHA-256(pkI_serialized), where pkI_serialized is the serialized version of pk as described in Section 4.1 of [ACT] (TODO actually write and sync serialization, CBOR and TLS seems weird).

6. Token Challenge Requirements

The ACT protocol uses a modified TokenChallenge structure from the one specified in [AUTHSCHEME]. In particular, the updated TokenChallenge structure is as follows:

struct {
    uint16_t token_type = 0xE5AC; /* Type ACT(Ristretto255) */
    opaque issuer_name<1..2^16-1>;
    opaque redemption_context<0..32>;
    opaque origin_info<0..2^16-1>;
    opaque credential_context<0..32>;
} TokenChallenge;

With the exception of credential_context, all fields are exactly as specified in Section 2.1.1 of [AUTHSCHEME]. The credential_context field is defined as follows:

Similar to the redemption_context field, the credential_context is used to bind information to the credential. This might be useful, for example, to enforce some expiration on the credential. Origins might do this by constructing credential_context as F(current time window), where F is a pseudorandom function. Semantically, this is equivalent to the Origin asking the Client for a token from a credential that is bound to "current time window."

OPEN ISSUE: give more guidance about how to construct credential_context and redemption_context depending on the application's needs.

!!! UNEDITED BELOW

In addition to this updated TokenChallenge, the HTTP authentication challenge also SHOULD contain the following additional attribute:

Implementation-specific steps: the client should store the Origin-provided input tokenChallenge so that when they receive a new tokenChallenge value, they can check if it has changed and which fields are different. This will inform the client's behavior - for example, if credential_context is being used to enforce an expiration on the credential, then if the credential_context has changed, this can prompt the client to request a new credential.

7. Credential Issuance Protocol

Issuers provide an Issuer Private and Public Key, denoted skI and pkI respectively, used to produce tokens as input to the protocol. See Section 5 for how these keys are generated.

Clients provide the following as input to the issuance protocol:

Given this configuration and these inputs, the two messages exchanged in this protocol to produce a credential are described below.

7.1. Client-to-Issuer Request

Given Origin-provided input tokenChallenge and the Issuer Public Key ID issuer_key_id, the Client first creates a credential request message using the CredentialRequest function from [ARC] as follows:

request_context = concat(tokenChallenge.issuer_name,
  tokenChallenge.origin_info,
  tokenChallenge.credential_context,
  issuer_key_id)
(clientSecrets, request) = CredentialRequest(request_context)

The Client then creates a TokenRequest structure as follows:

struct {
  uint16_t token_type = 0xE5AC; /* Type ARC(P-256) */
  uint8_t truncated_issuer_key_id;
  uint8_t encoded_request[Nrequest];
} TokenRequest;

The structure fields are defined as follows:

  • "token_type" is a 2-octet integer.

  • "truncated_issuer_key_id" is the least significant byte of the issuer_key_id (Section 5) in network byte order (in other words, the last 8 bits of issuer_key_id). This value is truncated so that Issuers cannot use issuer_key_id as a way of uniquely identifying Clients; see Section 9 and referenced information for more details.

  • "encoded_request" is the Nrequest-octet request, computed as the serialization of the request value as defined in Section 4.2.1 of [ARC].

The Client then generates an HTTP POST request to send to the Issuer Request URL, with the TokenRequest as the content. The media type for this request is "application/private-credential-request". An example request for the Issuer Request URL "https://issuer.example.net/request" is shown below.

POST /request HTTP/1.1
Host: issuer.example.net
Accept: application/private-credential-response
Content-Type: application/private-credential-request
Content-Length: <Length of TokenRequest>

<Bytes containing the TokenRequest>

7.2. Issuer-to-Client Response

Upon receipt of the request, the Issuer validates the following conditions:

  • The TokenRequest contains a supported token_type equal to value 0xE5AC.

  • The TokenRequest.truncated_token_key_id corresponds to the truncated key ID of an Issuer Public Key, with corresponding secret key skI, owned by the Issuer.

  • The TokenRequest.encoded_request is of the correct size (Nrequest).

If any of these conditions is not met, the Issuer MUST return an HTTP 422 (Unprocessable Content) error to the client.

If these conditions are met, the Issuer then tries to deserialize TokenRequest.encoded_request according to Section 4.2.1 of [ARC], yielding request. If this fails, the Issuer MUST return an HTTP 422 (Unprocessable Content) error to the client. Otherwise, if the Issuer is willing to produce a credential for the Client, the Issuer completes the issuance flow by an issuance response as follows:

response = CredentialResponse(skI, pkI, request)

The Issuer then creates a TokenResponse structured as follows:

struct {
   uint8_t encoded_response[Nresponse];
} TokenResponse;

The structure fields are defined as follows:

  • "encoded_response" is the Nresponse-octet encoded issuance response message, computed as the serialization of response as specified in Section 4.2.2 of [ARC].

The Issuer generates an HTTP response with status code 200 whose content consists of TokenResponse, with the content type set as "application/private-credential-response".

HTTP/1.1 200 OK
Content-Type: application/private-credential-response
Content-Length: <Length of TokenResponse>

<Bytes containing the TokenResponse>

7.3. Credential Finalization

Upon receipt, the Client handles the response and, if successful, deserializes the content values TokenResponse.encoded_response according to Section 4.2.2 of [ARC] yielding response. If deserialization fails, the Client aborts the protocol. Otherwise, the Client processes the response as follows:

credential = FinalizeCredential(clientSecrets, pkI, request, response)

The Client then saves the credential structure, associated with the given Issuer Name, to use when producing Token values in response to future token challenges.

8. Token Redemption Protocol

The token redemption protocol takes as input TokenChallenge and presentation limit values from [AUTHSCHEME], Section 2.1; the presentation limit is sent as an additional attribute within the HTTP challenge as described in Section 6. Clients use credentials from the issuance protocol in producing tokens bound to the TokenChallenge. The process for producing a token in this way, as well as verifying a resulting token, is described in the following sections.

8.1. Token Creation

Given a TokenChallenge value as input, denoted challenge, a presentation limit, denoted presentationLimit, and a previously computed credential that is valid for the Issuer identifier in the challenge, denoted credential, Clients compute a credential presentation value as follows:

presentation_context = concat(tokenChallenge.issuer_name,
  tokenChallenge.origin_info,
  tokenChallenge.redemption_context,
  issuer_key_id)
state = MakePresentationState(credential, presentation_context, presentationLimit)
newState, nonce, presentation = Present(state)

Subsequent presentations MUST use the updated state, denoted newState. Reusing the original state will break the presentation unlinkability properties of ARC; see Section 9.

The resulting Token value is then constructed as follows:

struct {
    uint16_t token_type = 0xE5AC; /* Type ARC(P-256) */
    uint32_t presentation_nonce;
    uint8_t challenge_digest[32];
    uint8_t issuer_key_id[Nid];
    uint8_t presentation[Npresentation];
} Token;

The structure fields are defined as follows:

  • "token_type" is a 2-octet integer, in network byte order, equal to 0xE5AC.

  • "presentation_nonce" is a 32-bit encoding of the nonce output from ARC.

  • "challenge_digest" is a 32-octet value containing the hash of the original TokenChallenge, SHA-256(TokenChallenge).

  • "issuer_key_id" is a Nid-octet identifier for the Issuer Public Key, computed as defined in Section 5.

  • "presentation" is a Npresentation-octet presentation, set to the serialized presentation value (see Section 4.3.2 of [ARC] for serialiation details).

8.2. Token Verification

Given a deserialized presentation from the token, denoted presentation and obtained by deserializing a presentation according to Section 4.3.2 of [ARC], a presentation limit, denoted presentation_limit, a presentation nonce from a token, denoted nonce, and the digest of a token challenge, denoted challenge_digest, verifying a Token requires invoking the VerifyPresentation function from Section 4.3.3 of [ARC] in the following ways:

request_context = concat(tokenChallenge.issuer_name,
  tokenChallenge.origin_info,
  tokenChallenge.credential_context,
  issuer_key_id)
presentation_context = concat(tokenChallenge.issuer_name,
  tokenChallenge.origin_info,
  tokenChallenge.redemption_context,
  issuer_key_id)
valid = VerifyPresentation(skI, pkI, request_context, presentation_context, nonce, presentation, presentation_limit)

This function returns True if the CredentialToken is valid, and False otherwise.

Implementation-specific steps: to prevent double spending, the Origin should perform a check that the tag (presentation.tag) has not previously been seen. It then stores the tag for use in future double spending checks. To reduce the overhead of performing double spend checks, the Origin can store and look up the tags corresponding to the associated request_context and presentation_context values.

9. Security Considerations

Privacy considerations for tokens based on deployment details, such as issuer configuration and issuer selection, are discussed in Section 6.1 of [ARCHITECTURE]. Note that ARC requires a joint Origin and Issuer configuration given that it is privately verifiable.

ARC offers Origin-Client unlinkability, Issuer-Client unlinkability, and redemption context unlinkability, as described in Section 3.3 of [ARCHITECTURE], with one exception. While redemption context unlinkability is achieved by re-randomizing credentials every time they are presented as tokens, there is a reduction in the anonymity set in the case of presentation nonce collisions, as detailed in Section 7.2 of [ARC].

10. IANA Considerations

This document updates the "Privacy Pass Token Type" Registry with the following entries.

11. References

11.1. Normative References

[ACT]
Schlesinger, S. and J. Katz, "Anonymous Credit Tokens", Work in Progress, Internet-Draft, draft-schlesinger-cfrg-act-00, , <https://datatracker.ietf.org/doc/html/draft-schlesinger-cfrg-act-00>.
[ARC]
Yun, C. and C. A. Wood, "Privacy Pass Issuance Protocol for Anonymous Rate-Limited Credentials", Work in Progress, Internet-Draft, draft-yun-privacypass-arc-01, , <https://datatracker.ietf.org/doc/html/draft-yun-privacypass-arc-01>.
[ARCHITECTURE]
Davidson, A., Iyengar, J., and C. A. Wood, "The Privacy Pass Architecture", RFC 9576, DOI 10.17487/RFC9576, , <https://www.rfc-editor.org/rfc/rfc9576>.
[ARC_PP]
Yun, C. and C. A. Wood, "Anonymous Rate-Limited Credentials", Work in Progress, Internet-Draft, draft-yun-cfrg-arc-01, , <https://datatracker.ietf.org/doc/html/draft-yun-cfrg-arc-01>.
[AUTHSCHEME]
Pauly, T., Valdez, S., and C. A. Wood, "The Privacy Pass HTTP Authentication Scheme", RFC 9577, DOI 10.17487/RFC9577, , <https://www.rfc-editor.org/rfc/rfc9577>.
[ISSUANCE]
Celi, S., Davidson, A., Valdez, S., and C. A. Wood, "Privacy Pass Issuance Protocols", RFC 9578, DOI 10.17487/RFC9578, , <https://www.rfc-editor.org/rfc/rfc9578>.
[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>.
[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>.
[TLS13]
Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, , <https://www.rfc-editor.org/rfc/rfc8446>.

11.2. Informative References

[RATE-LIMITED]
Hendrickson, S., Iyengar, J., Pauly, T., Valdez, S., and C. A. Wood, "Rate-Limited Token Issuance Protocol", Work in Progress, Internet-Draft, draft-ietf-privacypass-rate-limit-tokens-06, , <https://datatracker.ietf.org/doc/html/draft-ietf-privacypass-rate-limit-tokens-06>.

Acknowledgments

The authors would like to thank Tommy Pauly and the authors of [RATE-LIMITED] for helpful discussions on rate-limited tokens.

Authors' Addresses

Samuel Schlesinger
Google
Jonathan Katz
Google