Scope Enforcement
Scopes let you restrict what a runtime key is allowed to do. When you delegate a runtime key with a scope, that key can only sign messages that claim the same scope. This prevents a key delegated for “messaging” from being used to sign “deploy” actions.
What Is a Scope?
A scope is a plain text label — like "messaging", "deploy", or "read-only" — that gets hashed into a bytes32 value and stored on-chain as part of the delegation record.
# Delegate a key that can only sign "messaging" actionsvouch delegate --expiry 24h --scope messaging# Sign with an explicit scopevouch sign --payload '{"msg": "hello"}' --scope messaging
Under the hood, scopes are keccak256 hashes. A special value — the zero scope (32 zero bytes) — means “unrestricted.” The key can sign anything.
scope string: "messaging"↓keccak256("messaging")↓bytes32: 0x7a9c3e... (stored on-chain in the delegation record)
Scope Matching Rules
When vouch verify checks a delegated key (signer ≠ agent_id), it compares two scopes:
- Delegation scope — what the key was authorized to do (from the on-chain record)
- Envelope scope — what the message claims to be doing (from the signed envelope)
| Delegation Scope | Envelope Scope | Result |
|---|---|---|
| Zero (unrestricted) | Anything | Valid — unrestricted keys can sign anything |
| Any value | Zero (not claiming a scope) | Valid — not claiming a scope is always allowed |
keccak256("messaging") | keccak256("messaging") | Valid — scopes match exactly |
keccak256("messaging") | keccak256("deploy") | Rejected — scope mismatch |
Owner wallets skip scope checks. If the signer is the wallet itself (signer == agent_id), scope checks are skipped entirely. Your own wallet always has full authority — scopes only apply to delegated runtime keys.
When to Use Scopes
Use scopes when you delegate keys to different agents or processes that should have limited authority.
| Scope | Use case |
|---|---|
messaging | A chatbot agent that can only send messages |
deploy | A deployment agent that can authorize deploys |
read-only | A monitoring agent that can read but not take actions |
billing | A billing agent that handles payment-related operations |
| (empty / unrestricted) | A general-purpose agent with full authority |
Don’t use scopes when: you only have one agent per wallet, your agent needs full authority, or you’re just getting started (you can add scopes later).
Practical Example
Say you run two agents from the same wallet: a chat agent and a deploy agent. You want to make sure the chat agent can never sign deploy-related messages.
# Delegate a key for the chat agent — scoped to messagingvouch delegate --expiry 24h --scope messaging# Delegate a separate key for the deploy agent — scoped to deployvouch delegate --expiry 24h --scope deploy
When the chat agent signs a message:
# This works — scope matches the delegationvouch sign --key 0xCHAT_KEY --payload '{"msg":"hello"}' --scope messaging# This would fail verification — scope mismatchvouch sign --key 0xCHAT_KEY --payload '{"action":"deploy"}' --scope deploy
Even if a chat key is compromised, it can’t be used to authorize deploys.
Scopes in the Config File
After you delegate a key with a scope, the scope is saved in your config so you don’t have to specify it every time:
# ~/.vouch/config.toml (saved automatically after delegate)runtime_key_address = "0xDEF..."delegation_scope = "messaging"delegation_duration = "24h"delegation_expires_at = 1760003600
When you run vouch sign without --scope, it uses the delegation_scope from your config. When you run vouch delegate --renew, it reuses the same scope.
Enforcement Step by Step
Here’s exactly what happens during step 8b of verification when a delegated key is used:
1. Recover delegation scope from on-chain record via RPC (bytes32)2. Decode envelope scope from the signed envelope (bytes32)3. Apply rules:delegation == ZeroScope?→ Yes: key is unrestricted, PASS→ No: continueenvelope == ZeroScope?→ Yes: not claiming a scope, PASS→ No: continuedelegation == envelope?→ Yes: PASS (scope matched)→ No: FAIL ("envelope scope does not match delegation scope")
This means:
- An unrestricted key can sign anything (backwards compatible with pre-scope delegations)
- A message that doesn’t claim a scope is always allowed (no scope = no restriction to check)
- A scoped key signing a scoped message must have an exact match