A community based topic aggregation platform built on atproto
1# Phone Verification Implementation Guide 2 3## Overview 4This document outlines the complete implementation of phone verification for Coves using the hybrid approach: privacy-first storage with cryptographically signed, portable verification badges. 5 6## Architecture 7 8### Data Flow 9``` 101. User requests verification (mobile app) 11 122. Coves AppView validates + sends OTP via Telnyx 13 143. User enters OTP code 15 164. AppView validates code + creates signed verification 17 185. AppView writes verification to user's PDS profile 19 206. AppView stores phone_hash in local database 21 227. Third-party clients see verification badge (portable) 23``` 24 25### Privacy Model 26- **Never stored**: Plain phone numbers (anywhere) 27- **Hashed in AppView DB**: HMAC-SHA256(phone, pepper) for duplicate detection 28- **Stored on PDS**: Signed verification badge (no phone data) 29- **Portable**: Badge works across AppViews, can't be forged 30 31## Components Implemented 32 33### 1. Lexicon Updates 34-**actor/profile.json**: Added `verifications` array 35-**verification/requestPhone.json**: XRPC endpoint for OTP request 36-**verification/verifyPhone.json**: XRPC endpoint for OTP validation 37-**verification/getStatus.json**: XRPC endpoint for verification status 38 39### 2. Database Schema (005_create_phone_verification_tables.sql) 40```sql 41phone_verifications -- Completed verifications 42phone_verification_requests -- Pending OTP requests (10min TTL) 43phone_verification_rate_limits -- Rate limit tracking 44phone_verification_audit_log -- Security audit trail 45``` 46 47### 3. did:web DID for Coves 48- **Location**: `.well-known/did.json` 49- **DID**: `did:web:coves.social` 50- **Purpose**: Signs verification badges 51- **Public Key**: P-256 EC key (JWK format) 52 53### 4. Core Service Layer 54- **interfaces.go**: Service contracts 55- **service.go**: Verification logic 56- **errors.go**: Domain errors 57 58### 5. SMS Provider (Telnyx) 59- **telnyx/client.go**: API integration 60- **Why Telnyx**: 50% cheaper, owned infrastructure, international support 61 62## Environment Configuration 63 64### Required Variables (.env) 65```bash 66# DID Configuration 67VERIFICATION_SERVICE_DID=did:web:coves.social 68VERIFICATION_PRIVATE_KEY=<base64-encoded-P256-private-key> 69 70# Telnyx Configuration 71TELNYX_API_KEY=<your-api-key> 72TELNYX_MESSAGING_PROFILE_ID=<your-profile-id> 73TELNYX_FROM_NUMBER=<e164-phone-number> 74 75# Security 76PHONE_HASH_PEPPER=<base64-32-bytes> # NEVER change after initial setup! 77``` 78 79### Setup Steps 80 81#### 1. Generate DID Keypair 82```bash 83# Generate P-256 EC private key 84openssl ecparam -name prime256v1 -genkey -noout -out verification-key.pem 85 86# Extract public key 87openssl ec -in verification-key.pem -pubout -out verification-key-pub.pem 88 89# Convert to JWK format (use library or online tool for dev) 90# Update .well-known/did.json with x, y coordinates 91 92# Store private key in environment 93export VERIFICATION_PRIVATE_KEY="$(cat verification-key.pem | base64 -w 0)" 94``` 95 96#### 2. Generate Phone Hash Pepper 97```bash 98export PHONE_HASH_PEPPER="$(openssl rand -base64 32)" 99``` 100 101⚠️ **CRITICAL**: Never change `PHONE_HASH_PEPPER` after production launch! 102 103#### 3. Configure Telnyx 1041. Sign up at https://telnyx.com 1052. Create a Messaging Profile 1063. Purchase a phone number 1074. Get API key from dashboard 1085. Add to `.env` 109 110#### 4. Serve DID Document 111Ensure `https://coves.social/.well-known/did.json` is publicly accessible with: 112``` 113Content-Type: application/json 114Access-Control-Allow-Origin: * 115``` 116 117## Implementation Checklist 118 119### Backend (Go) 120- [ ] Run migration: `005_create_phone_verification_tables.sql` 121- [ ] Implement `PhoneHashProvider` (HMAC-SHA256 with pepper) 122- [ ] Implement `SignatureService` (ECDSA P-256 signing) 123- [ ] Implement `PDSWriter` (write to user's PDS via OAuth) 124- [ ] Implement `VerificationRepository` (PostgreSQL) 125- [ ] Create XRPC handlers for verification endpoints 126- [ ] Add verification routes to main.go 127- [ ] Add background cleanup job (expired requests) 128 129### Frontend (Mobile) 130- [ ] Add phone input screen with E.164 validation 131- [ ] Add OTP entry screen (6-digit code) 132- [ ] Call `/xrpc/social.coves.verification.requestPhone` 133- [ ] Call `/xrpc/social.coves.verification.verifyPhone` 134- [ ] Display verification badge on profiles 135- [ ] Handle re-verification flow (annual) 136 137### Testing 138- [ ] Unit tests for service layer 139- [ ] Integration tests for XRPC endpoints 140- [ ] Test rate limiting (per phone, per DID) 141- [ ] Test signature verification (third-party client) 142- [ ] Test PDS write-back 143- [ ] Test phone hash collision detection 144 145### Security Audit 146- [ ] Verify OTP uses crypto/rand 147- [ ] Verify constant-time code comparison (bcrypt) 148- [ ] Verify rate limits are enforced 149- [ ] Verify phone numbers never logged in plaintext 150- [ ] Verify audit logs don't leak sensitive data 151- [ ] Verify signatures can't be forged 152- [ ] Test SMS provider failover/retry logic 153 154## Security Considerations 155 156### Rate Limits 157- **Per Phone**: 3 requests/hour (prevents SMS spam to victim) 158- **Per DID**: 5 requests/day (prevents user abuse) 159 160### OTP Security 161- **Length**: 6 digits (1M combinations) 162- **Expiry**: 10 minutes 163- **Max Attempts**: 3 (then must request new code) 164- **Storage**: Bcrypt hashed (not reversible) 165 166### Phone Hash Security 167- **Algorithm**: HMAC-SHA256(phone, pepper) 168- **Pepper**: 32-byte secret, environment variable 169- **Purpose**: Prevent rainbow table attacks 170- **Uniqueness**: One phone = one verified account 171 172### Signature Security 173- **Algorithm**: ECDSA P-256 (ES256) 174- **Payload**: type + verifiedBy + verifiedAt + expiresAt + subjectDID 175- **Verification**: Third-party clients fetch public key from DID document 176 177## Annual Re-verification Flow 178 1791. **30 days before expiry**: Set `needsRenewal: true` in status API 1802. **Show banner in app**: "Your verification expires soon, renew now" 1813. **User re-verifies**: Same flow, new phone allowed 1824. **Old verification**: Expires, badge removed from profile 183 184## Phone Loss/Change Flow 185 1861. User reports "lost phone" in app 1872. AppView removes verification from PDS profile 1883. AppView deletes phone_hash from database 1894. User verifies new phone number 1905. Badge restored with new phone_hash 191 192## Third-Party Client Integration 193 194### Verifying Badges 195```javascript 196// 1. Fetch user profile from PDS 197const profileOwnerDID = 'did:plc:abc123' // The DID whose profile you're viewing 198const profile = await fetchProfileFromPDS(profileOwnerDID) 199 200// 2. Check for phone verification 201const phoneVerification = profile.verifications?.find(v => v.type === 'phone') 202if (!phoneVerification) { 203 // No phone verification 204 return false 205} 206 207// 3. Fetch Coves DID document 208const didDoc = await fetch('https://coves.social/.well-known/did.json').then(r => r.json()) 209const publicKey = didDoc.verificationMethod[0].publicKeyJwk 210 211// 4. Verify signature (CRITICAL: includes profileOwnerDID to prevent copying) 212const payload = phoneVerification.type + 213 phoneVerification.verifiedBy + 214 phoneVerification.verifiedAt + 215 phoneVerification.expiresAt + 216 profileOwnerDID // ← This binds verification to this specific user 217 218const isValid = await verifySignature(payload, phoneVerification.signature, publicKey) 219 220// 5. Check expiry 221const expired = new Date(phoneVerification.expiresAt) < new Date() 222 223return isValid && !expired 224 225// If Alice tries to copy this verification to her profile, the signature verification 226// will fail because her DID is different from the original subject DID in the payload 227``` 228 229## Monitoring & Observability 230 231### Key Metrics 232- SMS delivery rate (should be >99%) 233- Verification completion rate (request → verify) 234- Rate limit hit rate (should be low) 235- Average time to verify 236- Cost per verification 237 238### Alerts 239- SMS delivery failures spike 240- Unusual rate limit hits (possible attack) 241- Signature validation failures (bug or attack) 242- PDS write failures 243 244### Audit Log Queries 245```sql 246-- Failed verification attempts by DID 247SELECT did, COUNT(*) as failures 248FROM phone_verification_audit_log 249WHERE event_type = 'verification_failed' 250 AND created_at > NOW() - INTERVAL '24 hours' 251GROUP BY did 252HAVING COUNT(*) > 5; 253 254-- Rate limit hits (possible attack) 255SELECT phone_hash, COUNT(*) as hits 256FROM phone_verification_audit_log 257WHERE event_type = 'rate_limit_hit' 258 AND created_at > NOW() - INTERVAL '1 hour' 259GROUP BY phone_hash 260ORDER BY hits DESC 261LIMIT 10; 262``` 263 264## Cost Estimation 265 266### Telnyx Pricing (US) 267- **SMS**: $0.004/message 268- **Monthly estimate**: 10,000 verifications = $40/month 269- **Annual re-verification**: Ongoing cost, plan accordingly 270 271### Comparison 272- Twilio: $0.0079/message = $79/month (98% more expensive) 273- AWS SNS: $0.0064/message = $64/month (60% more expensive) 274 275## Future Enhancements 276 277### v1.1 - Email Verification 278- Same architecture, different `type: "email"` 279- Reuse signature service 280- Add email provider (e.g., AWS SES) 281 282### v1.2 - Domain Verification 283- Prove ownership of domain 284- `type: "domain"` 285- DNS TXT record validation 286 287### v1.3 - Government ID 288- KYC provider integration 289- `type: "government_id"` 290- Higher trust level 291 292## Support 293 294For questions or issues: 295- GitHub Issues: https://github.com/coves-social/coves/issues 296- Discord: [your-discord-link] 297- Email: support@coves.social