Skip to content

[Bug]: ReceiptClient rejects its declared EllipticCurvePublicKey input for valid receipts #360

Description

@sunruize93-cmyk

What happened

ReceiptClient.verify_receipt declares receipt_issuer_public_key as a cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey, but passing a P-256 public key of that type rejects an otherwise valid receipt:

verification_failed
JWT verification failed: Verification failed for all signatures["Failed: [ValueError('Unrecognized key type')]"]

Converting the same public key with JWK.from_pyca(public_key) makes the same JWT verify successfully. This reproduces for both checkout and payment receipts. A different JWK correctly fails signature verification.

What I expected

The public key type accepted at runtime should agree with the public API. Either normalize the declared cryptography EC public key before verification, or explicitly document/type the JWK-only contract so integrations do not receive a misleading signature-verification failure.

This report concerns valid-input compatibility, not acceptance of invalid signatures.

Steps to reproduce

  1. Use Python 3.11+ and install the current commit:
    python -m pip install "ap2 @ git+https://github.com/google-agentic-commerce/AP2.git@e1ea56db72a6385bce3e5c1112b3a56ce60acb43"
  2. Save the following as repro.py, then run python repro.py.
  3. Both declared-EC-key cases return verification_failed before calling the reference callback. Both equivalent-JWK cases return {"verified": true} and call the reference callback once. The final assertion fails.
Executable reproduction with a locally generated disposable key
"""Reproduce receipt verification's declared EC public key mismatch."""
import json

from ap2.sdk.generated.payment_receipt import PaymentReceipt
from ap2.sdk.jwt_helper import create_jwt
from ap2.sdk.receipt_wrapper import ReceiptClient
from cryptography.hazmat.primitives.asymmetric import ec
from jwcrypto.jwk import JWK

raw_key = ec.generate_private_key(ec.SECP256R1())
signing_key = JWK.from_pyca(raw_key)
raw_public_key = raw_key.public_key()
jwk_public_key = JWK.from_pyca(raw_public_key)
wrong_public_key = JWK.from_pyca(ec.generate_private_key(ec.SECP256R1()).public_key())
client = ReceiptClient()
reference = 'local-test-reference'
receipts = [
    ('checkout', client.create_checkout_receipt('merchant.example', reference, 'order-1'), False),
    ('payment', PaymentReceipt(status='Success', iss='psp.example', iat=1,
        reference=reference, payment_id='test-1', psp_confirmation_id='test-1',
        network_confirmation_id='test-1'), True),
]
results = []
for kind, receipt, is_payment in receipts:
    token = create_jwt({'alg': 'ES256'}, receipt.model_dump(mode='json'), signing_key)
    for label, public_key in [('declared EC key', raw_public_key), ('same key as JWK', jwk_public_key), ('wrong key control', wrong_public_key)]:
        seen_references = []
        def check_reference(value):
            seen_references.append(value)
            return value == reference
        result = client.verify_receipt(token, public_key, check_reference, is_payment)
        results.append({'receipt': kind, 'key': label, 'result': result, 'store_checks': len(seen_references)})
print(json.dumps(results, indent=2))
for row in results:
    if row['key'] == 'same key as JWK':
        assert row['result'] == {'verified': True} and row['store_checks'] == 1
    elif row['key'] == 'wrong key control':
        assert row['result']['error'] == 'verification_failed'
for row in results:
    if row['key'] == 'declared EC key':
        assert row['result'] == {'verified': True}, 'declared public-key type should verify the same valid receipt'

Additional context

  • Executed against main e1ea56db72a6385bce3e5c1112b3a56ce60acb43, Python 3.12.13, cryptography 46.0.5, jwcrypto 1.5.6, Pydantic 2.12.5.
  • The eight existing receipt-wrapper tests pass; their success cases use JWK inputs.
  • No external service, payment rail or real transaction is used. The reference-store callback is supplied in every case.
  • Workaround: explicitly convert the public key with JWK.from_pyca(...).
  • Searched existing issues/PRs for verify_receipt, ReceiptClient, EllipticCurvePublicKey, and the error message; no direct duplicate found.
  • Happy to contribute a focused fix and regression tests. Investigation and reproduction were AI-assisted; the script was executed locally.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions