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