nostro2-nips
NIP implementations for NostrO2, built
on the crate's own spec-compliant primitives — no third-party nostr stack.
| NIP | Module | What it gives you |
|---|---|---|
| 04 / 44 | nip_44 |
Versioned encrypted payloads; NIP-44 v2 (ChaCha20 + HMAC-SHA256), vector-gated |
| 17 | nip_17 |
Private direct messages |
| 46 | nip_46 |
Nostr Connect (remote signing) request/response |
| 59 | nip_59 |
Gift wrap (seal + rumor) |
| 104 | nip_104, nip_104_invite, nip_104_manager |
Double Ratchet E2EE DMs — see below |
Schnorr signing goes through the NostrSigner trait, ECDH through
NostrKeypair, verification through NostrNote::verify — so the crate links no
curve of its own. Pick one via features: k256 (default) or secp256k1.
NIP-104 — Double Ratchet
A native, dependency-light port of
mmalmi/nostr-double-ratchet
(the implementation chat.iris.to runs). Every primitive is byte-identical to
the reference, so sessions established here interoperate with Iris. The stack
is five layers:
nip_44— NIP-44 v2 primitives. The symmetric/DH foundation. Gated against the official test vectors.nip_104::Session— the 1:1 ratchet.new_initiator/new_responder, thenplan_send/plan_receive(which return the next state for atomic persistence) andapplyto commit. Handles chain stepping and skipped-message keys (MAX_SKIP). Cross-checked against the reference at the crypto layer.nip_104codec — kind:1060 wire events.plan_send_event/plan_receive_eventandMessageEnvelope::to_event/from_event. Events are signed by the sender's current ephemeral key; the ciphertext is thecontentand the encrypted header rides in a["header", …]tag. Cross-checked against the reference at the event layer.nip_104_invite::Invite— session bootstrap. Mint an invite (create_new), share it (to_url/to_event),acceptit (invitee → initiator session + signed kind:1059 response), andreceivethe response (inviter → mirror responder session). The response is triple-encrypted — inner DH authenticates the invitee, the shared-secret layer proves link possession, the envelope hides the invitee from other link holders.nip_104_manager::SessionManager— multi-device fan-out. Tracks many sessions keyed by(peer, device), grouping a peer's devices under their owner pubkey. Routes inbound events to whichever session decrypts them and fans asendout to every device. Side-effect free: methods hand back the events to publish (or the decrypted message), leaving transport, storage and scheduling to you.
End-to-end: bootstrap from an invite, then chat
use ;
// Each side owns a long-term identity keypair `K: NostrKeypair`.
let mut alice = new;
let mut bob = new;
// Alice mints an invite and shares the URL (she keeps the ephemeral secret).
let invite = ?;
let url = invite.to_url;
// Bob scans it, accepts, and publishes the response event.
let bob_side = from_url?;
let response = bob.accept_invite?; // publish `response`
// Alice consumes the response and now shares a session with Bob.
let peer = alice.receive_invite_response?;
// Bob (the initiator) sends first; Alice routes the inbound event home.
for event in bob.send?
// One send fans out to every device the peer runs.
for event in alice.send?
For a single session without the manager, drive Session directly:
let = session.plan_send?;
session.apply; // commit only after you've persisted/published
Scope
Layers 1–5 are the portable, interop-critical core. Deliberately left to the
application: persistent storage, live relay wiring, AppKeys device
authorization, read receipts, and disappearing-message expiration — all runtime
concerns the SessionManager is designed to sit beneath.