Skip to content

feat: SCOPE_POLICY, SCOPE_NONCE, admin=scope-0 (match EIP #9)#28

Merged
chunter-cb merged 11 commits into
mainfrom
feat/scope-policy-and-nonce-lane
Jul 13, 2026
Merged

feat: SCOPE_POLICY, SCOPE_NONCE, admin=scope-0 (match EIP #9)#28
chunter-cb merged 11 commits into
mainfrom
feat/scope-policy-and-nonce-lane

Conversation

@chunter-cb

@chunter-cb chunter-cb commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Brings the reference AccountConfiguration (and the example accounts/authenticators) in line with the current EIP-8130 draft: chunter-cb/EIPs#9

Authorization model

  • Admin is the predicate scope == 0x00; config changes (applySignedActorChanges), account lock, delegation, and upgrades all require scope == 0 — there is no separate CONFIG grant.
  • Scope grants: SENDER (0x01), POLICY (0x02), NONCE (0x04), SELF_PAYER (0x08), SPONSOR_PAYER (0x10); 0x20/0x40/0x80 spare. Stored verbatim — getActorConfig / authenticateActor never normalize or mask.
  • POLICY is gated initiation (gates the actor to its manager regardless of SENDER); it composes with SELF_PAYER, SPONSOR_PAYER, and NONCE.
  • PAYER is split: SELF_PAYER authorizes self-pay (payer == sender); SPONSOR_PAYER authorizes sponsoring a different sender (payer != sender).
  • ERC-1271 verifySignature() requires operational authority — admin, or SENDER without POLICY — not a dedicated grant (signing is an encoding of authority a SENDER key already holds via calls; approve ≡ permit). A POLICY actor is never operational and cannot sign.

Actor policies

  • Policy slots (policy_manager / policy_commitment) are written iff POLICY is set. policyData is manager(20) ‖ commitment(32) (52 bytes when POLICY is set, empty otherwise); neither field need be nonzero.
  • authenticateActor returns (uint8 scope, address policyTarget). Granular getters: getPolicy, getPolicyManager, getPolicyCommitment.

Account creation & import

  • InitialActor carries actorId, authenticator, scope, and policyData, all committed to the derived address and hashed into the import digest. Initial actors are non-expiring (expiry = 0); manager = account is expressible at import but not at create.

Account lock

  • Signed, relayable, admin-authorized applySignedLockChanges(account, op, unlockDelay, auth) (op 1 = lock, 2 = unlock) replaces the caller-based lock / initiateUnlock. Local channel only: the digest binds block.chainid and the current localSequence (sign-current-then-increment, shared with local config changes), authenticated as admin (scope == 0).

Delegate authenticator

  • The nested vouch stays admin-only (scope == 0), decoupled from the now-operational verifySignature, preserving non-escalation: a SENDER key on account B can produce a valid ERC-1271 signature for B but cannot vouch as a delegate on an account that delegates to B.

ABI

  • Packed ActorAuthorized / DelegationApplied events, uint256 chainId on import / apply (chainId bound in the import digest).

Test plan

Align AccountConfiguration with the EIP-8130 draft: gated initiation is
SCOPE_POLICY, policy slots are written from that bit, scope combos are
stored verbatim for protocol use-time checks, and nonceLane carries the
unbound / lane-or-nonceless / nonceless-only restriction.
Drop nonceLane and SCOPE_CONFIG to match EIP PR #9: ActorConfig is
authenticator/scope/expiry only, SCOPE_NONCE is stored verbatim for
protocol use, and actor-change / upgrade authority requires scope == 0.
@chunter-cb chunter-cb changed the title feat: SCOPE_POLICY initiation mode and nonceLane feat: SCOPE_POLICY, SCOPE_NONCE, admin=scope-0 (match EIP #9) Jul 10, 2026
…-nonce-lane

# Conflicts:
#	src/AccountConfiguration.sol
#	src/accounts/UpgradeableAccount.sol
#	test/unit/AccountConfiguration/applyAccountChange.t.sol
#	test/unit/AccountConfiguration/applyKeyChange.t.sol
#	test/unit/AccountConfiguration/authenticate.t.sol
#	test/unit/AccountConfiguration/policyAccessors.t.sol
…ifications

Restores the conditional policy-manager SLOAD in both auth paths so admin/non-policy
authentications avoid an unused cold read. Adds NatSpec/README notes on the shared
local sequence (lock vs actor changes) and zero-commitment policy actors, and corrects
the README SENDER+POLICY guidance to SHOULD NOT (POLICY gates regardless).
…cope

Initial actors carry their declared scope and policyData (external policy
manager expressible at create); only expiry is forced to 0. The prior
"always unrestricted owners (scope 0x00, no policy)" wording contradicted
the code and the merged EIP-8130 spec.
The EIP makes the packed account-state slot normative — nodes read it
directly for mempool rate-limit tiering. The prior implementation encoded
lock state via unlocksAt sentinels (type(uint40).max = hard-locked) plus a
separate unlockDelay field, which a spec-conformant node reading the raw
slot would misinterpret.

Align storage to the spec: a flags byte carrying LOCKED (bit 1) and
UNLOCK_INITIATED (bit 2) alongside DEFAULT_EOA_REVOKED (bit 0), and a single
uint40 lock_union reinterpreted by UNLOCK_INITIATED (unlock delay vs
unlocks_at). Field order/widths now match the spec table, with 3 reserved
zero bytes. Public ABI (getLockStatus, events, lock digest) is unchanged;
this is a storage-layout + internal-logic change only.

Adds a test covering the pending-unlock freeze on the config-change path.
@chunter-cb chunter-cb marked this pull request as ready for review July 13, 2026 16:58
@chunter-cb chunter-cb merged commit 687296f into main Jul 13, 2026
5 checks passed
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