Skip to Content
ArchitectureP2P Networking

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

  1. Both users open app

    • App starts advertising presence
    • Begins browsing for nearby peers
  2. Automatic discovery (usually 1 second)

    • MultipeerConnectivity handles discovery
    • Uses Bonjour service over local network
  3. Peer list appears

    • Shows nearby Solidarity users
    • Displays device names
  4. 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:

  1. Stateless Design

    • No user data stored
    • No logging of pass contents
    • Certificates stored in encrypted secrets only
  2. Minimal Payload

    • Only SHA256 hashes transmitted
    • No PII in manifest (just file hashes)
    • Signature returned immediately
  3. 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_CERT

Why 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.

Long-distance sharing - create shareable deep links.

solidarity://card/share?id=abc123&sig=xyz789&exp=1234567890

Parameters:

  • id: Card UUID
  • sig: Signature for verification
  • exp: 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

MethodTimeRange
MultipeerConnectivity (Infrastructure WiFi)<1sSame network
MultipeerConnectivity (Peer-to-peer WiFi)1-2s~30 feet
QR CodeInstantLine of sight

Transfer Speed

Data SizeTransfer 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

  1. P2P fails → Try QR code
  2. QR code unavailable → Try share link
  3. 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

Last updated on