ndr-ffi
UniFFI bindings for nostr-double-ratchet - enables iOS and Android apps to use the Nostr Double Ratchet protocol.
Overview
This crate provides FFI-friendly wrappers around the core nostr-double-ratchet library. All keys and events are represented as hex/JSON strings for easy interop with mobile platforms.
API
Key Generation
let keypair = generate_keypair;
// keypair.public_key_hex - 64 char hex string
// keypair.private_key_hex - 64 char hex string
Invite Handling
// Create invite
let invite = create_new?;
let url = invite.to_url?;
// Accept invite
let invite = from_url?;
let result = invite.accept?;
// result.session - ready to send messages
// result.response_event_json - publish to relays
// Serialize/deserialize for storage
let json = invite.serialize?;
let invite = deserialize?;
Session Messaging
// Send message
let result = session.send_text?;
// result.outer_event_json - encrypted event to publish
// result.inner_event_json - original message as event
// Receive message
let result = session.decrypt_event?;
// result.plaintext - decrypted message
// result.inner_event_json - inner event if JSON
// Serialize/deserialize for storage
let state = session.state_json?;
let session = from_state_json?;
Shared Multi-Device Helpers
These wrappers are intended to keep mobile clients on the same policy as the core library:
let devices = resolve_latest_app_keys_devices?;
let candidates = resolve_conversation_candidate_pubkeys;
resolve_latest_app_keys_devices(...)converges a set of AppKeys events into the latest monotonic authorized-device view.resolve_conversation_candidate_pubkeys(...)returns the ordered conversation candidates for a decrypted rumor, including self-DM and linked-device cases.
SessionManager Inspection
SessionManagerHandle now exposes supported inspection APIs so mobile apps do not need to read
user_<peer>.json files directly:
let manager = new_with_storage_path?;
manager.init?;
let peers = manager.known_peer_owner_pubkeys;
let stored = manager.get_stored_user_record_json?;
let authors = manager.get_message_push_author_pubkeys?;
let session_states = manager.get_message_push_session_states?;
known_peer_owner_pubkeys()lists peer owner pubkeys known from loaded state or persisted storage, so callers can enumerate peers beforeinit()without relying on filenames.get_stored_user_record_json(peer)returns the supported stored-user-record snapshot JSON for a peer owner, matching the library's persisted record shape without requiring callers to know filenames or storage layout.get_message_push_author_pubkeys(peer)returns the deduplicated sender pubkeys tracked by that peer's stored pairwise sessions.get_message_push_session_states(peer)returns session-state JSON snapshots plus tracked sender pubkeys and receiving-capability flags for push-routing repair flows.
SessionManager App Loop
Mobile integrations should usually treat SessionManagerHandle as the main surface, not bare
SessionHandle. This is the path used by
iris-chat-flutter.
let manager = new_with_storage_path?;
manager.init?;
manager.setup_user?;
for event in manager.drain_events?
// Feed relay events back into SessionManagerHandle.
manager.process_event?;
Native consumers use this shape: app-owned relay I/O, drain pubsub events after init,
setup_user, send, and accept-invite calls, then feed relay events back with process_event(...).
Building for Mobile
Android
# Prerequisites
# Build
Output:
target/android/jniLibs/{arm64-v8a,armeabi-v7a,x86_64,x86}/libndr_ffi.sotarget/android/bindings/*.kt
iOS
# Prerequisites (macOS only)
# Build
Output:
target/ios/NdrFfi.xcframeworktarget/ios/bindings/*.swift
Testing
Error Handling
All errors are mapped to NdrError:
InvalidKey- Invalid key formatInvalidEvent- Invalid event formatCryptoFailure- Encryption/decryption errorStateMismatch- Protocol state errorSerialization- JSON serialization errorInviteError- Invite-specific errorSessionNotReady- Session cannot send yet
License
MIT