Peer-to-Peer Networking
Solidarity enables instant, offline contact sharing through direct device-to-device connections.
MultipeerConnectivity Framework
Apple’s Native P2P Technology
Solidarity uses Apple’s MultipeerConnectivity framework for seamless device discovery and data transfer.
Key Benefits:
- Works without internet connection
- Automatic peer discovery
- Encrypted by default
- Supports WiFi and Bluetooth
How It Works
import MultipeerConnectivity
class P2PService: NSObject, MCSessionDelegate {
let serviceType = "solidarity-p2p"
let peerID = MCPeerID(displayName: UIDevice.current.name)
let session: MCSession
let advertiser: MCNearbyServiceAdvertiser
let browser: MCNearbyServiceBrowser
override init() {
self.session = MCSession(
peer: peerID,
securityIdentity: nil,
encryptionPreference: .required
)
self.advertiser = MCNearbyServiceAdvertiser(
peer: peerID,
discoveryInfo: nil,
serviceType: serviceType
)
self.browser = MCNearbyServiceBrowser(
peer: peerID,
serviceType: serviceType
)
super.init()
session.delegate = self
}
func startAdvertising() {
advertiser.startAdvertisingPeer()
}
func startBrowsing() {
browser.startBrowsingForPeers()
}
func send(card: BusinessCard, to peer: MCPeerID) throws {
let data = try JSONEncoder().encode(card)
try session.send(data, toPeers: [peer], with: .reliable)
}
}Sharing Methods
1. Proximity Sharing (P2P)
The killer feature - share with people nearby without internet.
Discovery Process
-
Both users open app
- App starts advertising presence
- Begins browsing for nearby peers
-
Automatic discovery (usually 1 second)
- MultipeerConnectivity handles discovery
- Uses Bonjour service over local network
-
Peer list appears
- Shows nearby Solidarity users
- Displays device names
-
One-tap share
- Tap peer name
- Choose privacy level
- Card sent instantly
Technical Details
Discovery:
- MultipeerConnectivity framework
- Bonjour service:
_airmeishi-share._tcp - Works over infrastructure WiFi and peer-to-peer WiFi
- No internet required
Connection:
- Automatic session establishment
- TLS encryption (
.required) - Perfect forward secrecy
- User consent for first connection
Transfer:
- ~1KB per card
- ~100ms transfer time
- Supports up to 8 concurrent peers
2. QR Code Sharing
Universal compatibility - works with any camera or QR scanner.
QR Code Generation
import CoreImage
class QRCodeService {
func generateQRCode(from card: BusinessCard) -> UIImage? {
let data = try? JSONEncoder().encode(card)
let filter = CIFilter.qrCodeGenerator()
filter.setValue(data, forKey: "inputMessage")
filter.setValue("H", forKey: "inputCorrectionLevel")
guard let ciImage = filter.outputImage else { return nil }
let transform = CGAffineTransform(scaleX: 10, y: 10)
let scaledImage = ciImage.transformed(by: transform)
return UIImage(ciImage: scaledImage)
}
}QR Code Format
{
"version": "1.0",
"id": "uuid-here",
"name": "Alice Chen",
"company": "TechCorp",
"email": "alice@company.com",
"phone": "+1234567890",
"level": "professional",
"proof": "zk_proof_data_if_applicable"
}Features:
- High error correction (level H)
- Works offline completely
- Standard format (works with any QR scanner)
- Can be saved and shared digitally
Scanning
import AVFoundation
class QRScannerService: NSObject, AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(
_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection
) {
guard let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let stringValue = readableObject.stringValue else { return }
// Decode card from QR data
if let data = stringValue.data(using: .utf8),
let card = try? JSONDecoder().decode(BusinessCard.self, from: data) {
// Save received card
}
}
}3. Apple Wallet Integration
Native iOS experience - save cards to Wallet app.
PassKit Implementation
import PassKit
class PassKitService {
func createPass(for card: BusinessCard) throws -> PKPass {
// Create pass.json
let passData = createPassData(card)
// Sign with Apple Pass certificate
let signedPass = signPass(passData)
return try PKPass(data: signedPass)
}
private func createPassData(_ card: BusinessCard) -> [String: Any] {
return [
"formatVersion": 1,
"passTypeIdentifier": "pass.com.solidarity.card",
"serialNumber": card.id.uuidString,
"teamIdentifier": "YOUR_TEAM_ID",
"organizationName": "Solidarity",
"description": "Business Card",
"generic": [
"primaryFields": [
[
"key": "name",
"label": "Name",
"value": card.name
]
],
"secondaryFields": [
[
"key": "company",
"label": "Company",
"value": card.company ?? ""
]
]
],
"barcode": [
"format": "PKBarcodeFormatQR",
"message": card.toQRString(),
"messageEncoding": "iso-8859-1"
]
]
}
}Features:
- Lock screen access
- QR code built-in
- Automatic updates
- NFC support (iPhone XS+)
Requirements:
- Apple Developer Pass Type ID
- Pass certificate configured
Pass Signing Architecture
Serverless Signing Service - Privacy-preserving credential issuance
Solidarity uses a Cloudflare Workers backend for Apple Wallet pass signing while maintaining zero user data storage.
Key Design:
iOS App (Local) Cloudflare Worker (Stateless)
│ │
│ 1. Generate manifest │
│ (SHA256 hashes) │
│ │
│ 2. POST /sign-pass │
├───────────────────────────>│
│ manifest.json │
│ │
│ │ 3. Sign with PKCS#7
│ │ (Apple certificates)
│ │
│ 4. Return signature │
│<───────────────────────────┤
│ (DER format) │
│ │
│ 5. Bundle .pkpass │
│ (locally) │
│ │Backend Implementation:
// Cloudflare Worker - airmeishi-backend
import forge from "node-forge";
// POST /sign-pass
async function signPass(manifestJson: string, env: Env) {
// 1. Load certificates from encrypted secrets
const passCertPem = Buffer.from(env.PASS_CERT, "base64").toString("utf-8");
const passKeyPem = Buffer.from(env.PASS_KEY, "base64").toString("utf-8");
const wwdrCertPem = Buffer.from(env.WWDR_CERT, "base64").toString("utf-8");
// 2. Create PKCS#7 detached signature
const p7 = forge.pkcs7.createSignedData();
p7.content = forge.util.createBuffer(manifestJson, "utf8");
const signerCert = forge.pki.certificateFromPem(passCertPem);
const signerKey = forge.pki.privateKeyFromPem(passKeyPem);
const wwdrCert = forge.pki.certificateFromPem(wwdrCertPem);
p7.addCertificate(signerCert);
p7.addCertificate(wwdrCert);
p7.addSigner({
key: signerKey,
certificate: signerCert,
digestAlgorithm: forge.pki.oids.sha1, // PassKit requires SHA-1
authenticatedAttributes: [
{ type: forge.pki.oids.contentType, value: forge.pki.oids.data },
{ type: forge.pki.oids.messageDigest },
{ type: forge.pki.oids.signingTime, value: new Date() }
]
});
// 3. Sign and return DER-encoded signature
p7.sign({ detached: true });
const signature = forge.asn1.toDer(p7.toAsn1()).getBytes();
return new Response(signature, {
headers: { "Content-Type": "application/octet-stream" }
});
}Privacy Guarantees:
-
Stateless Design
- No user data stored
- No logging of pass contents
- Certificates stored in encrypted secrets only
-
Minimal Payload
- Only SHA256 hashes transmitted
- No PII in manifest (just file hashes)
- Signature returned immediately
-
Security
- TLS encryption in transit
- Apple WWDR certificate chain verification
- Rate limiting (100 requests/IP/minute)
Certificate Setup:
# 1. Export certificates from Apple Developer
openssl pkcs12 -in pass.p12 -clcerts -nokeys -out passcert.pem
openssl pkcs12 -in pass.p12 -nocerts -nodes -out passkey.pem
# 2. Download Apple WWDR G4 certificate
curl -o AppleWWDRCAG4.cer \
https://www.apple.com/certificateauthority/AppleWWDRCAG4.cer
openssl x509 -inform DER -in AppleWWDRCAG4.cer -out wwdr.pem
# 3. Upload as Cloudflare secrets (base64 encoded)
cat passcert.pem | base64 | wrangler secret put PASS_CERT
cat passkey.pem | base64 | wrangler secret put PASS_KEY
cat wwdr.pem | base64 | wrangler secret put WWDR_CERTWhy This Architecture:
- Privacy: No backend database = no user data storage
- Performance: Cloudflare edge network (<50ms globally)
- Cost: Free tier supports 100K requests/day
- Security: Certificates never leave Cloudflare’s encrypted storage
- Simplicity: Single stateless endpoint
See airmeishi-backend repository for full implementation.
4. Share Link
Long-distance sharing - create shareable deep links.
Deep Link Format
solidarity://card/share?id=abc123&sig=xyz789&exp=1234567890Parameters:
id: Card UUIDsig: Signature for verificationexp: Expiration timestamp (optional)
Implementation
class DeepLinkService {
func createShareLink(
for card: BusinessCard,
expiration: Date?
) -> URL {
var components = URLComponents()
components.scheme = "solidarity"
components.host = "card"
components.path = "/share"
var queryItems = [
URLQueryItem(name: "id", value: card.id.uuidString),
URLQueryItem(name: "sig", value: sign(card))
]
if let exp = expiration {
queryItems.append(
URLQueryItem(name: "exp", value: "\(exp.timeIntervalSince1970)")
)
}
components.queryItems = queryItems
return components.url!
}
func handleShareLink(_ url: URL) -> BusinessCard? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let id = components.queryItems?.first(where: { $0.name == "id" })?.value,
let sig = components.queryItems?.first(where: { $0.name == "sig" })?.value
else { return nil }
// Verify signature and fetch card
return verifyAndFetchCard(id: id, signature: sig)
}
}Network Performance
Discovery Speed
| Method | Time | Range |
|---|---|---|
| MultipeerConnectivity (Infrastructure WiFi) | <1s | Same network |
| MultipeerConnectivity (Peer-to-peer WiFi) | 1-2s | ~30 feet |
| QR Code | Instant | Line of sight |
Transfer Speed
| Data Size | Transfer Time |
|---|---|
| Basic card (~500B) | 50ms |
| Card with photo (~10KB) | 200ms |
| Card with ZK proof (~1KB) | 100ms |
Battery Impact
Minimal power consumption:
- Discovery: ~1% battery per hour
- Transfer: Negligible (milliseconds)
- Idle: Zero (when not in use)
Connection Management
class ConnectionManager {
private var connectedPeers: Set<MCPeerID> = []
private let maxConcurrentConnections = 8
func managePeerConnection(_ peer: MCPeerID, isConnected: Bool) {
if isConnected {
connectedPeers.insert(peer)
// Disconnect oldest peer if limit reached
if connectedPeers.count > maxConcurrentConnections {
disconnectOldestPeer()
}
} else {
connectedPeers.remove(peer)
}
}
}Security
Encrypted Connections
All P2P connections use TLS encryption:
let session = MCSession(
peer: peerID,
securityIdentity: nil,
encryptionPreference: .required // Always encrypted
)Encryption details:
- TLS 1.3 (when available)
- Perfect forward secrecy
- Certificate validation
- No man-in-the-middle possible
Trust Model
First time pairing:
- User must accept connection
- Displays peer device name
- Visual confirmation required
Subsequent connections:
- Automatic (if previously paired)
- Can revoke trust anytime
Error Handling
Connection Issues
func handleConnectionError(_ error: Error) {
switch error {
case MCError.notConnected:
// Fallback to QR code
showQRCodeOption()
case MCError.timeout:
// Retry connection
retryConnection()
case MCError.cancelled:
// User cancelled, do nothing
break
default:
// Unknown error, show error message
showErrorAlert(error)
}
}Fallback Strategy
- P2P fails → Try QR code
- QR code unavailable → Try share link
- Everything fails → Manual entry option
Platform Support
iOS Compatibility
- iOS 16.0+: Full support
- iOS 15.0: Limited (no some SwiftUI features)
- iOS 14.0 and below: Not supported
Device Support
- iPhone: Full MultipeerConnectivity support
- iPad: Full MultipeerConnectivity support
- Mac: Limited (Catalyst apps with restrictions)
- Apple Watch: Not supported
Next: Learn about Zero-Knowledge Proofs