CIRCLE_TRUST_POLICIES_old.md
1 # Circle Trust Policies 2 3 > **Abzu Technical Specification** — January 2026 4 > *Configurable privacy-accountability trade-offs for group membership.* 5 6 --- 7 8 ## Executive Summary 9 10 Circle Trust Policies provide **configurable security postures** for group membership, enabling circles to operate anywhere on the spectrum from full accountability to complete anonymity. 11 12 This system addresses a fundamental tension in group messaging: 13 14 | Requirement | Tension | 15 |-------------|---------| 16 | **Accountability** | Communities need moderation tools and audit trails | 17 | **Privacy** | Users need protection from surveillance and social mapping | 18 | **Safety** | Activists need protection even if devices are seized | 19 20 The solution: **Three policy tiers** that circles can choose from at creation time. 21 22 --- 23 24 ## The Problem 25 26 ### OpSec Hazard: Permanent Social Graphs 27 28 The original Circle Trust Protocol (invite trees, vouch ledgers) creates a **permanent, traceable social graph**. This is valuable for moderation but dangerous in adversarial contexts. 29 30 **Scenario**: An activist group uses Circles for coordination. An adversary seizes one member's device. 31 32 With full trust ledger: 33 34 - ❌ Adversary traces entire invitation chain 35 - ❌ Adversary identifies the founder 36 - ❌ Adversary maps vouch relationships (who trusts whom) 37 - ❌ Every member is identified and their social position mapped 38 39 With anonymous policy: 40 41 - ✅ Adversary sees only a list of public keys 42 - ✅ No invitation chain exists 43 - ✅ No vouch history exists 44 - ✅ Social graph is invisible 45 46 ### The Pruning Fairness Problem 47 48 Cascading prune (remove target + all their invitees) can be unfair: 49 50 - Member A invites Member B 51 - Member B invites 50 people, builds thriving subcommunity 52 - Member A is banned for unrelated misconduct 53 - All 50 people lose access—"guilt by association" 54 55 Different prune modes address this with varying degrees of forgiveness. 56 57 --- 58 59 ## Trust Policy Architecture 60 61 ### The Three Tiers 62 63 ``` 64 ┌────────────────────────────────────────────────────────────────────────┐ 65 │ TRUST POLICY SPECTRUM │ 66 │ │ 67 │ ANONYMOUS PRIVATE ACCOUNTABLE │ 68 │ ┌────────┐ ┌────────┐ ┌────────────┐ │ 69 │ │No logs │ │No logs │ │Full audit │ │ 70 │ │No tree │ │Has tree│ │Full tree │ │ 71 │ │No graph│ │Prunable│ │Traceable │ │ 72 │ └────────┘ └────────┘ └────────────┘ │ 73 │ ▲ ▲ ▲ │ 74 │ │ │ │ │ 75 │ Activists Friend groups Moderated communities │ 76 │ Whistleblowers Private clubs Public servers │ 77 │ High-risk users Social circles Organizations │ 78 └────────────────────────────────────────────────────────────────────────┘ 79 ``` 80 81 ### Policy Definitions 82 83 ```rust 84 /// Trust policy for a Circle - determines privacy/accountability trade-off 85 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 86 pub enum TrustPolicy { 87 /// Full accountability: complete audit trail and invitation tree 88 /// Use case: Moderated communities, organizations, public servers 89 Accountable { 90 ledger_mode: LedgerMode, 91 prune_mode: PruneMode, 92 }, 93 94 /// Partial privacy: no ledger, but maintains invite tree for pruning 95 /// Use case: Friend groups, private clubs, social circles 96 Private { 97 prune_mode: PruneMode, 98 }, 99 100 /// Maximum privacy: no ledger, no invite tree, minimal metadata 101 /// Use case: Activists, whistleblowers, high-risk users 102 /// DEFAULT - safest option for adversarial contexts 103 Anonymous, 104 } 105 106 impl Default for TrustPolicy { 107 fn default() -> Self { 108 TrustPolicy::Anonymous // Safety-first default 109 } 110 } 111 ``` 112 113 --- 114 115 ## Ledger Modes 116 117 The `LedgerMode` enum controls what gets recorded in the trust ledger (only applicable to Accountable policy): 118 119 ```rust 120 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] 121 pub enum LedgerMode { 122 /// Complete audit trail: invites, vouches, prunes with full details 123 #[default] 124 Full, 125 126 /// Records only join/leave events, omits social graph details 127 /// (who invited whom, who vouched for whom) 128 MembershipOnly, 129 130 /// Rolling 30-day window, older entries automatically pruned 131 Ephemeral, 132 } 133 ``` 134 135 ### Ledger Mode Comparison 136 137 | Mode | Records Invites | Records Vouches | Records Prunes | Retention | 138 |------|-----------------|-----------------|----------------|-----------| 139 | **Full** | ✅ With details | ✅ With details | ✅ With details | Permanent | 140 | **MembershipOnly** | ✅ Join only | ❌ | ✅ Leave only | Permanent | 141 | **Ephemeral** | ✅ With details | ✅ With details | ✅ With details | 30 days | 142 143 --- 144 145 ## Prune Modes 146 147 The `PruneMode` enum controls what happens to downstream invitees when someone is pruned: 148 149 ```rust 150 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] 151 pub enum PruneMode { 152 /// Traditional: Target + all descendants removed 153 /// "If I don't trust Alice anymore, I don't trust anyone she brought in" 154 Cascade, 155 156 /// Merciful: Target removed, descendants become orphans (no inviter) 157 /// "Punish the individual, not their network" 158 #[default] 159 Orphan, 160 161 /// Transfer: Descendants reassigned to founder 162 /// "The founder vouches for everyone by proxy" 163 Reassign, 164 165 /// Voluntary only: Members can only remove themselves 166 /// "Nobody gets kicked out" 167 Voluntary, 168 } 169 ``` 170 171 ### Prune Mode Dynamics 172 173 ``` 174 BEFORE: Founder → Alice → Bob, Carol, Dave 175 └→ Eve (invited by Carol) 176 177 PRUNE ALICE WITH CASCADE: 178 Result: Alice, Bob, Carol, Dave, Eve ALL REMOVED 179 180 PRUNE ALICE WITH ORPHAN: 181 Result: Alice removed 182 Bob, Carol, Dave → invited_by: None (orphaned) 183 Eve still points to Carol 184 185 PRUNE ALICE WITH REASSIGN: 186 Result: Alice removed 187 Bob, Carol, Dave → invited_by: Founder 188 Eve still points to Carol 189 190 PRUNE ALICE WITH VOLUNTARY: 191 Result: Error - only Alice can remove herself 192 ``` 193 194 --- 195 196 ## Policy Behavior Matrix 197 198 ### What Gets Recorded 199 200 | Operation | Anonymous | Private | Accountable | 201 |-----------|-----------|---------|-------------| 202 | **Member joins** | pubkey only | pubkey + inviter | Full ledger entry | 203 | **Member vouched** | vouch stored | vouch stored | Ledger entry | 204 | **Member pruned** | member removed | member + descendants | Ledger entry | 205 | **Invite depth** | always 0 | tracked | tracked | 206 | **Invite signature** | none | stored | stored | 207 208 ### What Gets Stored Per Member 209 210 ```rust 211 // With Anonymous policy: 212 CircleMember { 213 pubkey: [u8; 32], 214 role: MemberRole, 215 joined_at: u64, 216 invited_by: None, // OpSec: not recorded 217 invite_signature: None, // OpSec: not recorded 218 invite_depth: 0, // OpSec: not recorded 219 vouches_received: Vec::new(), 220 capacity: TrustCapacity::default(), 221 } 222 223 // With Private/Accountable policy: 224 CircleMember { 225 pubkey: [u8; 32], 226 role: MemberRole, 227 joined_at: u64, 228 invited_by: Some(inviter_pubkey), // Tracked for pruning 229 invite_signature: Some(sig), // Proof of invitation 230 invite_depth: 3, // Distance from founder 231 vouches_received: vec![...], // Secondary endorsements 232 capacity: TrustCapacity { ... }, 233 } 234 ``` 235 236 --- 237 238 ## API Usage 239 240 ### Creating Circles with Explicit Policies 241 242 ```rust 243 // Default: Anonymous (safest) 244 let activist_circle = node.create_circle("Resistance".to_string())?; 245 assert_eq!(activist_circle.trust_policy, TrustPolicy::Anonymous); 246 247 // Private: For friend groups 248 let policy = TrustPolicy::Private { 249 prune_mode: PruneMode::Orphan, 250 }; 251 let friend_circle = node.create_circle_with_policy( 252 "Book Club".to_string(), 253 policy 254 )?; 255 256 // Accountable: For moderated communities 257 let policy = TrustPolicy::Accountable { 258 ledger_mode: LedgerMode::Full, 259 prune_mode: PruneMode::Cascade, 260 }; 261 let server = node.create_circle_with_policy( 262 "My Discord".to_string(), 263 policy 264 )?; 265 ``` 266 267 ### Checking Policy Capabilities 268 269 ```rust 270 impl TrustPolicy { 271 /// Returns whether this policy maintains a trust ledger 272 pub fn has_ledger(&self) -> bool { 273 matches!(self, TrustPolicy::Accountable { ledger_mode, .. } 274 if *ledger_mode != LedgerMode::Ephemeral) 275 } 276 277 /// Returns whether this policy records invite trees 278 pub fn records_invite_tree(&self) -> bool { 279 matches!(self, 280 TrustPolicy::Accountable { .. } | 281 TrustPolicy::Private { .. } 282 ) 283 } 284 285 /// Returns whether this policy records vouches 286 pub fn records_vouches(&self) -> bool { 287 matches!(self, 288 TrustPolicy::Accountable { .. } | 289 TrustPolicy::Private { .. } 290 ) 291 } 292 } 293 ``` 294 295 --- 296 297 ## Security Analysis 298 299 ### Anonymous Policy: Maximum OpSec 300 301 **Threat model**: Adversary with physical device access 302 303 | Attack Vector | Defense | 304 |---------------|---------| 305 | Extract invitation chain | ✅ Not stored | 306 | Map social graph | ✅ Not stored | 307 | Identify founding members | ✅ Founder pubkey is member, but no special markers | 308 | Trace vouch relationships | ✅ Vouches recorded on member, but no ledger | 309 | Timeline reconstruction | ⚠️ `joined_at` timestamps remain | 310 311 **Residual risk**: Membership list + timestamps. Future enhancement: timestamp fuzzing. 312 313 ### Private Policy: Pruning Without Surveillance 314 315 Maintains invite tree for functional pruning while avoiding permanent audit trail. 316 317 | Attack Vector | Defense | 318 |---------------|---------| 319 | Extract invitation chain | ⚠️ Stored per-member, can be traced | 320 | Map vouch relationships | ⚠️ Stored per-member | 321 | Permanent historical record | ✅ No ledger, only current state | 322 323 **Use case**: When you need moderation tools but don't want permanent records. 324 325 ### Accountable Policy: Full Transparency 326 327 Complete audit trail for organizations that require it. 328 329 | Feature | Benefit | 330 |---------|---------| 331 | Invite history | Know who introduced problematic members | 332 | Vouch history | Understand reputation networks | 333 | Prune history | Audit moderation decisions | 334 | Immutable record | Legal/compliance requirements | 335 336 --- 337 338 ## Design Rationale 339 340 ### Why Anonymous is Default 341 342 > [!IMPORTANT] 343 > **Safety-first design**: The most dangerous failure mode is exposing vulnerable 344 > users to surveillance. The most inconvenient failure mode is lacking moderation 345 > tools. We chose safety over convenience. 346 347 Communities that need accountability can explicitly opt-in. Users who don't think 348 about security get the safest option automatically. 349 350 ### Why Three Tiers (Not Two, Not Five) 351 352 The three tiers map to real-world social structures: 353 354 | Tier | Real-World Analog | Example | 355 |------|-------------------|---------| 356 | **Anonymous** | Secret society | Underground resistance, journalism sources | 357 | **Private** | Private club | Book club, friend group, gaming guild | 358 | **Accountable** | Public organization | Company Slack, Discord server, forum | 359 360 Fewer tiers = ambiguity about what you're getting. 361 More tiers = analysis paralysis, subtle differences users won't understand. 362 363 ### Why Orphan is Default Prune Mode 364 365 Cascade (traditional) punishes innocent people for someone else's misconduct. 366 Orphan preserves communities while removing bad actors. 367 368 --- 369 370 ## Implementation Notes 371 372 ### Files Modified 373 374 | File | Changes | 375 |------|---------| 376 | [node.rs](file:///Users/adrian/studio/abzu/abzu-core/src/node.rs) | `TrustPolicy`, `LedgerMode`, `PruneMode` enums; policy helper methods; `create_circle_with_policy()`; conditional ledger/tree storage | 377 | [lib.rs](file:///Users/adrian/studio/abzu/abzu-core/src/lib.rs) | Export new types | 378 | [circle_tests.rs](file:///Users/adrian/studio/abzu/abzu-core/tests/circle_tests.rs) | Policy-specific tests | 379 380 ### Test Coverage 381 382 12 trust-related tests including: 383 384 - `test_anonymous_policy_no_ledger` — Verifies no ledger/tree for OpSec 385 - `test_private_policy_no_ledger_but_has_invite_tree` — Verifies no ledger but keeps tree 386 - Existing tests updated to use Accountable policy for ledger verification 387 388 --- 389 390 ## Future Work 391 392 ### P1: Wire Protocol Frame 393 394 ```rust 395 CircleCreate { 396 id: [u8; 32], 397 name: Vec<u8>, 398 trust_policy: TrustPolicy, // NEW: Include in creation 399 } 400 ``` 401 402 ### P2: Policy Migration 403 404 Allow circles to upgrade their policy (Anonymous → Private → Accountable) but 405 never downgrade (can't un-audit). 406 407 ### P3: Timestamp Fuzzing 408 409 For Anonymous circles, fuzz `joined_at` timestamps to make timeline correlation 410 harder. 411 412 ### P4: SDK Integration 413 414 ```dart 415 // Dart SDK 416 final circle = await node.createCircle( 417 name: 'Activists', 418 policy: TrustPolicy.anonymous, 419 ); 420 ``` 421 422 --- 423 424 ## Conclusion 425 426 Circle Trust Policies transform Abzu's group messaging from a one-size-fits-all 427 solution into a flexible system that respects both organizational needs and 428 individual safety. 429 430 The key insight: **Default to safety, opt-in to accountability.** 431 432 ``` 433 ┌─────────────────────────────────────────────┐ 434 │ │ 435 │ "You can always add transparency. │ 436 │ You can never take back surveillance." │ 437 │ │ 438 └─────────────────────────────────────────────┘ 439 ``` 440 441 --- 442 443 *Document version: 1.0 • January 27, 2026*