Nostr RPC

Pub Version Dart CI License: MIT

End-to-end encrypted JSON-RPC 2.0 over Nostr – secure peer-to-peer communication for Dart and Flutter without servers, VPNs or port forwarding.

Nostr RPC wraps the familiar JSON-RPC 2.0 protocol in Nostr's NIP-59 Gift Wrap (with NIP-44 encryption), giving you decentralized, private and resilient RPC calls across relays.

What It Is

Nostr RPC is a Dart library that enables secure peer-to-peer communication using the Nostr protocol. It specializes in end-to-end encrypted JSON-RPC 2.0 calls wrapped in Nostr's NIP-59 Gift Wrap, ensuring your data stays private and tamper-proof.

Key highlights:

  • Decentralized: No central authority controls your communication
  • Encrypted: Messages are encrypted using NIP-44 and wrapped with NIP-59
  • Reliable: Multi-relay support with automatic deduplication
  • Flexible: Pluggable protocols for custom communication patterns

What It Is Not

This library is not a general-purpose Nostr client. It's specifically designed for RPC-style communication between peers. If you need:

  • General Nostr event publishing/subscribing β†’ Use dart_nostr instead
  • Wallet functionality β†’ Look for Nostr wallet libraries
  • Social media features β†’ Check other Nostr packages

Nostr RPC focuses solely on secure, structured RPC communication.

Features

πŸ”’ Strong End-to-End Encryption

Messages are protected with NIP-44 (AES-GCM) encryption and wrapped using NIP-59 Gift Wrap. Even if a relay or observer intercepts the message, they cannot read the content or reliably determine who is communicating with whom.

πŸš€ JSON-RPC 2.0 Support

Communicate using the industry-standard JSON-RPC 2.0 protocol. This familiar format makes it easy to integrate with existing systems and tools that already support RPC calls.

🌐 Multi-Relay Resilience

Connect through multiple Nostr relays simultaneously. If one relay goes down, your communication continues seamlessly through others. Automatic deduplication ensures you don't receive duplicate messages.

πŸŽ›οΈ Flexible Connection Control

Choose how you want to handle incoming connections:

  • Always Accept: Trust everyone (great for open services)
  • Always Ask: Manual approval for each connection
  • Cached Approval: Remember trusted peers automatically

πŸ“¦ Pluggable Protocols

Need something beyond JSON-RPC? Easily create custom protocols for your specific use case – chat, file transfer, or any structured communication pattern.

πŸ”„ Smart Ordering

Handle out-of-order messages gracefully with sequence caching and automatic reordering, ensuring your RPC calls and responses arrive in the correct sequence.

How It Works

To achieve maximum privacy on Nostr, messages are wrapped in multiple encryption layers. Each layer solves a different privacy problem:

To achieve maximum privacy on Nostr, messages are wrapped in multiple encryption layers. Each layer solves a different privacy problem:

  1. Innermost Layer – The Rumor (unsigned event) Your actual message content (e.g. a text message, RPC request, or response) exists as an unsigned Nostr event called the "Rumor". It is not signed with your private key.
  2. NIP-44 Encryption – Content Protection The Rumor is encrypted using NIP-44 (AES-GCM with modern cryptography). Only you and the intended recipient share the symmetric key (the conversation key). β†’ No one else can read the actual message content.
  3. Seal (Kind 13) – Sender Protection The encrypted Rumor is placed inside a "Seal" event. This Seal is encrypted again with NIP-44, but this time using a random, one-time (ephemeral) keypair. This completely hides the real sender’s public key.
  4. Gift Wrap (Kind 1059) – Anonymous Envelope The Seal is encrypted one final time with NIP-44 (again using a fresh ephemeral key) and wrapped into a Gift Wrap event. This outer envelope:
    • is signed by a completely random, temporary key (not your real account),
    • only contains the recipient’s public key as a tag ("p"),
    • looks like a generic, harmless Nostr event to everyone else.

What Does Each Party See?

Party Can See Sender? Can See Content? Can See Who Is Talking to Whom?
Relays / Observers ❌ (random key) ❌ receivers pubkey got blob from random public key.
Intermediaries ❌ ❌ ❌
Recipient βœ… (after decryption) βœ… βœ…

Key Benefits:

  • Content is strongly encrypted (NIP-44).
  • Metadata (who is talking to whom, when, from which account) is heavily obfuscated.
  • Forward secrecy thanks to ephemeral keys.
  • Plausible deniability (the inner event is unsigned).

Dependencies

We carefully selected dependencies to ensure security, reliability, and minimal footprint:

  • json_rpc_2 (4M+ downloads) - Official Dart team package for JSON-RPC
  • web_socket_channel (6M+ downloads) - Dart team's WebSocket implementation
  • stream_channel (5M+ downloads) - Dart team's streaming utilities
  • convert (5M+ downloads) - Dart team's encoding/decoding utilities
  • pointycastle (2M+ downloads) - Bouncy Castle's cryptographic library

All dependencies are from trusted sources (Dart team or Bouncy Castle) with millions of downloads and active maintenance.

Installation

dart pub add nostr_rpc

Examples

Quick Start: Basic JSON-RPC

import 'package:nostr_rpc/nostr_rpc.dart';

final rpc = NostrRpc<JsonRpcConnection>(
  relays: ['wss://relay.example.com'],
  identity: NostrIdentity.generate(),
  acceptanceStrategy: AlwaysAcceptStrategy(),
);

await rpc.start();

rpc.onPeerConnected.listen((connection) {
  connection.registerMethod('echo', (params) => print(params['text']));
});

// connect with peer and send a message
final connection = rpc.getOrCreateConnection('peer_pubkey_hex');
final response = await connection.sendRequest('echo', {'text': 'Hello, from nostr_rpc!'});
print(response)

See example/chat_simple.dart for a complete example.

Quick Start: Custom typed Protocol

import 'package:nostr_rpc/nostr_rpc.dart';

class ChatConnection extends RpcConnection {
  ChatConnection({required super.peerPubkeyHex, required super.channel});
}

class ChatProtocol extends RpcProtocol<ChatConnection> {
  @override
  ChatConnection createConnection(String peerPubkeyHex, RawChannel channel) {
    return ChatConnection(peerPubkeyHex: peerPubkeyHex, channel: channel);
  }
}

final rpc = NostrRpc<ChatConnection>(
  relays: ['wss://relay.example.com'],
  identity: NostrIdentity.generate(),
  protocol: ChatProtocol(),
  acceptanceStrategy: AlwaysAcceptStrategy(),
);

See example/chat_typed.dart for the full implementation.

API Overview

For detailed API documentation, see pub.dev.

Contributing

We welcome contributions! See CONTRIBUTION.md for guidelines.

Acknowledgments

This library builds on the foundational work of the Nostr community, including the Nostr Protocol specification and dart_nostr.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Libraries

nostr_rpc
nostr_rpc β€” E2E-encrypted JSON-RPC 2.0 over Nostr NIP-59 Gift Wrap