Skip to content

[Proposal]: Optional Privacy-Preserving Authorization for IntentMandate using Anonymous VDCs  #120

@mpratapa

Description

@mpratapa

Background

The AP2 specification (Section 4.1.2) states that IntentMandates in human-not-present flows must be cryptographically signed by the user. Currently, IntentMandate in src/ap2/types/mandate.py lacks a standardized authorization field. If this is incorrect, please point me to the right sources. Additionally, Section 5.2 describes 'Human Not Present' flows where an IntentMandate is shared with merchants. In the 'Discovery & Negotiation' phase (Section 5.1), sharing a static signature with multiple merchants creates a correlation risk.

Problem: Linkability Risks in Agent-to-Agent (A2A) Discovery

This proposal specifically targets Agent-to-Agent interactions during the discovery and negotiation phases.

In fully autonomous scenarios, a user's agent may query dozens of merchant agents to compare prices. While the merchant eventually needs shipping/payment details for a final transaction, they do not need this data during the browsing phase. If initial IntentMandates are signed using standard signatures (e.g., SD-JWT/JWS using RSA or ECDSA) with a persistent key identifier, they act as a "super-cookie." This allows merchant agents to collude and correlate the user's shopping behavior across the network before any purchase decision is made.

This creates a significant barrier to adoption: Users must feel confident that their agent can "shop around" on their behalf with the same anonymity as a human browsing as a guest without logging in.

Requirement: Since Section 5.2 allows merchants to reject an IntentMandate, the VDC format used here must support Unlinkability (e.g., via Zero-Knowledge Proofs) using existing web infrastructure (DNS/HTTPS) to prevent tracking during these high-volume A2A interactions.

Describe the solution you'd like

Proposal: Optional Support for Anonymous Verifiable Digital Credentials

To keep existing signing mechanisms intact while supporting anonymous credentials, I propose adding an optional authorization field to IntentMandate. This is just an opt-in mechanism.

user_authorization: Optional[UserAuthorization]

class UserAuthorization(TypedDict):
    kind: Literal["raw_sig", "anon_cred_bbs"]
    proof: str              # actual signature or ZK proof
    issuer: Optional[str]   # only for anon_cred_bbs
    schema: Optional[str]   # only for anon_cred_bbs

Example Payload (Anonymous):

{
  "kind": "anon_cred_bbs",
  "proof": "<zk-proof-string>",
  "issuer": "https://auth.example.com", 
  "schema": "<credential-schema-id>"
}

Mechanism: BBS+ Signatures & Context Binding

Using schemes like BBS+, we achieve the necessary privacy properties for multi-merchant discovery:

  1. Single Issuance: The issuer (e.g., the user's platform) signs one credential authorizing the agent.
  2. Multiple Presentations: The agent generates unique zero-knowledge proofs for each merchant agent using fresh randomness.
  3. Unlinkability: While all proofs verify against the same public parameters, they are cryptographically distinct.
  4. Key Discovery: Verifiers fetch the issuer's public keys via a standard .well-known endpoint (e.g., https://auth.example.com/.well-known/jwks.json), removing the need for DIDs.

Context Binding (Anti-Replay & Anti-Linkage):
To ensure security, each anonymous proof MUST be bound to the specific IntentMandate and the verifier. The proof generation includes a presentation context:

presentation_context = Hash(IntentMandate_contents || merchant_identifier || verifier_nonce)

This ensures:

  • No Replay: Proofs cannot be replayed across merchants.
  • No Forwarding: A colluding merchant cannot forward a proof to another merchant.
  • Integrity: Authorization is cryptographically tied to this specific IntentMandate.

Trust Model

This implementation uses a trusted issuer model (similar to Idemix).

  • Issuance: The issuer verifies the user's identity once.
  • Presentation: The agent presents proofs to merchant agents.
  • Privacy: The issuer cannot see where the credential is presented, and merchants cannot see who the user is.

Addressing Auditability: Idemix/BBS+-Style Enrollment Nym (Pseudonymous Identifier)

Instead of hashing user_identifier || nonce, the credential includes an Enrollment Nym derived from the user's identifier secret, following the Idemix/BBS+ pseudonym construction.

First, the issuer defines a public base $H_{\text{isk}}$. The user computes a base pseudonym:

$$ N = H_{\text{isk}}^{s_{\text{user}}} $$

where $s_{\text{user}}$ is the secret identifier bound to the credential.

For each transaction or audit context, the agent generates a randomized pseudonym:

$$ \text{Nym} \leftarrow N \cdot H_r^{r_n} $$

where $H_r$ is a second public base and $r_n$ is fresh randomness.

This construction provides:

  1. Privacy by Default:
    Only the randomized $\text{Nym}$ appears inside the ZK presentation. The verifier never learns $N$, $s_{\text{user}}$, or any stable identifier.

  2. Selective Disclosure for Audit:
    In a dispute, the user can produce a zero-knowledge proof that the Nym used in the transaction corresponds to the same secret $s_{\text{user}}$ embedded in their credential, without revealing the secret or exposing any permanent identifier.

  3. Unlinkability:
    Each merchant receives a different randomized $\text{Nym}$ due to fresh $r_n$. Even if merchant agents collude, they cannot correlate them back to a single user. This mirrors the unlinkable pseudonym techniques used in modern anonymous-credential systems (Idemix/BBS+), while avoiding the long-term linkability risks of hash-based commitments.

Threat Model (Scope Clarification)

Protects Against:

  • Cross-merchant linkability caused by stable cryptographic identifiers.
  • Agents correlating user behavior during A2A discovery.
  • Issuers learning where or when user credentials are presented.

Does NOT Protect Against: Network-level tracking (IP addresses, TLS fingerprinting, etc.) or device fingerprinting through metadata.

The goal is protocol-layer unlinkability, full anonymity in all channels is out-of-scope for this proposal.

Revocation

Revocation can follow standard VC/BBS+ practices (e.g., status lists or accumulator-based revocation). Verifiers check revocation status before validating the anonymous proof. Because presentations are unlinkable, revocation is checked only against the credential and does not reveal which proofs the user previously generated.

Reference Implementation

I have developed a functional prototype validating this approach using the @mattrglobal/pairing-crypto library.

Scope of Prototype:
This is a "toy model" focused strictly on the cryptography. It is a standalone script that:

  • Simulates the Issuer, User Agent, and Verifier roles. There are no actual merchants or revocation implementation.
  • Generates BBS+ signatures and multiple unlinkable proofs from a single IntentMandate.

References:

[1] Idemix: https://github.com/IBM/idemix/tree/main
[2] BBS+ Signatures: https://github.com/mattrglobal/pairing_crypto

Describe alternatives you've considered

Alternative Considered: OpenID Connect (OIDC)

The Technical Limitation of OIDC:
OIDC relies on Interactive Signing. To generate a valid token for a Relying Party (Merchant), the User Agent must contact the Identity Provider (IdP) at the time of the transaction. The IdP must use its signing keys to generate a token specific to that session. Consequently, the IdP is architecturally required to be a participant in every transaction, creating an unavoidable metadata trail and a central bottleneck. Also, it is not suitable for offline or autonomous A2A flows where the user is not present.

Additionally, OIDC tokens typically include a stable subject identifier (sub), which makes sessions linkable across merchants unless the IdP implements pairwise subject identifiers.

How this Proposal (AP2) Overcomes It:
This proposal utilizes Non-Interactive / Zero-Knowledge Proofs, which shifts the architecture from Server-Side Signing to Client-Side Proofs.

  1. Decoupled Signing: The IdP uses its keys only once: during the initial issuance of the Verifiable Credential to the user's device.
  2. Local Derivation: When the user visits a merchant, the User Agent (not the IdP) cryptographically derives a unique proof locally.
  3. Result: The IdP is technically incapable of tracking where the credential is being used. Furthermore, this reduces latency for the user and eliminates the computational burden on the IdP for every subsequent transaction. The agent can generate unique, unlinkable proofs for new merchants without internet access to the IdP.

Additional context

Feedback Requested

  • Is this optional privacy-preserving authorization mechanism in scope for the reference implementation?
  • Would maintainers be open to a follow-up PR with this implementation?

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions