| Internet-Draft | MoLE Protocols | October 2026 |
| Schlesinger, et al. | Expires 11 April 2027 | [Page] |
This document defines protocols that instantiate the MoLE architecture: two endorsement protocols, by which a Client proves to a Moderator that it holds an Endorsement from a trusted Anchor without revealing which one, and two credential protocols, by which a Moderator issues, verifies, and updates per-Client state without being able to link presentations. It also establishes the registries that identify these protocols.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://moderation-of-unlinkable-endorsements.github.io/internet-drafts/draft-jms-mole-protocols.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-jms-mole-protocols/.¶
Source for this draft and an issue tracker can be found at https://github.com/Moderation-of-unLinkable-Endorsements/internet-drafts.¶
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 11 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
The MoLE architecture [ARCHITECTURE] defines three roles. Clients obtain Endorsements from Anchors, redeem them at Moderators in exchange for Credentials, and present those Credentials to Moderators to access resources. The architecture states the required properties of Endorsements and Credentials but does not say how to build them. This document does.¶
TODO: the protocols below reflect our current understanding of how MoLE may work, and showcase agility. They are not final. Some may be removed, others added.¶
It defines two endorsement protocols and two credential protocols. Each is identified by a type value from a registry established in this document (Section 9). The HTTP carriage of challenges, requests, redemptions, and presentations is defined by [HTTP-TRANSPORT]. This document defines the messages themselves and, for the grant flow, the HTTP exchanges that carry them.¶
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.¶
Protocol messages are described in TLS presentation language (Section 3 of [TLS13]). This document also uses the optional-value and variable-size
vector conventions (optional<T>, <V>) defined in [HTTP-TRANSPORT].
All constants are in network byte order.¶
This document uses the following terms for protocol actions:¶
An Anchor gives a Client an Endorsement.¶
A Client spends an Endorsement at one logical Moderator. A Client MUST NOT attempt to redeem the same Endorsement at a second Moderator. The Moderator enforces replay protection within its configured replay protection scope.¶
A Moderator gives a Client a Credential in return for a redemption.¶
A Client shows a Credential to a Moderator. Each Credential can be presented once within the Moderator's configured replay protection scope. The update replaces it.¶
The Moderator's adjustment to a presented Credential, returned in the same exchange.¶
The Client-local step that turns a protocol response into a stored Endorsement or Credential.¶
Every outer MoLE message that selects a protocol carries a uint16 type field.
These messages are EndorsementRequest, EndorsementResponse,
CredentialRequest, CredentialResponse, CredentialPresentation,
CredentialUpdate, ModeratorChallenge, and CredentialChallenge. Values are
assigned in Section 9. A Client ignores an unknown Challenge. An unknown request
or response is rejected. A Moderator treats an unknown optional presentation
as absent and rejects an unknown required presentation. Challenge wrappers are
exchanged only between a Moderator and Client. They are never sent to an Anchor.¶
The value 0x0000 is reserved in both registries and MUST NOT appear on the wire. Endorsement type 0x0001 means that no Endorsement is required.¶
In order to prevent Moderators from becoming incompatible with future
credential types, Clients SHOULD send presentations whose credential_type is a
random value from the reserved greased values (Section 9.2.3), with some
non-trivial probability. The body of a greased presentation is random bytes. A
Moderator handles it as if no Credential were presented.¶
The greased values follow the pattern 0x?A?A, spread uniformly across the registry space. Moderators MUST handle them exactly as any other unknown type and MUST NOT special-case the reserved list. A Moderator that enumerates greased values defeats their purpose and will still receive unknown types it did not enumerate.¶
Additionally, when a credential is not required, Clients SHOULD randomly choose not to send a presentation with some non-trivial probability. This helps ensure that Moderators maintain their behavior for handling Clients without credentials, rather than relying on a presentation always being present.¶
A Moderator Challenge is a message sent by a Moderator to a Client. It selects the operation and carries any type-specific input chosen by the Moderator. A Moderator Challenge, or a value derived from it, MUST NOT be sent to an Anchor.¶
A Context is a protocol-specific cryptographic input. A protocol defines how the Client and Moderator derive the same Context from authenticated configuration, the operation, and, when required, the Moderator Challenge. A Context can include the complete Moderator Challenge, a digest of it, or selected fields. Moderator Challenge and Context are therefore not interchangeable terms.¶
Each credential protocol defines the contents of its type-specific
CredentialChallenge body. Endorsement protocols use the common Challenge in
Section 4.1. For a new operation, a Moderator MUST be able to
verify, from retained state or by recomputing the Challenge, that a response
uses a Challenge it issued and that remains valid. A credential protocol can return an already accepted result after its
Challenge expires, without authorizing a new operation
(Section 5.2.5).¶
An endorsement protocol has two parts. First, before contacting a Moderator, the Client runs one or more request/response exchanges with an Anchor and finalizes the result into an Endorsement. This is the grant. Second, the Client redeems the Endorsement at a Moderator, proving it came from an Anchor in the Moderator's accepted set without revealing which one. Redemption happens inside the Redeem & Issue flow (Section 5).¶
Exchanges with the Anchor are HTTP POST requests. The request body has media
type application/mole-endorsement-request and contains an
EndorsementRequest. The response body has media type
application/mole-endorsement-response and contains an
EndorsementResponse. The endorsement type determines how many exchanges
are needed and what the body field contains at each step.¶
struct {
uint16 endorsement_type;
opaque body<V>;
} EndorsementRequest;
struct {
uint16 endorsement_type;
opaque body<V>;
} EndorsementResponse;
¶
The structure fields are:¶
endorsement_type identifies a registered endorsement protocol.¶
body is that protocol's grant message.¶
The Anchor returns 200 (OK) with the response media type only when it produced
a complete EndorsementResponse. A Client MUST reject a non-success status, an
unexpected media type, a response whose type differs from its request, trailing
bytes, or malformed type-specific content. Clients MUST NOT automatically
redirect a grant POST carrying protocol state. If the second Rollatini response
is lost, the consumed session cannot be replayed. The Client starts a fresh
grant session.¶
Every endorsement protocol defines a Redemption structure. It is the message
a Client sends to redeem the Endorsement, carried in the
endorsement_presentation field of a CredentialRequest
(Section 5).¶
The type-specific body of ModeratorChallenge is:¶
struct {
opaque redemption_context[32];
} RedemptionChallenge;
¶
The structure fields are:¶
redemption_context scopes the redemption. The Moderator constructs it
and the Client uses it as received. It MUST distinguish the Moderator and
the policy it issues under, and SHOULD distinguish a time window.¶
The Client does not send the challenge back. The Moderator either keeps the
challenges it sends or recomputes redemption_context when a request
arrives, for instance as an authenticated encoding of its origin, policy,
and time window, trying adjacent windows.¶
Endorsement protocols compute the following value when creating or verifying a redemption:¶
challenge_digest = SHA-256(moderator_challenge)¶
The values are defined as follows:¶
moderator_challenge is the complete decoded TLS-presentation encoding of
the ModeratorChallenge sent by the Moderator. It is not required to contain
an origin.¶
challenge_digest is the 32-octet SHA-256 digest of
moderator_challenge. It is not computed over a base64url or other textual
encoding. SHA-256 is defined in [SHA2].¶
challenge_digest enters the Fiat-Shamir transcript in both Rollatini and
Longfellow, but is not a Longfellow circuit public input. A redemption created
for one ModeratorChallenge does not verify under another, so a Moderator
that accepts only the redemption_context it constructs rejects a
redemption made under another Moderator's challenge. This does not stop a
Moderator that relays another Moderator's challenge to the Client.¶
The Challenge algorithm and ChallengeMessage in Rollatini issuance are
defined by [ROLLATINI] and are unrelated to a ModeratorChallenge.¶
Each endorsement protocol defines these abstract operations:¶
RedeemRequest(endorsement, moderator_challenge, configuration) -> redemption | INVALID FinalizeRedeem(redemption, moderator_challenge, configuration) -> replay_protection_ids | INVALID¶
RedeemRequest runs at the Client. FinalizeRedeem runs at the Moderator,
verifies the redemption and Challenge binding, and returns a possibly empty
set of replay_protection_ids or INVALID. Both operations derive
challenge_digest from moderator_challenge as specified above. The
Moderator MUST atomically insert every returned identifier in the protocol's
configured replay protection scope, each only if absent. If any is already
present, the Moderator MUST treat the result as INVALID and MUST NOT issue
a Credential for the accompanying IssuanceRequest. There is no global
replay protection service.¶
Endorsement type 0x0001 indicates that the Moderator does not require an
Endorsement under its policy. It has no grant. The endorsement input and
Redemption are both the distinguished empty value. RedeemRequest returns
that empty value. FinalizeRedeem returns the empty set when the
Moderator's policy permits issuance without an Endorsement, and INVALID
otherwise. How often a Client can obtain a Credential this way is up to
Moderator policy, for instance rate limiting.¶
Endorsement type: 0x0002.¶
This protocol uses Rollatini [ROLLATINI], a pairing-free Issuer-Hiding Anonymous Token (IHAT). The Anchor blindly signs a Client-chosen nullifier. The Client later proves, with an issuer-hiding proof, that its Endorsement verifies under one of the Anchor keys the Moderator accepts. The cryptographic operations, and the contents and encodings of every message body, are defined in [ROLLATINI].¶
The following primitive types are ciphersuite-dependent:¶
opaque Scalar[Ns]; opaque Element[Ne];¶
The Client needs, from Anchor configuration (Section 6):¶
A ciphersuite identifier defined by [ROLLATINI]. It determines Element,
Scalar, and all cryptographic encodings.¶
pkA, an Element, as generated in [ROLLATINI], with a stable key ID.¶
ctx_iss, the canonical encoding of the issuance epoch. Endorsements are
valid for that epoch, see Section 6.¶
ctx_red, the ASCII string "MoLE-Rollatini-ctx_red-v1", without a
terminating NUL byte. This fixed, domain-separated value is the same for all
Moderators. This is the cryptographic redemption Context defined by
[ROLLATINI], not a Moderator Challenge. A specific redemption operation is
bound separately by challenge_digest.¶
The grant takes two HTTP exchanges and three protocol messages. The Anchor speaks first, as specified by [ROLLATINI]:¶
The Client sends an EndorsementRequest with an empty body. The Anchor
runs Commit(ctx_iss), stores the returned state under a fresh
session_id, and returns a CommitMessage in the response body.¶
The Client runs Challenge(pkA, ctx_iss, ctx_red, commitment), stores the
returned state, and sends a ChallengeMessage containing the returned
issuance challenge and opaque session_id. The Anchor atomically claims and
consumes that identifier before reading the single-use state. Only the
instance that wins the claim runs Respond(skA, state, challenge) and
returns a ResponseMessage.
Tombstones are retained through session expiry. All later requests for the
identifier fail without invoking Respond.¶
The Client runs Finalize(pkA, state, response) as specified by
[ROLLATINI]. On failure it MUST discard the session state and MUST NOT
retry with that state.¶
CommitMessage, ChallengeMessage, ResponseMessage, session_id, and the
Endorsement encoding are defined by [ROLLATINI]. The session identifier is
only transport correlation and is not bound into the Endorsement.¶
The Anchor learns neither nf nor the final Endorsement. Under the statistical
blindness claim in [ROLLATINI], its protocol transcript does not let it
recognize the Endorsement when it is later redeemed. Timing, network, and
configuration metadata are outside that claim.¶
The type-specific Redemption payload is the encoding of Redemption in
[ROLLATINI]. RedeemRequest derives challenge_digest as in
Section 4.1 and calls
Redeem(anchor_set, index, endorsement, ctx_iss, ctx_red, challenge_digest).
The ordered anchor_set comes from Moderator configuration; index selects
the Anchor that issued the Endorsement. The Client rejects a set that omits
its Anchor.¶
FinalizeRedeem checks that the issuance epoch and Moderator Challenge are
accepted, decodes the payload, and calls
VerifyRedemption(anchor_set, redemption, ctx_iss, ctx_red, challenge_digest).
It returns {nf}, scoped to ctx_iss in the Moderator's store. Any
decoding or verification failure returns INVALID.
The common replay protection rules apply before Credential issuance. The
same nf is exposed on repeated redemptions, including across Moderators;
the scheme does not enforce global single use.¶
Endorsement type: 0x0003.¶
Where Rollatini requires Anchors to run new cryptography, this protocol preserves backward compatibility with credentials Clients may hold, such as mdocs. The Client proves in zero knowledge, using the scheme of [LONGFELLOW], that it holds a valid credential from one of an accepted set of issuers, without revealing which issuer or any credential attribute. An experimental circuit is described in [HIDDEN-ISSUER-CIRCUIT].¶
There is no grant exchange in this protocol. The Client obtains its credential from the Anchor out of band, through whatever legacy issuance that credential uses, before contacting a Moderator. A compressed circuit artifact containing the two Longfellow circuits is likewise distributed out of band and identified by its hash.¶
The Client needs, from Moderator configuration (Section 6):¶
circuit_artifact_id, the SHA-256 hash of the compressed circuit artifact
both parties use.¶
An out-of-band manifest that names the artifact version, the two circuit identifiers, and all proof parameters needed to interpret and verify it.¶
the credential-issuer certificates the Moderator accepts, in a fixed published order.¶
the validity window redemptions must fall in.¶
the canonical origin of the Moderator. Its source is trusted configuration.
It MUST NOT be derived from a client-controlled HTTP Host value.¶
verification_time, a Moderator-selected time within the current epoch.¶
The accepted issuer set, circuit artifact, epoch, Moderator origin, and
verification time come from authenticated configuration. The selected
redemption operation supplies the digest of the complete decoded
ModeratorChallenge as challenge_digest, as defined in
Section 4.1.¶
The Client evaluates the two circuits over its credential to produce a proof and a nullifier. The nullifier is derived, inside the circuit, from a credential-bound secret, the canonical Moderator origin, and the current epoch. One credential therefore yields exactly one valid nullifier per Moderator origin and epoch.¶
struct {
opaque circuit_artifact_id[32];
opaque nullifier<V>;
opaque proof<V>;
} Redemption;
¶
The structure fields are:¶
circuit_artifact_id is the SHA-256 digest of the configured circuit
artifact.¶
nullifier is the circuit-produced replay protection identifier.¶
proof is the encoded Longfellow proof.¶
The circuit public inputs are the accepted issuer set, epoch, canonical
Moderator origin, verification_time, and nullifier. The proof establishes
validFrom <= verification_time <= validUntil. challenge_digest
(Section 4.1) is bound into the Longfellow Fiat-Shamir transcript.
It is not a circuit public input.¶
Longfellow implements the common API in Section 4.
RedeemRequest checks that the artifact and manifest match configuration,
evaluates the circuits, and returns the encoded Redemption above.
FinalizeRedeem checks the artifact identifier, verifies the proof and its
transcript binding, and checks that the configured epoch and
verification_time remain current. It returns {nullifier}, or INVALID
on any failure. Both operations derive challenge_digest from
moderator_challenge. The common caller performs the
atomic replay protection check.¶
Longfellow does not inherently require an Anchor to change its issuance protocol. Scarcity then depends on the legacy credential's own issuance limits and on whether it contains a credential-bound Client secret suitable for nullifier derivation. An Anchor that participates in MoLE can control scarcity and arrange for such a secret to be committed during issuance. Some existing credential formats may already provide a suitable Client-contributed or device-bound secret. The one-nullifier-per-origin-and-epoch rule prevents repeat redemption of one credential in that scope. It does not limit how many credentials a Client can obtain.¶
Editor note. This protocol is blocked on normative circuit definitions, public-input encodings and ordering, transcript binding, artifact lifecycle and size limits, and test vectors.¶
A credential protocol has two parts. In Redeem & Issue, the Client redeems an Endorsement and receives a Credential from the Moderator. In Presentation and Update, the Client shows the Credential and receives an update in the same exchange.¶
Both parts ride on HTTP requests to the Moderator. Redeem & Issue carries a
CredentialRequest in the
Authorization header and receives the CredentialResponse in the
Mole-Credential response header. The Moderator runs the selected endorsement
protocol's FinalizeRedeem operation before it issues a Credential.
Presentation uses the same authentication scheme.¶
struct {
uint16 endorsement_type;
opaque endorsement_presentation<V>; /* encoded Redemption */
uint16 credential_type;
opaque issuance_request<V>;
} CredentialRequest;
struct {
uint16 credential_type;
opaque issuance_response<V>;
} CredentialResponse;
¶
The structure fields are:¶
endorsement_type identifies the endorsement protocol.¶
endorsement_presentation is its encoded Redemption. It is empty for
endorsement type 0x0001 (Section 4.3).¶
credential_type identifies the credential protocol.¶
issuance_request is its encoded IssuanceRequest.¶
issuance_response is its encoded IssuanceResponse.¶
A recipient of CredentialResponse MUST reject it unless credential_type
matches the type selected by CredentialRequest.¶
All credential protocols define the same four payloads:¶
A Client-generated request for a new Credential.¶
The Moderator's response to IssuanceRequest.¶
A presentation of a Credential and any request needed to replace or update it.¶
The Moderator's response to PresentationAndUpdate, when the Client remains
eligible for a Credential.¶
The type-specific encodings fill the opaque fields of the outer structures
above and of CredentialPresentation and CredentialUpdate from
[HTTP-TRANSPORT].¶
Each credential protocol defines these abstract operations:¶
CreateIssuanceRequest(moderator_challenge, configuration)
-> (issuance_state, issuance_request) | INVALID
IssueCredential(issuance_request, moderator_challenge, policy, configuration)
-> issuance_response | INVALID
FinalizeIssuance(issuance_state, issuance_response, configuration)
-> credential | INVALID
CreatePresentationAndUpdate(credential, credential_challenge, configuration)
-> (presentation_state, presentation_and_update) | INVALID
ProcessPresentation(
presentation_and_update, credential_challenge, policy, configuration)
-> INVALID
| ACCEPTED_NO_UPDATE
| ACCEPTED_WITH_UPDATE(update)
FinalizeUpdate(presentation_state, update, configuration)
-> credential | INVALID
¶
CreateIssuanceRequest, FinalizeIssuance, CreatePresentationAndUpdate, and
FinalizeUpdate run at the Client. IssueCredential and ProcessPresentation
run at the Moderator. State values are local and are not sent on the wire.
ACCEPTED_NO_UPDATE means that the presentation succeeded but the Credential
was consumed without replacement. ACCEPTED_WITH_UPDATE carries the encoded
Update that the Client finalizes into its replacement Credential by calling
FinalizeUpdate. The Client does not call FinalizeUpdate for
ACCEPTED_NO_UPDATE. A complete protocol specification defines its Context
derivation, replay protection, and retry behavior. The Moderator completes that
replay protection before returning an accepted result.¶
Credential type: 0x0001.¶
An ACT Credential, specified in [ACT], carries a hidden balance c, a
number of credits. The Moderator issues a Credential with an initial
balance of its choosing. A presentation spends a public amount s,
proving that the balance covers it, and may accept a top-up of at most
a that the challenge offers. In the same exchange the Moderator issues a refund
message with a return amount t of its choosing, with 0 <= t <= s + a.
The Client finalizes this message into a fresh Credential with balance
c - s + t. The presentation reveals s, a, and a single-use nullifier;
the balance is hidden beyond the public amounts and predicates
(Section 7.1).¶
A Credential is invalid once spent, and its replacement exists only once the Moderator has answered, so one Credential supports one presentation in flight at a time. This gives the Moderator:¶
A Credential copied to several parties can be spent by only one of them; every other copy presents a nullifier the Moderator has already recorded.¶
The Moderator ends a Credential chain, and its remaining balance, by declining to refund, without waiting for the Credential to expire.¶
A balance changes only through s, a, and t, each of which can follow
the Moderator's assessment of the request, subject to Section 7.1.¶
The abstract credential API maps onto [ACT] as follows:¶
| Abstract operation | ACT |
|---|---|
CreateIssuanceRequest
|
IssueRequest, with key selection (Section 5.2.2) |
IssueCredential
|
IssueResponse (Section 5.2.2) |
FinalizeIssuance
|
FinalizeIssue
|
CreatePresentationAndUpdate
|
ProveSpend (Section 5.2.3.2) |
ProcessPresentation
|
VerifySpend and IssueRefund (Section 5.2.3.2) |
FinalizeUpdate
|
FinalizeRefund
|
The Client's presentation_state is the ClientSpendState of [ACT], the
pkM, credential_context, L, and Moderator identity and endpoint stored
with the spent Credential, and the PresentationAndUpdate octets with the
HTTP request information needed to resend them to their original
destination (Section 5.2.5). ProcessPresentation returns
ACCEPTED_WITH_UPDATE with a refund, and ACCEPTED_NO_UPDATE when the
Moderator declines to refund. For a retry answered from a record
(Section 5.2.5), it returns the recorded result, which carries the
Update only and does not authorize the protected operation.
The ACT response rules in [HTTP-TRANSPORT] define the HTTP status and
Mole-Credential header for each outcome.¶
The Moderator publishes, in its configuration (Section 6):¶
pkM, encoded with SerializeElement. Its identifier is
key_id = SHA-256(SerializeElement(pkM)), and truncated_key_id is the
last byte of key_id.¶
L, fixed for the lifetime of the key and credential context ([ACT]).¶
credential_context, an opaque byte string of at most 2^16 - 2 bytes
that names the epoch or policy that Credentials under the key are valid
for.¶
The recovery interval after acceptance of a presentation. Recovery ends earlier if its key or credential context is retired (Section 5.2.5).¶
The credential context of [ACT] is¶
ctx_cred = credential_context || I2OSP(L, 1)¶
which includes L as [ACT] recommends. A Moderator expires every
outstanding Credential under a key by publishing a new credential_context,
without changing the key. A refund is issued under the context of the spent
Credential, so balances do not carry over to a new context. A Moderator
SHOULD announce the successor context and a grace period before expiry.
Clients obtain Credentials under the new context through Redeem & Issue;
this document does not specify balance migration.¶
A Moderator MAY have several active keys, for instance during a rotation. It
MUST ensure that no two active keys share a truncated_key_id, SHOULD NOT
give a new key the truncated_key_id of a recently retired one, and SHOULD
keep the number of active keys small (Section 7.1).¶
struct {
uint8 truncated_key_id;
opaque credential_context<V>;
IssueRequestMessage request;
} IssuanceRequest;
struct {
IssueResponseMessage response;
} IssuanceResponse;
¶
IssueRequestMessage and IssueResponseMessage are defined in [ACT].¶
The Client runs IssueRequest(), keeps the returned state, and sends the
request with the truncated identifier of the key and the
credential_context it holds in its configuration.¶
The Moderator processes a CredentialRequest in this order:¶
It selects the active key whose truncated_key_id matches, and checks
that credential_context is the context it issues under for that key.
It rejects the request if either check fails.¶
It checks that request deserializes and that its proof verifies, as
IssueResponse does, and rejects the request otherwise.¶
It validates the accompanying redemption with FinalizeRedeem
(Section 4).¶
It chooses the initial balance c under its policy and runs
IssueResponse(skM, ctx_cred, c, request). It then records the
replay_protection_ids returned by FinalizeRedeem. Recording is atomic
and fails if any identifier is already recorded. If any step fails, or
it is unknown whether the identifiers were recorded, no response is
released.¶
It returns the result in a CredentialResponse.¶
The Endorsement is therefore consumed only together with a response, and a malformed request does not cost the Client its Endorsement.¶
The Client runs FinalizeIssue(pkM, ctx_cred, state, response) and stores
the resulting Credential with pkM, credential_context, L, the
identity of the Moderator, and its configured Redeem & Issue endpoint.
If finalization fails, the Client MUST discard the state, SHOULD refresh
its configuration, and MAY run Redeem & Issue again.¶
The ACT Challenge names the amounts, the context the Moderator accepts,
and the scope of the authorization it asks for:¶
struct {
uint64 s;
uint64 a;
opaque credential_context<V>;
opaque request_context<V>;
} Challenge;
¶
s is the amount to spend and is the Predicate of [ARCHITECTURE]: a
presentation succeeds only if the balance is at least s. a is the
largest top-up the Moderator offers in this presentation, 0 if none. A
challenge with s = a = 0 asks for a refresh, which replaces the nullifier
and leaves the balance unchanged, since t <= s + a = 0. A Moderator that
wants to lower balances challenges with s > 0 and returns t < s; one that
wants to raise them offers a > 0. credential_context repeats the
configured value, so that a Client can tell that its Credential has expired
without refetching the configuration. It is the challenge field that
partitions cached Credentials ([HTTP-TRANSPORT]).¶
The spend context of [ACT] is¶
ctx_spend = SHA-256(credential_challenge)¶
where credential_challenge is the complete TLS encoding of the
CredentialChallenge of [HTTP-TRANSPORT], including its credential_type
and the length-prefixed ACT Challenge. The Client hashes those octets, not
their base64url encoding. This digest is separate from the redemption digest
of Section 4.1.¶
The presentation does not carry the challenge. The Moderator determines the
challenge a presentation answers from its policy and the request it is
authorizing, and selects one of two profiles by what it puts in
request_context:¶
request_context is empty. Every challenge with the same s, a, and
credential_context then has the same octets and the same digest, and a
presentation is a one-use bearer authorization for those values: the
Moderator accepts it once, against whichever request carries it
(Section 8.1).¶
request_context is non-empty and identifies the request, session, or
time window the presentation is for. The Moderator MUST accept a
presentation only under a value it issued for the request being
authorized. The value MAY be stateless, for instance an
authenticated encoding of the origin, the method and target, a session
identifier, or a time window, which the Moderator recomputes from the
request. The binding is only as specific as what the value encodes; to
bind one operation, it MUST distinguish every request field that affects
authorization, including the request body where relevant.¶
When several challenges could apply to a request, for instance for adjacent time windows or for two credential contexts during a rollover, the Moderator runs steps 2 and 3 of Section 5.2.3.2 for each until one succeeds. The two profiles differ only in the challenge octets. The cryptographic operations, the message formats, and the nullifier rules are the same.¶
A Client that holds no Usable Credential (Section 5.2.4) for this
Moderator under the challenged credential_context, or whose Credential
ProveSpend would reject for the challenged s and a, does not present.
It runs Redeem & Issue, first obtaining an Endorsement if the Moderator
requires one (Section 4).¶
Otherwise the Client computes ctx_spend, runs
ProveSpend(credential, ctx_cred, s, a, ctx_spend), stores the returned
state and the presentation as Section 5.2.4 requires, and sends:¶
struct {
opaque key_id[32];
SpendMessage spend;
} PresentationAndUpdate;
struct {
RefundMessage refund;
} Update;
¶
SpendMessage and RefundMessage are defined in [ACT]. The full key_id
is sent: the Moderator issued the Credential and knows its key, so the
identifier tells it nothing more.¶
The Moderator processes a presentation as follows. Steps 1 and 2 read public state and the nullifier store, step 3 ends by atomically recording the result, and step 4 follows.¶
It checks that key_id names an active key, then looks up
(key_id, spend.k) in the nullifier store. If a record exists, the
Moderator answers from it only if it holds the SHA-256 digest of the
received PresentationAndUpdate octets, its result is still retained,
and its credential context is still accepted for that key
(Section 5.2.5). The challenge may have expired. A record that
fails any of these checks causes the presentation to be rejected. Only
an unrecorded nullifier proceeds to step 2.¶
It determines the challenge (Section 5.2.3.1), and checks
that its credential_context is accepted for that key and that
spend.s and spend.a equal its s and a. It rejects the presentation
if any check fails.¶
It runs VerifySpend(skM, ctx_cred, ctx_spend, spend), decides under its
policy whether to refund and, if so, the return amount t, and runs
IssueRefund(skM, ctx_cred, spend, t) if it refunds. It then records the
result under (key_id, spend.k): the digest of the presentation
octets, ctx_cred, and the decision, either Refund(t) with
the octets of the RefundMessage, or NoUpdate. Recording is atomic and
MUST fail if a result is already recorded under that nullifier or if the
key or context is no longer accepted, so of two racing presentations at
most one is recorded. If any step fails, or it is unknown whether the
result was recorded, no refund is released. The Moderator MUST NOT
attempt the protected operation unless this request's result was
successfully recorded.¶
It attempts the protected operation. Its HTTP response carries the
recorded credential result as an Update: the refund, or, for
NoUpdate, an absent update, which leaves the Client without a
replacement. The Moderator MUST include this result even when the
protected operation fails ([HTTP-TRANSPORT]).¶
Spending credits authorizes one attempt of the protected operation. A failure after step 3 can charge the Client without completing step 4; a retry recovers the result but does not authorize another attempt. Reliable execution needs application-specific recovery, for example a durable outbox written with the step 3 record and a worker that retries the operation idempotently under the presentation digest. This document does not specify that recovery.¶
The Client runs FinalizeRefund(pkM, ctx_cred, state, refund) and stores
the resulting Credential in place of the spent one, with the same pkM,
credential_context, and L. If the refund does not verify, the Client
discards it and proceeds as in Section 5.2.5.¶
An ACT Credential goes through the following states:¶
The Client holds a Credential with balance c, together with pkM,
credential_context, and L, and may present it as Section 5.2.3.2
describes.¶
The Client has run ProveSpend, and the Credential is invalid whether or
not the presentation is sent or answered ([ACT]). Besides the spend state
that [ACT] requires it to store, the Client MUST durably store the
presentation octets and the HTTP request needed to resend them before
the presentation leaves it.¶
A valid refund returns the Client to Usable. A Credential chain is therefore serialized: one presentation at a time, each waiting for the Moderator's answer to the previous one. When a Client learns that a key or credential context has been retired, it MUST discard the Credentials and pending spend states under that key or context. It obtains a Credential under the current configuration through Redeem & Issue.¶
A presentation may be sent and its response lost, or the Moderator may fail between recording the result in step 3 of Section 5.2.3.2 and returning it. The Client cannot then finalize, and the spent Credential cannot be reused.¶
A Moderator MUST keep each result until the earlier of the end of its
published result retention period (Section 5.2.1), counted from
the recording in step 3, and retirement of the key or credential context.
It MUST delete the result when this interval ends. A Client missing an
Update MAY resend the byte-identical PresentationAndUpdate during the
interval. Step 1 of Section 5.2.3.2 checks the record and the continued
acceptance of its key and credential context before returning a result.
Recovery remains available after the presentation's challenge expires:¶
For a recorded Refund(t), the Moderator returns the refund it stored.¶
For a recorded NoUpdate, the Moderator returns an absent update.¶
The recovery handle ([HTTP-TRANSPORT]) is
key_id || SerializeScalar(spend.k) || SHA-256(PresentationAndUpdate).¶
The Moderator MUST NOT authorize another operation for a recorded nullifier,
and MUST NOT change a recorded decision. It returns the recovered result
with HTTP status 409 as specified in [HTTP-TRANSPORT].¶
Once recovery ends, a retry is rejected, and the Client MUST discard the spend state. To continue, it runs Redeem & Issue. The Moderator MUST retain the nullifier after deleting its result for as long as the key and credential context are accepted (Section 6).¶
A SpendMessage grows linearly in L ([ACT]). With P-256 and L = 32,
it is about 4.5 KB when one of s and a is nonzero and 8.7 KB when both
are, before base64url encoding in the Authorization header. Deployments
with tight header limits should choose L accordingly.¶
Credential type: 0x0002.¶
The Credential is a single Privacy Pass token. The Moderator's configuration names both the registered token type used for presentation and the registered token type used for update issuance. Presentation consumes the token. The update, if granted, is a fresh token issued through [REVERSE-FLOW], with redemption in place of attestation.¶
For the protocol described here, the presented and reissued token use the same token type and Moderator key. Changing either partitions Clients and leaks state.¶
IssuanceRequest is a TokenRequest and IssuanceResponse is a
TokenResponse, both as defined for the configured token type in
[PRIVACYPASS-PROTOCOLS]. That token type's finalization operation produces
the Credential. These messages implement CreateIssuanceRequest,
IssueCredential, and FinalizeIssuance.¶
struct {
opaque token<V>;
opaque token_request<V>; /* TokenRequest */
} PresentationAndUpdate;
struct {
opaque token_response<V>; /* TokenResponse */
} Update;
¶
The structure fields are:¶
token is the presented Privacy Pass Token.¶
token_request is the encoded replacement TokenRequest.¶
token_response is the encoded replacement TokenResponse.¶
These structures implement CreatePresentationAndUpdate, ProcessPresentation,
and FinalizeUpdate.¶
The token field carries a Token as defined in [PRIVACYPASS-AUTH].
Privacy Pass names its cryptographic Context TokenChallenge. In MoLE, the
type-specific body of CredentialChallenge carries that configured
TokenChallenge. It is therefore both sent by the Moderator as part of a
Challenge and supplied to the Privacy Pass replacement-issuance and
verification operations as their Context. Initial issuance obtains the same
TokenChallenge from authenticated configuration.¶
TokenChallenge is fixed when the token is issued. It MUST be stable for every
Client using the same configured credential type, key, and epoch, and MUST NOT
contain a per-Client or per-request value. This is necessary because a
replacement token is issued before the operation in which it will be
presented.
The Privacy Pass Token.challenge_digest field MUST equal SHA-256 over the
encoded configured TokenChallenge. It is distinct from the MoLE endorsement
challenge_digest defined in Section 4.1. The Moderator MUST reject
a token carrying any other digest. Replay protection comes from token single use.
The Moderator MUST verify the token as specified by [PRIVACYPASS-AUTH]
against the configured token type, key, and TokenChallenge, then atomically
record its nonce before accepting it. An invalid or previously recorded nonce
is rejected, except for the retry behavior below. Holder binding remains an
open limitation below.¶
If the Moderator's policy allows continued access, it returns an Update. If
not, it returns no update and the Client is out of credentials.¶
A Client that receives no response MAY retry the byte-identical
PresentationAndUpdate only to recover from that loss. The Moderator MUST
return the same accepted result while it retains the idempotency record,
including the same Update or the same absence of an update. It MUST reject the
replay after that record expires and MUST NOT issue a second Credential. The
Client MUST NOT combine the same presented Credential with a different update
request. These requirements are the retry semantics of [REVERSE-FLOW].¶
The recovery handle ([HTTP-TRANSPORT]) is
token.token_key_id || token.nonce || SHA-256(PresentationAndUpdate).¶
TODO: define a device binding mechanism, issuing tokens bound to a Client key so that presentation requires proof of possession. This would restore the binding between update and presented credential. Open problem.¶
This draft assumes authenticated configuration supplies endpoints, supported types, keys, epochs, accepted issuer sets, and type-specific inputs. The order of accepted sets is significant because Rollatini proof branches and Longfellow issuer inputs match elements by position.¶
ACT configuration adds, per key, the ciphersuite, balance width, credential context, and result retention period (Section 5.2.1). ACT nullifiers MUST remain recorded while their credential context is accepted for their key. A Moderator that publishes different keys or contexts to different Clients partitions them ([ARCHITECTURE]).¶
Editor note. Configuration discovery, serialization, authentication, canonical encodings, consistency, and rotation remain undefined. The wire protocols are not interoperable until these are specified.¶
TODO. The list to cover:¶
Anchor set verification: the Client must be able to verify the number of Anchors in an accepted set, and that these Anchors are real rather than fabricated by the Moderator. A set padded with fake Anchors shrinks the effective anonymity set to the Clients of the real ones.¶
Configuration partitioning: accepted-set contents and order as a fingerprinting vector (with [ARCHITECTURE]).¶
Epoch width versus anonymity set size.¶
During redemption of an Endorsement, the Client uses the accepted Anchor Set from the Moderator's authenticated configuration. If the Client does not have an Endorsement issued by one of the Anchors in the Anchor Set, it must either abort the redemption flow or pause it until it can obtain a suitable Endorsement. The Client must take care to ensure its actions in this case do not inadvertently reveal the issuing Anchor of an accepted Endorsement. For example, if the Client initiates two concurrent redemption flows, the Moderator can select Anchor Sets that differ by just one Anchor. If one flow aborts but the other does not, then the Moderator immediately learns the Anchor of the accepted Endorsement.¶
An ACT presentation reveals s, a, and a fresh nullifier, and, because
the proof verifies, the key and credential context of the Credential. Its
Update reveals t. The unlinkability of [ACT] holds among the Clients
that share the key, the context, and the amounts, so each of these
partitions Clients:¶
The truncated key identifier shortens the encoding, but it still selects one active key, and every active key splits the Clients holding Credentials under it. [ACT] requires the credential context to be coarse: a per-Client or per-session context makes presentations linkable without breaking any cryptographic property.¶
The initial balance, s, a, and t MUST NOT vary per Client under a
policy, since a Moderator could recognize a Client by the values it
uses. A Moderator SHOULD draw them from a small set of values fixed by
policy, so that a value reveals no more than the policy decision it
encodes. A Client whose balance plus a reaches 2^L cannot present,
which sets it apart. A Moderator SHOULD choose L so that the largest
balance its policy lets Clients reach, plus the largest a it offers, is
below 2^L.¶
A presentation succeeds only if the balance covers s. A Moderator that
challenges a Client repeatedly with amounts of its choosing can bound the
balance, and in the end recover it and use it to link presentations
([ARCHITECTURE]). The serialized chain of Section 5.2.4 limits
such probing to one presentation per round trip. Clients SHOULD limit the
number of presentations they make in one context, as [ARCHITECTURE]
recommends.¶
The refund arrives in the same exchange as the presentation, so a Client that presents it in another context at once is likely the one that just received it. [ARCHITECTURE] keeps a Credential used in one context locked to that context for a period of time; this applies to the refund that replaces it.¶
All exchanges defined in this document and [HTTP-TRANSPORT] MUST be carried over HTTPS.¶
TODO. The list to cover:¶
Nullifier store sizing and eviction: the store is per epoch, and a Moderator that evicts early re-admits spent Endorsements.¶
Anchor key compromise: an attacker with an Anchor key in an accepted set can mint Endorsements freely. Blast radius and rotation response.¶
Reverse-flow update transfer: the two-credential attack of Section 5.3, and why single-credential enforcement cannot be verified.¶
Timing and error side channels during verification, especially distinguishing "bad proof" from "spent nullifier".¶
The replay protection store is shared only within its configured scope. A deployment can use one atomic scope across all regions, including by routing each replay protection identifier to an authoritative region. This does not require the Client to know that region. A deployment can instead operate independent regional stores, but then the same Endorsement can be accepted once in each region. Credentials can likewise be presented once in each region, potentially creating divergent updates. Separate Moderators also do not coordinate stores. For ACT, all servers accepting the same key and credential context MUST share one atomic nullifier store; independent stores need disjoint keys or contexts. Otherwise a Client can fork a balance across stores, which breaks the credit conservation of [ACT]. A Rollatini redemption exposes the same nullifier in each scope, making cross-scope reuse linkable if records are compared. Proof rerandomization does not hide that reuse.¶
Step 3 of Section 5.2.3.2 is the atomic recording that [ACT] requires. The store MUST be durable: a Moderator that loses it re-admits every Credential spent under a context for as long as that context is accepted. Nullifiers MAY be partitioned by key and context and discarded once the context is no longer accepted; a Moderator MUST NOT accept that key and context again afterwards.¶
Step 2 of Section 5.2.3.2 requires spend.s and spend.a to equal the
challenge's s and a. Accepting another s would let the Client choose
its charge, and accepting a larger a would let it choose how far its
balance may rise. [ACT] gives the range checks that credit conservation
needs.¶
Copies of one Credential yield one presentation: the first to reach the Moderator is recorded in step 3, and every other copy is rejected as a double spend and is linkable to the first ([ACT]).¶
A recorded result is returned only for a byte-identical presentation and
never authorizes another operation. A party that replays an observed
presentation receives a refund it cannot finalize without the spend
state. A recorded NoUpdate stays NoUpdate; answering a retry with a
refund would undo the Moderator's revocation.¶
A Moderator that records NoUpdate ends that Credential chain, and the
Client loses its remaining balance with no cryptographic recourse.
Clients therefore trust Moderators to refund honest presentations; a
Moderator that withholds refunds indiscriminately costs its Clients their
Endorsements. Client Vendors can limit their Clients' use of such a
Moderator.¶
Under the policy-scoped profile of Section 5.2.3.1, a party that sees a presentation before the Moderator does, such as an intermediary terminating TLS for the Moderator, can attach it to another request under the same policy. The nullifier ensures that only one use is accepted, not which. Deployments that need a presentation tied to one request use the request-bound profile.¶
An unrecorded nullifier passes step 1 of Section 5.2.3.2, so a forged
presentation costs the Moderator a full VerifySpend, whose cost is
linear in L. Mitigations are rate limiting applied before verification,
which identifies Clients no more than the transport already does, and a
small L.¶
This document sketches two candidate registries under a future "MoLE" group. The values below are candidate values for discussion in this -00 draft and are not stable assignments.¶
New registrations use Specification Required as defined by [IANA]. A
specification MUST define the structures and abstract operations required by
the relevant common API, including canonical encodings and maximum sizes. An
endorsement registration MUST provide issuer hiding where applicable, define
its replay protection behavior, and state how a Challenge affects its cryptographic
Context. A credential registration MUST define IssuanceRequest,
IssuanceResponse, PresentationAndUpdate, Update, finalization, retry
behavior, and Challenge-to-Context derivation. Outer type-selecting messages
carry the registered uint16 type as specified in Section 3.¶
| Value | Name | Reference |
|---|---|---|
| 0x0000 | Reserved | this document |
| 0x0001 | No Endorsement Required | Section 4.3 |
| 0x0002 | Rollatini | Section 4.4 |
| 0x0003 | Longfellow | Section 4.5 |
| 0xFF00 - 0xFFFF | Reserved for testing | this document |
The registration template contains:¶
Value: The two-byte endorsement type.¶
Name: A short name for the protocol.¶
Exchanges: The number of request/response exchanges with the Anchor, or "none" if the grant is out of band.¶
Publicly Verifiable: Whether the Endorsement can be verified without Anchor secret key material.¶
Reference: Where the protocol is defined.¶
The following initial registrations are candidates only.¶
One MoLE credential type identifies the issuance, presentation, and update payloads defined by a credential protocol.¶
| Value | Name | Reference |
|---|---|---|
| 0x0000 | Reserved | this document |
| 0x0001 | ACT | Section 5.2 |
| 0x0002 | Privacy Pass Reverse Flow | Section 5.3 |
| 0x0A0A, 0x1A1A, ..., 0xFAFA | Reserved for greasing | Section 3.2 |
| 0xFF00 - 0xFFFF | Reserved for testing | this document |
The registration template contains:¶
Value: The two-byte credential type.¶
Name: A short name for the protocol.¶
Bound Update: Whether updates provably apply to the presented credential.¶
Reference: Where the protocol is defined.¶
Value: 0x0002¶
Name: Privacy Pass Reverse Flow¶
Bound Update: No¶
Reference: Section 5.3¶
Value: 0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA, 0xCACA, 0xDADA, 0xEAEA, 0xFAFA¶
Name: RESERVED¶
Bound Update: N/A¶
Reference: Section 3.2¶
These values MUST NOT be assigned. Message bodies carrying them contain random bytes (Section 3.2).¶
| Media Type | Reference |
|---|---|
| application/mole-endorsement-request | Section 4 |
| application/mole-endorsement-response | Section 4 |
Editor note. The final registry names and expert-review instructions remain to be specified.¶
A Client requests a resource protected by a Moderator that uses credential type 0x0002 (Privacy Pass Reverse Flow) and accepts endorsement type 0x0002 (Rollatini). The Client obtains the Endorsement before contacting that Moderator.¶
The first Rollatini request has an empty body. The Anchor returns a
CommitMessage. The Client then sends the corresponding ChallengeMessage,
and the Anchor returns a ResponseMessage:¶
POST <anchor-grant-link> HTTP/1.1
Host: anchor.example
Content-Type: application/mole-endorsement-request
EndorsementRequest { 0x0002, "" }
¶
The Client finalizes the ResponseMessage into an Endorsement. It then obtains
a ModeratorChallenge from the Moderator, computes its digest, and sends an
HTTP request with a CredentialRequest containing the Rollatini Redemption
and a Privacy Pass TokenRequest to its Redeem & Issue endpoint:¶
POST /issue HTTP/1.1 Host: moderator.example Authorization: Mole credential-request="<credential-request>"¶
The Moderator verifies the redemption against the Challenge it sent and
atomically records its nullifier.
It then processes the issuance request and returns a CredentialResponse
carrying a TokenResponse in the Mole-Credential header. The Client finalizes
the issuance response. On a later request, it obtains a
CredentialChallenge carrying the configured Privacy Pass TokenChallenge
before presenting the resulting token:¶
GET /resource HTTP/1.1 Host: moderator.example Authorization: Mole presentation="<credential-presentation>"¶
The Moderator verifies the presentation and serves the resource. Its response
carries an Update for a fresh token, or no update if it chose to consume the
Credential.¶
TODO acknowledge.¶