Skip to content

Client Library

pub struct SyncClient {
// Internal: connection, state, crypto
}
impl SyncClient {
/// Create new client
pub async fn new(config: SyncConfig) -> Result<Self>;
/// Connect to relay
pub async fn connect(&mut self) -> Result<ConnectionInfo>;
/// Disconnect gracefully
pub async fn disconnect(&mut self) -> Result<()>;
/// Push encrypted blob
pub async fn push(&self, data: &[u8]) -> Result<PushResult>;
/// Pull blobs after cursor
pub async fn pull(&self, after_cursor: u64) -> Result<PullResult>;
/// Subscribe to events
pub fn subscribe(&self) -> Receiver<SyncEvent>;
/// Current status
pub fn status(&self) -> ConnectionStatus;
/// Last synced cursor (persisted)
pub fn last_cursor(&self) -> u64;
/// Device public key
pub fn device_id(&self) -> DeviceId;
}
pub struct SyncConfig {
/// Relay backend selection
pub backend: RelayBackend,
/// Device keypair (generated or loaded)
pub device_keypair: Option<Keypair>,
/// Group key (from pairing)
pub group_key: Option<GroupKey>,
/// Auto-reconnect on disconnect
pub auto_reconnect: bool,
/// Reconnect backoff (initial)
pub reconnect_delay: Duration,
/// Max reconnect backoff
pub max_reconnect_delay: Duration,
}
pub enum RelayBackend {
/// Tier 1: iroh public network (P2P + iroh-relay fallback)
/// No custom infrastructure needed.
Iroh,
/// Tiers 2-3: Self-hosted sync-relay (iroh Endpoint)
/// Connect to a known sync-relay by NodeId.
SyncRelay {
node_id: iroh::NodeId,
relay_url: Option<Url>, // Optional custom iroh-relay for this deployment
},
/// Tiers 4-5: Managed Cloud
/// API key resolves to a sync-relay NodeId via discovery service.
ManagedCloud {
api_key: String,
},
/// Tier 6: Enterprise
/// Dedicated sync-relay with enterprise authentication.
Enterprise {
node_id: iroh::NodeId,
auth: EnterpriseAuth,
},
}
pub enum SyncEvent {
/// Connected to relay
Connected { info: ConnectionInfo },
/// Disconnected from relay
Disconnected { reason: String },
/// New blob available (NOTIFY received)
BlobAvailable { id: BlobId, cursor: u64, sender: DeviceId },
/// Blob successfully pushed
BlobPushed { id: BlobId, cursor: u64 },
/// Error occurred
Error { code: u32, message: String },
/// Connection status changed
StatusChanged { status: ConnectionStatus },
}
pub enum ConnectionStatus {
Disconnected,
Connecting,
Connected,
Reconnecting { attempt: u32 },
}

Encrypt (before push):

plaintext
→ serialize (MessagePack)
→ encrypt (XChaCha20-Poly1305 with Group Key + random 192-bit nonce)
→ wrap in Envelope
→ send via iroh (QUIC/TLS 1.3)

Decrypt (after pull):

receive via iroh (QUIC/TLS 1.3)
→ unwrap Envelope
→ decrypt (XChaCha20-Poly1305 with Group Key + nonce from envelope)
→ deserialize (MessagePack)
→ plaintext

Why XChaCha20 (not standard ChaCha20)? Standard ChaCha20-Poly1305 uses 96-bit nonces with a safe threshold of ~4.3 billion messages. XChaCha20 uses 192-bit nonces, making random nonce generation safe without cross-device coordination (safe threshold: 2^80).