Skip to content

feat(lore-0034): add the receiver authorizer and drop UptoProxy nonce storage - #181

Open
karczuRF wants to merge 6 commits into
developfrom
lore-0034-review-fixes
Open

karczuRF wants to merge 6 commits into
developfrom
lore-0034-review-fixes

Conversation

@karczuRF

Copy link
Copy Markdown
Collaborator

Summary

Task 0034 (merge upto for Stellar upstream): the maintainer review of our upstream spec PR asked for two changes. This PR makes both in UptoProxy, redeploys it on testnet, runs the e2e suite again and updates the docs and the spec draft. It is for our team's review first: nothing has been posted upstream, and nothing will be until we agree on it here.

  • Receiver authorizer (contracts/upto-proxy/src/lib.rs): the bound key is now receiver_authorizer, either the facilitator's key (facilitator-delegated mode) or the resource server's own key (self-managed mode), as in the SVM upto spec. Its require_auth() already covered actual_amount, so only the name changes, in the argument and the upto_settled event.
  • No contract nonce storage (same file): Soroban's auth nonce is the single-use check. is_nonce_used and error 7 (NonceUsed) are gone, and nonce stays as a signed payment ID. A second entry signed with the same ID is a second payment, and it settles.
  • New testnet deployment: CDPHI4JYNSWNAQFXUAZAWTBUBYLZZRWFU6F6EJIHLSTAL6MJWXOVEA2X (WASM a3d7bd0a…f666), now in deploy/testnet.env.example. CAL7SBTO…V2VC is retired.
  • e2e suite settles again (contracts/upto-proxy/e2e): the signer pool refuses source-account auth entries, which had kept the suite from settling until 0015. The receiver authorizer now signs its own address-credential entry. A new scenario covers self-managed mode: the seller's account signs the receiver authorization and another facilitator relays it.
  • Docs (contracts/upto-proxy/README.md, ADR 0010 with D6 amended and D12 added, docs/threat-model.md, docs/upto-proxy-testnet-report.md): updated for both changes. docs/scf/ milestone 1 files keep the old address on purpose, since they record what milestone 1 delivered.
  • Spec draft (lore/1-tasks/active/0034_FEATURE_upstream-upto-stellar/drafts/scheme_upto_stellar.md): both modes, extra.receiverAuthorizer, payload.receiverAuthorization, single use through the auth nonce, and rewritten verification, settlement, error and security sections.

Please confirm

  1. Self-managed /verify can't run the enforcing simulation (payments): the server signs only at /settle. /verify simulates in recording mode and checks the client's signature and auth nonce itself. A smart-wallet client whose __check_auth fails is therefore caught only at settlement, which the spec now lists under the server's credit risk.
  2. Receiver authorizer keys (payments): the spec allows G... and C... accounts. A C... authorizer is verified only by the enforcing simulation under the fee ceiling, as a smart-wallet client is. Our seller SDK will sign with a G... key first.
  3. The settlement record (payments): the facilitator records (from, auth nonce), not the payment ID, before every settlement. Zero settlements need it because they never reach the chain. Non-zero ones need it so that concurrent /settle calls on one payload don't pay for a duplicate that would fail. The record is per facilitator, so in self-managed mode a server could still charge a zero-settled entry through another facilitator, within its ceiling. In self-managed mode a zero /settle still needs payload.receiverAuthorization.
  4. The receiver authorization's expiry (payments): at most the client's allowance_expiration_ledger, because the facilitator pays the rent on its nonce.
  5. Facilitators that don't relay self-managed payments (payments): they refuse them at /verify with invalid_upto_stellar_payload_receiver_authorization, so the server learns before it serves. In self-managed mode /verify reads the client's nonce entry with getLedgerEntries, because a recording-mode simulation can't see it.
  6. Operation source (payments, platform): the spec no longer says the facilitator SHOULD be the operation source. In facilitator-delegated mode it MAY be, but source-account credentials MUST NOT be used when the operation has no source of its own, since they would authorize as the channel account.

Testing

  • pnpm contracts:test → 40 passed, 0 failed; pnpm contracts:test:wasm → both WASM tests pass (only settle_upto exported, 3,727 bytes).
  • pnpm contracts:e2e on testnet, 2026-11-12 20:58 UTC → 61/61, 29 transactions, new proxy CDPHI4JY…EA2X.
    • A replayed entry fails with Error(Auth, ExistingValue).
    • A reused payment ID under a new signature settles on all three tokens.
    • A self-managed payment, with the seller's key as receiver authorizer and relayed by the other facilitator, settles on all three tokens (USDC cfed0770…7ad9, UPTOE2E ca1ec753…7ce8, TEST 5ed0e7af…975f).
    • A settlement at the ceiling cost 21,399–23,061 stroops.
    • Details are in the testnet report. An earlier run that day, before the self-managed scenario was added, passed 58/58.
  • Code review before the PR (4 reviewers): 3 decisions and 15 fixes applied, in the last two commits.
  • pnpm format:check (tracked files), pnpm lint, pnpm typecheck, pnpm build, pnpm contracts:lint → pass.
  • pnpm test was not run locally, because no package code changed; CI runs it.

The maintainer review of the upstream spec PR landed with two
comments, so the task is active again. Record both decisions
(receiver authorizer with self-managed and facilitator-delegated
modes; no x402 nonce storage in UptoProxy), the open points okarcz
decided, and the choices that emerged while writing the spec.
The bound key is now `receiver_authorizer`: the facilitator's key
(facilitator-delegated) or the resource server's own key
(self-managed). Its require_auth already covered actual_amount, so
only the name changes, in the argument and the upto_settled event.

Single use is Soroban's auth nonce. The contract no longer stores
(from, nonce): `nonce` is a signed payment ID, a second entry with
the same ID is a second payment the client signed and settles.

BREAKING CHANGE: `is_nonce_used` and error 7 (`NonceUsed`) are gone;
the `facilitator` argument and event field are `receiver_authorizer`.
New WASM, new testnet address.
The signer pool refuses source-account auth entries, which kept the
suite from settling. The receiver authorizer now signs its own
address-credential entry for the call, the self-managed shape. The
NonceUsed scenario becomes "settles a reused payment ID under a new
signature". 58/58 on testnet against CDPHI4JY…EA2X.
Contract README, ADR 0010 (D6 amended, D12 added), threat model,
the 2026-11-12 testnet run, the new testnet address and the
scheme_upto_stellar.md draft: both receiver authorizer modes,
payload.receiverAuthorization, single use through the auth nonce.
The seller's own account is the receiver authorizer: it signs its
entry for the actual amount without simulation, and the other
facilitator relays it through its own channel and fee bump. The
reused-payment-ID scenario now uses fresh terms, so a slow run
can't push it past its deadline. 61/61 on testnet.
Record every settlement before submitting it, so concurrent /settle
calls don't pay for a doomed duplicate. In self-managed mode /verify
reads the client's nonce entry, since recording-mode simulation
can't see it. A facilitator that doesn't relay self-managed payments
refuses them at /verify. Name extra.receiverAuthorizer in
/supported, keep retired delegated keys, note that the settlement
record is per facilitator, key rotation, and the 61/61 run.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant