import 'dart:convert'; import 'dart:math'; import 'package:actors/actors.dart'; import 'package:bip340/bip340.dart' as bip340; import 'package:crypto/crypto.dart'; sealed class SignerMessage { const SignerMessage(); } final class SignEvent extends SignerMessage { final int kind; final List> tags; final String content; final int? createdAt; const SignEvent({ required this.kind, this.tags = const [], required this.content, this.createdAt, }); } final class GetPublicKey extends SignerMessage { const GetPublicKey(); } sealed class SignerResult { const SignerResult(); } final class EventSigned extends SignerResult { final String event; const EventSigned(this.event); } final class PublicKeyResult extends SignerResult { final String publicKey; const PublicKeyResult(this.publicKey); } class NostrSignerActor with Handler { final String _privateKey; final String _publicKey; NostrSignerActor(String privateKey) : _privateKey = privateKey, _publicKey = bip340.getPublicKey(privateKey); @override Future handle(SignerMessage message) => switch (message) { SignEvent(:final kind, :final tags, :final content, :final createdAt) => Future.value(_signEvent(kind, tags, content, createdAt)), GetPublicKey() => Future.value(PublicKeyResult(_publicKey)), }; EventSigned _signEvent( int kind, List> tags, String content, int? createdAt, ) { final ts = createdAt ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; final ser = jsonEncode([0, _publicKey, ts, kind, tags, content]); final idBytes = sha256.convert(utf8.encode(ser)).bytes; final id = idBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); final rng = Random.secure(); final auxBytes = List.generate(32, (_) => rng.nextInt(256)); final aux = auxBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); final sig = bip340.sign(_privateKey, id, aux); return EventSigned(jsonEncode({ 'id': id, 'pubkey': _publicKey, 'created_at': ts, 'kind': kind, 'tags': tags, 'content': content, 'sig': sig, })); } }