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
- Use Python 3.11+ and install the current commit:
python -m pip install "ap2 @ git+https://github.com/google-agentic-commerce/AP2.git@e1ea56db72a6385bce3e5c1112b3a56ce60acb43"
- Save the following as
repro.py, then run python repro.py.
- 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.
What happened
ReceiptClient.verify_receiptdeclaresreceipt_issuer_public_keyas acryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey, but passing a P-256 public key of that type rejects an otherwise valid receipt: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
python -m pip install "ap2 @ git+https://github.com/google-agentic-commerce/AP2.git@e1ea56db72a6385bce3e5c1112b3a56ce60acb43"repro.py, then runpython repro.py.verification_failedbefore 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
Additional context
e1ea56db72a6385bce3e5c1112b3a56ce60acb43, Python 3.12.13, cryptography 46.0.5, jwcrypto 1.5.6, Pydantic 2.12.5.JWK.from_pyca(...).verify_receipt,ReceiptClient,EllipticCurvePublicKey, and the error message; no direct duplicate found.