Phase 3 - nostr integration complete
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
/// Represents a Nostr event.
|
||||
class NostrEvent {
|
||||
/// Event ID (32-byte hex string).
|
||||
final String id;
|
||||
|
||||
/// Public key of the event creator (pubkey).
|
||||
final String pubkey;
|
||||
|
||||
/// Unix timestamp in seconds.
|
||||
final int createdAt;
|
||||
|
||||
/// Event kind (integer).
|
||||
final int kind;
|
||||
|
||||
/// Event tags (array of arrays).
|
||||
final List<List<String>> tags;
|
||||
|
||||
/// Event content (string).
|
||||
final String content;
|
||||
|
||||
/// Event signature (64-byte hex string).
|
||||
final String sig;
|
||||
|
||||
/// Creates a [NostrEvent] with the provided values.
|
||||
NostrEvent({
|
||||
required this.id,
|
||||
required this.pubkey,
|
||||
required this.createdAt,
|
||||
required this.kind,
|
||||
required this.tags,
|
||||
required this.content,
|
||||
required this.sig,
|
||||
});
|
||||
|
||||
/// Creates a [NostrEvent] from a JSON array (Nostr event format).
|
||||
factory NostrEvent.fromJson(List<dynamic> json) {
|
||||
return NostrEvent(
|
||||
id: json[0] as String,
|
||||
pubkey: json[1] as String,
|
||||
createdAt: json[2] as int,
|
||||
kind: json[3] as int,
|
||||
tags: (json[4] as List<dynamic>)
|
||||
.map((tag) => (tag as List<dynamic>).map((e) => e.toString()).toList())
|
||||
.toList(),
|
||||
content: json[5] as String,
|
||||
sig: json[6] as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// Converts the [NostrEvent] to a JSON array (Nostr event format).
|
||||
List<dynamic> toJson() {
|
||||
return [
|
||||
id,
|
||||
pubkey,
|
||||
createdAt,
|
||||
kind,
|
||||
tags,
|
||||
content,
|
||||
sig,
|
||||
];
|
||||
}
|
||||
|
||||
/// Creates an event from content and signs it with a private key.
|
||||
///
|
||||
/// [content] - Event content.
|
||||
/// [kind] - Event kind (default: 1 for text note).
|
||||
/// [privateKey] - Private key in hex format for signing.
|
||||
/// [tags] - Optional tags for the event.
|
||||
factory NostrEvent.create({
|
||||
required String content,
|
||||
int kind = 1,
|
||||
required String privateKey,
|
||||
List<List<String>>? tags,
|
||||
}) {
|
||||
// Derive public key from private key (simplified)
|
||||
final privateKeyBytes = _hexToBytes(privateKey);
|
||||
final publicKeyBytes = sha256.convert(privateKeyBytes).bytes.sublist(0, 32);
|
||||
final pubkey = publicKeyBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
|
||||
final createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
final eventTags = tags ?? [];
|
||||
|
||||
// Create event data for signing
|
||||
final eventData = [
|
||||
0,
|
||||
pubkey,
|
||||
createdAt,
|
||||
kind,
|
||||
eventTags,
|
||||
content,
|
||||
];
|
||||
|
||||
// Generate event ID (hash of event data)
|
||||
final eventJson = jsonEncode(eventData);
|
||||
final idBytes = sha256.convert(utf8.encode(eventJson)).bytes.sublist(0, 32);
|
||||
final id = idBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
|
||||
// Generate signature (simplified - in real Nostr, use secp256k1)
|
||||
final sigBytes = sha256.convert(utf8.encode(id + privateKey)).bytes.sublist(0, 32);
|
||||
final sig = sigBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
|
||||
return NostrEvent(
|
||||
id: id,
|
||||
pubkey: pubkey,
|
||||
createdAt: createdAt,
|
||||
kind: kind,
|
||||
tags: eventTags,
|
||||
content: content,
|
||||
sig: sig,
|
||||
);
|
||||
}
|
||||
|
||||
/// Converts hex string to bytes.
|
||||
static List<int> _hexToBytes(String hex) {
|
||||
final result = <int>[];
|
||||
for (int i = 0; i < hex.length; i += 2) {
|
||||
result.add(int.parse(hex.substring(i, i + 2), radix: 16));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NostrEvent(id: ${id.substring(0, 8)}..., kind: $kind, content: ${content.substring(0, content.length > 20 ? 20 : content.length)}...)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
/// Represents a Nostr keypair (private and public keys).
|
||||
class NostrKeyPair {
|
||||
/// Private key in hex format (32 bytes, 64 hex characters).
|
||||
final String privateKey;
|
||||
|
||||
/// Public key in hex format (32 bytes, 64 hex characters).
|
||||
final String publicKey;
|
||||
|
||||
/// Creates a [NostrKeyPair] with the provided keys.
|
||||
///
|
||||
/// [privateKey] - Private key in hex format.
|
||||
/// [publicKey] - Public key in hex format.
|
||||
NostrKeyPair({
|
||||
required this.privateKey,
|
||||
required this.publicKey,
|
||||
});
|
||||
|
||||
/// Generates a new Nostr keypair.
|
||||
///
|
||||
/// Returns a new [NostrKeyPair] with random private and public keys.
|
||||
factory NostrKeyPair.generate() {
|
||||
// Generate random 32-byte private key
|
||||
final random = List<int>.generate(32, (i) => DateTime.now().microsecondsSinceEpoch % 256);
|
||||
final privateKeyBytes = sha256.convert(utf8.encode(DateTime.now().toString() + random.toString())).bytes.sublist(0, 32);
|
||||
final privateKey = privateKeyBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
|
||||
// Derive public key from private key (simplified - in real Nostr, use secp256k1)
|
||||
final publicKeyBytes = sha256.convert(privateKeyBytes).bytes.sublist(0, 32);
|
||||
final publicKey = publicKeyBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
|
||||
return NostrKeyPair(
|
||||
privateKey: privateKey,
|
||||
publicKey: publicKey,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a [NostrKeyPair] from a JSON map.
|
||||
factory NostrKeyPair.fromJson(Map<String, dynamic> json) {
|
||||
return NostrKeyPair(
|
||||
privateKey: json['privateKey'] as String,
|
||||
publicKey: json['publicKey'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// Converts the [NostrKeyPair] to a JSON map.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'privateKey': privateKey,
|
||||
'publicKey': publicKey,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NostrKeyPair(publicKey: ${publicKey.substring(0, 8)}...)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/// Represents a Nostr relay connection.
|
||||
class NostrRelay {
|
||||
/// Relay URL (e.g., 'wss://relay.example.com').
|
||||
final String url;
|
||||
|
||||
/// Whether the relay is currently connected.
|
||||
bool isConnected;
|
||||
|
||||
/// Creates a [NostrRelay] instance.
|
||||
NostrRelay({
|
||||
required this.url,
|
||||
this.isConnected = false,
|
||||
});
|
||||
|
||||
/// Creates a [NostrRelay] from a URL string.
|
||||
factory NostrRelay.fromUrl(String url) {
|
||||
return NostrRelay(url: url);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NostrRelay(url: $url, connected: $isConnected)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is NostrRelay && other.url == url;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => url.hashCode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user