Nostr RPC
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_nostrinstead - 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:
- 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.
- 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.
- 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.
- 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.
NostrRpc<T>β The core RPC engine that manages connections and relaysNostrIdentityβ Handles key generation and cryptographic operationsRpcProtocol<T>β Abstract base for custom communication protocolsJsonRpcProtocolβ Default JSON-RPC 2.0 implementationJsonRpcWithSequenceCacheProtocolβ Ordered JSON-RPC with sequence handlingOrderingStrategyβ Interfaces for message ordering (NoCacheOrdering,SequenceCacheOrdering)AcceptanceStrategyβ Connection approval logic (AlwaysAcceptStrategy,AlwaysAskStrategy,CachedApprovalStrategy)
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