Skip to content

Commit 382bfba

Browse files
committed
update sa docs
1 parent efb7794 commit 382bfba

5 files changed

Lines changed: 240 additions & 198 deletions

File tree

content/stellar-contracts/accounts/authorization-flow.mdx

Lines changed: 38 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Authorization Flow
33
---
44

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.
5+
Authorization in smart accounts is determined by matching each auth context against explicitly selected context rules. The off-chain client specifies which rule to use for each operation via `context_rule_ids` in the `AuthPayload`. The selected rule is validated, its signers authenticated, and its policies enforced. If any step fails, authorization is denied.
66

77
## Detailed Flow
88
```mermaid
@@ -14,11 +14,13 @@ sequenceDiagram
1414
participant Verifier
1515
participant Policy
1616
17-
User->>SmartAccount: Signatures
18-
SmartAccount->>ContextRule: Match context<br/>(CallContract, Default, ...)
19-
ContextRule->>ContextRule: Filter expired rules<br/>Sort newest first
17+
User->>SmartAccount: AuthPayload (signers + context_rule_ids)
18+
SmartAccount->>SmartAccount: Compute auth_digest<br/>sha256(payload || rule_ids.to_xdr())
19+
20+
loop Each auth context
21+
SmartAccount->>ContextRule: Look up rule by ID<br/>from context_rule_ids
22+
ContextRule->>ContextRule: Validate not expired<br/>and matches context type
2023
21-
loop Each rule until match
2224
Note over ContextRule,DelegatedSigner: Built-in authorization <br/>for delegated signers
2325
ContextRule->>DelegatedSigner: require_auth_for_args()
2426
DelegatedSigner-->>ContextRule: Authorized
@@ -27,37 +29,25 @@ sequenceDiagram
2729
ContextRule->>Verifier: verify()
2830
Verifier-->>ContextRule: Valid
2931
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
32+
Note over ContextRule,Policy: Policy enforcement (panics on failure)
33+
ContextRule->>Policy: enforce()
34+
Policy->>Policy: Validate + update state
35+
36+
ContextRule-->>SmartAccount: ✓ Authorized
4137
end
4238
4339
SmartAccount-->>User: Success
4440
```
4541

46-
### 1. Rule Collection
42+
### 1. Rule Lookup
4743

48-
The smart account gathers all relevant context rules for evaluation:
44+
The smart account reads the `context_rule_ids` from the `AuthPayload`. There must be exactly one rule ID per auth context — a mismatch is rejected with `ContextRuleIdsLengthMismatch`.
4945

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)
46+
For each auth context, the corresponding rule ID is used to look up the context rule directly. The rule must:
5347

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.
48+
- Exist in the account's storage
49+
- Not be expired (if `valid_until` is set, it must be ≥ current ledger sequence)
50+
- Match the context type: a `CallContract(address)` rule matches a `CallContract(address)` context, and `Default` rules match any context
6151

6252
### 2. Rule Evaluation
6353

@@ -72,51 +62,33 @@ Extract authenticated signers from the rule's signer list. A signer is considere
7262

7363
Only authenticated signers proceed to the next step.
7464

75-
#### Step 2.2: Policy Validation
65+
#### Step 2.2: Policy Enforcement
7666

77-
If the rule has attached policies, verify that all can be enforced:
67+
If the rule has attached policies, the smart account calls `enforce()` on each policy. The `enforce()` method both validates conditions and applies state changes — it panics if the policy conditions are not satisfied:
7868

7969
```rust
8070
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-
}
71+
policy.enforce(e, context, authenticated_signers, context_rule, smart_account);
72+
// Panics if policy conditions aren't satisfied, causing the rule to fail
8473
}
8574
```
8675

87-
If any policy's `can_enforce()` returns false, the rule fails and evaluation moves to the next rule.
76+
If any policy panics, authorization fails for that context.
77+
78+
Policy enforcement requires the smart account's authorization, ensuring that policies can only be enforced by the account itself.
8879

8980
#### Step 2.3: Authorization Check
9081

9182
The authorization check depends on whether policies are present:
9283

9384
**With Policies:**
94-
- Success if all policies passed `can_enforce()`
95-
- The presence of authenticated signers is verified during policy evaluation
85+
- Success if all policies' `enforce()` calls completed without panicking
9686

9787
**Without Policies:**
9888
- Success if all signers in the rule are authenticated
9989
- At least one signer must be authenticated for the rule to match
10090

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
91+
### 3. Result
12092

12193
**Success:** Authorization is granted and the transaction proceeds. All policy state changes are committed.
12294

@@ -151,13 +123,13 @@ ContextRule {
151123
**Authorization Entries:** `[passkey_signature]`
152124

153125
**Flow:**
154-
1. Collect: Rules 2 (specific) and 1 (default)
126+
1. Lookup: Client specifies rule ID 2 in `context_rule_ids`
155127
2. Evaluate Rule 2:
128+
- Rule matches `CallContract(dex_address)` context and is not expired
156129
- 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
130+
- Policy enforcement: Spending limit validates and updates counters
131+
- Authorization check: All policies enforced successfully → Success
132+
3. Result: Authorized
161133

162134
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).
163135

@@ -188,10 +160,9 @@ ContextRule {
188160
**Authorization Entries:** `[ed25519_alice_signature, ed25519_bob_signature]`
189161

190162
**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
163+
1. Lookup: Client specifies rule ID 1 in `context_rule_ids` (rule 2 is known to be expired)
164+
2. Evaluate Rule 1: Both Alice and Bob authenticated, no policies to enforce → Success
165+
3. Result: Authorized
195166

196167
The expired session rule is automatically filtered out, and authorization falls back to the default admin rule.
197168

@@ -213,12 +184,11 @@ ContextRule {
213184
**Authorization Entries:** `[alice_signature]`
214185

215186
**Flow:**
216-
1. Collect: Default rule retrieved
187+
1. Lookup: Client specifies default rule ID in `context_rule_ids`
217188
2. Evaluate:
218189
- 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)
190+
- Policy enforcement: Threshold policy requires 2 signers, only 1 present → Panics
191+
3. Result: Denied (transaction reverts)
222192

223193
## Performance Considerations
224194

content/stellar-contracts/accounts/context-rules.mdx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,27 @@ Each rule must contain at least one signer OR one policy. This enables pure poli
4444
### Multiple Rules Per Context
4545
Multiple rules can exist for the same context type with different signer sets and policies. This allows progressive authorization models where different combinations of credentials grant access to the same operations.
4646

47-
### Rule Precedence
48-
Rules are evaluated in reverse chronological order (newest first). The first matching rule wins. This enables seamless permission updates: adding a new rule with different requirements immediately takes precedence over older rules for the same context.
47+
### Explicit Rule Selection
48+
Rules are not iterated or auto-discovered. Instead, off-chain clients specify exactly which context rule to use for each operation via the `context_rule_ids` field in `AuthPayload`. This explicit selection prevents downgrade attacks where an attacker could force the system to fall back to a weaker rule.
4949

5050
### Automatic Expiration
51-
Expired rules are automatically filtered out during authorization evaluation.
51+
Expired rules are rejected during authorization evaluation.
5252

5353
## Context Rule Limits
5454

55-
The framework enforces limits to keep costs predictable and encourage proactive context rule management (remove expired or non-valid rules):
56-
57-
- Maximum context rules per smart account: 15
5855
- Maximum signers per context rule: 15
5956
- Maximum policies per context rule: 5
57+
- Maximum context rule name size: 20 bytes
58+
- Maximum external signer key size: 256 bytes
6059

6160
## Authorization Matching
6261

6362
During authorization, the framework:
6463

65-
1. Gathers all non-expired rules matching the context type plus default rules
66-
2. Sorts rules by creation time (newest first)
67-
3. Evaluates rules in order until one matches
68-
4. Returns the first matching rule or fails if none match
64+
1. Reads the `context_rule_ids` from the `AuthPayload` (one rule ID per auth context)
65+
2. Looks up each rule directly by ID
66+
3. Validates the rule is not expired and matches the context type
67+
4. Authenticates signers and enforces policies, or fails if conditions aren't met
6968

7069
For detailed documentation on the authorization flow, see [Authorization Flow](/stellar-contracts/accounts/authorization-flow).
7170

@@ -101,6 +100,7 @@ smart_account::add_context_rule(
101100
e,
102101
ContextRuleType::Default,
103102
String::from_str(e, "Sudo"),
103+
None, // No expiration
104104
vec![
105105
e,
106106
Signer::External(bls_verifier, alice_key),
@@ -111,7 +111,6 @@ smart_account::add_context_rule(
111111
e,
112112
(threshold_policy, threshold_params) // 2-of-3 Threshold
113113
],
114-
None, // No expiration
115114
);
116115

117116
// This rule applies only to calls to the USDC contract, expires in 1 year,
@@ -120,6 +119,7 @@ smart_account::add_context_rule(
120119
e,
121120
ContextRuleType::CallContract(usdc_addr),
122121
String::from_str(e, "Dapp1 Subscription"),
122+
Some(current_ledger + 1_year),
123123
vec![
124124
e,
125125
Signer::External(ed25519_verifier, dapp1_key)
@@ -128,7 +128,6 @@ smart_account::add_context_rule(
128128
e,
129129
(spending_limit_policy, spending_params)
130130
],
131-
Some(current_ledger + 1_year)
132131
);
133132

134133
// This rule applies only to calls to the dApp contract, expires in 7 days,
@@ -137,6 +136,7 @@ smart_account::add_context_rule(
137136
e,
138137
ContextRuleType::CallContract(dapp_addr),
139138
String::from_str(e, "Dapp2 Session"),
139+
Some(current_ledger + 7_days),
140140
vec![
141141
e,
142142
Signer::External(ed25519_verifier, dapp2_key)
@@ -146,7 +146,6 @@ smart_account::add_context_rule(
146146
(rate_limit_policy, rate_limit_params),
147147
(time_window_policy, time_window_params)
148148
],
149-
Some(current_ledger + 7_days)
150149
);
151150

152151
// This rule applies only to calls to a specific contract, expires in 12 hours,
@@ -155,6 +154,7 @@ smart_account::add_context_rule(
155154
e,
156155
ContextRuleType::CallContract(some_addr),
157156
String::from_str(e, "AI Agent"),
157+
Some(current_ledger + 12_hours),
158158
vec![
159159
e,
160160
Signer::External(secp256r1_verifier, agent_key)
@@ -163,7 +163,6 @@ smart_account::add_context_rule(
163163
e,
164164
(volume_cap_policy, volume_cap_params)
165165
],
166-
Some(current_ledger + 12_hours)
167166
);
168167
```
169168

0 commit comments

Comments
 (0)