Skip to content

Commit 8e93a6a

Browse files
authored
Merge pull request #64 from OpenZeppelin/stellar-accounts
Stellar Smart Accounts
2 parents f9d87eb + 4d25460 commit 8e93a6a

File tree

8 files changed

+1411
-0
lines changed

8 files changed

+1411
-0
lines changed
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
---
2+
title: Authorization Flow
3+
---
4+
5+
Authorization in smart accounts is determined by matching the current context against the account's context rules. Rules are gathered, ordered by recency, and evaluated until one satisfies the requirements. If a matching rule is found, its policies (if any) are enforced. Otherwise, authorization fails.
6+
7+
## Detailed Flow
8+
```mermaid
9+
sequenceDiagram
10+
participant User
11+
participant SmartAccount
12+
participant ContextRule
13+
participant DelegatedSigner
14+
participant Verifier
15+
participant Policy
16+
17+
User->>SmartAccount: Signatures
18+
SmartAccount->>ContextRule: Match context<br/>(CallContract, Default, ...)
19+
ContextRule->>ContextRule: Filter expired rules<br/>Sort newest first
20+
21+
loop Each rule until match
22+
Note over ContextRule,DelegatedSigner: Built-in authorization <br/>for delegated signers
23+
ContextRule->>DelegatedSigner: require_auth_for_args()
24+
DelegatedSigner-->>ContextRule: Authorized
25+
26+
Note over ContextRule,Verifier: Signature verification for external signers
27+
ContextRule->>Verifier: verify()
28+
Verifier-->>ContextRule: Valid
29+
30+
Note over ContextRule,Policy: Policy pre-checks
31+
ContextRule->>Policy: can_enforce()
32+
Policy-->>ContextRule: True/False
33+
34+
alt All checks pass
35+
ContextRule->>Policy: enforce()
36+
Policy->>Policy: Update state
37+
ContextRule-->>SmartAccount: ✓ Authorized
38+
else Any check fails
39+
ContextRule->>ContextRule: Try next rule
40+
end
41+
end
42+
43+
SmartAccount-->>User: Success
44+
```
45+
46+
### 1. Rule Collection
47+
48+
The smart account gathers all relevant context rules for evaluation:
49+
50+
- Retrieve all non-expired rules for the specific context type
51+
- Include default rules that apply to any context
52+
- Sort specific and default rules by creation time (newest first)
53+
54+
**Context Type Matching:**
55+
- For a `CallContract(address)` context, both specific `CallContract(address)` rules and `Default` rules are collected
56+
- For a `CreateContract(wasm_hash)` context, both specific `CreateContract(wasm_hash)` rules and `Default` rules are collected
57+
- For any other context, only `Default` rules are collected
58+
59+
**Expiration Filtering:**
60+
Rules with `valid_until` set to a ledger sequence that has passed are automatically filtered out during collection.
61+
62+
### 2. Rule Evaluation
63+
64+
For each rule in order (newest and most specific first):
65+
66+
#### Step 2.1: Signer Filtering
67+
68+
Extract authenticated signers from the rule's signer list. A signer is considered authenticated if:
69+
70+
- **Delegated Signer**: The address has authorized the operation via `require_auth_for_args(payload)`
71+
- **External Signer**: The verifier contract confirms the signature is valid for the public key
72+
73+
Only authenticated signers proceed to the next step.
74+
75+
#### Step 2.2: Policy Validation
76+
77+
If the rule has attached policies, verify that all can be enforced:
78+
79+
```rust
80+
for policy in rule.policies {
81+
if !policy.can_enforce(e, account, rule_id, signers, auth_context) {
82+
// This rule fails, try the next rule
83+
}
84+
}
85+
```
86+
87+
If any policy's `can_enforce()` returns false, the rule fails and evaluation moves to the next rule.
88+
89+
#### Step 2.3: Authorization Check
90+
91+
The authorization check depends on whether policies are present:
92+
93+
**With Policies:**
94+
- Success if all policies passed `can_enforce()`
95+
- The presence of authenticated signers is verified during policy evaluation
96+
97+
**Without Policies:**
98+
- Success if all signers in the rule are authenticated
99+
- At least one signer must be authenticated for the rule to match
100+
101+
#### Step 2.4: Rule Precedence
102+
103+
The first matching rule wins. Newer rules take precedence over older rules for the same context type. This allows overwriting old rules.
104+
105+
### 3. Policy Enforcement
106+
107+
If authorization succeeds, the smart account calls `enforce()` on all matched policies in order:
108+
109+
```rust
110+
for policy in matched_rule.policies {
111+
policy.enforce(e, account, rule_id, signers, auth_context);
112+
}
113+
```
114+
115+
This triggers any necessary state changes such as updating spending counters, recording timestamps, emitting audit events, or modifying allowances.
116+
117+
Policy enforcement requires the smart account's authorization, ensuring that policies can only be enforced by the account itself.
118+
119+
### 4. Result
120+
121+
**Success:** Authorization is granted and the transaction proceeds. All policy state changes are committed.
122+
123+
**Failure:** Authorization is denied and the transaction reverts. No state changes are committed.
124+
125+
## Examples
126+
127+
### Specific Context with Policy
128+
129+
**Configuration:**
130+
```rust
131+
// DEX-specific rule with session key and spending limit
132+
ContextRule {
133+
id: 2,
134+
context_type: CallContract(dex_address),
135+
valid_until: Some(current_ledger + 24_hours),
136+
signers: [passkey],
137+
policies: [spending_limit_policy]
138+
}
139+
140+
// Default admin rule
141+
ContextRule {
142+
id: 1,
143+
context_type: Default,
144+
signers: [ed25519_alice, ed25519_bob],
145+
policies: []
146+
}
147+
```
148+
149+
**Call Context:** `CallContract(dex_address)`
150+
151+
**Authorization Entries:** `[passkey_signature]`
152+
153+
**Flow:**
154+
1. Collect: Rules 2 (specific) and 1 (default)
155+
2. Evaluate Rule 2:
156+
- Signer filtering: Passkey authenticated
157+
- Policy validation: Spending limit check passes
158+
- Authorization check: All policies enforceable → Success
159+
3. Enforce: Update spending counters, emit events
160+
4. Result: Authorized
161+
162+
If the spending limit had been exceeded, Rule 2 would fail and evaluation would continue to Rule 1 (which would also fail since the passkey doesn't match Alice or Bob).
163+
164+
### Fallback to Default
165+
166+
**Configuration:**
167+
```rust
168+
// Session rule (expired)
169+
ContextRule {
170+
id: 2,
171+
context_type: CallContract(dex_address),
172+
valid_until: Some(current_ledger - 100), // Expired
173+
signers: [session_key],
174+
policies: [spending_limit_policy]
175+
}
176+
177+
// Default admin rule
178+
ContextRule {
179+
id: 1,
180+
context_type: Default,
181+
signers: [ed25519_alice, ed25519_bob],
182+
policies: []
183+
}
184+
```
185+
186+
**Call Context:** `CallContract(dex_address)`
187+
188+
**Authorization Entries:** `[ed25519_alice_signature, ed25519_bob_signature]`
189+
190+
**Flow:**
191+
1. Collect: Rule 2 filtered out (expired), only Rule 1 collected
192+
2. Evaluate Rule 1: Both Alice and Bob authenticated → Success
193+
3. Enforce: No policies to enforce
194+
4. Result: Authorized
195+
196+
The expired session rule is automatically filtered out, and authorization falls back to the default admin rule.
197+
198+
### Authorization Failure
199+
200+
**Configuration:**
201+
```rust
202+
// Default rule requiring 2-of-3 threshold
203+
ContextRule {
204+
id: 1,
205+
context_type: Default,
206+
signers: [alice, bob, carol],
207+
policies: [threshold_policy(2)]
208+
}
209+
```
210+
211+
**Call Context:** `CallContract(any_address)`
212+
213+
**Authorization Entries:** `[alice_signature]`
214+
215+
**Flow:**
216+
1. Collect: Default rule retrieved
217+
2. Evaluate:
218+
- Signer filtering: Only Alice authenticated
219+
- Policy validation: Threshold policy requires 2 signers, only 1 present → Fail
220+
3. No more rules to evaluate
221+
4. Result: Denied (transaction reverts)
222+
223+
## Performance Considerations
224+
225+
Protocol 23 optimizations make the authorization flow efficient:
226+
- **Marginal storage read costs**: Reading multiple context rules has negligible cost
227+
- **Cheaper cross-contract calls**: Calling verifiers and policies is substantially cheaper
228+
229+
The framework enforces limits to maintain predictability:
230+
- Maximum context rules per smart account: 15
231+
- Maximum signers per context rule: 15
232+
- Maximum policies per context rule: 5
233+
234+
## See Also
235+
236+
- [Smart Account](/stellar-contracts/accounts/smart-account)
237+
- [Context Rules](/stellar-contracts/accounts/context-rules)
238+
- [Signers and Verifiers](/stellar-contracts/accounts/signers-and-verifiers)
239+
- [Policies](/stellar-contracts/accounts/policies)

0 commit comments

Comments
 (0)