···
1
+
# Phone Verification Implementation Guide
4
+
This document outlines the complete implementation of phone verification for Coves using the hybrid approach: privacy-first storage with cryptographically signed, portable verification badges.
10
+
1. User requests verification (mobile app)
12
+
2. Coves AppView validates + sends OTP via Telnyx
14
+
3. User enters OTP code
16
+
4. AppView validates code + creates signed verification
18
+
5. AppView writes verification to user's PDS profile
20
+
6. AppView stores phone_hash in local database
22
+
7. Third-party clients see verification badge (portable)
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
31
+
## Components Implemented
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
39
+
### 2. Database Schema (005_create_phone_verification_tables.sql)
41
+
phone_verifications -- Completed verifications
42
+
phone_verification_requests -- Pending OTP requests (10min TTL)
43
+
phone_verification_rate_limits -- Rate limit tracking
44
+
phone_verification_audit_log -- Security audit trail
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)
53
+
### 4. Core Service Layer
54
+
- **interfaces.go**: Service contracts
55
+
- **service.go**: Verification logic
56
+
- **errors.go**: Domain errors
58
+
### 5. SMS Provider (Telnyx)
59
+
- **telnyx/client.go**: API integration
60
+
- **Why Telnyx**: 50% cheaper, owned infrastructure, international support
62
+
## Environment Configuration
64
+
### Required Variables (.env)
67
+
VERIFICATION_SERVICE_DID=did:web:coves.social
68
+
VERIFICATION_PRIVATE_KEY=<base64-encoded-P256-private-key>
70
+
# Telnyx Configuration
71
+
TELNYX_API_KEY=<your-api-key>
72
+
TELNYX_MESSAGING_PROFILE_ID=<your-profile-id>
73
+
TELNYX_FROM_NUMBER=<e164-phone-number>
76
+
PHONE_HASH_PEPPER=<base64-32-bytes> # NEVER change after initial setup!
81
+
#### 1. Generate DID Keypair
83
+
# Generate P-256 EC private key
84
+
openssl ecparam -name prime256v1 -genkey -noout -out verification-key.pem
86
+
# Extract public key
87
+
openssl ec -in verification-key.pem -pubout -out verification-key-pub.pem
89
+
# Convert to JWK format (use library or online tool for dev)
90
+
# Update .well-known/did.json with x, y coordinates
92
+
# Store private key in environment
93
+
export VERIFICATION_PRIVATE_KEY="$(cat verification-key.pem | base64 -w 0)"
96
+
#### 2. Generate Phone Hash Pepper
98
+
export PHONE_HASH_PEPPER="$(openssl rand -base64 32)"
101
+
⚠️ **CRITICAL**: Never change `PHONE_HASH_PEPPER` after production launch!
103
+
#### 3. Configure Telnyx
104
+
1. Sign up at https://telnyx.com
105
+
2. Create a Messaging Profile
106
+
3. Purchase a phone number
107
+
4. Get API key from dashboard
110
+
#### 4. Serve DID Document
111
+
Ensure `https://coves.social/.well-known/did.json` is publicly accessible with:
113
+
Content-Type: application/json
114
+
Access-Control-Allow-Origin: *
117
+
## Implementation Checklist
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)
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)
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
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
154
+
## Security Considerations
157
+
- **Per Phone**: 3 requests/hour (prevents SMS spam to victim)
158
+
- **Per DID**: 5 requests/day (prevents user abuse)
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)
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
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
177
+
## Annual Re-verification Flow
179
+
1. **30 days before expiry**: Set `needsRenewal: true` in status API
180
+
2. **Show banner in app**: "Your verification expires soon, renew now"
181
+
3. **User re-verifies**: Same flow, new phone allowed
182
+
4. **Old verification**: Expires, badge removed from profile
184
+
## Phone Loss/Change Flow
186
+
1. User reports "lost phone" in app
187
+
2. AppView removes verification from PDS profile
188
+
3. AppView deletes phone_hash from database
189
+
4. User verifies new phone number
190
+
5. Badge restored with new phone_hash
192
+
## Third-Party Client Integration
194
+
### Verifying Badges
196
+
// 1. Fetch user profile from PDS
197
+
const profileOwnerDID = 'did:plc:abc123' // The DID whose profile you're viewing
198
+
const profile = await fetchProfileFromPDS(profileOwnerDID)
200
+
// 2. Check for phone verification
201
+
const phoneVerification = profile.verifications?.find(v => v.type === 'phone')
202
+
if (!phoneVerification) {
203
+
// No phone verification
207
+
// 3. Fetch Coves DID document
208
+
const didDoc = await fetch('https://coves.social/.well-known/did.json').then(r => r.json())
209
+
const publicKey = didDoc.verificationMethod[0].publicKeyJwk
211
+
// 4. Verify signature (CRITICAL: includes profileOwnerDID to prevent copying)
212
+
const payload = phoneVerification.type +
213
+
phoneVerification.verifiedBy +
214
+
phoneVerification.verifiedAt +
215
+
phoneVerification.expiresAt +
216
+
profileOwnerDID // ← This binds verification to this specific user
218
+
const isValid = await verifySignature(payload, phoneVerification.signature, publicKey)
221
+
const expired = new Date(phoneVerification.expiresAt) < new Date()
223
+
return isValid && !expired
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
229
+
## Monitoring & Observability
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
239
+
- SMS delivery failures spike
240
+
- Unusual rate limit hits (possible attack)
241
+
- Signature validation failures (bug or attack)
242
+
- PDS write failures
244
+
### Audit Log Queries
246
+
-- Failed verification attempts by DID
247
+
SELECT did, COUNT(*) as failures
248
+
FROM phone_verification_audit_log
249
+
WHERE event_type = 'verification_failed'
250
+
AND created_at > NOW() - INTERVAL '24 hours'
252
+
HAVING COUNT(*) > 5;
254
+
-- Rate limit hits (possible attack)
255
+
SELECT phone_hash, COUNT(*) as hits
256
+
FROM phone_verification_audit_log
257
+
WHERE event_type = 'rate_limit_hit'
258
+
AND created_at > NOW() - INTERVAL '1 hour'
259
+
GROUP BY phone_hash
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
272
+
- Twilio: $0.0079/message = $79/month (98% more expensive)
273
+
- AWS SNS: $0.0064/message = $64/month (60% more expensive)
275
+
## Future Enhancements
277
+
### v1.1 - Email Verification
278
+
- Same architecture, different `type: "email"`
279
+
- Reuse signature service
280
+
- Add email provider (e.g., AWS SES)
282
+
### v1.2 - Domain Verification
283
+
- Prove ownership of domain
285
+
- DNS TXT record validation
287
+
### v1.3 - Government ID
288
+
- KYC provider integration
289
+
- `type: "government_id"`
290
+
- Higher trust level
294
+
For questions or issues:
295
+
- GitHub Issues: https://github.com/coves-social/coves/issues
296
+
- Discord: [your-discord-link]
297
+
- Email: support@coves.social