nostr_signaling

Dart SDK License pub.dev

A Nostr-based signaling library for Dart. Exchange arbitrary binary data between Nostr peers, with optional GZip compression and multi-relay redundancy.

Features

  • Nostr signaling — publish and subscribe to arbitrary binary data over the Nostr protocol.
  • GZip compression — reduce payload size automatically; detects and rejects already-compressed data.
  • Multi-relay — publish/subscribe across multiple Nostr relays for reliability and redundancy.
  • Key management — generate, import, and validate Nostr key pairs.
  • Pluggable engine — implement ICompressionEngine to add custom compression algorithms.

Getting started

Add nostr_signaling to your pubspec.yaml:

dependencies:
  nostr_signaling: ^0.3.0

Usage

Basic signaling (no compression)

import 'package:nostr_signaling/nostr_signaling.dart';

void main() async {
  final signaling = NostrSignalingFactory.create(
    pubkey: 'your-public-key-hex',
    privkey: 'your-private-key-hex',
  );

  await signaling.connect();

  // Publish raw bytes to a peer
  final eventId = await signaling.publish([1, 2, 3, 4, 5]);

  // Subscribe to messages from a peer
  await signaling.subscribe('peer-pubkey-hex', (id, data) {
    print('Received from $id: $data');
  });

  await signaling.disconnect();
}

With GZip compression

final signaling = NostrSignalingFactory.createWithGzipCompression(
  pubkey: 'your-public-key-hex',
  privkey: 'your-private-key-hex',
);

await signaling.connect();

// Large data is compressed automatically before publishing
final largeData = List<int>.generate(10000, (i) => i % 256);
await signaling.publish(largeData);

Key generation

// Generate a new random key pair
final keyPair = NostrKeys.generate();
print('Private: ${keyPair.privateKey}');
print('Public:  ${keyPair.publicKey}');

// Import from an existing private key
final imported = NostrKeys.fromPrivateKeyHex('your-private-key-hex');

// Import and validate a full key pair
final validated = NostrKeys.fromHex(
  privateKeyHex: 'private-key-hex',
  publicKeyHex: 'public-key-hex',
);

Custom compression engine

class MyCompressionEngine implements ICompressionEngine {
  @override
  Future<CompressedData> compress(List<int> data) async {
    // Your custom compression logic
    return CompressedData(
      originalSize: data.length,
      compressedSize: data.length,
      compressionRatio: 0,
      data: data,
      timestamp: DateTime.now().millisecondsSinceEpoch,
    );
  }

  @override
  Future<List<int>> decompress(CompressedData compressed) async {
    // Your custom decompression logic
    return compressed.data;
  }
}

final signaling = NostrSignalingFactory.create(
  pubkey: 'pubkey',
  privkey: 'privkey',
  useCompression: true,
  compressionEngine: MyCompressionEngine(),
);

Event deduplication

EventCallback is now a class with built-in dedup. The same event hash is never processed twice.

final callback = EventCallback(
  (id, data) => print('Received from $id: ${data.length} bytes'),
  maxRecords: 5000,           // keep last 5000 hashes
);

Multi-relay redundancy

final signaling = NostrSignalingFactory.createWithMultipleRelays(
  pubkey: 'pubkey',
  privkey: 'privkey',
  relayUrls: [
    'wss://relay.damus.io',
    'wss://nos.lol',
    'wss://relay.primal.net',
  ],
);

Config file

NostrConfig persists relay URLs and key pair to a JSON file on disk.

// Save config
final config = NostrConfig(
  keyPair: myKeyPair,
  relays: ['wss://relay.damus.io'],
);
await config.save();                // → nostr_config.json

// Load config
final loaded = await NostrConfig.load();      // async
final loadedSync = NostrConfig.loadSync();    // sync

Initial point from config (no required parameters)

Loads key pair and relays from the config file automatically. Throws StateError if the file is missing or has no key pair.

// Singleton DI — no parameters needed
await initialPointNostrSignalingFromConfig();

final signaling = getINostrSignaling();

// Registry DI — supports multiple named instances
initialPointNostrSignalingRegistryFromConfig(key: 'alice');
initialPointNostrSignalingRegistryFromConfig(key: 'bob');

Both accept an optional configPath (default: nostr_config.json).

API overview

Class / Interface Description
INostrSignaling Abstract signaling interface (connect, publish, subscribe, etc.)
NostrSignalingImpl Concrete signaling implementation
INostrRelay Abstract relay interface
NostrRelayImpl Concrete WebSocket relay via dart_nostr
ICompressionEngine Pluggable compression interface
GzipCompressionEngine GZip compression with auto-detection
EventCallback Callback wrapper with automatic hash-based deduplication
PayloadHashLength Payload hash length (32/64/256 bits)
NostrConfig Config file persistence (relays + key pair)
NostrSignalingFactory Factory with pre-configured factory methods
NostrKeys / NostrKeyPair Key generation, import, and validation
CompressedData Compression metadata (original size, ratio, timestamp)
NostrTestKeys / NostrTestRelays Test constants

Event kinds

Kind Description
1000 Compressed payload (base64-encoded)
1001 Raw payload (base64-encoded)

Additional information

Libraries

generated/nostr_signaling_impl_di
nostr_signaling
A Nostr-based signaling library for exchanging binary data between peers.