--- title: Device Pairing sidebarTitle: Device Pairing --- Device pairing establishes trust between Spacedrive instances using cryptographic signatures and user-friendly codes. Once paired, devices can communicate securely and share data directly. ## How Pairing Works Pairing uses a 12-word code to create a secure connection between two devices. The initiator generates the code, and the joiner enters it to establish trust. ### The Pairing Code Spacedrive uses BIP39 mnemonic codes for pairing, which come in two formats: #### Text Format (Local Network Only) A 12-word BIP39 mnemonic for manual entry: ``` brave lion sunset river eagle mountain forest ocean thunder crystal diamond phoenix ``` This format: - Works only on the same local network (mDNS discovery) - Easy to read and type - Contains 128 bits of entropy - Valid for 5 minutes - Never reused #### QR Code Format (Local + Internet) A JSON structure that enables both local and cross-network pairing: ```json { "version": 2, "words": "brave lion sunset river eagle mountain forest ocean thunder crystal diamond phoenix", "node_id": "6jn4e7l3pzx2kqhv..." } ``` This format: - Works across different networks and the internet - Includes the initiator's node_id for pkarr discovery - Enables automatic relay fallback - Same 5-minute expiration - Recommended for most use cases ### Security Model The pairing protocol provides multiple security guarantees: **Authentication**: Devices prove their identity using Ed25519 signatures **Confidentiality**: All communication encrypted with session keys **Integrity**: Challenge-response prevents tampering **Forward secrecy**: New keys for each session ## Choosing a Pairing Method ### When to Use Text Codes Text-based codes are best for: - Devices on the same local network (home, office) - Quick pairing without scanning QR codes - Situations where QR scanning is inconvenient **Limitations:** - Only works on the same subnet - Cannot traverse NATs or firewalls - Requires both devices to be on the same physical or virtual network ### When to Use QR Codes QR codes are recommended for: - Pairing across different networks - Remote device pairing over the internet - Maximum reliability (falls back to relay if needed) - Most production use cases **Benefits:** - Works anywhere with internet connectivity - Automatic relay fallback for NAT traversal - Faster on local networks (dual-path discovery) - More reliable overall ## Pairing Process ### For the Initiator Call the pairing API to generate a code: ```typescript const result = await client.action("network.pair.generate", {}); // For local network pairing (manual entry) console.log(`Share this code: ${result.code}`); // For cross-network pairing (QR code) console.log(`QR code data: ${result.qr_json}`); // Contains: { version: 2, words: "...", node_id: "..." } ``` The device advertises via mDNS (local) and pkarr (internet) and waits for a joiner. The code expires after 5 minutes. **Advertisement includes:** - Session ID (via mDNS user_data) - Node address published to dns.iroh.link (via pkarr) When a joiner connects, the initiator sends a cryptographic challenge to verify they have the correct code and own their device keys. After verification, both devices exchange session keys and save the pairing relationship. ### For the Joiner Enter the code from the initiator (text or QR): ```typescript // Manual entry (local network only) await client.action("network.pair.join", { code: "brave lion sunset river eagle mountain forest ocean thunder crystal diamond phoenix" }); // QR code scan (local + internet) await client.action("network.pair.join", { code: '{"version":2,"words":"brave lion sunset...","node_id":"..."}' }); // Manual entry with node_id (enables internet pairing) await client.action("network.pair.join", { code: "brave lion sunset...", node_id: "6jn4e7l3pzx2kqhv..." }); ``` The system searches for the initiator using: - **Local network** (mDNS) - Scans for matching session_id - **Internet** (pkarr/DNS) - Queries dns.iroh.link for node address (requires node_id) - **Relay servers** - Automatic fallback if direct connection fails With QR codes, both paths run simultaneously and the first to succeed wins. Sign a challenge from the initiator to prove you have the code and own your device keys. Store the paired device information and session keys for future communication. ## Technical Architecture ### Protocol Messages The pairing protocol uses four message types: ```rust pub enum PairingMessage { // Joiner → Initiator: "I want to pair" PairingRequest { session_id: Uuid, device_info: DeviceInfo, public_key: Vec, }, // Initiator → Joiner: "Prove you have the code" Challenge { session_id: Uuid, challenge: Vec, // 32 random bytes device_info: DeviceInfo, }, // Joiner → Initiator: "Here's my signature" Response { session_id: Uuid, response: Vec, // 64-byte Ed25519 signature device_info: DeviceInfo, }, // Initiator → Joiner: "Pairing complete" Complete { session_id: Uuid, success: bool, reason: Option, }, } ``` ### State Machine The PairingProtocolHandler manages session state: ```rust pub enum PairingState { // Initiator states WaitingForConnection, // Code generated, waiting ChallengeIssued, // Sent challenge to joiner // Joiner states Connecting, // Looking for initiator ChallengeReceived, // Got challenge, signing // Terminal states Completed, // Success! Failed(String), // Something went wrong } ``` ### Session Management Each pairing attempt creates a session: ```rust pub struct PairingSession { session_id: Uuid, // Derived from code state: PairingState, // Current state remote_device: Option, created_at: SystemTime, expires_at: SystemTime, // 5 minutes later } ``` Sessions expire after 5 minutes. Users must complete pairing within this time window. ## Discovery Mechanisms Devices find each other through multiple methods, depending on the pairing code format: ### Local Network (mDNS) On the same network, devices discover each other instantly using multicast DNS: ```rust // Initiator broadcasts session_id via user_data endpoint.set_user_data_for_discovery(Some(session_id)); // Joiner listens for matching session_id discovery_stream.filter(|item| { item.node_info().data.user_data() == session_id }); ``` **How it works:** - Initiator includes session_id in mDNS broadcasts - Joiner scans local network for matching session_id - Typically connects in 1-3 seconds - Only works on the same subnet ### Internet (Pkarr/DNS) For pairing across networks, Spacedrive uses pkarr to publish and resolve node addresses via DNS: ```rust // Automatic pkarr publishing (done by Iroh) .add_discovery(PkarrPublisher::n0_dns()) // Publish to dns.iroh.link .add_discovery(DnsDiscovery::n0_dns()) // Resolve from dns.iroh.link // Joiner queries by node_id let node_addr = NodeAddr::new(node_id); // Pkarr resolves in background endpoint.connect(node_addr, PAIRING_ALPN).await?; ``` **How it works:** - Initiator automatically publishes its address to `dns.iroh.link` via pkarr - Record includes relay_url and any direct addresses - Joiner queries `dns.iroh.link` with the node_id from QR code - Pkarr returns all connection options (relay + direct) - Takes 5-15 seconds including DNS resolution Pkarr uses DNS-based discovery backed by the Mainline DHT. It's more reliable than traditional DHT for NAT traversal and works globally. ### Dual-Path Discovery When using QR codes (with node_id), Spacedrive races both discovery methods: ```rust tokio::select! { result = try_mdns_discovery(session_id) => { // Fast path: local network } result = try_relay_discovery(node_id) => { // Reliable path: internet via pkarr } } // First to succeed wins, other is canceled ``` This approach optimizes for speed on local networks while ensuring reliability across the internet. ### Relay Servers When direct connection fails, devices automatically connect through relay servers: ```rust // Relay mode configured at startup .relay_mode(RelayMode::Default) // Uses n0's production relays // Automatic relay fallback during connection endpoint.connect(node_addr, PAIRING_ALPN).await?; // Tries direct, then relay ``` **Current Configuration:** - Uses n0's default relay servers (North America, Europe, Asia-Pacific) - Relay URLs discovered automatically via pkarr - Custom relay support coming soon (configurable per-node) Relay servers only forward encrypted QUIC traffic. They cannot decrypt your data or compromise security. ## Cryptographic Details ### Challenge-Response Authentication The challenge-response prevents replay attacks and verifies device identity: ```rust // Initiator generates challenge let challenge = rand::thread_rng().gen::<[u8; 32]>(); // Joiner signs challenge let signature = signing_key.sign(&challenge); // Initiator verifies signature let valid = verifying_key.verify(&challenge, &signature).is_ok(); ``` ### Key Derivation Session keys are derived from the pairing code and device identities: ```rust // Derive shared secret from pairing code let shared_secret = hkdf::extract( &pairing_code.secret, &[initiator_id, joiner_id].concat() ); // Generate session keys let (tx_key, rx_key) = hkdf::expand( &shared_secret, b"spacedrive-session-keys", 64 ); ``` ### Pkarr Implementation Spacedrive uses pkarr for decentralized node address resolution: ```rust // Automatic publishing (initiator) let endpoint = Endpoint::builder() .add_discovery(PkarrPublisher::n0_dns()) // Publishes to dns.iroh.link .bind().await?; // Automatic resolution (joiner) let endpoint = Endpoint::builder() .add_discovery(DnsDiscovery::n0_dns()) // Resolves from dns.iroh.link .bind().await?; // Discovery happens automatically during connection endpoint.connect(NodeAddr::new(node_id), PAIRING_ALPN).await?; ``` **How Pkarr Works:** - Uses DNS TXT records backed by the Mainline DHT - Records include relay URL and direct addresses - Automatic publishing every time the node's address changes - TTL-based caching for performance - No manual DHT interaction required ### Transport Security All pairing communication uses encrypted channels: 1. **QUIC encryption**: TLS 1.3 at transport layer 2. **Application encryption**: Additional layer using session keys 3. **Perfect forward secrecy**: New keys each session ## Error Handling ### Common Errors ```rust pub enum PairingError { // User errors InvalidCode, // Wrong or malformed code CodeExpired, // Took too long // Network errors DeviceNotFound, // Can't find initiator ConnectionFailed, // Network issues // Security errors InvalidSignature, // Challenge verification failed UntrustedDevice, // Device key mismatch // State errors SessionNotFound, // Unknown session ID InvalidState, // Wrong state transition } ``` ### Recovery Strategies **Invalid code**: Check spelling, ensure correct code **Connection failed**: Check network, firewall settings **Timeout**: Generate new code and try again **Signature failed**: Restart both applications ## Implementation Guide ### Starting Pairing (Initiator) ```rust // High-level API pub async fn start_pairing_as_initiator( &self ) -> Result { // Generate secure code let code = PairingCode::generate(); let session_id = code.derive_session_id(); // Create session let session = PairingSession::new_initiator(session_id); self.sessions.insert(session_id, session); // Advertise on network self.advertise_pairing(session_id).await?; Ok(code) } ``` ### Joining Pairing (Joiner) ```rust // High-level API pub async fn start_pairing_as_joiner( &self, code: &str ) -> Result<()> { // Parse and validate code let pairing_code = PairingCode::from_str(code)?; let session_id = pairing_code.derive_session_id(); // Create session let session = PairingSession::new_joiner(session_id); self.sessions.insert(session_id, session); // Find and connect to initiator let initiator = self.discover_initiator(session_id).await?; self.connect_and_pair(initiator, session_id).await?; Ok(()) } ``` ### Handling Protocol Messages ```rust impl PairingProtocolHandler { async fn handle_message( &mut self, msg: PairingMessage, peer_id: PeerId, ) -> Result<()> { match msg { PairingMessage::PairingRequest { .. } => { self.handle_pairing_request(..); } PairingMessage::Challenge { .. } => { self.handle_challenge(..); } PairingMessage::Response { .. } => { self.handle_response(..); } PairingMessage::Complete { .. } => { self.handle_complete(..); } } } } ``` ## Testing Pairing ### Unit Tests ```rust #[test] fn test_pairing_code_generation() { let code = PairingCode::generate(); assert_eq!(code.words.len(), 12); assert!(code.is_valid()); } #[test] fn test_challenge_response() { let (signing_key, verifying_key) = generate_keypair(); let challenge = generate_challenge(); let signature = signing_key.sign(&challenge); assert!(verifying_key.verify(&challenge, &signature).is_ok()); } ``` ### Integration Tests ```rust #[tokio::test] async fn test_full_pairing_flow() { // Start initiator let code = initiator.start_pairing_as_initiator().await?; // Join with code joiner.start_pairing_as_joiner(&code.to_string()).await?; // Verify both paired assert!(initiator.is_paired_with(joiner.device_id())); assert!(joiner.is_paired_with(initiator.device_id())); } ``` ## Best Practices ### For Users 1. **Prefer QR codes**: Use QR codes for reliability across any network 2. **Share codes securely**: Use encrypted messaging or voice calls for text codes 3. **Complete quickly**: Codes expire in 5 minutes 4. **Verify device names**: Check the paired device is correct 5. **One code at a time**: Cancel old attempts before starting new ones 6. **Check network connectivity**: For cross-network pairing, ensure internet access ### For Developers 1. **Handle all states**: Account for every possible state transition 2. **Clean up sessions**: Remove expired sessions promptly 3. **Log failures**: Record why pairing failed for debugging 4. **Test edge cases**: Network failures, timeouts, wrong codes ## Troubleshooting ### Pairing Fails Immediately Check: - Both devices have network connectivity - Firewalls allow Spacedrive traffic - System time is roughly correct (within 5 minutes) ### Cannot Find Device **For text-based codes:** - Ensure both devices are on the same local network - Check that mDNS is not blocked by firewalls - Text codes only work locally - use QR codes for cross-network pairing **For QR codes:** - Ensure both devices have internet connectivity - Check that the node_id is included in the QR code - Verify dns.iroh.link is accessible (not blocked by corporate firewalls) - Try generating a fresh code ### Code Invalid or Expired Solutions: - Double-check spelling of all 12 words - Ensure code was entered within 5 minutes - Generate new code if expired - Check for typos in word order ## Related Documentation - [Networking](/docs/core/networking) - Network transport details - [Devices](/docs/core/devices) - Device management system - [Security](/docs/core/security) - Cryptographic architecture