> ## Documentation Index
> Fetch the complete documentation index at: https://v2.spacedrive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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 theme={null}
{
  "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

<Steps>
  <Step title="Generate Code">
    Call the pairing API to generate a code:

    ```typescript theme={null}
    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: "..." }
    ```
  </Step>

  <Step title="Wait for Connection">
    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)
  </Step>

  <Step title="Verify Joiner">
    When a joiner connects, the initiator sends a cryptographic challenge to verify they have the correct code and own their device keys.
  </Step>

  <Step title="Complete Pairing">
    After verification, both devices exchange session keys and save the pairing relationship.
  </Step>
</Steps>

### For the Joiner

<Steps>
  <Step title="Enter Code">
    Enter the code from the initiator (text or QR):

    ```typescript theme={null}
    // 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..."
    });
    ```
  </Step>

  <Step title="Discover Device">
    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.
  </Step>

  <Step title="Prove Identity">
    Sign a challenge from the initiator to prove you have the code and own your
    device keys.
  </Step>

  <Step title="Save Relationship">
    Store the paired device information and session keys for future communication.
  </Step>
</Steps>

## Proxy pairing

Proxy pairing lets a new device join a network after a single direct pairing. The device that completed the direct pairing can vouch for the new device to other trusted devices. Each receiving device can accept or reject the vouch.

See [Proxy Pairing](/docs/core/proxy-pairing) for the full protocol, resource model, and flows.

## Technical Architecture

### Protocol Messages

The pairing protocol uses four message types:

```rust theme={null}
pub enum PairingMessage {
    // Joiner → Initiator: "I want to pair"
    PairingRequest {
        session_id: Uuid,
        device_info: DeviceInfo,
        public_key: Vec<u8>,
    },

    // Initiator → Joiner: "Prove you have the code"
    Challenge {
        session_id: Uuid,
        challenge: Vec<u8>,  // 32 random bytes
        device_info: DeviceInfo,
    },

    // Joiner → Initiator: "Here's my signature"
    Response {
        session_id: Uuid,
        response: Vec<u8>,   // 64-byte Ed25519 signature
        device_info: DeviceInfo,
    },

    // Initiator → Joiner: "Pairing complete"
    Complete {
        session_id: Uuid,
        success: bool,
        reason: Option<String>,
    },
}
```

### State Machine

The PairingProtocolHandler manages session state:

```rust theme={null}
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 theme={null}
pub struct PairingSession {
    session_id: Uuid,           // Derived from code
    state: PairingState,        // Current state
    remote_device: Option<DeviceInfo>,
    created_at: SystemTime,
    expires_at: SystemTime,     // 5 minutes later
}
```

<Warning>
  Sessions expire after 5 minutes. Users must complete pairing within this time
  window.
</Warning>

## 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 theme={null}
// 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 theme={null}
// 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

<Info>
  Pkarr uses DNS-based discovery backed by the Mainline DHT. It's more reliable than traditional DHT for NAT traversal and works globally.
</Info>

### Dual-Path Discovery

When using QR codes (with node\_id), Spacedrive races both discovery methods:

```rust theme={null}
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 theme={null}
// 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)

<Info>
  Relay servers only forward encrypted QUIC traffic. They cannot decrypt your data or compromise security.
</Info>

## Cryptographic Details

### Challenge-Response Authentication

The challenge-response prevents replay attacks and verifies device identity:

```rust theme={null}
// 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 theme={null}
// 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 theme={null}
// 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 theme={null}
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 theme={null}
// High-level API
pub async fn start_pairing_as_initiator(
    &self
) -> Result<PairingCode> {
    // 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 theme={null}
// 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 theme={null}
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 theme={null}
#[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 theme={null}
#[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
* [Proxy Pairing](/docs/core/proxy-pairing) - Vouching based pairing flow
* [Security](/docs/core/security) - Cryptographic architecture
