Skip to content

feat: authorization-zone intents for third-party keepers (#370) - #373

Open
AbuJulaybeeb wants to merge 1 commit into
TevaLabs:mainfrom
AbuJulaybeeb:feat/intent-keeper-auth-zone
Open

feat: authorization-zone intents for third-party keepers (#370)#373
AbuJulaybeeb wants to merge 1 commit into
TevaLabs:mainfrom
AbuJulaybeeb:feat/intent-keeper-auth-zone

Conversation

@AbuJulaybeeb

Copy link
Copy Markdown

Summary

  • Implemented an on-chain authorization-zone intent and keeper framework allowing third parties to execute scoped operational actions on behalf of users/protocol without transferring asset custody or granting unbounded permissions.
  • Added 3 core keeper action flows:
    1. execute_keeper_resolve: permissioned oracle payload resolution under an active KeeperScope::Resolve intent.
    2. execute_keeper_claim: user winning claims executed by an authorized keeper under a KeeperScope::Claim intent, guaranteeing that all claimed winnings are credited directly to the user's custody (accumulate_pending).
    3. execute_keeper_create_next: automated template-based round rollover executed under a KeeperScope::CreateNext intent.
  • Added strict capability security controls:
    • Scoped authority (KeeperScope) preventing privilege escalation across action domains.
    • Per-user/scope sequential nonces preventing intent replay attacks.
    • Expiry ledger enforcement (expires_at_ledger) with validation (MIN_INTENT_EXPIRY_LEDGERS = 6, MAX_INTENT_EXPIRY_LEDGERS = 172,800).
    • User revocation mechanism (revoke_keeper_intent).
    • Optional admin-controlled keeper registration gate (register_keeper, deregister_keeper, set_keeper_registration_required).
  • Documented security analysis in docs/INTENT_THREAT_MODEL.md.
  • Exported TypeScript client bindings and error classes in bindings/src/helpers.ts.

Why

Third-party automation services (keepers, bots, relayer networks) need the ability to perform operational tasks (settling expired rounds, claiming user rewards, rolling over rounds) without:

  1. Gaining custody over user funds or tokens.
  2. Holding permanent or unscoped execution authority.
  3. Being vulnerable to signature replay across different contract rounds or ledgers.

This capability-security authorization framework enforces strict scoped permissions, replay resistance, expiry windows, and revocation safety.

Implementation

  1. Core Intent Module (contracts/src/intents.rs):

    • authorize_keeper_intent(env, user, keeper, scope, duration_ledgers) -> u64: Authorizes a keeper with a monotonic per-scope nonce and expiry ledger.
    • revoke_keeper_intent(env, user, scope, nonce) -> Result<(), ContractError>: Explicit user revocation.
    • get_keeper_intent(env, user, scope, nonce) -> Option<KeeperIntent>: Observability query.
    • execute_keeper_resolve(env, keeper, user, nonce, payload) -> Result<(), ContractError>: Gated resolution.
    • execute_keeper_claim(env, keeper, user, nonce) -> Result<i128, ContractError>: Gated claim preserving user custody.
    • execute_keeper_create_next(env, keeper, user, nonce) -> Result<u64, ContractError>: Gated round rollover.
    • register_keeper, deregister_keeper, set_keeper_registration_required, is_keeper_registered, is_keeper_registration_required: Optional keeper allowlist gating.
  2. Types & Storage (contracts/src/types.rs, contracts/src/errors.rs):

    • Added KeeperScope (Resolve, Claim, CreateNext), KeeperIntentStatus (Active, Consumed, Revoked), KeeperIntent, IntentKey.
    • Added ContractError codes 80–87 for intent error handling.
    • Implemented manual conversion traits for ContractError to handle the SDK error conversion and variant limits safely.
  3. Contract Interface (contracts/src/contract.rs, contracts/src/lib.rs):

    • Exposed all 11 intent/keeper entrypoints on VirtualTokenContract.
  4. Test Suite (contracts/src/tests/intents.rs):

    • 10 automated unit test cases covering happy path claim, create-next, authorization, expiry enforcement, replay rejection, scope isolation, revocation, and keeper registration gates.

Testing

Executed automated test suite:

  • cargo check --lib (clean build)
  • cargo test --lib -- tests::intents
running 10 tests
test tests::intents::test_invalid_expiry_rejected ... ok
test tests::intents::test_authorize_and_get_intent ... ok
test tests::intents::test_keeper_mismatch_rejected ... ok
test tests::intents::test_expired_intent_rejected ... ok
test tests::intents::test_keeper_create_next_happy_path ... ok
test tests::intents::test_revoked_intent_rejected ... ok
test tests::intents::test_keeper_registration_requirement ... ok
test tests::intents::test_scope_cannot_escalate ... ok
test tests::intents::test_replayed_intent_rejected ... ok
test tests::intents::test_keeper_claim_happy_path_and_custody_preservation ... ok

test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; finished in 0.92s
Scope / Risk
Affected areas: contracts/src/intents.rs, contracts/src/contract.rs, contracts/src/types.rs, contracts/src/errors.rs, docs/INTENT_THREAT_MODEL.md, bindings/src/helpers.ts.
Breaking changes: None. Existing direct resolution, direct claim, and manual round creation remain fully backward-compatible and open.
Issue
closes #370

@josephchimebuka josephchimebuka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — Request changes

Solid direction on the intent/keeper framework and threat-model doc. There are blocking auth bugs that make the keeper paths fail (or worse, unsafe) outside mock_all_auths(), plus a privilege-model issue on Resolve / CreateNext.

Blocking

  1. Keeper execution calls auth-gated entrypoints that require a different signer

    • execute_keeper_claimclaim_winningsuser.require_auth()
    • execute_keeper_resolveresolve_roundoracle.require_auth()
    • execute_keeper_create_nextbetting::create_roundadmin.require_auth()

    Only the keeper signs these txs. In production (no mock_all_auths), these will fail auth. Tests pass only because setup_env() mocks all auths, which hides the bug.

    Fix: add internal helpers that skip the original require_auth after intent validation, e.g.:

    • _claim_winnings_for_user(env, user) (no user.require_auth, destination still hard-coded to user)
    • _resolve_round_as_oracle(env, payload) or require the intent authorizer to be the oracle and keep oracle auth intentionally
    • _create_next_from_template_internal(env) (no admin.require_auth)

    Prefer CEI: mark intent Consumed (or write a “in-flight” lock) before external effects if any path can partially succeed; today consume is after the call (OK under Soroban atomicity, but document it).

  2. Who may authorize high-privilege scopes?
    Any user can call authorize_keeper_intent(..., Resolve|CreateNext, ...).

    • Resolve is normally oracle-only
    • CreateNext / create_round is normally admin-only

    If you fix (1) by bypassing oracle/admin auth after a user-issued intent, that is a privilege escalation (user-nominated keeper becomes de-facto oracle/admin).

    Fix: restrict authorization:

    • Claim → user may authorize
    • Resolve → only current oracle (or admin) may authorize
    • CreateNext → only admin may authorize
      Add tests that a random user cannot authorize Resolve/CreateNext.
  3. Tombstone never checked
    _consume_intent writes ConsumedIntentNonce, but _check_intent_active only reads status. Either check the tombstone on execute, or drop the unused key to avoid false confidence in the threat model (§3.1).

High

  1. PR description vs code constants
    Description says MAX_INTENT_EXPIRY_LEDGERS = 172,800; code uses 1_036_800. Align docs/PR/INTENT_THREAT_MODEL.md.

  2. Bindings error codes
    IntentAlreadyConsumedError uses code 79 in helpers.ts, while other work on main uses 79 for AccessDenied. Reconcile with ContractError so clients map correctly.

  3. Missing tests for resolve happy path / auth failure
    No test that execute_keeper_resolve works end-to-end, and no test with selective auth (mock only keeper, not user/oracle/admin) proving claim/resolve/create_next work as designed.

Medium / nits

  1. Scope-isolation test expects IntentNotFound (key includes scope) — fine, but also assert IntentScopeMismatch if you ever look up by nonce alone.
  2. create_next path loads template then calls create_round instead of create_next_from_template — inconsistent with the public API and PR description.
  3. Large mechanical churn in types.rs / unrelated modules — please keep this PR scoped to intents + minimal glue, or call out unavoidable storage-split changes clearly.

Suggested acceptance checklist before re-review

  • Internal auth-bypass helpers for keeper paths (or intentional co-sign model documented + tested)
  • Restrict Resolve/CreateNext intent authorization to oracle/admin
  • Auth-selective tests (no mock_all_auths for the execute step)
  • Tombstone checked or removed; constants/docs aligned
  • Bindings error codes match ContractError

Happy to re-review quickly once those are addressed.

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.

2 participants