···
+
# Phone Verification Implementation Guide
+
This document outlines the complete implementation of phone verification for Coves using the hybrid approach: privacy-first storage with cryptographically signed, portable verification badges.
+
1. User requests verification (mobile app)
+
2. Coves AppView validates + sends OTP via Telnyx
+
3. User enters OTP code
+
4. AppView validates code + creates signed verification
+
5. AppView writes verification to user's PDS profile
+
6. AppView stores phone_hash in local database
+
7. Third-party clients see verification badge (portable)
+
- **Never stored**: Plain phone numbers (anywhere)
+
- **Hashed in AppView DB**: HMAC-SHA256(phone, pepper) for duplicate detection
+
- **Stored on PDS**: Signed verification badge (no phone data)
+
- **Portable**: Badge works across AppViews, can't be forged
+
## Components Implemented
+
- โ
**actor/profile.json**: Added `verifications` array
+
- โ
**verification/requestPhone.json**: XRPC endpoint for OTP request
+
- โ
**verification/verifyPhone.json**: XRPC endpoint for OTP validation
+
- โ
**verification/getStatus.json**: XRPC endpoint for verification status
+
### 2. Database Schema (005_create_phone_verification_tables.sql)
+
phone_verifications -- Completed verifications
+
phone_verification_requests -- Pending OTP requests (10min TTL)
+
phone_verification_rate_limits -- Rate limit tracking
+
phone_verification_audit_log -- Security audit trail
+
### 3. did:web DID for Coves
+
- **Location**: `.well-known/did.json`
+
- **DID**: `did:web:coves.social`
+
- **Purpose**: Signs verification badges
+
- **Public Key**: P-256 EC key (JWK format)
+
### 4. Core Service Layer
+
- **interfaces.go**: Service contracts
+
- **service.go**: Verification logic
+
- **errors.go**: Domain errors
+
### 5. SMS Provider (Telnyx)
+
- **telnyx/client.go**: API integration
+
- **Why Telnyx**: 50% cheaper, owned infrastructure, international support
+
## Environment Configuration
+
### Required Variables (.env)
+
VERIFICATION_SERVICE_DID=did:web:coves.social
+
VERIFICATION_PRIVATE_KEY=<base64-encoded-P256-private-key>
+
TELNYX_API_KEY=<your-api-key>
+
TELNYX_MESSAGING_PROFILE_ID=<your-profile-id>
+
TELNYX_FROM_NUMBER=<e164-phone-number>
+
PHONE_HASH_PEPPER=<base64-32-bytes> # NEVER change after initial setup!
+
#### 1. Generate DID Keypair
+
# Generate P-256 EC private key
+
openssl ecparam -name prime256v1 -genkey -noout -out verification-key.pem
+
openssl ec -in verification-key.pem -pubout -out verification-key-pub.pem
+
# Convert to JWK format (use library or online tool for dev)
+
# Update .well-known/did.json with x, y coordinates
+
# Store private key in environment
+
export VERIFICATION_PRIVATE_KEY="$(cat verification-key.pem | base64 -w 0)"
+
#### 2. Generate Phone Hash Pepper
+
export PHONE_HASH_PEPPER="$(openssl rand -base64 32)"
+
โ ๏ธ **CRITICAL**: Never change `PHONE_HASH_PEPPER` after production launch!
+
#### 3. Configure Telnyx
+
1. Sign up at https://telnyx.com
+
2. Create a Messaging Profile
+
3. Purchase a phone number
+
4. Get API key from dashboard
+
#### 4. Serve DID Document
+
Ensure `https://coves.social/.well-known/did.json` is publicly accessible with:
+
Content-Type: application/json
+
Access-Control-Allow-Origin: *
+
## Implementation Checklist
+
- [ ] Run migration: `005_create_phone_verification_tables.sql`
+
- [ ] Implement `PhoneHashProvider` (HMAC-SHA256 with pepper)
+
- [ ] Implement `SignatureService` (ECDSA P-256 signing)
+
- [ ] Implement `PDSWriter` (write to user's PDS via OAuth)
+
- [ ] Implement `VerificationRepository` (PostgreSQL)
+
- [ ] Create XRPC handlers for verification endpoints
+
- [ ] Add verification routes to main.go
+
- [ ] Add background cleanup job (expired requests)
+
- [ ] Add phone input screen with E.164 validation
+
- [ ] Add OTP entry screen (6-digit code)
+
- [ ] Call `/xrpc/social.coves.verification.requestPhone`
+
- [ ] Call `/xrpc/social.coves.verification.verifyPhone`
+
- [ ] Display verification badge on profiles
+
- [ ] Handle re-verification flow (annual)
+
- [ ] Unit tests for service layer
+
- [ ] Integration tests for XRPC endpoints
+
- [ ] Test rate limiting (per phone, per DID)
+
- [ ] Test signature verification (third-party client)
+
- [ ] Test PDS write-back
+
- [ ] Test phone hash collision detection
+
- [ ] Verify OTP uses crypto/rand
+
- [ ] Verify constant-time code comparison (bcrypt)
+
- [ ] Verify rate limits are enforced
+
- [ ] Verify phone numbers never logged in plaintext
+
- [ ] Verify audit logs don't leak sensitive data
+
- [ ] Verify signatures can't be forged
+
- [ ] Test SMS provider failover/retry logic
+
## Security Considerations
+
- **Per Phone**: 3 requests/hour (prevents SMS spam to victim)
+
- **Per DID**: 5 requests/day (prevents user abuse)
+
- **Length**: 6 digits (1M combinations)
+
- **Expiry**: 10 minutes
+
- **Max Attempts**: 3 (then must request new code)
+
- **Storage**: Bcrypt hashed (not reversible)
+
### Phone Hash Security
+
- **Algorithm**: HMAC-SHA256(phone, pepper)
+
- **Pepper**: 32-byte secret, environment variable
+
- **Purpose**: Prevent rainbow table attacks
+
- **Uniqueness**: One phone = one verified account
+
- **Algorithm**: ECDSA P-256 (ES256)
+
- **Payload**: type + verifiedBy + verifiedAt + expiresAt + subjectDID
+
- **Verification**: Third-party clients fetch public key from DID document
+
## Annual Re-verification Flow
+
1. **30 days before expiry**: Set `needsRenewal: true` in status API
+
2. **Show banner in app**: "Your verification expires soon, renew now"
+
3. **User re-verifies**: Same flow, new phone allowed
+
4. **Old verification**: Expires, badge removed from profile
+
## Phone Loss/Change Flow
+
1. User reports "lost phone" in app
+
2. AppView removes verification from PDS profile
+
3. AppView deletes phone_hash from database
+
4. User verifies new phone number
+
5. Badge restored with new phone_hash
+
## Third-Party Client Integration
+
// 1. Fetch user profile from PDS
+
const profileOwnerDID = 'did:plc:abc123' // The DID whose profile you're viewing
+
const profile = await fetchProfileFromPDS(profileOwnerDID)
+
// 2. Check for phone verification
+
const phoneVerification = profile.verifications?.find(v => v.type === 'phone')
+
if (!phoneVerification) {
+
// No phone verification
+
// 3. Fetch Coves DID document
+
const didDoc = await fetch('https://coves.social/.well-known/did.json').then(r => r.json())
+
const publicKey = didDoc.verificationMethod[0].publicKeyJwk
+
// 4. Verify signature (CRITICAL: includes profileOwnerDID to prevent copying)
+
const payload = phoneVerification.type +
+
phoneVerification.verifiedBy +
+
phoneVerification.verifiedAt +
+
phoneVerification.expiresAt +
+
profileOwnerDID // โ This binds verification to this specific user
+
const isValid = await verifySignature(payload, phoneVerification.signature, publicKey)
+
const expired = new Date(phoneVerification.expiresAt) < new Date()
+
return isValid && !expired
+
// If Alice tries to copy this verification to her profile, the signature verification
+
// will fail because her DID is different from the original subject DID in the payload
+
## Monitoring & Observability
+
- SMS delivery rate (should be >99%)
+
- Verification completion rate (request โ verify)
+
- Rate limit hit rate (should be low)
+
- Average time to verify
+
- Cost per verification
+
- SMS delivery failures spike
+
- Unusual rate limit hits (possible attack)
+
- Signature validation failures (bug or attack)
+
-- Failed verification attempts by DID
+
SELECT did, COUNT(*) as failures
+
FROM phone_verification_audit_log
+
WHERE event_type = 'verification_failed'
+
AND created_at > NOW() - INTERVAL '24 hours'
+
-- Rate limit hits (possible attack)
+
SELECT phone_hash, COUNT(*) as hits
+
FROM phone_verification_audit_log
+
WHERE event_type = 'rate_limit_hit'
+
AND created_at > NOW() - INTERVAL '1 hour'
+
### Telnyx Pricing (US)
+
- **SMS**: $0.004/message
+
- **Monthly estimate**: 10,000 verifications = $40/month
+
- **Annual re-verification**: Ongoing cost, plan accordingly
+
- Twilio: $0.0079/message = $79/month (98% more expensive)
+
- AWS SNS: $0.0064/message = $64/month (60% more expensive)
+
### v1.1 - Email Verification
+
- Same architecture, different `type: "email"`
+
- Reuse signature service
+
- Add email provider (e.g., AWS SES)
+
### v1.2 - Domain Verification
+
- Prove ownership of domain
+
- DNS TXT record validation
+
### v1.3 - Government ID
+
- KYC provider integration
+
- `type: "government_id"`
+
For questions or issues:
+
- GitHub Issues: https://github.com/coves-social/coves/issues
+
- Discord: [your-discord-link]
+
- Email: support@coves.social