feat(samples): add Tenzro DID-based settlement sample#257
feat(samples): add Tenzro DID-based settlement sample#257hilarl wants to merge 2 commits intogoogle-agentic-commerce:mainfrom
Conversation
Adds a runnable Python sample demonstrating the AP2 mandate chain (IntentMandate → CartMandate → PaymentMandate) running against a DID-based identity layer with on-chain delegation and a STARK settlement commitment. Uses TDIP/did:tenzro: as one concrete instance; the structure is identity-system-agnostic. Signed-off-by: Hilal Agil <hilaal@gmail.com>
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request adds a DID-based settlement sample using the Tenzro Decentralized Identity Protocol (TDIP), including a main script, a JSON-RPC client, and tests. Feedback addresses a critical bug regarding ZK witness field overflow and provides suggestions for improving time-handling consistency and thread safety.
| service_proof_hash = hashlib.sha256( | ||
| payment_mandate.model_dump_json().encode("utf-8") | ||
| ).digest() | ||
| service_proof_int = int.from_bytes(service_proof_hash[:4], "little") |
There was a problem hiding this comment.
The service_proof_int is derived from 4 bytes of a hash, which can result in a value up to tenzro_client.py (line 186), the KoalaBear field modulus is
| service_proof_int = int.from_bytes(service_proof_hash[:4], "little") | |
| service_proof_int = int.from_bytes(service_proof_hash[:4], "little") % 2130706433 |
| ip = intent_vdc["payload"] | ||
| cp = cart_vdc["payload"] | ||
| results: list[tuple[str, bool, str]] = [] | ||
|
|
||
| # 1. AP2 IntentMandate constraints | ||
| intent_ok = ( | ||
| cp["total_amount"] <= ip["max_amount"] | ||
| and ip["agent_did"] == cp["agent_did"] | ||
| and (not ip["allowed_merchants"] or cp["merchant_did"] in ip["allowed_merchants"]) | ||
| and datetime.fromisoformat(ip["expires_at"]) > datetime.now(UTC) | ||
| ) | ||
| results.append( | ||
| ( | ||
| "AP2 IntentMandate constraints", | ||
| intent_ok, | ||
| f"max_amount={ip['max_amount']} cents, cart total={cp['total_amount']} cents", | ||
| ) | ||
| ) | ||
|
|
||
| # 2. AP2 CartMandate consistency | ||
| recomputed = sum(item["total"] for item in cp["items"]) | ||
| cart_ok = ( | ||
| recomputed == cp["total_amount"] | ||
| and datetime.fromisoformat(cp["expires_at"]) > datetime.now(UTC) | ||
| ) |
There was a problem hiding this comment.
The local_validate_mandates function calls datetime.now(UTC) multiple times (lines 346 and 360). This can lead to minor inconsistencies if the system clock advances between calls, and it makes the function less deterministic for testing. It is better to capture the current time once at the start of the function.
now = datetime.now(UTC)
ip = intent_vdc["payload"]
cp = cart_vdc["payload"]
results: list[tuple[str, bool, str]] = []
# 1. AP2 IntentMandate constraints
intent_ok = (
cp["total_amount"] <= ip["max_amount"]
and ip["agent_did"] == cp["agent_did"]
and (not ip["allowed_merchants" ] or cp["merchant_did"] in ip["allowed_merchants"])
and datetime.fromisoformat(ip["expires_at"]) > now
)
results.append(
(
"AP2 IntentMandate constraints",
intent_ok,
f"max_amount={ip['max_amount']} cents, cart total={cp['total_amount']} cents",
)
)
# 2. AP2 CartMandate consistency
recomputed = sum(item["total"] for item in cp["items"])
cart_ok = (
recomputed == cp["total_amount"]
and datetime.fromisoformat(cp["expires_at"]) > now
)| rpc_id = self._next_id | ||
| self._next_id += 1 |
There was a problem hiding this comment.
The increment of self._next_id is not thread-safe. While this sample is likely used in a single-threaded context, if the TenzroClient were used in a multi-threaded environment (e.g., within a concurrent web server), multiple requests could end up with the same JSON-RPC id. Although many servers handle duplicate IDs, it is best practice to use a thread-safe counter or a lock to ensure uniqueness per session.
Spellcheck: add domain-specific words (tenzro, tdip, plonky, micropayment, rustaceans, pdis) and standard test-fixture names (rsps for the responses library, capsys for pytest). Two local variable / string-prefix tweaks (deleg_ok -> delegation_ok, chan- -> channel-) avoid adding non-words to the dictionary. main.py: reduce service_proof_int modulo the KoalaBear field prime (2,130,706,433) so the witness is always a valid field element. Without this, ~50% of 4-byte LE samples exceed the modulus and the prover would reject the witness. Resolves Gemini HIGH review comment. main.py: capture datetime.now(UTC) once at the start of local_validate_mandates so all expiry checks observe the same instant. Resolves Gemini LOW review comment. tenzro_client.py: guard self._next_id increment with a threading.Lock so the same TenzroClient instance can be safely shared across threads without two concurrent calls picking the same JSON-RPC id. Resolves Gemini LOW review comment. Signed-off-by: Hilal Agil <hilaal@gmail.com>
|
@googlebot I signed it! |
1 similar comment
|
@googlebot I signed it! |
|
Withdrawing. |
feat(samples): add Tenzro DID-based settlement sample
Summary
Adds a new runnable Python sample under
code/samples/python/tenzro_did_settlement/that demonstrates the AP2mandate chain (
IntentMandate→CartMandate→PaymentMandate)running against a DID-based identity layer, with an on-chain
delegation primitive and a STARK-based settlement commitment.
The sample uses TDIP and
did:tenzro:as one concrete instance of a DID-based identity layer,but the structure is identity-system-agnostic — the chain-specific glue
is isolated in a single thin
tenzro_client.TenzroClientmodule so thesame sample skeleton works against
did:web,did:key,did:ion,did:eth, or any other W3C DID method whose chain exposes a comparabledelegation primitive and a settlement-proof primitive.
What's in the sample
The flow exercises all four nested validation ceilings that an AP2
mandate chain can be subject to in a DID-based deployment:
IntentMandateconstraints —max_amount, allowedmerchants, allowed SKUs, expiry. (Plain AP2 SDK semantics.)
CartMandateconsistency — total = sum of line items,parent intent matches, expiry. (Plain AP2 SDK semantics.)
DelegationScope::enforce_operation— a protocol-levelceiling set when the machine identity was registered
(
max_transaction_value,allowed_operations,allowed_payment_protocols,allowed_chains,time_bound).SpendingPolicy::check— an execution-levelceiling that the agent's runtime registry enforces (e.g. daily-spend
windows). Mutable; orthogonal to the protocol ceiling.
Settlement: a Plonky3 STARK over the
settlementAIR is generated viatenzro_createZkProof; its 32-byte SHA-256 commitment matches theon-chain
ZkCommitmentRegistryformat so the EVMZK_VERIFYprecompile becomes an O(1) HashSet lookup.
The
PaymentMandate.payment_method.supported_methodsistenzro:micropayment-channel, dispatching to a Tenzro per-messagebilling channel.
The sample also adds a one-paragraph subsection
"DID-based identity layer (example: TDIP)" to
docs/ap2/implementation_considerations.md(see the diff below).Why this fills a gap
The existing samples in
code/samples/python/scenarios/a2a/areexcellent demonstrations of card-based and x402-based flows, but they
use opaque identifiers for the user / agent / merchant parties. They do
not show what AP2 looks like when those identifiers are first-class
DIDs with on-chain delegation enforcement and verifiable-credential-
style scope binding.
AP2's mandate types are identity-system-agnostic — the principal,
agent, and merchant identifiers are opaque strings. This PR makes
that explicit by giving implementers a runnable, end-to-end example
of layering AP2 on top of a DID method.
What this PR does NOT touch
docs/ap2/specification.md,payment_mandate.md,flows.md, etc. are FIDO Alliance's responsibility now.code/sdk/python/ap2/is untouched. The sampleconsumes the SDK exactly as published.
requests,pydantic(a transitive dep ofap2already), and the test usesresponsesfor HTTP mocking. All deps are publicly available onPyPI; nothing private, nothing wallet-secret.
Test plan
The sample ships with
test_main.py, which runs the fullmain.run()flow end-to-end against a stub Tenzro JSON-RPC backendmocked with
responses:Expected: 5 tests pass.
test_intent_and_cart_build_and_round_trip— AP2 SDK objectsbuild correctly and round-trip into the Tenzro VDC envelope shape.
test_local_validation_passes_within_all_ceilings— all fourceilings green-light a within-budget cart.
test_local_validation_rejects_when_daily_window_exceeded— theSpendingPolicyceiling correctly fails when the daily window istoo small.
test_compute_zk_commitment_matches_expected_shape— the SHA-256commitment matches Tenzro's
compute_zk_commitmentshape.test_run_end_to_end_against_stub_rpc— the fullmain.run()flow drives the stub RPC through
resolve_did,ap2ValidateMandatePair, andcreateZkProof.test_run_offline_mode_skips_rpc—--offlineflag works.The sample also runs against the live testnet at
rpc.tenzro.networkwithout modification — the read-only RPCs (DID resolution, mandate
validation, ZK proof generation) require no funded keys; the channel
update is intentionally simulated because that step requires a
pre-opened, funded channel.
CLA acknowledgment
I have signed the Google CLA. This sample is a Tenzro-funded
contribution to the AP2 ecosystem; the copyright headers in
tenzro_client.pyandmain.pydeclare Apache-2.0 licensing,matching the rest of the repository.
AI assistance disclosure
Author: Hilal Agil <hilal@tenzro.com>, GitHub
@hilarl.
Parts of this contribution were AI-assisted via Claude Code. All
technical decisions — the choice to layer AP2 on top of TDIP, the
mapping from AP2's three mandate types onto Tenzro's IntentMandate /
CartMandate VDC envelope, the use of the
settlementPlonky3 AIR forthe commitment, the four-ceiling validation model — are human-authored
and consistent with Tenzro's production AP2 validator
(
tenzro_payments::ap2::MandateValidator::validate_with_delegation_and_policy).The drafting of boilerplate (docstrings, repetitive Pydantic model
construction, test scaffolding) was AI-assisted; every file was
reviewed and tested by the author before submission.
Checklist
code/samples/python/tenzro_did_settlement/only.requirements.txtpinned to specific versions.pytest test_main.py).docs/ap2/specification.md(FIDO scope).code/sdk/python/ap2/(SDK scope).docs/ap2/implementation_considerations.md(descriptive doc, notspec) — single paragraph cross-referencing the sample. Reviewers
who consider that file out of scope can drop it; the sample
stands on its own.
the existing repo convention.
rpc.tenzro.networktestnetend-to-end, no environment variables required.
DCO
Signed-off-by: Hilal Agil <hilal@tenzro.com>