A community based topic aggregation platform built on atproto

Phone Verification Implementation Summary#

Quick Start#

We've implemented a privacy-first, cryptographically-signed phone verification system for Coves that:

✅ Keeps phone numbers completely private (never stored in plaintext) ✅ Creates portable verification badges (works across third-party apps) ✅ Uses did:web DID for cryptographic signing ✅ Integrates with Telnyx SMS (50% cheaper than Twilio) ✅ Supports annual re-verification and phone number changes ✅ Includes rate limiting and audit logging for security

Files Created#

Lexicons#

  • internal/atproto/lexicon/social/coves/actor/profile.json (updated)
  • internal/atproto/lexicon/social/coves/verification/requestPhone.json
  • internal/atproto/lexicon/social/coves/verification/verifyPhone.json
  • internal/atproto/lexicon/social/coves/verification/getStatus.json

Database#

  • internal/db/migrations/005_create_phone_verification_tables.sql

Service Layer#

  • internal/core/verification/interfaces.go
  • internal/core/verification/service.go
  • internal/core/verification/errors.go

SMS Provider#

  • internal/sms/telnyx/client.go

Configuration#

  • .env.example (with all required environment variables)
  • .well-known/did.json (DID document for signature verification)

Documentation#

  • docs/DID_SETUP.md (How to set up the DID and keypair)
  • docs/PHONE_VERIFICATION_IMPLEMENTATION.md (Complete implementation guide)

SMS Provider Decision: Telnyx#

Winner: Telnyx Pricing: $0.004/SMS (US) - 50% cheaper than Twilio Why: Owned infrastructure, 10x throughput, free support, international coverage

Cost estimate: 10,000 verifications/month = $40/month

Architecture Summary#

How It Works#

  1. User requests verification → AppView sends OTP via Telnyx
  2. User enters code → AppView validates (max 3 attempts)
  3. AppView creates signed verification using did:web:coves.social private key
  4. AppView writes to user's PDS via com.atproto.repo.putRecord (OAuth)
  5. Badge appears on profile → Third-party apps can verify signature
  6. AppView stores phone_hash → Prevents duplicate verifications

Privacy Guarantees#

Data Stored Where Format
Phone number NOWHERE Never stored
Phone hash AppView DB HMAC-SHA256(phone, pepper)
OTP code AppView DB (temp) Bcrypt hash, 10min TTL
Verification badge User's PDS Signed JSON (no phone data)

Security Features#

  • Rate limits: 3/hour per phone, 5/day per user
  • OTP expiry: 10 minutes
  • Max attempts: 3 per OTP request
  • Cryptographic signatures: ECDSA P-256 (can't be forged)
  • Audit logging: All events tracked for fraud detection
  • Constant-time comparison: Prevents timing attacks

Next Steps to Deploy#

1. Generate Secrets#

# DID signing keypair
openssl ecparam -name prime256v1 -genkey -noout -out verification-key.pem
export VERIFICATION_PRIVATE_KEY="$(cat verification-key.pem | base64 -w 0)"

# Phone hash pepper
export PHONE_HASH_PEPPER="$(openssl rand -base64 32)"

2. Configure Telnyx#

3. Update DID Document#

  • Extract public key from verification-key.pem (convert to JWK)
  • Update .well-known/did.json with actual coordinates
  • Deploy to https://coves.social/.well-known/did.json

4. Implement Missing Components#

  • PhoneHashProvider (HMAC-SHA256 implementation)
  • SignatureService (ECDSA P-256 signing)
  • PDSWriter (write verifications to PDS via OAuth)
  • VerificationRepository (PostgreSQL implementation)
  • XRPC handlers (routes + error mapping)
  • Background cleanup job (expired OTP requests)

5. Frontend Integration#

  • Phone input screen (E.164 validation)
  • OTP entry screen
  • Verification badge display
  • Re-verification flow

Questions Answered#

Q: Where is the verification badge stored?#

A: In the user's PDS profile (social.coves.actor.profile record), in the verifications array. Third-party apps can read it directly from the PDS.

Q: Can users fake the verification?#

A: No. The badge includes a cryptographic signature that third-party apps can verify using Coves' public key from the DID document.

Q: What if someone forks Coves?#

A: They set VERIFICATION_SERVICE_DID=did:web:their-domain.com and generate their own keypair. The system is fully self-hostable.

Q: How do third-party apps verify the badge?#

A:

  1. Fetch the DID document from https://coves.social/.well-known/did.json
  2. Extract the public key from verificationMethod[0].publicKeyJwk
  3. CRITICAL: Verify the signature includes the profile owner's DID in the payload
  4. Reconstruct payload: type + verifiedBy + verifiedAt + expiresAt + profileOwnerDID
  5. Verify signature matches payload

Security Note: The signature MUST include the subject DID. This prevents users from copying someone else's verification to their profile (the signature won't match because the DID is different).

Q: What happens on phone loss?#

A: User reports lost phone → AppView removes verification from PDS → User verifies new number → Badge restored.

Q: Why annual re-verification?#

A: Ensures active users, detects account takeovers, gives a yearly touchpoint for security checks.

File Locations Reference#

Coves/
├── .env.example                                        # Environment config template
├── .well-known/
│   └── did.json                                       # DID document (serves at /.well-known/did.json)
├── docs/
│   ├── DID_SETUP.md                                   # DID keypair setup guide
│   ├── PHONE_VERIFICATION_IMPLEMENTATION.md           # Complete implementation guide
│   └── PHONE_VERIFICATION_SUMMARY.md                  # This file
├── internal/
│   ├── atproto/lexicon/social/coves/
│   │   ├── actor/profile.json                         # Updated with verifications array
│   │   └── verification/
│   │       ├── requestPhone.json                      # XRPC: Request OTP
│   │       ├── verifyPhone.json                       # XRPC: Verify OTP
│   │       └── getStatus.json                         # XRPC: Get verification status
│   ├── core/verification/
│   │   ├── interfaces.go                              # Service contracts
│   │   ├── service.go                                 # Verification logic
│   │   └── errors.go                                  # Domain errors
│   ├── db/migrations/
│   │   └── 005_create_phone_verification_tables.sql   # Database schema
│   └── sms/telnyx/
│       └── client.go                                  # Telnyx API integration

Resources#

Contact#

Questions? Open an issue or reach out on Discord!