import 'dart:convert'; import 'package:bip340/bip340.dart' as bip340; import 'package:crypto/crypto.dart'; /// A minimal Nostr event (NIP-01): enough to build, id, sign and serialise /// NIP-99 classified listings. Throwaway — production would use a vetted client. class NostrEvent { NostrEvent({ required this.pubkey, required this.createdAt, required this.kind, required this.tags, required this.content, this.id = '', this.sig = '', }); final String pubkey; // x-only hex final int createdAt; // unix seconds final int kind; final List> tags; final String content; String id; String sig; /// NIP-01 id: sha256 of the compact JSON array /// `[0, pubkey, created_at, kind, tags, content]`. String computeId() { final serialized = jsonEncode([0, pubkey, createdAt, kind, tags, content]); final digest = sha256.convert(utf8.encode(serialized)); return digest.toString(); // lower-case hex } /// Fills [id] and [sig] (BIP340 Schnorr over the id) with [privateKeyHex]. void signWith(String privateKeyHex, {String? auxRandHex}) { id = computeId(); // Deterministic aux in the spike (derived from id) so round-trip tests are // reproducible; production uses fresh randomness per BIP340. final aux = auxRandHex ?? id; sig = bip340.sign(privateKeyHex, id, aux); } /// Verifies the signature against [pubkey]. Cross-checks that a real relay / /// peer would accept what we produced. bool verify() => id == computeId() && bip340.verify(pubkey, id, sig); Map toJson() => { 'id': id, 'pubkey': pubkey, 'created_at': createdAt, 'kind': kind, 'tags': tags, 'content': content, 'sig': sig, }; static NostrEvent fromJson(Map j) => NostrEvent( id: j['id'] as String, pubkey: j['pubkey'] as String, createdAt: j['created_at'] as int, kind: j['kind'] as int, tags: (j['tags'] as List) .map((t) => (t as List).map((e) => e as String).toList()) .toList(), content: j['content'] as String, sig: j['sig'] as String, ); /// First value of the first tag named [name], or null. String? tag(String name) { for (final t in tags) { if (t.isNotEmpty && t[0] == name && t.length > 1) return t[1]; } return null; } }