diff --git a/packages/amber/lib/data_layer/repositories/signers/amber_event_signer.dart b/packages/amber/lib/data_layer/repositories/signers/amber_event_signer.dart index 840280758..92c785b43 100644 --- a/packages/amber/lib/data_layer/repositories/signers/amber_event_signer.dart +++ b/packages/amber/lib/data_layer/repositories/signers/amber_event_signer.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:ndk/ndk.dart'; -import 'package:ndk/shared/nips/nip19/nip19.dart'; import '../../data_sources/amber_flutter.dart'; @@ -18,13 +17,15 @@ class AmberEventSigner implements EventSigner { }); @override - Future sign(Nip01Event event) async { + Future sign(Nip01Event event) async { final npub = publicKey.startsWith('npub') ? publicKey : Nip19.encodePubKey(publicKey); Map map = await amberFlutterDS.amber.signEvent( - currentUser: npub, eventJson: jsonEncode(event.toJson()), id: event.id); - event.sig = map['signature']; + currentUser: npub, + eventJson: Nip01EventModel.fromEntity(event).toJsonString(), + id: event.id); + return event.copyWith(sig: map['signature']); } @override diff --git a/packages/isar/lib/data_layer/models/db/db_event.dart b/packages/isar/lib/data_layer/models/db/db_event.dart index f2ae50502..520638171 100644 --- a/packages/isar/lib/data_layer/models/db/db_event.dart +++ b/packages/isar/lib/data_layer/models/db/db_event.dart @@ -42,7 +42,7 @@ class DbEvent extends Nip01Event { String get content => super.content; @override - String get sig => super.sig; + String? get sig => super.sig; @override bool? get validSig => super.validSig; @@ -56,12 +56,9 @@ class DbEvent extends Nip01Event { required super.tags, required super.content, super.createdAt, - required String sig, + String? sig, bool? validSig, required List sources}) { - super.sig = sig; - super.validSig = validSig; - super.sources = sources; } static DbEvent fromNip01Event(Nip01Event event) { diff --git a/packages/isar/lib/data_layer/models/db/db_event.g.dart b/packages/isar/lib/data_layer/models/db/db_event.g.dart index 01e350655..99d58637c 100644 --- a/packages/isar/lib/data_layer/models/db/db_event.g.dart +++ b/packages/isar/lib/data_layer/models/db/db_event.g.dart @@ -87,7 +87,7 @@ int serializeDbEvent(IsarWriter writer, DbEvent object) { IsarCore.endList(writer, listWriter); } IsarCore.writeString(writer, 7, object.content); - IsarCore.writeString(writer, 8, object.sig); + IsarCore.writeString(writer, 8, object.sig ?? ''); { final value = object.validSig; if (value == null) { diff --git a/packages/isar/pubspec.lock b/packages/isar/pubspec.lock index 89c202684..d26977b97 100644 --- a/packages/isar/pubspec.lock +++ b/packages/isar/pubspec.lock @@ -356,10 +356,9 @@ packages: ndk: dependency: "direct main" description: - name: ndk - sha256: "88c371ca73014fa41b8f711036fb64d01c99045e7ce7e9560fa49a0976c36e18" - url: "https://pub.dev" - source: hosted + path: "../ndk" + relative: true + source: path version: "0.6.1-dev.1" node_preamble: dependency: transitive diff --git a/packages/ndk/example/bunkers_example.dart b/packages/ndk/example/bunkers_example.dart index 81fe39b6f..42dbd9e3d 100644 --- a/packages/ndk/example/bunkers_example.dart +++ b/packages/ndk/example/bunkers_example.dart @@ -22,9 +22,9 @@ Future main() async { tags: [], ); - await ndk.accounts.sign(event); + final signedEvent = await ndk.accounts.sign(event); log('Event signed successfully!'); - log('Event ID: ${event.id}'); + log('Event ID: ${signedEvent.id}'); } catch (e) { log('Error: $e'); } diff --git a/packages/ndk/example/nip13_pow_example.dart b/packages/ndk/example/nip13_pow_example.dart index 3b24b4cc6..f8f403157 100644 --- a/packages/ndk/example/nip13_pow_example.dart +++ b/packages/ndk/example/nip13_pow_example.dart @@ -1,21 +1,30 @@ import 'dart:developer'; -import 'package:ndk/domain_layer/entities/nip_01_event.dart'; +import 'package:ndk/ndk.dart'; import 'package:ndk/shared/nips/nip01/bip340.dart'; void main() async { final keyPair = Bip340.generatePrivateKey(); - final minedEvent = Nip01Event( + final event = Nip01Event( pubKey: keyPair.publicKey, kind: 1, tags: [], content: 'message', - ).minePoW(12); + ); - log(minedEvent.id); // the id will start with "000" + /// your global NDK instance + final ndk = Ndk.emptyBootstrapRelaysConfig(); - if (minedEvent.checkPoWDifficulty(10)) { + /// pass your event to the proof of work usecase + final minedEvent = + await ndk.proofOfWork.minePoW(event: event, targetDifficulty: 10); + + /// the id will start with "000" + log(minedEvent.id); + + /// check the difficulty + if (ndk.proofOfWork.checkPoWDifficulty(event: event, targetDifficulty: 10)) { log('Event has difficulty >= 10'); } } diff --git a/packages/ndk/example/nostrconnect_example.dart b/packages/ndk/example/nostrconnect_example.dart index f05c11d4e..d751b3eb1 100644 --- a/packages/ndk/example/nostrconnect_example.dart +++ b/packages/ndk/example/nostrconnect_example.dart @@ -28,9 +28,9 @@ Future main() async { tags: [], ); - await ndk.accounts.sign(event); + final signedEvent = await ndk.accounts.sign(event); log('Event signed successfully!'); - log('Event ID: ${event.id}'); + log('Event ID: ${signedEvent.id}'); } catch (e) { log('Error: $e'); } diff --git a/packages/ndk/lib/data_layer/models/nip_01_event_model.dart b/packages/ndk/lib/data_layer/models/nip_01_event_model.dart new file mode 100644 index 000000000..59188e36a --- /dev/null +++ b/packages/ndk/lib/data_layer/models/nip_01_event_model.dart @@ -0,0 +1,181 @@ +import 'dart:convert'; + +import '../../domain_layer/entities/nip_01_event.dart'; +import '../../shared/helpers/list_casting.dart'; +import '../../shared/nips/nip19/nip19.dart'; + +/// Data model for NIP-01 Event +/// Extends [Nip01Event] entity with serialization methods to/from JSON and other formats +class Nip01EventModel extends Nip01Event { + /// creates a new [Nip01EventModel] instance + Nip01EventModel({ + required super.id, + required super.pubKey, + required super.createdAt, + required super.kind, + required super.tags, + required super.content, + required super.sig, + super.validSig, + super.sources = const [], + }); + + /// creates a copy of this event with the given fields replaced by the new values \ + /// needed so other packages depending on the extened nip01_event.dart can use copyWith + @override + Nip01EventModel copyWith({ + String? id, + String? pubKey, + int? createdAt, + int? kind, + List>? tags, + String? content, + String? sig, + bool? validSig, + List? sources, + }) { + return Nip01EventModel( + id: id ?? this.id, + pubKey: pubKey ?? this.pubKey, + createdAt: createdAt ?? this.createdAt, + kind: kind ?? this.kind, + tags: tags ?? this.tags, + content: content ?? this.content, + sig: sig ?? this.sig, + validSig: validSig ?? this.validSig, + sources: sources ?? this.sources); + } + + /** + * encoding/decoding methods + */ + + /// creates a new [Nip01EventModel] instance from a [Nip01Event] entity + factory Nip01EventModel.fromEntity(Nip01Event event) { + return Nip01EventModel( + id: event.id, + pubKey: event.pubKey, + createdAt: event.createdAt, + kind: event.kind, + tags: event.tags, + content: event.content, + sig: event.sig, + sources: event.sources, + validSig: event.validSig, + ); + } + + /// creates a new [Nip01EventModel] instance from a JSON object + factory Nip01EventModel.fromJson(Map data) { + final id = data['id'] as String? ?? ''; + final pubKey = data['pubkey'] as String? ?? ''; + final createdAt = data['created_at'] as int; + final kind = data['kind'] as int; + final tags = castToListOfListOfString(data['tags']); + final content = data['content'] as String? ?? ''; + + /// '' to support rumor events + final sig = (data['sig'] as String?); + + return Nip01EventModel( + id: id, + pubKey: pubKey, + createdAt: createdAt, + kind: kind, + tags: tags, + content: content, + sig: sig, + ); + } + + /// Returns the Event object as a JSON object + Map toJson() { + return { + 'id': id, + 'pubkey': pubKey, + 'created_at': createdAt, + 'kind': kind, + 'tags': tags, + 'content': content, + 'sig': sig + }; + } + + /// Returns the Event object as a JSON string + String toJsonString() { + final jsonMap = toJson(); + return json.encode(jsonMap); + } + + /// Encode this event as a nevent (NIP-19 event reference) + /// + /// Returns a bech32-encoded nevent string that includes: + /// - Event ID (required) + /// - Author pubkey (included) + /// - Kind (included) + /// - Relay hints from event.sources (if available) + /// + /// Usage: `final nevent = event.nevent;` + String get nevent { + return Nip19.encodeNevent( + eventId: id, + author: pubKey, + kind: kind, + relays: sources.isEmpty ? null : sources, + ); + } + + /// Encode this event as an naddr (NIP-19 addressable event coordinate) + /// + /// Only works for addressable/replaceable events (kind >= 10000 or kind 0, 3, 41) + /// Requires a "d" tag to identify the event. + /// + /// Returns a bech32-encoded naddr string or null if: + /// - Event is not addressable/replaceable + /// - Event doesn't have a "d" tag + /// + /// Usage: `final naddr = event.naddr;` + String? get naddr { + // Check if this is an addressable event + if (!_isAddressableKind(kind)) { + return null; + } + + // Get the "d" tag identifier + final identifier = getDtag(); + if (identifier == null) { + return null; + } + + return Nip19.encodeNaddr( + identifier: identifier, + pubkey: pubKey, + kind: kind, + relays: sources.isEmpty ? null : sources, + ); + } + + /// Returns the Event object as a base64-encoded JSON string + String toBase64() { + return base64Encode(utf8.encode(json.encode(toJson()))); + } + + /// Check if an event kind is addressable/replaceable + /// + /// According to NIP-01: + /// - Replaceable events: 0, 3, 41 + /// - Parameterized replaceable events: 10000-19999, 30000-39999 + bool _isAddressableKind(int kind) { + // Replaceable events + if (kind == 0 || kind == 3 || kind == 41) { + return true; + } + + // Parameterized replaceable events + if ((kind >= 10000 && kind <= 19999) || (kind >= 30000 && kind <= 39999)) { + return true; + } + + return false; + } +} diff --git a/packages/ndk/lib/data_layer/repositories/blossom/blossom_impl.dart b/packages/ndk/lib/data_layer/repositories/blossom/blossom_impl.dart index f73e45064..bc9cd173d 100644 --- a/packages/ndk/lib/data_layer/repositories/blossom/blossom_impl.dart +++ b/packages/ndk/lib/data_layer/repositories/blossom/blossom_impl.dart @@ -7,6 +7,7 @@ import '../../../domain_layer/entities/nip_01_event.dart'; import '../../../domain_layer/entities/tuple.dart'; import '../../../domain_layer/repositories/blossom.dart'; import '../../data_sources/http_request.dart'; +import '../../models/nip_01_event_model.dart'; class BlossomRepositoryImpl implements BlossomRepository { final HttpRequestDS client; @@ -169,7 +170,8 @@ class BlossomRepositoryImpl implements BlossomRepository { headers: { if (contentType != null) 'Content-Type': contentType, if (authorization != null) - 'Authorization': "Nostr ${authorization.toBase64()}", + 'Authorization': + "Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}", 'Content-Length': '${data.length}', }, ); @@ -212,7 +214,8 @@ class BlossomRepositoryImpl implements BlossomRepository { url: Uri.parse('$serverUrl/mirror'), body: myBody, headers: { - 'Authorization': "Nostr ${authorization.toBase64()}", + 'Authorization': + "Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}", 'Content-Type': 'application/json', }, ); @@ -259,7 +262,8 @@ class BlossomRepositoryImpl implements BlossomRepository { } if (authorization != null) { - headers['Authorization'] = "Nostr ${authorization.toBase64()}"; + headers['Authorization'] = + "Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}"; } final response = await client.get( @@ -298,7 +302,8 @@ class BlossomRepositoryImpl implements BlossomRepository { final headers = {}; if (authorization != null) { - headers['Authorization'] = "Nostr ${authorization.toBase64()}"; + headers['Authorization'] = + "Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}"; } for (final url in serverUrls) { @@ -420,7 +425,8 @@ class BlossomRepositoryImpl implements BlossomRepository { final headers = {}; if (authorization != null) { - headers['Authorization'] = "Nostr ${authorization.toBase64()}"; + headers['Authorization'] = + "Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}"; } final response = await client.get( @@ -466,7 +472,8 @@ class BlossomRepositoryImpl implements BlossomRepository { final response = await client.delete( url: Uri.parse('$serverUrl/$sha256'), headers: { - 'Authorization': "Nostr ${authorization.toBase64()}", + 'Authorization': + "Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}", }, ); @@ -504,7 +511,8 @@ class BlossomRepositoryImpl implements BlossomRepository { required String sha256, required Nip01Event reportEvent, }) async { - final String myBody = jsonEncode(reportEvent.toJson()); + final String myBody = + jsonEncode(Nip01EventModel.fromEntity(reportEvent).toJson()); final response = await client.put( url: Uri.parse('$serverUrl/report'), diff --git a/packages/ndk/lib/data_layer/repositories/signers/bip340_event_signer.dart b/packages/ndk/lib/data_layer/repositories/signers/bip340_event_signer.dart index 3687bab8a..2bbb16317 100644 --- a/packages/ndk/lib/data_layer/repositories/signers/bip340_event_signer.dart +++ b/packages/ndk/lib/data_layer/repositories/signers/bip340_event_signer.dart @@ -20,10 +20,11 @@ class Bip340EventSigner implements EventSigner { }); @override - Future sign(Nip01Event event) async { + Future sign(Nip01Event event) async { if (Helpers.isNotBlank(privateKey)) { - event.sig = Bip340.sign(event.id, privateKey!); + return event.copyWith(sig: Bip340.sign(event.id, privateKey!)); } + throw Exception('Private key is required for signing'); } @override diff --git a/packages/ndk/lib/data_layer/repositories/signers/nip46_event_signer.dart b/packages/ndk/lib/data_layer/repositories/signers/nip46_event_signer.dart index d3ffd163f..6de7ab83a 100644 --- a/packages/ndk/lib/data_layer/repositories/signers/nip46_event_signer.dart +++ b/packages/ndk/lib/data_layer/repositories/signers/nip46_event_signer.dart @@ -96,6 +96,7 @@ class Nip46EventSigner implements EventSigner { ); final requestEvent = Nip01Event( + createdAt: 0, pubKey: localEventSigner.publicKey, kind: BunkerRequest.kKind, tags: [ @@ -104,9 +105,9 @@ class Nip46EventSigner implements EventSigner { content: encryptedRequest!, ); - await localEventSigner.sign(requestEvent); + final signedEvent = await localEventSigner.sign(requestEvent); final broadcastRes = broadcast.broadcast( - nostrEvent: requestEvent, + nostrEvent: signedEvent, specificRelays: connection.relays, ); await broadcastRes.broadcastDoneFuture; @@ -186,7 +187,7 @@ class Nip46EventSigner implements EventSigner { } @override - Future sign(Nip01Event event) async { + Future sign(Nip01Event event) async { final eventMap = { "kind": event.kind, "content": event.content, @@ -202,8 +203,7 @@ class Nip46EventSigner implements EventSigner { final signedEventJson = await remoteRequest(request: request); final signedEvent = jsonDecode(signedEventJson); - event.id = signedEvent["id"]; - event.sig = signedEvent["sig"]; + return event.copyWith(id: signedEvent["id"], sig: signedEvent["sig"]); } Future ping() async { diff --git a/packages/ndk/lib/data_layer/repositories/verifiers/bip340_event_verifier.dart b/packages/ndk/lib/data_layer/repositories/verifiers/bip340_event_verifier.dart index eb43da84a..773589d6f 100644 --- a/packages/ndk/lib/data_layer/repositories/verifiers/bip340_event_verifier.dart +++ b/packages/ndk/lib/data_layer/repositories/verifiers/bip340_event_verifier.dart @@ -8,6 +8,9 @@ import '../../../domain_layer/repositories/event_verifier.dart'; class Bip340EventVerifier implements EventVerifier { @override Future verify(Nip01Event event) async { - return bip340.verify(event.pubKey, event.id, event.sig); + if (event.sig == null) { + return false; + } + return bip340.verify(event.pubKey, event.id, event.sig!); } } diff --git a/packages/ndk/lib/domain_layer/entities/contact_list.dart b/packages/ndk/lib/domain_layer/entities/contact_list.dart index 3f11ba2d6..67d6cf037 100644 --- a/packages/ndk/lib/domain_layer/entities/contact_list.dart +++ b/packages/ndk/lib/domain_layer/entities/contact_list.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import '../../shared/logger/logger.dart'; import '../../shared/nips/nip01/helpers.dart'; +import 'nip_01_utils.dart'; import 'nip_01_event.dart'; import 'read_write_marker.dart'; diff --git a/packages/ndk/lib/domain_layer/entities/metadata.dart b/packages/ndk/lib/domain_layer/entities/metadata.dart index e2fde2fdb..fd2f956f0 100644 --- a/packages/ndk/lib/domain_layer/entities/metadata.dart +++ b/packages/ndk/lib/domain_layer/entities/metadata.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import '../../shared/nips/nip01/helpers.dart'; +import 'nip_01_utils.dart'; import 'nip_01_event.dart'; /// basic nostr metadata diff --git a/packages/ndk/lib/domain_layer/entities/nip_01_event.dart b/packages/ndk/lib/domain_layer/entities/nip_01_event.dart index 15a97bc09..ce07f2b04 100644 --- a/packages/ndk/lib/domain_layer/entities/nip_01_event.dart +++ b/packages/ndk/lib/domain_layer/entities/nip_01_event.dart @@ -1,124 +1,89 @@ -import 'dart:convert'; - -import 'package:crypto/crypto.dart'; - -import '../../shared/helpers/list_casting.dart'; -import '../../shared/nips/nip01/bip340.dart'; -import '../../shared/nips/nip13/nip13.dart'; -import '../../shared/nips/nip19/nip19.dart'; +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; /// basic nostr nip01 event data structure class Nip01Event { static const int kTextNodeKind = 1; - /// Creates a new Nostr event. - /// - /// [pubKey] is the author's public key. - /// [kind] is the event kind. - /// [tags] is a JSON object of event tags. - /// [content] is an arbitrary string. - /// - /// Nostr event `id` and `created_at` fields are calculated automatically. - /// - Nip01Event({ - required this.pubKey, - required this.kind, - required this.tags, - required this.content, - int createdAt = 0, - }) { - this.createdAt = (createdAt == 0) - ? DateTime.now().millisecondsSinceEpoch ~/ 1000 - : createdAt; - id = _calculateId(pubKey, this.createdAt, kind, tags, content); - } - - Nip01Event._( - this.id, - this.pubKey, - this.createdAt, - this.kind, - this.tags, - this.content, - this.sig, - ); - - factory Nip01Event.fromJson(Map data) { - final id = data['id'] as String? ?? ''; - final pubKey = data['pubkey'] as String? ?? ''; - final createdAt = data['created_at'] as int; - final kind = data['kind'] as int; - final tags = castToListOfListOfString(data['tags']); - final content = data['content'] as String? ?? ''; - - /// '' to support rumor events - final sig = (data['sig'] as String?) ?? ''; - - return Nip01Event._(id, pubKey, createdAt, kind, tags, content, sig); - } - /// The event ID is a 32-byte SHA256 hash of the serialised event data. - String id = ''; + late final String id; /// The event author's public key. final String pubKey; /// Event creation timestamp in Unix time. - late int createdAt; + late final int createdAt; /// Event kind identifier (e.g. text_note, set_metadata, etc). final int kind; /// A JSON array of event tags. - List> tags; // Modified by proof-of-work + final List> tags; // Modified by proof-of-work /// Event content. - String content; + final String content; /// 64-byte Schnorr signature of [Nip01Event.id]. - String sig = ''; + final String? sig; /// has signature been validated? - bool? validSig; + final bool? validSig; /// Relay that an event was received from - List sources = []; - - /// Returns the Event object as a JSON object - Map toJson() { - return { - 'id': id, - 'pubkey': pubKey, - 'created_at': createdAt, - 'kind': kind, - 'tags': tags, - 'content': content, - 'sig': sig - }; - } + final List sources; - /// Returns the Event object as a base64-encoded JSON string - String toBase64() { - return base64Encode(utf8.encode(json.encode(toJson()))); - } - - /// sign the event with given privateKey - /// [WARN] only for testing! Use [EventSigner] to sign events in production - void sign(String privateKey) { - sig = Bip340.sign(id, privateKey); + /// Creates a new Nostr event. + /// + /// [pubKey] is the author's public key. + /// [kind] is the event kind. + /// [tags] is a JSON object of event tags. + /// [content] is an arbitrary string. + /// + /// Nostr event `id` and `created_at` fields are calculated automatically. + /// + Nip01Event({ + String? id, + required this.pubKey, + required this.kind, + required this.tags, + required this.content, + this.sig, + this.validSig, + this.sources = const [], + int createdAt = 0, + }) { + this.createdAt = (createdAt == 0) + ? DateTime.now().millisecondsSinceEpoch ~/ 1000 + : createdAt; + this.id = id ?? Nip01Utils.calculateEventIdSync( + pubKey: pubKey, + createdAt: createdAt, + kind: kind, + tags: tags, + content: content + ); } - /// is Id valid? - bool get isIdValid { - // Validate event data - if (id != _calculateId(pubKey, createdAt, kind, tags, content)) { - return false; - } - // Validate proof of work if present - if (!Nip13.validateEvent(this)) { - return false; - } - return true; + Nip01Event copyWith({ + String? id, + String? pubKey, + int? createdAt, + int? kind, + List>? tags, + String? content, + String? sig, + bool? validSig, + List? sources, + }) { + return Nip01Event( + id: id ?? this.id, + pubKey: pubKey ?? this.pubKey, + createdAt: createdAt ?? this.createdAt, + kind: kind ?? this.kind, + tags: tags ?? this.tags, + content: content ?? this.content, + sig: sig ?? this.sig, + validSig: validSig ?? this.validSig, + sources: sources ?? this.sources); } // Individual events with the same "id" are equivalent @@ -133,15 +98,6 @@ class Nip01Event { return secondsSinceEpoch; } - static String _calculateId(String publicKey, int createdAt, int kind, - List tags, String content) { - final jsonData = - json.encode([0, publicKey, createdAt, kind, tags, content]); - final bytes = utf8.encode(jsonData); - final digest = sha256.convert(bytes); - return digest.toString(); - } - /// return first `e` tag found String? getEId() { return getFirstTag("e"); @@ -214,112 +170,4 @@ class Nip01Event { String toString() { return 'Nip01Event{pubKey: $pubKey, createdAt: $createdAt, kind: $kind, tags: $tags, content: $content, sources: $sources}'; } - - Nip01Event copyWith({ - String? pubKey, - int? createdAt, - int? kind, - List>? tags, - String? content, - String? sig, - List? sources, - }) { - final event = Nip01Event( - pubKey: pubKey ?? this.pubKey, - createdAt: createdAt ?? this.createdAt, - kind: kind ?? this.kind, - tags: tags ?? this.tags, - content: content ?? this.content, - ); - event.sig = sig ?? this.sig; - event.sources = sources ?? this.sources; - return event; - } - - /// Mine this event with proof of work - Nip01Event minePoW(int targetDifficulty, {int? maxIterations}) { - return Nip13.mineEvent(this, targetDifficulty, - maxIterations: maxIterations); - } - - /// Get the proof of work difficulty of this event - int get powDifficulty => Nip13.getDifficulty(id); - - /// Check if this event meets a specific difficulty target - bool checkPoWDifficulty(int targetDifficulty) { - return Nip13.checkDifficulty(id, targetDifficulty); - } - - /// Get the target difficulty from nonce tag if present - int? get targetPoWDifficulty => Nip13.getTargetDifficultyFromEvent(this); - - /// Calculate the commitment (work done) for this event - int get powCommitment => Nip13.calculateCommitment(id); - - /// Encode this event as a nevent (NIP-19 event reference) - /// - /// Returns a bech32-encoded nevent string that includes: - /// - Event ID (required) - /// - Author pubkey (included) - /// - Kind (included) - /// - Relay hints from event.sources (if available) - /// - /// Usage: `final nevent = event.nevent;` - String get nevent { - return Nip19.encodeNevent( - eventId: id, - author: pubKey, - kind: kind, - relays: sources.isEmpty ? null : sources, - ); - } - - /// Encode this event as an naddr (NIP-19 addressable event coordinate) - /// - /// Only works for addressable/replaceable events (kind >= 10000 or kind 0, 3, 41) - /// Requires a "d" tag to identify the event. - /// - /// Returns a bech32-encoded naddr string or null if: - /// - Event is not addressable/replaceable - /// - Event doesn't have a "d" tag - /// - /// Usage: `final naddr = event.naddr;` - String? get naddr { - // Check if this is an addressable event - if (!_isAddressableKind(kind)) { - return null; - } - - // Get the "d" tag identifier - final identifier = getDtag(); - if (identifier == null) { - return null; - } - - return Nip19.encodeNaddr( - identifier: identifier, - pubkey: pubKey, - kind: kind, - relays: sources.isEmpty ? null : sources, - ); - } - - /// Check if an event kind is addressable/replaceable - /// - /// According to NIP-01: - /// - Replaceable events: 0, 3, 41 - /// - Parameterized replaceable events: 10000-19999, 30000-39999 - bool _isAddressableKind(int kind) { - // Replaceable events - if (kind == 0 || kind == 3 || kind == 41) { - return true; - } - - // Parameterized replaceable events - if ((kind >= 10000 && kind <= 19999) || (kind >= 30000 && kind <= 39999)) { - return true; - } - - return false; - } } diff --git a/packages/ndk/lib/domain_layer/entities/nip_01_event_raw.dart b/packages/ndk/lib/domain_layer/entities/nip_01_event_raw.dart deleted file mode 100644 index a222d79bb..000000000 --- a/packages/ndk/lib/domain_layer/entities/nip_01_event_raw.dart +++ /dev/null @@ -1,50 +0,0 @@ -enum NostrMessageRawType { - notice, - event, - eose, - ok, - closed, - auth, - unknown, -} - -//? needed until Nip01Event is refactored to be immutable -class Nip01EventRaw { - final String id; - - final String pubKey; - - final int createdAt; - - final int kind; - - final List> tags; - - final String content; - - final String sig; - - Nip01EventRaw({ - required this.id, - required this.pubKey, - required this.createdAt, - required this.kind, - required this.tags, - required this.content, - required this.sig, - }); -} - -class NostrMessageRaw { - final NostrMessageRawType type; - final Nip01EventRaw? nip01Event; - final String? requestId; - final dynamic otherData; - - NostrMessageRaw({ - required this.type, - this.nip01Event, - this.requestId, - this.otherData, - }); -} diff --git a/packages/ndk/lib/domain_layer/entities/nip_01_utils.dart b/packages/ndk/lib/domain_layer/entities/nip_01_utils.dart new file mode 100644 index 000000000..b46e12b17 --- /dev/null +++ b/packages/ndk/lib/domain_layer/entities/nip_01_utils.dart @@ -0,0 +1,138 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; + +import '../../data_layer/models/nip_01_event_model.dart'; +import '../../shared/isolates/isolate_manager.dart'; +import '../../shared/nips/nip01/bip340.dart'; +import '../../shared/nips/nip13/nip13.dart'; +import 'nip_01_event.dart'; + +class Nip01Utils { + /// create event and calculate id \ + /// [returns] event with calculated id + static Nip01Event createEventCalculateId({ + required String pubKey, + required int kind, + required List> tags, + required String content, + int createdAt = 0, + }) { + final id = calculateEventIdSync( + pubKey: pubKey, + createdAt: createdAt, + kind: kind, + tags: tags, + content: content, + ); + + final event = Nip01Event( + id: id, + pubKey: pubKey, + kind: kind, + tags: tags, + content: content, + createdAt: createdAt, + sources: [], + ); + + return event; + } + + /// checks if event id is valid, including proof of work if present + static bool isIdValid(Nip01Event event) { + // Validate event data + if (event.id != + calculateEventIdSync( + pubKey: event.pubKey, + createdAt: event.createdAt, + kind: event.kind, + tags: event.tags, + content: event.content, + )) { + return false; + } + // Validate proof of work if present + if (!Nip13.validateEvent(event)) { + return false; + } + return true; + } + + /// Calculate event id synchronously + static String calculateEventIdSync({ + required String pubKey, + required int createdAt, + required int kind, + required List tags, + required String content, + }) { + return calculateId( + Nip01Event( + id: '', + pubKey: pubKey, + createdAt: createdAt, + kind: kind, + tags: + List>.from(tags.map((tag) => List.from(tag))), + content: content, + sig: null, + validSig: null, + sources: [], + ), + ); + } + + /// Calculate event id asynchronously running in an isolate + static Future calculateEventId({ + required String publicKey, + required int createdAt, + required int kind, + required List tags, + required String content, + }) async { + final id = + await IsolateManager.instance.runInComputeIsolate( + calculateId, + Nip01Event( + pubKey: publicKey, + createdAt: createdAt, + kind: kind, + tags: + List>.from(tags.map((tag) => List.from(tag))), + content: content, + ), + ); + return id; + } + + static String calculateId(Nip01Event event) { + final model = Nip01EventModel.fromEntity(event); + final jsonData = json.encode([ + 0, + model.pubKey, + model.createdAt, + model.kind, + model.tags, + model.content + ]); + final bytes = utf8.encode(jsonData); + final digest = sha256.convert(bytes); + return digest.toString(); + } + + /// this method signs the event with the provided private key + /// mainly used for testing purposes, please use the account usecase to sign events! + static Nip01Event signWithPrivateKey({ + required Nip01Event event, + required String privateKey, + }) { + String? id = event.id; + id ??= calculateId(event); + final signature = Bip340.sign( + id, + privateKey, + ); + return event.copyWith(sig: signature, validSig: true); + } +} diff --git a/packages/ndk/lib/domain_layer/entities/nip_51_list.dart b/packages/ndk/lib/domain_layer/entities/nip_51_list.dart index 5b321bb2f..8eeadb5ef 100644 --- a/packages/ndk/lib/domain_layer/entities/nip_51_list.dart +++ b/packages/ndk/lib/domain_layer/entities/nip_51_list.dart @@ -76,7 +76,7 @@ class Nip51List { kA ]; - late String id; + late String? id; late String pubKey; late int kind; diff --git a/packages/ndk/lib/domain_layer/entities/nostr_message_raw.dart b/packages/ndk/lib/domain_layer/entities/nostr_message_raw.dart new file mode 100644 index 000000000..06b00606a --- /dev/null +++ b/packages/ndk/lib/domain_layer/entities/nostr_message_raw.dart @@ -0,0 +1,25 @@ +import 'package:ndk/ndk.dart'; + +enum NostrMessageRawType { + notice, + event, + eose, + ok, + closed, + auth, + unknown, +} + +class NostrMessageRaw { + final NostrMessageRawType type; + final Nip01Event? nip01Event; + final String? requestId; + final dynamic otherData; + + NostrMessageRaw({ + required this.type, + this.nip01Event, + this.requestId, + this.otherData, + }); +} diff --git a/packages/ndk/lib/domain_layer/repositories/event_signer.dart b/packages/ndk/lib/domain_layer/repositories/event_signer.dart index e3540da2c..ed65789f5 100644 --- a/packages/ndk/lib/domain_layer/repositories/event_signer.dart +++ b/packages/ndk/lib/domain_layer/repositories/event_signer.dart @@ -1,7 +1,8 @@ import '../entities/nip_01_event.dart'; abstract class EventSigner { - Future sign(Nip01Event event); + /// Signs the given event and returns the signed event + Future sign(Nip01Event event); String getPublicKey(); diff --git a/packages/ndk/lib/domain_layer/usecases/accounts/accounts.dart b/packages/ndk/lib/domain_layer/usecases/accounts/accounts.dart index 2b732c26a..6fa32d210 100644 --- a/packages/ndk/lib/domain_layer/usecases/accounts/accounts.dart +++ b/packages/ndk/lib/domain_layer/usecases/accounts/accounts.dart @@ -155,7 +155,7 @@ class Accounts { } /// low-level method, should not be used directly in most cases, use broadcast instead which calls signing on the signer - Future sign(Nip01Event event) async { + Future sign(Nip01Event event) async { Account? account = getLoggedAccount(); if (account != null && account.signer.canSign()) { return account.signer.sign(event); diff --git a/packages/ndk/lib/domain_layer/usecases/broadcast/broadcast.dart b/packages/ndk/lib/domain_layer/usecases/broadcast/broadcast.dart index cc157c8db..85b8826b9 100644 --- a/packages/ndk/lib/domain_layer/usecases/broadcast/broadcast.dart +++ b/packages/ndk/lib/domain_layer/usecases/broadcast/broadcast.dart @@ -65,8 +65,9 @@ class Broadcast { // register broadcast state _globalState.inFlightBroadcasts[nostrEvent.id] = broadcastState; - final signer = - nostrEvent.sig == '' ? _checkSinger(customSigner: customSigner) : null; + final signer = nostrEvent.sig == null + ? _checkSinger(customSigner: customSigner) + : null; final cleanedSpecificRelays = specificRelays != null ? cleanRelayUrls(specificRelays.toList()) : null; diff --git a/packages/ndk/lib/domain_layer/usecases/bunkers/bunkers.dart b/packages/ndk/lib/domain_layer/usecases/bunkers/bunkers.dart index 9448b2631..8f9039d83 100644 --- a/packages/ndk/lib/domain_layer/usecases/bunkers/bunkers.dart +++ b/packages/ndk/lib/domain_layer/usecases/bunkers/bunkers.dart @@ -5,6 +5,7 @@ import 'package:ndk/domain_layer/usecases/bunkers/models/nostr_connect.dart'; import '../../../data_layer/repositories/signers/bip340_event_signer.dart'; import '../../../data_layer/repositories/signers/nip46_event_signer.dart'; +import '../../entities/nip_01_utils.dart'; import 'models/bunker_request.dart'; import 'models/bunker_connection.dart'; import '../../../shared/nips/nip01/bip340.dart'; @@ -74,9 +75,9 @@ class Bunkers { content: encryptedRequest!, ); - await localEventSigner.sign(requestEvent); + final signedEvent = await localEventSigner.sign(requestEvent); final broadcastRes = - _broadcast.broadcast(nostrEvent: requestEvent, specificRelays: relays); + _broadcast.broadcast(nostrEvent: signedEvent, specificRelays: relays); await broadcastRes.broadcastDoneFuture; final subscription = _requests.subscription( diff --git a/packages/ndk/lib/domain_layer/usecases/files/blossom.dart b/packages/ndk/lib/domain_layer/usecases/files/blossom.dart index 809c12caf..0d2cd3973 100644 --- a/packages/ndk/lib/domain_layer/usecases/files/blossom.dart +++ b/packages/ndk/lib/domain_layer/usecases/files/blossom.dart @@ -7,6 +7,7 @@ import '../../entities/nip_01_event.dart'; import '../../repositories/blossom.dart'; import '../../repositories/event_signer.dart'; import '../accounts/accounts.dart'; +import '../../entities/nip_01_utils.dart'; import 'blossom_user_server_list.dart'; /// direct access usecase to blossom \ @@ -75,7 +76,7 @@ class Blossom { ], ); - await _signer.sign(myAuthorization); + final signedAuthorization = await _signer.sign(myAuthorization); serverUrls ??= await _userServerList .getUserServerList(pubkeys: [_signer.getPublicKey()]); @@ -87,7 +88,7 @@ class Blossom { return _blossomImpl.uploadBlob( data: data, serverUrls: serverUrls, - authorization: myAuthorization, + authorization: signedAuthorization, contentType: contentType, strategy: strategy, mediaOptimisation: serverMediaOptimisation, @@ -104,6 +105,7 @@ class Blossom { String? pubkeyToFetchUserServerList, }) async { Nip01Event? myAuthorization; + Nip01Event? signedAuthorization; if (useAuth) { _checkSigner(); @@ -121,7 +123,7 @@ class Blossom { ], ); - await _signer.sign(myAuthorization); + signedAuthorization = await _signer.sign(myAuthorization); } if (serverUrls == null) { @@ -140,7 +142,7 @@ class Blossom { return _blossomImpl.getBlob( sha256: sha256, - authorization: myAuthorization, + authorization: signedAuthorization, serverUrls: serverUrls, ); } @@ -158,6 +160,7 @@ class Blossom { String? pubkeyToFetchUserServerList, }) async { Nip01Event? myAuthorization; + Nip01Event? signedAuthorization; if (useAuth) { _checkSigner(); @@ -175,7 +178,7 @@ class Blossom { ], ); - await _signer.sign(myAuthorization); + signedAuthorization = await _signer.sign(myAuthorization); } if (serverUrls == null) { @@ -194,7 +197,7 @@ class Blossom { return _blossomImpl.checkBlob( sha256: sha256, - authorization: myAuthorization, + authorization: signedAuthorization, serverUrls: serverUrls, ); } @@ -210,6 +213,7 @@ class Blossom { int chunkSize = 1024 * 1024, // 1MB chunks, }) async { Nip01Event? myAuthorization; + Nip01Event? signedAuthorization; if (useAuth) { _checkSigner(); @@ -227,7 +231,7 @@ class Blossom { ], ); - await _signer.sign(myAuthorization); + signedAuthorization = await _signer.sign(myAuthorization); } if (serverUrls == null) { @@ -245,7 +249,7 @@ class Blossom { return _blossomImpl.getBlobStream( sha256: sha256, - authorization: myAuthorization, + authorization: signedAuthorization, serverUrls: serverUrls, chunkSize: chunkSize, ); @@ -263,6 +267,7 @@ class Blossom { DateTime? until, }) async { Nip01Event? myAuthorization; + Nip01Event? signedAuthorization; if (useAuth) { _checkSigner(); @@ -279,7 +284,7 @@ class Blossom { ], ); - await _signer.sign(myAuthorization); + signedAuthorization = await _signer.sign(myAuthorization); } /// fetch user server list from nostr @@ -294,7 +299,7 @@ class Blossom { since: since, until: until, serverUrls: serverUrls, - authorization: myAuthorization, + authorization: signedAuthorization, ); } @@ -322,7 +327,7 @@ class Blossom { ], ); - await _signer.sign(myAuthorization); + final signedAuthorization = await _signer.sign(myAuthorization); /// fetch user server list from nostr serverUrls ??= await _userServerList @@ -333,7 +338,7 @@ class Blossom { } return _blossomImpl.deleteBlob( sha256: sha256, - authorization: myAuthorization, + authorization: signedAuthorization, serverUrls: serverUrls, ); } @@ -376,11 +381,11 @@ class Blossom { ], ); - await _signer.sign(reportEvent); + final signedReport = await _signer.sign(reportEvent); return _blossomImpl.report( sha256: sha256, - reportEvent: reportEvent, + reportEvent: signedReport, serverUrl: serverUrl, ); } diff --git a/packages/ndk/lib/domain_layer/usecases/files/blossom_user_server_list.dart b/packages/ndk/lib/domain_layer/usecases/files/blossom_user_server_list.dart index ff2b99c6a..9c340dbd2 100644 --- a/packages/ndk/lib/domain_layer/usecases/files/blossom_user_server_list.dart +++ b/packages/ndk/lib/domain_layer/usecases/files/blossom_user_server_list.dart @@ -3,6 +3,7 @@ import '../../entities/filter.dart'; import '../../entities/nip_01_event.dart'; import '../accounts/accounts.dart'; import '../broadcast/broadcast.dart'; +import '../../entities/nip_01_utils.dart'; import '../requests/requests.dart'; import 'blossom.dart'; diff --git a/packages/ndk/lib/domain_layer/usecases/gift_wrap/gift_wrap.dart b/packages/ndk/lib/domain_layer/usecases/gift_wrap/gift_wrap.dart index aab2e2b5b..f89e4fa5f 100644 --- a/packages/ndk/lib/domain_layer/usecases/gift_wrap/gift_wrap.dart +++ b/packages/ndk/lib/domain_layer/usecases/gift_wrap/gift_wrap.dart @@ -1,9 +1,11 @@ import 'dart:convert'; +import '../../../data_layer/models/nip_01_event_model.dart'; import '../../../data_layer/repositories/signers/bip340_event_signer.dart'; import '../../entities/nip_01_event.dart'; import '../accounts/accounts.dart'; import '../../../shared/nips/nip01/bip340.dart'; +import '../../entities/nip_01_utils.dart'; class GiftWrap { static const int kSealEventKind = 13; @@ -84,7 +86,7 @@ class GiftWrap { } final encryptedContent = await account.signer.encryptNip44( - plaintext: jsonEncode(rumor), + plaintext: Nip01EventModel.fromEntity(rumor).toJsonString(), recipientPubKey: recipientPubkey, ); @@ -121,7 +123,7 @@ class GiftWrap { // Parse the rumor event final Map rumorJson = jsonDecode(decryptedRumorJson); - final rumor = Nip01Event.fromJson(rumorJson); + final rumor = Nip01EventModel.fromJson(rumorJson); return rumor; } @@ -143,7 +145,7 @@ class GiftWrap { ); final encryptedSeal = await ephemeralSigner.encryptNip44( - plaintext: jsonEncode(sealEvent), + plaintext: Nip01EventModel.fromEntity(sealEvent).toJsonString(), recipientPubKey: recipientPublicKey, ); @@ -174,9 +176,9 @@ class GiftWrap { // Sign with ephemeral key final signature = Bip340.sign(giftWrapEvent.id, ephemeralKeys.privateKey!); - giftWrapEvent.sig = signature; + final gWEventSigned = giftWrapEvent.copyWith(sig: signature); - return giftWrapEvent; + return gWEventSigned; } Future unwrapEvent({ @@ -198,7 +200,7 @@ class GiftWrap { // Parse the seal event final Map sealJson = jsonDecode(decryptedEventJson); - final event = Nip01Event.fromJson(sealJson); + final event = Nip01EventModel.fromJson(sealJson); return event; } } diff --git a/packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart b/packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart index 7cf209e71..e82728f07 100644 --- a/packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart +++ b/packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart @@ -159,8 +159,11 @@ class JitEngine with Logger implements NetworkEngine { Future asyncStuff() async { await relayManagerLight.seedRelaysConnected; + final Nip01Event workingNostrEvent; if (signer != null) { - await signer.sign(nostrEvent); + workingNostrEvent = await signer.sign(nostrEvent); + } else { + workingNostrEvent = nostrEvent; } if (specificRelays != null) { @@ -168,7 +171,7 @@ class JitEngine with Logger implements NetworkEngine { return RelayJitBroadcastSpecificRelaysStrategy.broadcast( specificRelays: cleanedSpecificRelays, relayManager: relayManagerLight, - eventToPublish: nostrEvent, + eventToPublish: workingNostrEvent, connectedRelays: relayManagerLight.connectedRelays .whereType>() .toList(), @@ -177,7 +180,7 @@ class JitEngine with Logger implements NetworkEngine { // default publish to own outbox RelayJitBroadcastOutboxStrategy.broadcast( - eventToPublish: nostrEvent, + eventToPublish: workingNostrEvent, connectedRelays: relayManagerLight.connectedRelays .whereType>() .toList(), @@ -187,15 +190,16 @@ class JitEngine with Logger implements NetworkEngine { ); // check if we need to publish to others inboxes - if (nostrEvent.pTags.isNotEmpty && nostrEvent.kind != ContactList.kKind) { + if (workingNostrEvent.pTags.isNotEmpty && + workingNostrEvent.kind != ContactList.kKind) { RelayJitBroadcastOtherReadStrategy.broadcast( - eventToPublish: nostrEvent, + eventToPublish: workingNostrEvent, connectedRelays: relayManagerLight.connectedRelays .whereType>() .toList(), cacheManager: cache, relayManager: relayManagerLight, - pubkeysOfInbox: nostrEvent.pTags, + pubkeysOfInbox: workingNostrEvent.pTags, ); } } diff --git a/packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart b/packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart index e40263d38..8110c468f 100644 --- a/packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart +++ b/packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart @@ -74,9 +74,11 @@ class Lnurl { // ZAP ? if (lnurlResponse.doesAllowsNostr && zapRequest != null && - zapRequest.sig.isNotEmpty) { - Logger.log.d(jsonEncode(zapRequest)); - var eventStr = Uri.encodeQueryComponent(jsonEncode(zapRequest)); + zapRequest.sig != null && + zapRequest.sig!.isNotEmpty) { + final zapRequstString = + Nip01EventModel.fromEntity(zapRequest).toJsonString(); + final eventStr = Uri.encodeQueryComponent(zapRequstString); callback += "&nostr=$eventStr"; } diff --git a/packages/ndk/lib/domain_layer/usecases/metadatas/metadatas.dart b/packages/ndk/lib/domain_layer/usecases/metadatas/metadatas.dart index 6558b554f..a99133d25 100644 --- a/packages/ndk/lib/domain_layer/usecases/metadatas/metadatas.dart +++ b/packages/ndk/lib/domain_layer/usecases/metadatas/metadatas.dart @@ -11,6 +11,7 @@ import '../../repositories/cache_manager.dart'; import '../../repositories/event_signer.dart'; import '../accounts/accounts.dart'; import '../broadcast/broadcast.dart'; +import '../../entities/nip_01_utils.dart'; import '../requests/requests.dart'; /// nostr metadata usecase diff --git a/packages/ndk/lib/domain_layer/usecases/nip42/auth_event.dart b/packages/ndk/lib/domain_layer/usecases/nip42/auth_event.dart index 527969493..4e3ca523f 100644 --- a/packages/ndk/lib/domain_layer/usecases/nip42/auth_event.dart +++ b/packages/ndk/lib/domain_layer/usecases/nip42/auth_event.dart @@ -1,5 +1,7 @@ import 'package:ndk/domain_layer/entities/nip_01_event.dart'; +import '../../entities/nip_01_utils.dart'; + /// auth event to send to relays class AuthEvent extends Nip01Event { /// auth kind @@ -7,8 +9,33 @@ class AuthEvent extends Nip01Event { static const int KIND = 22242; /// Zap Request - AuthEvent({ + AuthEvent._({ required super.pubKey, required super.tags, - }) : super(kind: KIND, content: ''); + required super.id, + }) : super( + kind: KIND, + content: '', + sig: null, + validSig: null, + ); + + factory AuthEvent({ + required String pubKey, + required List> tags, + }) { + final calculatedId = Nip01Utils.calculateEventIdSync( + pubKey: pubKey, + createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, + kind: KIND, + tags: tags, + content: '', + ); + + return AuthEvent._( + pubKey: pubKey, + tags: tags, + id: calculatedId, + ); + } } diff --git a/packages/ndk/lib/domain_layer/usecases/proof_of_work/proof_of_work.dart b/packages/ndk/lib/domain_layer/usecases/proof_of_work/proof_of_work.dart new file mode 100644 index 000000000..d6dc5724b --- /dev/null +++ b/packages/ndk/lib/domain_layer/usecases/proof_of_work/proof_of_work.dart @@ -0,0 +1,39 @@ +import '../../../shared/nips/nip13/nip13.dart'; +import '../../entities/nip_01_event.dart'; + +class ProofOfWork { + /// Mine this event with proof of work + Future minePoW({ + required Nip01Event event, + required int targetDifficulty, + int? maxIterations, + }) { + return Nip13.mineEvent(event, targetDifficulty, + maxIterations: maxIterations); + } + + /// Get the proof of work difficulty of this event + + int getPoWDifficulty(Nip01Event event) { + return Nip13.getDifficulty(event.id); + } + + /// Check if this event meets a specific difficulty target + bool checkPoWDifficulty({ + required Nip01Event event, + required int targetDifficulty, + }) { + return Nip13.checkDifficulty(event.id, targetDifficulty); + } + + /// Get the target difficulty from nonce tag if present + + int? getTargetDifficultyFromEvent(Nip01Event event) { + return Nip13.getTargetDifficultyFromEvent(event); + } + + /// Calculate the commitment (work done) for this event + int powCommitment(Nip01Event event) { + return Nip13.calculateCommitment(event.id); + } +} diff --git a/packages/ndk/lib/domain_layer/usecases/relay_manager.dart b/packages/ndk/lib/domain_layer/usecases/relay_manager.dart index 73accd62c..33254ec70 100644 --- a/packages/ndk/lib/domain_layer/usecases/relay_manager.dart +++ b/packages/ndk/lib/domain_layer/usecases/relay_manager.dart @@ -16,7 +16,7 @@ import '../entities/connection_source.dart'; import '../entities/filter.dart'; import '../entities/global_state.dart'; import '../entities/nip_01_event.dart'; -import '../entities/nip_01_event_raw.dart'; +import '../entities/nostr_message_raw.dart'; import '../entities/relay.dart'; import '../entities/relay_connectivity.dart'; import '../entities/relay_info.dart'; @@ -391,15 +391,39 @@ class RelayManager { }); } + // tracking to process in order + Completer? _lastMessageCompleter; + Future _handleIncomingMessage( dynamic message, RelayConnectivity relayConnectivity) async { - /// decode in isolate - final nostrMsg = await IsolateManager.instance - .runInEncodingIsolate( - decodeNostrMsg, - message, - ); + final previousMessage = _lastMessageCompleter; + + final myCompleter = Completer(); + _lastMessageCompleter = myCompleter; + + NostrMessageRaw nostrMsg; + try { + nostrMsg = await IsolateManager.instance + .runInEncodingIsolate( + decodeNostrMsg, + message, + ); + } catch (e) { + // Isolates not available on web + nostrMsg = decodeNostrMsg(message); + } + + if (previousMessage != null) { + await previousMessage.future; + } + + myCompleter.complete(); + _processDecodedMessage(nostrMsg, relayConnectivity, message); + } + + void _processDecodedMessage(NostrMessageRaw nostrMsg, + RelayConnectivity relayConnectivity, dynamic message) { if (nostrMsg.type == NostrMessageRawType.unknown) { Logger.log.w( "Received non NostrMessageRaw message from ${relayConnectivity.url}: $nostrMsg"); @@ -458,8 +482,9 @@ class RelayManager { ["relay", relayConnectivity.url], ["challenge", challenge] ]); - _accounts.sign(auth).then((e) { - send(relayConnectivity, ClientMsg(ClientMsgType.kAuth, event: auth)); + _accounts.sign(auth).then((signedEvent) { + send(relayConnectivity, + ClientMsg(ClientMsgType.kAuth, event: signedEvent)); }); } else { Logger.log @@ -478,7 +503,7 @@ class RelayManager { void _handleIncomingEvent(NostrMessageRaw nostrMsgRaw, RelayConnectivity connectivity, int messageSize) { final requestId = nostrMsgRaw.requestId!; - final eventRaw = nostrMsgRaw.nip01Event!; + final event = nostrMsgRaw.nip01Event!; if (globalState.inFlightRequests[requestId] == null) { Logger.log.w( @@ -486,15 +511,6 @@ class RelayManager { return; } - Nip01Event event = Nip01Event( - pubKey: eventRaw.pubKey, - createdAt: eventRaw.createdAt, - kind: eventRaw.kind, - tags: eventRaw.tags, - content: eventRaw.content, - ); - event.sig = eventRaw.sig; - event.id = eventRaw.id; connectivity.stats.incStatsByNewEvent(event, messageSize); RequestState? state = globalState.inFlightRequests[requestId]; @@ -504,14 +520,16 @@ class RelayManager { Logger.log.w("No RelayRequestState found for id $requestId"); return; } - event.sources.add(connectivity.url); + + final eventWithSources = + event.copyWith(sources: [...event.sources, connectivity.url]); if (state.networkController.isClosed) { // this might happen because relays even after we send a CLOSE subscription.id, they'll still send more events Logger.log.t( "tried to add event to an already closed STREAM ${state.request.id} ${state.request.filters}"); } else { - state.networkController.add(event); + state.networkController.add(eventWithSources); } } } diff --git a/packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart b/packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart index f8dfc1b88..3808d838b 100644 --- a/packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart +++ b/packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart @@ -241,17 +241,21 @@ class RelaySetsEngine implements NetworkEngine { Iterable? specificRelays, }) { Future asyncStuff() async { - // ===================================================================================== - // specific relays - // ===================================================================================== + final Nip01Event workingEvent; + if (signer != null) { - await signer.sign(nostrEvent); + workingEvent = await signer.sign(nostrEvent); + } else { + workingEvent = nostrEvent; } + // ===================================================================================== + // specific relays + // ===================================================================================== if (specificRelays != null) { for (final relayUrl in specificRelays) { // broadcast async - doRelayBroadcast(relayUrl, nostrEvent); + doRelayBroadcast(relayUrl, workingEvent); } return; } @@ -260,7 +264,7 @@ class RelaySetsEngine implements NetworkEngine { // ===================================================================================== // TODO should not only depend on cached, but go fetch it if not present in cache final nip65List = (await UserRelayLists.getUserRelayListCacheLatest( - pubkeys: [nostrEvent.pubKey], + pubkeys: [workingEvent.pubKey], cacheManager: _cacheManager, )); var writeRelaysUrls = _relayManager.globalState.relays.keys; @@ -295,7 +299,7 @@ class RelaySetsEngine implements NetworkEngine { } _relayManager.registerRelayBroadcast( - eventToPublish: nostrEvent, + eventToPublish: workingEvent, relayUrl: relayUrl, ); @@ -303,16 +307,16 @@ class RelaySetsEngine implements NetworkEngine { relay, ClientMsg( ClientMsgType.kEvent, - event: nostrEvent, + event: workingEvent, )); } // ===================================================================================== // other inbox // ===================================================================================== - if (nostrEvent.pTags.isNotEmpty) { + if (workingEvent.pTags.isNotEmpty) { final nip65Data = await UserRelayLists.getUserRelayListCacheLatest( - pubkeys: nostrEvent.pTags, + pubkeys: workingEvent.pTags, cacheManager: _cacheManager, ); @@ -353,7 +357,7 @@ class RelaySetsEngine implements NetworkEngine { } _relayManager.registerRelayBroadcast( - eventToPublish: nostrEvent, + eventToPublish: workingEvent, relayUrl: relayUrl, ); @@ -361,7 +365,7 @@ class RelaySetsEngine implements NetworkEngine { relay, ClientMsg( ClientMsgType.kEvent, - event: nostrEvent, + event: workingEvent, )); } } diff --git a/packages/ndk/lib/domain_layer/usecases/requests/verify_event_stream.dart b/packages/ndk/lib/domain_layer/usecases/requests/verify_event_stream.dart index 1387a669a..cb7722cc3 100644 --- a/packages/ndk/lib/domain_layer/usecases/requests/verify_event_stream.dart +++ b/packages/ndk/lib/domain_layer/usecases/requests/verify_event_stream.dart @@ -27,13 +27,14 @@ class VerifyEventStream { Future _verifyEvent(Nip01Event data) async { final valid = await eventVerifier.verify(data); - data.validSig = valid; if (!valid) { Logger.log.w('WARNING: Event with id ${data.id} has invalid signature'); return null; } - return data; + final checkedEvent = data.copyWith(validSig: valid); + + return checkedEvent; } } diff --git a/packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart b/packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart index 9b0695b9b..491af9337 100644 --- a/packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart +++ b/packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import '../../../data_layer/models/nip_01_event_model.dart'; import '../../../shared/logger/logger.dart'; import '../../entities/nip_01_event.dart'; import '../lnurl/lnurl.dart'; @@ -56,7 +57,7 @@ class ZapReceipt { } paidAt = event.createdAt; if (zapRequestJson != null && zapRequestJson.isNotEmpty) { - Nip01Event event = Nip01Event.fromJson(jsonDecode(zapRequestJson)); + Nip01Event event = Nip01EventModel.fromJson(jsonDecode(zapRequestJson)); comment = event.content; sender = event.pubKey; lnurl = event.getFirstTag('lnurl'); diff --git a/packages/ndk/lib/domain_layer/usecases/zaps/zap_request.dart b/packages/ndk/lib/domain_layer/usecases/zaps/zap_request.dart index faeeb2d7a..922d979c2 100644 --- a/packages/ndk/lib/domain_layer/usecases/zaps/zap_request.dart +++ b/packages/ndk/lib/domain_layer/usecases/zaps/zap_request.dart @@ -1,11 +1,64 @@ import '../../entities/nip_01_event.dart'; +import '../../entities/nip_01_utils.dart'; /// Zap Request class ZapRequest extends Nip01Event { static const kZapRequestKind = 9734; + /// creates a zap request from a nip01 event \ + /// [event] the nip01 event \ + /// returns the zap request \ + /// kind is set to [kZapRequestKind] + ZapRequest.nip01Event({ + required Nip01Event event, + }) : super( + pubKey: event.pubKey, + tags: event.tags, + content: event.content, + id: event.id, + sig: event.sig, + kind: kZapRequestKind, + validSig: event.validSig, + ); + /// Zap Request - ZapRequest( - {required super.pubKey, required super.tags, required super.content}) - : super(kind: kZapRequestKind); + ZapRequest._({ + required super.pubKey, + required super.tags, + required super.content, + required super.id, + super.sig, + }) : super( + kind: kZapRequestKind, + validSig: null, + ); + + /// creates a zap request \ + /// [pubKey] the pubkey of the zap requester \ + /// [tags] the tags of the zap request \ + /// [content] the content of the zap request \ + /// [sig] the signature of the zap request (optional) \ + /// returns the zap request \ + factory ZapRequest({ + required String pubKey, + required List> tags, + required String content, + String? sig, + }) { + final calculatedId = Nip01Utils.calculateEventIdSync( + pubKey: pubKey, + createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, + kind: kZapRequestKind, + tags: tags, + content: content, + ); + + return ZapRequest._( + pubKey: pubKey, + tags: tags, + content: content, + id: calculatedId, + sig: sig, + ); + } } diff --git a/packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart b/packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart index ed6c34900..fb8f9fec2 100644 --- a/packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart +++ b/packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart @@ -81,10 +81,12 @@ class Zaps { if (pollOption != null) { tags.add(["poll_option", pollOption]); } - var event = ZapRequest( + final zapRequestEvent = ZapRequest( pubKey: signer.getPublicKey(), tags: tags, content: comment ?? ''); - await signer.sign(event); - return event; + final signedEvent = await signer.sign(zapRequestEvent); + + final sigendZapRequest = ZapRequest.nip01Event(event: signedEvent); + return sigendZapRequest; } /// zap or pay some lnurl, for zap to be created it is necessary: diff --git a/packages/ndk/lib/entities.dart b/packages/ndk/lib/entities.dart index 4b646ddd3..af49ed87b 100644 --- a/packages/ndk/lib/entities.dart +++ b/packages/ndk/lib/entities.dart @@ -31,3 +31,6 @@ export 'domain_layer/entities/user_relay_list.dart'; export 'domain_layer/entities/blossom_blobs.dart'; export 'domain_layer/entities/ndk_file.dart'; export 'domain_layer/entities/account.dart'; + +/// models +export 'data_layer/models/nip_01_event_model.dart'; diff --git a/packages/ndk/lib/ndk.dart b/packages/ndk/lib/ndk.dart index 44edab661..18db26f4c 100644 --- a/packages/ndk/lib/ndk.dart +++ b/packages/ndk/lib/ndk.dart @@ -19,6 +19,7 @@ export 'domain_layer/entities/broadcast_response.dart'; * */ export 'domain_layer/entities/nip_01_event.dart'; +export 'data_layer/models/nip_01_event_model.dart'; export 'domain_layer/entities/filter.dart'; export 'domain_layer/entities/nip_51_list.dart'; export 'domain_layer/entities/contact_list.dart'; @@ -81,6 +82,8 @@ export 'domain_layer/usecases/gift_wrap/gift_wrap.dart'; export 'domain_layer/usecases/bunkers/bunkers.dart'; export 'domain_layer/usecases/bunkers/models/bunker_connection.dart'; export 'domain_layer/usecases/bunkers/models/nostr_connect.dart'; +export 'domain_layer/usecases/proof_of_work/proof_of_work.dart'; +export 'domain_layer/entities/nip_01_utils.dart'; /** * other stuff diff --git a/packages/ndk/lib/presentation_layer/init.dart b/packages/ndk/lib/presentation_layer/init.dart index 8abafed0c..9cd7756a4 100644 --- a/packages/ndk/lib/presentation_layer/init.dart +++ b/packages/ndk/lib/presentation_layer/init.dart @@ -1,6 +1,6 @@ import 'package:http/http.dart' as http; -import '../shared/net/user_agent.dart'; +import '../shared/net/user_agent.dart'; import '../data_layer/data_sources/http_request.dart'; import '../data_layer/repositories/blossom/blossom_impl.dart'; import '../data_layer/repositories/lnurl_http_impl.dart'; @@ -14,6 +14,7 @@ import '../domain_layer/repositories/nip_05_repo.dart'; import '../domain_layer/usecases/accounts/accounts.dart'; import '../domain_layer/usecases/broadcast/broadcast.dart'; import '../domain_layer/usecases/bunkers/bunkers.dart'; +import '../domain_layer/usecases/proof_of_work/proof_of_work.dart'; import '../domain_layer/usecases/cache_read/cache_read.dart'; import '../domain_layer/usecases/cache_write/cache_write.dart'; import '../domain_layer/usecases/connectivity/connectivity.dart'; @@ -79,6 +80,7 @@ class Initialization { late Search search; late GiftWrap giftWrap; late Connectivy connectivity; + late ProofOfWork proofOfWork; late VerifyNip05 verifyNip05; @@ -243,6 +245,8 @@ class Initialization { connectivity = Connectivy(relayManager); + proofOfWork = ProofOfWork(); + /// set the user configured log level Logger.setLogLevel(_ndkConfig.logLevel); } diff --git a/packages/ndk/lib/presentation_layer/ndk.dart b/packages/ndk/lib/presentation_layer/ndk.dart index c37528ae7..653ef07e5 100644 --- a/packages/ndk/lib/presentation_layer/ndk.dart +++ b/packages/ndk/lib/presentation_layer/ndk.dart @@ -16,6 +16,7 @@ import '../domain_layer/usecases/lists/lists.dart'; import '../domain_layer/usecases/metadatas/metadatas.dart'; import '../domain_layer/usecases/nip05/verify_nip_05.dart'; import '../domain_layer/usecases/nwc/nwc.dart'; +import '../domain_layer/usecases/proof_of_work/proof_of_work.dart'; import '../domain_layer/usecases/relay_manager.dart'; import '../domain_layer/usecases/relay_sets/relay_sets.dart'; import '../domain_layer/usecases/requests/requests.dart'; @@ -135,6 +136,8 @@ class Ndk { /// for faster reconnects Connectivy get connectivity => _initialization.connectivity; + ProofOfWork get proofOfWork => _initialization.proofOfWork; + /// Nostr Wallet connect @experimental // needs more docs & tests Nwc get nwc => _initialization.nwc; diff --git a/packages/ndk/lib/shared/decode_nostr_msg/decode_nostr_msg.dart b/packages/ndk/lib/shared/decode_nostr_msg/decode_nostr_msg.dart index 6fff3ffd7..cca7fdc7e 100644 --- a/packages/ndk/lib/shared/decode_nostr_msg/decode_nostr_msg.dart +++ b/packages/ndk/lib/shared/decode_nostr_msg/decode_nostr_msg.dart @@ -1,6 +1,7 @@ import 'dart:convert'; -import '../../domain_layer/entities/nip_01_event_raw.dart'; +import '../../domain_layer/entities/nip_01_event.dart'; +import '../../domain_layer/entities/nostr_message_raw.dart'; NostrMessageRaw decodeNostrMsg(String msgJsonStr) { try { @@ -21,7 +22,7 @@ NostrMessageRaw decodeNostrMsg(String msgJsonStr) { } final requestId = decoded[1]; final eventData = decoded[2]; - final nip01Event = Nip01EventRaw( + final nip01Event = Nip01Event( id: eventData['id'], pubKey: eventData['pubkey'], createdAt: eventData['created_at'], @@ -31,6 +32,7 @@ NostrMessageRaw decodeNostrMsg(String msgJsonStr) { .toList(), content: eventData['content'], sig: eventData['sig'], + validSig: null, ); return NostrMessageRaw( type: NostrMessageRawType.event, @@ -40,6 +42,7 @@ NostrMessageRaw decodeNostrMsg(String msgJsonStr) { case 'EOSE': return NostrMessageRaw( type: NostrMessageRawType.eose, + requestId: decoded.length > 1 ? decoded[1] : null, otherData: decoded, ); case 'OK': diff --git a/packages/ndk/lib/shared/isolates/isolate_manager.dart b/packages/ndk/lib/shared/isolates/isolate_manager.dart index 1b810eff3..f18b6236e 100644 --- a/packages/ndk/lib/shared/isolates/isolate_manager.dart +++ b/packages/ndk/lib/shared/isolates/isolate_manager.dart @@ -1,12 +1,43 @@ import 'dart:async'; import 'dart:isolate'; +const int encodingIsolatePoolSize = 20; +const int computeIsolatePoolSize = 20; + class IsolateConfig { Isolate isolate; SendPort sendPort; IsolateConfig(this.isolate, this.sendPort); } +class IsolatePool { + final List _isolates = []; + int _currentIndex = 0; + + List get isolates => _isolates; + + void add(IsolateConfig config) { + _isolates.add(config); + } + + SendPort getNextSendPort() { + if (_isolates.isEmpty) { + throw StateError('Isolate pool is empty'); + } + final sendPort = _isolates[_currentIndex].sendPort; + _currentIndex = (_currentIndex + 1) % _isolates.length; + return sendPort; + } + + void killAll() { + for (final config in _isolates) { + config.isolate.kill(priority: Isolate.immediate); + } + _isolates.clear(); + _currentIndex = 0; + } +} + class IsolateManager { static IsolateManager? _instance; static IsolateManager get instance { @@ -14,11 +45,8 @@ class IsolateManager { return _instance!; } - Isolate? _encodeIsolate; - Isolate? _computeIsolate; - - SendPort? _encodeSendPort; - SendPort? _computeSendPort; + final IsolatePool _encodePool = IsolatePool(); + final IsolatePool _computePool = IsolatePool(); final Completer _readyCompleter = Completer(); IsolateManager._() { @@ -27,14 +55,17 @@ class IsolateManager { Future _initialize() async { try { - _encodeSendPort = await _createIsolate((sendPort) { - _encodeIsolate = sendPort.isolate; - return sendPort.sendPort; - }); - _computeSendPort = await _createIsolate((sendPort) { - _computeIsolate = sendPort.isolate; - return sendPort.sendPort; - }); + // Initialize encoding isolate pool + for (int i = 0; i < encodingIsolatePoolSize; i++) { + final config = await _createIsolate(); + _encodePool.add(config); + } + + // Initialize compute isolate pool + for (int i = 0; i < computeIsolatePoolSize; i++) { + final config = await _createIsolate(); + _computePool.add(config); + } if (!_readyCompleter.isCompleted) { _readyCompleter.complete(); @@ -46,22 +77,19 @@ class IsolateManager { } } - Future _createIsolate(Function(IsolateConfig) isolateConfig) async { + Future _createIsolate() async { final receivePort = ReceivePort(); final isolate = await Isolate.spawn(_isolateEntry, receivePort.sendPort); final sendPort = await receivePort.first as SendPort; - isolateConfig(IsolateConfig(isolate, sendPort)); - return sendPort; + return IsolateConfig(isolate, sendPort); } Future _runTask( R Function(Q) task, Q argument, - SendPort? sendPort, + IsolatePool pool, ) async { - if (sendPort == null) { - throw StateError('Isolate not initialized'); - } + final sendPort = pool.getNextSendPort(); final completer = Completer(); final port = ReceivePort(); @@ -85,7 +113,7 @@ class IsolateManager { Q argument, ) async { await ready; - return _runTask(task, argument, _encodeSendPort); + return _runTask(task, argument, _encodePool); } /// dedicated for compute operations (like crypto, hashing, etc) @@ -94,18 +122,12 @@ class IsolateManager { Q argument, ) async { await ready; - return _runTask(task, argument, _computeSendPort); + return _runTask(task, argument, _computePool); } Future dispose() async { - _encodeIsolate?.kill(priority: Isolate.immediate); - _computeIsolate?.kill(priority: Isolate.immediate); - - _encodeIsolate = null; - _computeIsolate = null; - - _encodeSendPort = null; - _computeSendPort = null; + _encodePool.killAll(); + _computePool.killAll(); _instance = null; } } diff --git a/packages/ndk/lib/shared/nips/nip01/client_msg.dart b/packages/ndk/lib/shared/nips/nip01/client_msg.dart index b2763a899..4dada428c 100644 --- a/packages/ndk/lib/shared/nips/nip01/client_msg.dart +++ b/packages/ndk/lib/shared/nips/nip01/client_msg.dart @@ -1,6 +1,8 @@ import 'package:ndk/domain_layer/entities/nip_01_event.dart'; import 'package:ndk/domain_layer/entities/filter.dart'; +import '../../../data_layer/models/nip_01_event_model.dart'; + /// this class is used to send messages from the client to the relay /// /// "EVENT", <event JSON as defined above> @@ -56,7 +58,8 @@ class ClientMsg { } List _eventToJson() { - return [type, event!.toJson()]; + final model = Nip01EventModel.fromEntity(event!); + return [type, model.toJson()]; } List _reqToJson() { diff --git a/packages/ndk/lib/shared/nips/nip13/nip13.dart b/packages/ndk/lib/shared/nips/nip13/nip13.dart index 87098afa5..339af1fc5 100644 --- a/packages/ndk/lib/shared/nips/nip13/nip13.dart +++ b/packages/ndk/lib/shared/nips/nip13/nip13.dart @@ -1,5 +1,19 @@ import 'dart:math'; import '../../../domain_layer/entities/nip_01_event.dart'; +import '../../../domain_layer/entities/nip_01_utils.dart'; +import '../../isolates/isolate_manager.dart'; + +class _MiningParams { + final Nip01Event event; + final int targetDifficulty; + final int maxIterations; + + _MiningParams({ + required this.event, + required this.targetDifficulty, + required this.maxIterations, + }); +} class Nip13 { static int countLeadingZeroBits(String hex) { @@ -26,37 +40,46 @@ class Nip13 { return getDifficulty(eventId) >= targetDifficulty; } - static Nip01Event mineEvent( + static Future mineEvent( Nip01Event event, int targetDifficulty, { int? maxIterations, - }) { + }) async { + return await IsolateManager.instance + .runInComputeIsolate<_MiningParams, Nip01Event>( + _mineEventInIsolate, + _MiningParams( + event: event, + targetDifficulty: targetDifficulty, + maxIterations: maxIterations ?? 1000000, + ), + ); + } + + static Nip01Event _mineEventInIsolate(_MiningParams params) { final random = Random(); int nonce = 0; int iterations = 0; - final maxIter = maxIterations ?? 1000000; - - List> tags = List.from(event.tags); + List> tags = List.from(params.event.tags); tags.removeWhere((tag) => tag.isNotEmpty && tag[0] == 'nonce'); - while (iterations < maxIter) { - // Use 0x100000000 instead of 1 << 32 for web compatibility - // On web, 1 << 32 evaluates to 0 due to JS bitwise operation limits + while (iterations < params.maxIterations) { nonce = random.nextInt(0x100000000); final updatedTags = List>.from(tags); - updatedTags.add(['nonce', nonce.toString(), targetDifficulty.toString()]); + updatedTags + .add(['nonce', nonce.toString(), params.targetDifficulty.toString()]); final minedEvent = Nip01Event( - pubKey: event.pubKey, - kind: event.kind, + pubKey: params.event.pubKey, + kind: params.event.kind, tags: updatedTags, - content: event.content, - createdAt: event.createdAt, + content: params.event.content, + createdAt: params.event.createdAt, ); - if (checkDifficulty(minedEvent.id, targetDifficulty)) { + if (checkDifficulty(minedEvent.id, params.targetDifficulty)) { return minedEvent; } @@ -64,7 +87,7 @@ class Nip13 { } throw Exception( - 'Failed to mine event with difficulty $targetDifficulty after $maxIter iterations'); + 'Failed to mine event with difficulty ${params.targetDifficulty} after ${params.maxIterations} iterations'); } static int? getTargetDifficultyFromEvent(Nip01Event event) { diff --git a/packages/ndk/test/mocks/mock_relay.dart b/packages/ndk/test/mocks/mock_relay.dart index 6cbeb20be..7d67ad567 100644 --- a/packages/ndk/test/mocks/mock_relay.dart +++ b/packages/ndk/test/mocks/mock_relay.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:bip340/bip340.dart'; import 'package:ndk/domain_layer/usecases/bunkers/models/bunker_request.dart'; import 'package:ndk/entities.dart'; +import 'package:ndk/ndk.dart'; import 'package:ndk/shared/nips/nip01/helpers.dart'; import 'package:ndk/shared/nips/nip01/key_pair.dart'; import 'package:ndk/shared/nips/nip01/bip340.dart'; @@ -116,8 +117,8 @@ class MockRelay { var eventJson = json.decode(message); if (eventJson[0] == "AUTH") { - Nip01Event event = Nip01Event.fromJson(eventJson[1]); - if (verify(event.pubKey, event.id, event.sig)) { + Nip01Event event = Nip01EventModel.fromJson(eventJson[1]); + if (verify(event.pubKey, event.id, event.sig!)) { String? relay = event.getFirstTag("relay"); String? eventChallenge = event.getFirstTag("challenge"); if (eventChallenge == challenge && relay == url) { @@ -143,8 +144,8 @@ class MockRelay { } if (eventJson[0] == "EVENT") { - Nip01Event newEvent = Nip01Event.fromJson(eventJson[1]); - if (verify(newEvent.pubKey, newEvent.id, newEvent.sig)) { + Nip01Event newEvent = Nip01EventModel.fromJson(eventJson[1]); + if (verify(newEvent.pubKey, newEvent.id, newEvent.sig!)) { if (newEvent.kind == ContactList.kKind) { _contactLists[newEvent.pubKey] = newEvent; } else if (newEvent.kind == Metadata.kKind) { @@ -228,12 +229,13 @@ class MockRelay { void _respondToRequest(List filters, String requestId) { if (sendMalformedEvents) { - final malformedEventJson = '["EVENT", "$requestId", {"id":null,"pubkey":null,"created_at":${DateTime.now().millisecondsSinceEpoch ~/ 1000},"kind":0,"tags":[],"content":null,"sig":null}]'; + final malformedEventJson = + '["EVENT", "$requestId", {"id":null,"pubkey":null,"created_at":${DateTime.now().millisecondsSinceEpoch ~/ 1000},"kind":0,"tags":[],"content":null,"sig":null}]'; _webSocket!.add(malformedEventJson); _webSocket!.add(jsonEncode(["EOSE", requestId])); return; } - + Set allMatchingEvents = {}; for (Filter filter in filters) { @@ -292,11 +294,17 @@ class MockRelay { (filter.kinds == null || filter.kinds!.contains(Nip65.kKind))) { Nip01Event eventToAdd = entry.value.toEvent(); // Creates a new event instance + final Nip01Event? eventToAddSigned; if (signEvents && entry.key.privateKey != null) { // Sign the new instance, not the one in _nip65s - eventToAdd.sign(entry.key.privateKey!); + + eventToAddSigned = Nip01Utils.signWithPrivateKey( + event: eventToAdd, privateKey: entry.key.privateKey!); + } else { + eventToAddSigned = null; } - eventsForThisFilter.add(eventToAdd); + + eventsForThisFilter.add(eventToAddSigned ?? eventToAdd); } } } @@ -319,19 +327,24 @@ class MockRelay { if (authorsMatch && kindsMatch) { // Clone the event from the map before signing to avoid mutating the stored original - Nip01Event eventToAdd = Nip01Event.fromJson(entry.value.toJson()); + Nip01Event eventToAdd = entry.value.copyWith(); + Nip01Event? eventToAddSigned; if (signEvents && entry.key.privateKey != null) { - eventToAdd.sign(entry.key.privateKey!); + eventToAddSigned = Nip01Utils.signWithPrivateKey( + event: eventToAdd, privateKey: entry.key.privateKey!); + } else { + eventToAddSigned = null; } - eventsForThisFilter.add(eventToAdd); + eventsForThisFilter.add(eventToAddSigned ?? eventToAdd); } } } allMatchingEvents.addAll(eventsForThisFilter); } - for (var event in allMatchingEvents) { - _webSocket!.add(jsonEncode(["EVENT", requestId, event.toJson()])); + for (final event in allMatchingEvents) { + _webSocket!.add(jsonEncode( + ["EVENT", requestId, Nip01EventModel.fromEntity(event).toJson()])); } _webSocket!.add(jsonEncode(["EOSE", requestId])); @@ -348,11 +361,17 @@ class MockRelay { throw Exception("WebSocket is not connected"); } + Nip01Event? signedEvent; if (keyPair != null) { - event.sign(keyPair.privateKey!); + signedEvent = Nip01Utils.signWithPrivateKey( + event: event, privateKey: keyPair.privateKey!); } - _webSocket!.add(jsonEncode(["EVENT", subId, event])); + final eventToSend = signedEvent ?? event; + + final eventToSendModel = Nip01EventModel.fromEntity(eventToSend); + + _webSocket!.add(jsonEncode(["EVENT", subId, eventToSendModel])); } /// sends a CLOSED message for a given subscription ID @@ -418,7 +437,8 @@ class MockRelay { } // Create NIP-46 response event - Nip01Event responseEvent = Nip01Event( + Nip01Event responseEventUnsinged = + Nip01Event( pubKey: remoteSignerPublicKey, kind: kNip46Kind, tags: [ @@ -427,11 +447,8 @@ class MockRelay { content: encryptedResponse, createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, ); - - // Sign the response event - responseEvent.id = responseEvent.id; - responseEvent.sig = - Bip340.sign(responseEvent.id, _remoteSignerPrivateKey); + Nip01Event responseEvent = Nip01Utils.signWithPrivateKey( + event: responseEventUnsinged, privateKey: _remoteSignerPrivateKey); // NIP-46 events are ephemeral (kind 24133), don't store them // Instead, deliver directly to matching subscriptions @@ -473,8 +490,11 @@ class MockRelay { // If this filter matches, send the event with the correct subscription ID if (matches) { - webSocket.add( - jsonEncode(["EVENT", subscriptionId, responseEvent.toJson()])); + webSocket.add(jsonEncode([ + "EVENT", + subscriptionId, + Nip01EventModel.fromEntity(responseEvent).toJson() + ])); break; // Only send once per subscription } } @@ -538,7 +558,8 @@ class MockRelay { } // Use the Nip01Event constructor directly - Nip01Event eventToSign = Nip01Event( + final Nip01Event eventToSign = + Nip01Event( pubKey: remoteSignerPublicKey, kind: eventData["kind"] ?? 1, tags: List>.from(eventData["tags"] ?? []), @@ -546,14 +567,11 @@ class MockRelay { createdAt: eventData["created_at"] ?? eventData["createdAt"] ?? 0, ); - // Sign the event - eventToSign.sig = Bip340.sign( - eventToSign.id, - _remoteSignerPrivateKey, - ); + final Nip01Event signedEvent = Nip01Utils.signWithPrivateKey( + event: eventToSign, privateKey: _remoteSignerPrivateKey); return { - 'result': jsonEncode(eventToSign.toJson()), + 'result': Nip01EventModel.fromEntity(signedEvent).toJsonString(), }; case 'nip04_encrypt': diff --git a/packages/ndk/test/ndk_test.dart b/packages/ndk/test/ndk_test.dart index be4457497..9728db2e4 100644 --- a/packages/ndk/test/ndk_test.dart +++ b/packages/ndk/test/ndk_test.dart @@ -23,13 +23,16 @@ void main() async { Nip01Event textNote(KeyPair key2) { Nip01Event event = Nip01Event( - kind: Nip01Event.kTextNodeKind, - pubKey: key2.publicKey, - content: "some note from key ${keyNames[key2]}", - tags: [], - createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000); - event.sign(key2.privateKey!); - return event; + kind: Nip01Event.kTextNodeKind, + pubKey: key2.publicKey, + content: "some note from key ${keyNames[key2]}", + tags: [], + createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, + ); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, privateKey: key2.privateKey!); + + return signedEvent; } Map key1TextNotes = {key1: textNote(key1)}; @@ -89,7 +92,7 @@ void main() async { await ndk.relays.seedRelaysConnected; final response = ndk.requests.query(filters: [ - Filter(ids: [key1TextNotes[key1]!.id]) + Filter(ids: [key1TextNotes[key1]!.id!]) ]); await expectLater(response.stream, emitsInAnyOrder(key1TextNotes.values)); @@ -97,7 +100,7 @@ void main() async { await cache.saveEvent(key1TextNotes[key1]!); final response2 = ndk.requests.query(filters: [ - Filter(ids: [key1TextNotes[key1]!.id]) + Filter(ids: [key1TextNotes[key1]!.id!]) ]); await expectLater( diff --git a/packages/ndk/test/nips/nip02_test.dart b/packages/ndk/test/nips/nip02_test.dart index aeabd8d43..6feb79a15 100644 --- a/packages/ndk/test/nips/nip02_test.dart +++ b/packages/ndk/test/nips/nip02_test.dart @@ -1,6 +1,7 @@ import 'package:ndk/domain_layer/entities/nip_01_event.dart'; import 'package:ndk/domain_layer/entities/contact_list.dart'; import 'package:ndk/domain_layer/entities/read_write_marker.dart'; +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/ndk/test/nips/nip13_test.dart b/packages/ndk/test/nips/nip13_test.dart index 2afd2e17e..038624b0f 100644 --- a/packages/ndk/test/nips/nip13_test.dart +++ b/packages/ndk/test/nips/nip13_test.dart @@ -1,9 +1,12 @@ +import 'package:ndk/ndk.dart'; import 'package:ndk/shared/nips/nip01/bip340.dart'; import 'package:test/test.dart'; -import 'package:ndk/domain_layer/entities/nip_01_event.dart'; import 'package:ndk/shared/nips/nip13/nip13.dart'; void main() { + final ndk = Ndk.emptyBootstrapRelaysConfig(); + final pow = ndk.proofOfWork; + group('NIP-13 Proof of Work', () { group('countLeadingZeroBits', () { test('should count single hex digits correctly', () { @@ -83,7 +86,7 @@ void main() { }); }); - test('mineEvent should meet difficulty', () { + test('mineEvent should meet difficulty', () async { final keypair = Bip340.generatePrivateKey(); final event = Nip01Event( @@ -92,11 +95,13 @@ void main() { tags: [], content: 'Hello, Nostr!', createdAt: 1234567890, - ).minePoW(4); + ); - final minedEvent = Nip13.mineEvent(event, 4, maxIterations: 100000); + final minedEvent = await pow.minePoW( + event: event, targetDifficulty: 4, maxIterations: 100000); - expect(minedEvent.checkPoWDifficulty(2), isTrue); + expect(pow.checkPoWDifficulty(event: minedEvent, targetDifficulty: 2), + isTrue); }); test('should return null when no nonce tag present', () { @@ -133,25 +138,27 @@ void main() { reason: 'Should return null for malformed nonce tag'); }); - test('check target difficulty', () { + test('check target difficulty', () async { final keypair = Bip340.generatePrivateKey(); - final minedEvent = Nip01Event( + final event = Nip01Event( pubKey: keypair.publicKey, kind: 1, tags: [], content: 'Hello, Nostr!', createdAt: 1234567890, - ).minePoW(4); + ); - final value = Nip13.getTargetDifficultyFromEvent(minedEvent); + final minedEvent = await pow.minePoW(event: event, targetDifficulty: 4); + + final value = pow.getTargetDifficultyFromEvent(minedEvent); expect(value, equals(4)); }); }); test('validate event: greater POW', () { - final minedEvent = Nip01Event.fromJson({ + final minedEvent = Nip01EventModel.fromJson({ "id": "00302f635d4e2059c5cdddca2c00b5f455ec6706cfd960a410acc3e9abe36100", "pubkey": "6d46059232af4d121456d1fff7fa8dadc32b02510e46e85b912b0585cf038574", @@ -176,16 +183,18 @@ void main() { expect(invalidValue, isFalse); }); - test('validate event: id check', () { + test('validate event: id check', () async { final keypair = Bip340.generatePrivateKey(); - final minedEvent = Nip01Event( + final event = Nip01Event( pubKey: keypair.publicKey, kind: 1, tags: [], content: 'Hello, Nostr!', createdAt: 1234567890, - ).minePoW(10); + ); + + final minedEvent = await pow.minePoW(event: event, targetDifficulty: 10); final value = Nip13.validateEvent(minedEvent); @@ -195,20 +204,22 @@ void main() { ['nonce', '123', '4'] ]); - final invalidValue = invalidEvent.isIdValid; + final invalidValue = Nip01Utils.isIdValid(invalidEvent); expect(invalidValue, isFalse); }); - test('check commitment', () { + test('check commitment', () async { final keypair = Bip340.generatePrivateKey(); - final minedEvent = Nip01Event( + final event = Nip01Event( pubKey: keypair.publicKey, kind: 1, tags: [], content: 'Hello, Nostr!', createdAt: 1234567890, - ).minePoW(4); + ); + + final minedEvent = await pow.minePoW(event: event, targetDifficulty: 4); final commitment = Nip13.calculateCommitment(minedEvent.id); expect(commitment, greaterThan(0)); diff --git a/packages/ndk/test/nips/nip19_event_getters_test.dart b/packages/ndk/test/nips/nip19_event_getters_test.dart index 8c28d2276..a2c912f8d 100644 --- a/packages/ndk/test/nips/nip19_event_getters_test.dart +++ b/packages/ndk/test/nips/nip19_event_getters_test.dart @@ -1,5 +1,6 @@ +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; +import 'package:ndk/entities.dart'; import 'package:test/test.dart'; -import 'package:ndk/domain_layer/entities/nip_01_event.dart'; import 'package:ndk/shared/nips/nip19/nip19.dart'; void main() { @@ -15,7 +16,9 @@ void main() { createdAt: 1234567890, ); - final nevent = event.nevent; + final eventMode = Nip01EventModel.fromEntity(event); + + final nevent = eventMode.nevent; expect(nevent.startsWith('nevent1'), true); @@ -27,7 +30,7 @@ void main() { }); test('should use event sources as relay hints', () { - final event = Nip01Event( + final eventInit = Nip01Event( pubKey: '76c71aae3a491f1d9eec47cba17e229cda4113a0bbb6e6ae1776d7643e29cafa', kind: 1, @@ -35,12 +38,15 @@ void main() { content: 'Hello Nostr!', createdAt: 1234567890, ); - event.sources = ['wss://nos.lol/', 'wss://relay.damus.io/']; + final event = eventInit + .copyWith(sources: ['wss://nos.lol/', 'wss://relay.damus.io/']); - final nevent = event.nevent; + final eventModel = Nip01EventModel.fromEntity(event); + + final nevent = eventModel.nevent; final decoded = Nip19.decodeNevent(nevent); - expect(decoded.relays, event.sources); + expect(decoded.relays, eventModel.sources); }); test('should not include relays if sources is empty', () { @@ -52,8 +58,9 @@ void main() { content: 'Hello Nostr!', createdAt: 1234567890, ); + final eventModel = Nip01EventModel.fromEntity(event); - final nevent = event.nevent; + final nevent = eventModel.nevent; final decoded = Nip19.decodeNevent(nevent); expect(decoded.relays, null); @@ -72,8 +79,9 @@ void main() { content: 'Article content', createdAt: 1234567890, ); + final eventModel = Nip01EventModel.fromEntity(event); - final naddr = event.naddr; + final naddr = eventModel.naddr; expect(naddr, isNotNull); expect(naddr!.startsWith('naddr1'), true); @@ -81,8 +89,8 @@ void main() { // Verify we can decode it back final decoded = Nip19.decodeNaddr(naddr); expect(decoded.identifier, 'my-article'); - expect(decoded.pubkey, event.pubKey); - expect(decoded.kind, event.kind); + expect(decoded.pubkey, eventModel.pubKey); + expect(decoded.kind, eventModel.kind); }); test('should encode replaceable event (kind 0) as naddr', () { @@ -96,8 +104,9 @@ void main() { content: '{"name":"Alice"}', createdAt: 1234567890, ); + final eventModel = Nip01EventModel.fromEntity(event); - final naddr = event.naddr; + final naddr = eventModel.naddr; expect(naddr, isNotNull); final decoded = Nip19.decodeNaddr(naddr!); @@ -116,8 +125,9 @@ void main() { content: 'Hello', createdAt: 1234567890, ); + final eventModel = Nip01EventModel.fromEntity(event); - final naddr = event.naddr; + final naddr = eventModel.naddr; expect(naddr, isNull); }); @@ -131,8 +141,9 @@ void main() { content: 'Article content', createdAt: 1234567890, ); + final eventModel = Nip01EventModel.fromEntity(event); - final naddr = event.naddr; + final naddr = eventModel.naddr; expect(naddr, isNull); }); @@ -148,13 +159,16 @@ void main() { content: '{}', createdAt: 1234567890, ); - event.sources = ['wss://relay.example.com']; + final eventWithSources = + event.copyWith(sources: ['wss://relay.example.com']); - final naddr = event.naddr; + final eventModel = Nip01EventModel.fromEntity(eventWithSources); + + final naddr = eventModel.naddr; expect(naddr, isNotNull); final decoded = Nip19.decodeNaddr(naddr!); - expect(decoded.relays, event.sources); + expect(decoded.relays, eventModel.sources); }); }); @@ -173,7 +187,9 @@ void main() { ], content: '', ); - expect(event.naddr, isNotNull, + + final eventModel = Nip01EventModel.fromEntity(event); + expect(eventModel.naddr, isNotNull, reason: 'Kind $kind should be addressable'); } }); @@ -189,7 +205,9 @@ void main() { ], content: '', ); - expect(event.naddr, isNotNull, + + final eventModel = Nip01EventModel.fromEntity(event); + expect(eventModel.naddr, isNotNull, reason: 'Kind $kind should be addressable'); } }); @@ -205,7 +223,8 @@ void main() { ], content: '', ); - expect(event.naddr, isNull, + final eventModel = Nip01EventModel.fromEntity(event); + expect(eventModel.naddr, isNull, reason: 'Kind $kind should not be addressable'); } }); diff --git a/packages/ndk/test/nips/nip51_test.dart b/packages/ndk/test/nips/nip51_test.dart index 512df5a21..0e87db69a 100644 --- a/packages/ndk/test/nips/nip51_test.dart +++ b/packages/ndk/test/nips/nip51_test.dart @@ -1,3 +1,4 @@ +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:ndk/shared/nips/nip01/bip340.dart'; import 'package:ndk/data_layer/repositories/signers/bip340_event_signer.dart'; import 'package:ndk/domain_layer/entities/nip_01_event.dart'; diff --git a/packages/ndk/test/nips/nip65/nip65_test.dart b/packages/ndk/test/nips/nip65/nip65_test.dart index eb195e14d..e193d339d 100644 --- a/packages/ndk/test/nips/nip65/nip65_test.dart +++ b/packages/ndk/test/nips/nip65/nip65_test.dart @@ -2,6 +2,7 @@ import 'package:ndk/domain_layer/entities/nip_01_event.dart'; import 'package:ndk/domain_layer/entities/nip_65.dart'; import 'package:ndk/domain_layer/entities/read_write_marker.dart'; import 'package:ndk/domain_layer/entities/read_write.dart'; +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/ndk/test/nips/nip65/relay_ranking_test.dart b/packages/ndk/test/nips/nip65/relay_ranking_test.dart index 9dafd58ce..3ac087e22 100644 --- a/packages/ndk/test/nips/nip65/relay_ranking_test.dart +++ b/packages/ndk/test/nips/nip65/relay_ranking_test.dart @@ -4,6 +4,7 @@ import 'package:ndk/domain_layer/entities/nip_01_event.dart'; import 'package:ndk/domain_layer/entities/nip_65.dart'; import 'package:ndk/domain_layer/entities/read_write_marker.dart'; import 'package:ndk/domain_layer/entities/user_relay_list.dart'; +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:ndk/shared/nips/nip65/relay_ranking.dart'; import 'package:ndk/domain_layer/usecases/jit_engine/relay_jit_request_strategies/relay_jit_pubkey_strategy.dart'; import 'package:test/test.dart'; diff --git a/packages/ndk/test/relays/nip42_test.dart b/packages/ndk/test/relays/nip42_test.dart index 0b8dcc822..72be52cf3 100644 --- a/packages/ndk/test/relays/nip42_test.dart +++ b/packages/ndk/test/relays/nip42_test.dart @@ -24,8 +24,10 @@ void main() async { content: "some note from key ${keyNames[key1]}", tags: [], createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000); - event.sign(key.privateKey!); - return event; + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, privateKey: key.privateKey!); + + return signedEvent; } Map key1TextNotes = {key1: textNote(key1)}; diff --git a/packages/ndk/test/relays/relay_manager_test.dart b/packages/ndk/test/relays/relay_manager_test.dart index 2ae8c1cf7..7acb47ede 100644 --- a/packages/ndk/test/relays/relay_manager_test.dart +++ b/packages/ndk/test/relays/relay_manager_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'package:ndk/data_layer/repositories/nostr_transport/websocket_client_nostr_transport_factory.dart'; import 'package:ndk/domain_layer/entities/ndk_request.dart'; +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:ndk/domain_layer/usecases/relay_manager.dart'; import 'package:ndk/entities.dart'; import 'package:test/test.dart'; diff --git a/packages/ndk/test/relays/trailing_slash_test.dart b/packages/ndk/test/relays/trailing_slash_test.dart index a896584b4..daa1f048b 100644 --- a/packages/ndk/test/relays/trailing_slash_test.dart +++ b/packages/ndk/test/relays/trailing_slash_test.dart @@ -26,9 +26,13 @@ void main() { tags: [], content: "Test", ); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: keyPair.privateKey!, + ); final broadcast = ndk.broadcast.broadcast( - nostrEvent: event, + nostrEvent: signedEvent, specificRelays: [relay.url], ); final broadcastResponses = await broadcast.broadcastDoneFuture; @@ -128,9 +132,13 @@ void main() { tags: [], content: "Test", ); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: keyPair.privateKey!, + ); final broadcast = ndk.broadcast.broadcast( - nostrEvent: event, + nostrEvent: signedEvent, specificRelays: [relay.url], ); final broadcastResponses = await broadcast.broadcastDoneFuture; @@ -154,9 +162,13 @@ void main() { tags: [], content: "Test", ); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: keyPair.privateKey!, + ); final broadcast = ndk.broadcast.broadcast( - nostrEvent: event, + nostrEvent: signedEvent, specificRelays: ["${relay.url}/"], ); final broadcastResponses = await broadcast.broadcastDoneFuture; @@ -272,9 +284,13 @@ void main() { tags: [], content: "Test", ); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: keyPair.privateKey!, + ); final broadcast = ndk.broadcast.broadcast( - nostrEvent: event, + nostrEvent: signedEvent, specificRelays: [relay.url], ); final broadcastResponses = await broadcast.broadcastDoneFuture; @@ -303,8 +319,13 @@ void main() { content: "Test", ); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: keyPair.privateKey!, + ); + final broadcast = ndk.broadcast.broadcast( - nostrEvent: event, + nostrEvent: signedEvent, specificRelays: ["${relay.url}/"], ); final broadcastResponses = await broadcast.broadcastDoneFuture; diff --git a/packages/ndk/test/shared/bloom_filter/bloom_filter_prehash_test.dart b/packages/ndk/test/shared/bloom_filter/bloom_filter_prehash_test.dart index d9671ba11..4ded9e662 100644 --- a/packages/ndk/test/shared/bloom_filter/bloom_filter_prehash_test.dart +++ b/packages/ndk/test/shared/bloom_filter/bloom_filter_prehash_test.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:typed_data'; +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:ndk/entities.dart'; import 'package:ndk/shared/bloom_filter/bloom_filter_prehash.dart'; import 'package:ndk/shared/nips/nip01/bip340.dart'; diff --git a/packages/ndk/test/shared/event_filters/nip51_mute_event_filter_test.dart b/packages/ndk/test/shared/event_filters/nip51_mute_event_filter_test.dart index e421ac1f4..4b4c91391 100644 --- a/packages/ndk/test/shared/event_filters/nip51_mute_event_filter_test.dart +++ b/packages/ndk/test/shared/event_filters/nip51_mute_event_filter_test.dart @@ -1,3 +1,4 @@ +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:test/test.dart'; import 'package:ndk/domain_layer/entities/nip_01_event.dart'; import 'package:ndk/domain_layer/entities/nip_51_list.dart'; diff --git a/packages/ndk/test/signers/nip46_event_signer_test.dart b/packages/ndk/test/signers/nip46_event_signer_test.dart index 39d6fc491..026483a13 100644 --- a/packages/ndk/test/signers/nip46_event_signer_test.dart +++ b/packages/ndk/test/signers/nip46_event_signer_test.dart @@ -62,10 +62,10 @@ void main() { content: 'Hello, world!', ); - await signer.sign(event); + final signedEvent = await signer.sign(event); - expect(event.id, isNotNull); - expect(event.sig, isNotNull); + expect(signedEvent.id, isNotNull); + expect(signedEvent.sig, isNotNull); }); test('getPublicKey should throw when not cached', () { diff --git a/packages/ndk/test/usecases/broadcast_jit_test.dart b/packages/ndk/test/usecases/broadcast_jit_test.dart index 8bd7a5257..019fca079 100644 --- a/packages/ndk/test/usecases/broadcast_jit_test.dart +++ b/packages/ndk/test/usecases/broadcast_jit_test.dart @@ -53,7 +53,12 @@ void main() async { kind: Nip01Event.kTextNodeKind, tags: [], content: ""); - await ndk.broadcast.broadcast(nostrEvent: event).broadcastDoneFuture; + + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, privateKey: key0.privateKey!); + await ndk.broadcast + .broadcast(nostrEvent: signedEvent) + .broadcastDoneFuture; List result = await ndk.requests.query( filters: [ @@ -62,12 +67,17 @@ void main() async { ).future; expect(result.length, 1); - event = Nip01Event( + final event2 = Nip01Event( pubKey: key0.publicKey, kind: Nip01Event.kTextNodeKind, tags: [], content: "my content"); - await ndk.broadcast.broadcast(nostrEvent: event).broadcastDoneFuture; + + final signedEvent2 = Nip01Utils.signWithPrivateKey( + event: event2, privateKey: key0.privateKey!); + await ndk.broadcast + .broadcast(nostrEvent: signedEvent2) + .broadcastDoneFuture; result = await ndk.requests.query( filters: [ @@ -85,20 +95,23 @@ void main() async { kind: Nip01Event.kTextNodeKind, tags: [], content: ""); + + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, privateKey: key0.privateKey!); NdkBroadcastResponse response = - ndk.broadcast.broadcast(nostrEvent: event); + ndk.broadcast.broadcast(nostrEvent: signedEvent); await response.broadcastDoneFuture; List list = await ndk.requests.query(filters: [ - Filter(authors: [event.pubKey], kinds: [Nip01Event.kTextNodeKind]) + Filter(authors: [signedEvent.pubKey], kinds: [Nip01Event.kTextNodeKind]) ]).future; - expect(list.first, event); + expect(list.first, signedEvent); - response = ndk.broadcast.broadcastDeletion(eventId: event.id); + response = ndk.broadcast.broadcastDeletion(eventId: signedEvent.id); await response.broadcastDoneFuture; list = await ndk.requests.query(filters: [ - Filter(authors: [event.pubKey], kinds: [Nip01Event.kTextNodeKind]) + Filter(authors: [signedEvent.pubKey], kinds: [Nip01Event.kTextNodeKind]) ]).future; expect(list, isEmpty); }); diff --git a/packages/ndk/test/usecases/broadcast_test.dart b/packages/ndk/test/usecases/broadcast_test.dart index b78996c4f..7e68879c2 100644 --- a/packages/ndk/test/usecases/broadcast_test.dart +++ b/packages/ndk/test/usecases/broadcast_test.dart @@ -55,7 +55,14 @@ void main() async { kind: Nip01Event.kTextNodeKind, tags: [], content: ""); - await ndk.broadcast.broadcast(nostrEvent: event).broadcastDoneFuture; + + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: key0.privateKey!, + ); + await ndk.broadcast + .broadcast(nostrEvent: signedEvent) + .broadcastDoneFuture; List result = await ndk.requests.query( filters: [ @@ -64,12 +71,19 @@ void main() async { ).future; expect(result.length, 1); - event = Nip01Event( + final event2 = Nip01Event( pubKey: key0.publicKey, kind: Nip01Event.kTextNodeKind, tags: [], content: "my content"); - await ndk.broadcast.broadcast(nostrEvent: event).broadcastDoneFuture; + + final signedEvent2 = Nip01Utils.signWithPrivateKey( + event: event2, + privateKey: key0.privateKey!, + ); + await ndk.broadcast + .broadcast(nostrEvent: signedEvent2) + .broadcastDoneFuture; result = await ndk.requests.query( filters: [ @@ -87,20 +101,25 @@ void main() async { kind: Nip01Event.kTextNodeKind, tags: [], content: ""); + + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: key0.privateKey!, + ); NdkBroadcastResponse response = - ndk.broadcast.broadcast(nostrEvent: event); + ndk.broadcast.broadcast(nostrEvent: signedEvent); await response.broadcastDoneFuture; List list = await ndk.requests.query(filters: [ - Filter(authors: [event.pubKey], kinds: [Nip01Event.kTextNodeKind]) + Filter(authors: [signedEvent.pubKey], kinds: [Nip01Event.kTextNodeKind]) ]).future; - expect(list.first, event); + expect(list.first, signedEvent); - response = ndk.broadcast.broadcastDeletion(eventId: event.id); + response = ndk.broadcast.broadcastDeletion(eventId: signedEvent.id); await response.broadcastDoneFuture; list = await ndk.requests.query(filters: [ - Filter(authors: [event.pubKey], kinds: [Nip01Event.kTextNodeKind]) + Filter(authors: [signedEvent.pubKey], kinds: [Nip01Event.kTextNodeKind]) ]).future; expect(list, isEmpty); }); @@ -113,28 +132,40 @@ void main() async { kind: Nip01Event.kTextNodeKind, tags: [], content: "1"); + + final signedEvent1 = Nip01Utils.signWithPrivateKey( + event: event1, + privateKey: key0.privateKey!, + ); Nip01Event event2 = Nip01Event( pubKey: key0.publicKey, kind: Nip01Event.kTextNodeKind, tags: [], content: "2"); + + final signedEvent2 = Nip01Utils.signWithPrivateKey( + event: event2, + privateKey: key0.privateKey!, + ); NdkBroadcastResponse response1 = - ndk.broadcast.broadcast(nostrEvent: event1); + ndk.broadcast.broadcast(nostrEvent: signedEvent1); await response1.broadcastDoneFuture; NdkBroadcastResponse response2 = - ndk.broadcast.broadcast(nostrEvent: event2); + ndk.broadcast.broadcast(nostrEvent: signedEvent2); await response2.broadcastDoneFuture; List list = await ndk.requests.query(filters: [ - Filter(authors: [event1.pubKey], kinds: [Nip01Event.kTextNodeKind]) + Filter( + authors: [signedEvent1.pubKey], kinds: [Nip01Event.kTextNodeKind]) ]).future; - response1 = - ndk.broadcast.broadcastDeletion(eventIds: [event1.id, event2.id]); + response1 = ndk.broadcast + .broadcastDeletion(eventIds: [signedEvent1.id, signedEvent2.id]); await response1.broadcastDoneFuture; list = await ndk.requests.query(filters: [ - Filter(authors: [event1.pubKey], kinds: [Nip01Event.kTextNodeKind]) + Filter( + authors: [signedEvent1.pubKey], kinds: [Nip01Event.kTextNodeKind]) ]).future; expect(list, isEmpty); }); @@ -147,22 +178,28 @@ void main() async { kind: Nip01Event.kTextNodeKind, tags: [], content: ""); + + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: key0.privateKey!, + ); + NdkBroadcastResponse response = - ndk.broadcast.broadcast(nostrEvent: event); + ndk.broadcast.broadcast(nostrEvent: signedEvent); await response.broadcastDoneFuture; List list = await ndk.requests.query(filters: [ - Filter(authors: [event.pubKey], kinds: [Nip01Event.kTextNodeKind]) + Filter(authors: [signedEvent.pubKey], kinds: [Nip01Event.kTextNodeKind]) ]).future; - expect(list.first, event); + expect(list.first, signedEvent); final reaction = "♡"; response = ndk.broadcast - .broadcastReaction(eventId: event.id, reaction: reaction); + .broadcastReaction(eventId: signedEvent.id, reaction: reaction); await response.broadcastDoneFuture; list = await ndk.requests.query(filters: [ - Filter(authors: [event.pubKey], kinds: [Reaction.kKind]) + Filter(authors: [signedEvent.pubKey], kinds: [Reaction.kKind]) ]).future; expect(list.first.content, reaction); }); @@ -202,11 +239,16 @@ void main() async { tags: [], content: "testing timeout"); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: key0.privateKey!, + ); + final startTime = DateTime.now(); final customTimeout = const Duration(milliseconds: 500); NdkBroadcastResponse response = ndk.broadcast.broadcast( - nostrEvent: event, + nostrEvent: signedEvent, timeout: customTimeout, specificRelays: [slowRelay.url, relay0.url], considerDonePercent: 1); @@ -269,10 +311,15 @@ void main() async { tags: [], content: "testing considerDonePercent"); + final signedEvent = Nip01Utils.signWithPrivateKey( + event: event, + privateKey: key0.privateKey!, + ); + final startTime = DateTime.now(); NdkBroadcastResponse response = ndk.broadcast.broadcast( - nostrEvent: event, + nostrEvent: signedEvent, considerDonePercent: 0.66, // 66% = 2 out of 3 relays timeout: const Duration( seconds: 5), // Long timeout to ensure it's not timing out diff --git a/packages/ndk/test/usecases/cache_read_test.dart b/packages/ndk/test/usecases/cache_read_test.dart index 6b2ec8cd7..5cd13be78 100644 --- a/packages/ndk/test/usecases/cache_read_test.dart +++ b/packages/ndk/test/usecases/cache_read_test.dart @@ -10,12 +10,18 @@ void main() async { final CacheManager myCacheManager = MemCacheManager(); final List myEvens = [ - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), ]; setUp(() async { @@ -165,15 +171,32 @@ void main() async { final CacheManager myCacheManager = MemCacheManager(); final CacheRead myUsecase = CacheRead(myCacheManager); - final eventId0 = - Nip01Event(pubKey: "pubKey0", kind: 1, tags: [], content: "content0"); - eventId0.id = "id0"; - final eventId1 = - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1"); - eventId1.id = "id1"; - final eventId2 = - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2"); - eventId2.id = "id2"; + final eventId0 = Nip01Event( + pubKey: "pubKey0", + kind: 1, + tags: [], + content: "content0", + id: "id0", + sig: null, + validSig: null); + + final eventId1 = Nip01Event( + pubKey: "pubKey1", + kind: 1, + tags: [], + content: "content1", + id: "id1", + sig: null, + validSig: null); + + final eventId2 = Nip01Event( + pubKey: "pubKey2", + kind: 1, + tags: [], + content: "content2", + id: "id2", + sig: null, + validSig: null); final List idEvents = [ eventId0, @@ -217,15 +240,32 @@ void main() async { final CacheManager myCacheManager = MemCacheManager(); final CacheRead myUsecase = CacheRead(myCacheManager); - final eventId0 = - Nip01Event(pubKey: "pubKey0", kind: 1, tags: [], content: "content0"); - eventId0.id = "id0"; - final eventId1 = - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1"); - eventId1.id = "id1"; - final eventId2 = - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2"); - eventId2.id = "id2"; + final eventId0 = Nip01Event( + pubKey: "pubKey0", + kind: 1, + tags: [], + content: "content0", + id: "id0", + sig: null, + validSig: null); + + final eventId1 = Nip01Event( + pubKey: "pubKey1", + kind: 1, + tags: [], + content: "content1", + id: "id1", + sig: null, + validSig: null); + + final eventId2 = Nip01Event( + pubKey: "pubKey2", + kind: 1, + tags: [], + content: "content2", + id: "id2", + sig: null, + validSig: null); final List idEvents = [ eventId0, diff --git a/packages/ndk/test/usecases/cache_write_test.dart b/packages/ndk/test/usecases/cache_write_test.dart index 79e342faa..3e2fb101b 100644 --- a/packages/ndk/test/usecases/cache_write_test.dart +++ b/packages/ndk/test/usecases/cache_write_test.dart @@ -9,26 +9,43 @@ void main() async { final CacheManager myCacheManager = MemCacheManager(); final List myEvens = [ - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), ]; final List expectedEvents = [ - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), ]; setUp(() async {}); diff --git a/packages/ndk/test/usecases/lists/lists_test.dart b/packages/ndk/test/usecases/lists/lists_test.dart index d6c855b22..9d632ff5a 100644 --- a/packages/ndk/test/usecases/lists/lists_test.dart +++ b/packages/ndk/test/usecases/lists/lists_test.dart @@ -48,10 +48,11 @@ void main() async { Nip01Event event0 = await bookmarkListKey0.toEvent(signer0); Nip01Event event1 = await favoriteRelaysKey1.toEvent(signer1); - await signer0.sign(event0); - await signer1.sign(event1); + final signedEvent0 = await signer0.sign(event0); + final signedEvent1 = await signer1.sign(event1); - await relay0.startServer(textNotes: {key0: event0, key1: event1}); + await relay0 + .startServer(textNotes: {key0: signedEvent0, key1: signedEvent1}); final cache = MemCacheManager(); final NdkConfig config = NdkConfig( @@ -354,12 +355,12 @@ void main() async { final oldEvent = await oldSet.toEvent(signer1); final newEvent = await newSet.toEvent(signer1); - await signer1.sign(oldEvent); - await signer1.sign(newEvent); + final singedOldEvent = await signer1.sign(oldEvent); + final signedNewEvent = await signer1.sign(newEvent); // Save both to cache - await ndk.config.cache.saveEvent(oldEvent); - await ndk.config.cache.saveEvent(newEvent); + await ndk.config.cache.saveEvent(singedOldEvent); + await ndk.config.cache.saveEvent(signedNewEvent); // Fetch the set - should return the newer one final fetchedSet = await ndk.lists.getSetByName( @@ -428,10 +429,10 @@ void main() async { content: nip04Content, ); - await signer1.sign(nip04Event); + final signedNip04Event = await signer1.sign(nip04Event); // Parse the event back to set - should handle NIP-04 format - final parsedSet = await Nip51Set.fromEvent(nip04Event, signer1); + final parsedSet = await Nip51Set.fromEvent(signedNip04Event, signer1); expect(parsedSet, isNotNull); expect(parsedSet!.name, "nip04-test-set"); diff --git a/packages/ndk/test/usecases/metadatas/metadata_entity_test.dart b/packages/ndk/test/usecases/metadatas/metadata_entity_test.dart index bc6bda159..b40560987 100644 --- a/packages/ndk/test/usecases/metadatas/metadata_entity_test.dart +++ b/packages/ndk/test/usecases/metadatas/metadata_entity_test.dart @@ -1,3 +1,4 @@ +import 'package:ndk/domain_layer/entities/nip_01_utils.dart'; import 'package:test/test.dart'; import 'package:ndk/entities.dart'; import 'dart:convert'; diff --git a/packages/ndk/test/usecases/stream_response_cleaner/stream_response_cleaner_test.dart b/packages/ndk/test/usecases/stream_response_cleaner/stream_response_cleaner_test.dart index f3a2673be..f044e763b 100644 --- a/packages/ndk/test/usecases/stream_response_cleaner/stream_response_cleaner_test.dart +++ b/packages/ndk/test/usecases/stream_response_cleaner/stream_response_cleaner_test.dart @@ -12,25 +12,41 @@ void main() async { tags: [], content: "content1_a", ), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), ]; final List myEventsNoDublicate = [ - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), - Nip01Event(pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), - Nip01Event(pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), - Nip01Event(pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_a"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_b"), + Nip01Event( + pubKey: "pubKey1", kind: 1, tags: [], content: "content1_c"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_a"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_b"), + Nip01Event( + pubKey: "pubKey2", kind: 1, tags: [], content: "content2_c"), + Nip01Event( + pubKey: "duplicate", kind: 1, tags: [], content: "duplicate"), ]; group('stream response cleaner', () { diff --git a/packages/ndk/test/usecases/zaps/zaps_test.dart b/packages/ndk/test/usecases/zaps/zaps_test.dart index d28bdd661..cf0ac100b 100644 --- a/packages/ndk/test/usecases/zaps/zaps_test.dart +++ b/packages/ndk/test/usecases/zaps/zaps_test.dart @@ -115,7 +115,7 @@ void main() { ['relays', ...relays] ])); expect(zapRequest.content, comment); - expect(zapRequest.sig, isNotEmpty); + expect(zapRequest.sig, isNotNull); }); test('zapRequest throws error for negative amount', () async { diff --git a/packages/ndk/test/verifiers/verify_event_stream_test.dart b/packages/ndk/test/verifiers/verify_event_stream_test.dart index e90f4c8a2..d1e223e57 100644 --- a/packages/ndk/test/verifiers/verify_event_stream_test.dart +++ b/packages/ndk/test/verifiers/verify_event_stream_test.dart @@ -1,8 +1,7 @@ import 'dart:async'; -import 'package:ndk/domain_layer/entities/nip_01_event.dart'; -import 'package:ndk/domain_layer/repositories/event_verifier.dart'; import 'package:ndk/domain_layer/usecases/requests/verify_event_stream.dart'; +import 'package:ndk/ndk.dart'; import 'package:test/test.dart'; import '../mocks/mock_event_verifier.dart'; diff --git a/packages/nip07_event_signer/lib/src/nip07_event_signer_stub.dart b/packages/nip07_event_signer/lib/src/nip07_event_signer_stub.dart index 50207373c..71c1c8c37 100644 --- a/packages/nip07_event_signer/lib/src/nip07_event_signer_stub.dart +++ b/packages/nip07_event_signer/lib/src/nip07_event_signer_stub.dart @@ -4,7 +4,7 @@ class Nip07EventSigner implements EventSigner { String? cachedPublicKey; Nip07EventSigner({this.cachedPublicKey}); - + @override bool canSign() { return false; @@ -46,7 +46,7 @@ class Nip07EventSigner implements EventSigner { } @override - Future sign(Nip01Event event) async { + Future sign(Nip01Event event) async { throw UnsupportedError('NIP-07 is not available on this platform'); } } diff --git a/packages/nip07_event_signer/lib/src/nip07_event_signer_web.dart b/packages/nip07_event_signer/lib/src/nip07_event_signer_web.dart index 8604e1b0a..88b1f5296 100644 --- a/packages/nip07_event_signer/lib/src/nip07_event_signer_web.dart +++ b/packages/nip07_event_signer/lib/src/nip07_event_signer_web.dart @@ -89,7 +89,7 @@ class Nip07EventSigner implements EventSigner { } @override - Future sign(Nip01Event event) async { + Future sign(Nip01Event event) async { if (js.nostr == null) { throw Exception('NIP-07 extension not available'); } @@ -107,8 +107,6 @@ class Nip07EventSigner implements EventSigner { // Sign the event using NIP-07 final signedEvent = await js.nostr!.signEvent(jsEvent).toDart; - // Update the original event with the signature and id - event.id = signedEvent.id!; - event.sig = signedEvent.sig!; + return event.copyWith(id: signedEvent.id!, sig: signedEvent.sig!); } } diff --git a/packages/nip07_event_signer/test/nip07_event_signer_test.dart b/packages/nip07_event_signer/test/nip07_event_signer_test.dart index 39d4ee9e3..a73da20cd 100644 --- a/packages/nip07_event_signer/test/nip07_event_signer_test.dart +++ b/packages/nip07_event_signer/test/nip07_event_signer_test.dart @@ -335,13 +335,19 @@ void main() { createdAt: 1234567890, ); - await nip07Signer.sign(event); + final signedEvent = await nip07Signer.sign(event); - expect(event.id, isNotNull); - expect(event.id.length, equals(64)); // SHA-256 hash is 64 hex characters - expect(RegExp(r'^[a-f0-9]{64}$').hasMatch(event.id), isTrue); - expect(event.sig, isNotNull); - expect(event.sig.length, equals(64)); // Signature is 64 hex characters + expect(signedEvent.id, isNotNull); + expect( + signedEvent.id.length, + equals(64), + ); // SHA-256 hash is 64 hex characters + expect(RegExp(r'^[a-f0-9]{64}$').hasMatch(signedEvent.id), isTrue); + expect(signedEvent.sig, isNotNull); + expect( + signedEvent.sig!.length, + equals(64), + ); // Signature is 64 hex characters }); test('encrypt using NIP-04', () async { @@ -602,14 +608,14 @@ void main() { content: 'GM', ); - await nip07Signer.sign(event); + final signedEvent = await nip07Signer.sign(event); // Verify event was signed - expect(event.id, isNotNull); - expect(event.sig, isNotNull); - expect(event.id.length, equals(64)); - expect(RegExp(r'^[a-f0-9]{64}$').hasMatch(event.id), isTrue); - expect(event.sig.length, equals(64)); + expect(signedEvent.id, isNotNull); + expect(signedEvent.sig, isNotNull); + expect(signedEvent.id.length, equals(64)); + expect(RegExp(r'^[a-f0-9]{64}$').hasMatch(signedEvent.id), isTrue); + expect(signedEvent.sig!.length, equals(64)); }); }); }); diff --git a/packages/nip07_event_signer/test/test_nip07_event_signer/lib/main.dart b/packages/nip07_event_signer/test/test_nip07_event_signer/lib/main.dart index f2c74bcb8..63b68e2e5 100644 --- a/packages/nip07_event_signer/test/test_nip07_event_signer/lib/main.dart +++ b/packages/nip07_event_signer/test/test_nip07_event_signer/lib/main.dart @@ -133,14 +133,10 @@ Future testSign(Nip07EventSigner nip07Signer) async { content: "GM", ); - print(jsonEncode(event.toJson())); + final signedEvent = await nip07Signer.sign(event); - await nip07Signer.sign(event); - - print(jsonEncode(event.toJson())); - - return await Bip340EventVerifier().verify(event) && - event.kind == 1 && - event.pubKey == pubKey && - event.content == "GM"; + return await Bip340EventVerifier().verify(signedEvent) && + signedEvent.kind == 1 && + signedEvent.pubKey == pubKey && + signedEvent.content == "GM"; } diff --git a/packages/objectbox/lib/data_layer/db/object_box/schema/db_nip_01_event.dart b/packages/objectbox/lib/data_layer/db/object_box/schema/db_nip_01_event.dart index 366f4d4fa..7632128d4 100644 --- a/packages/objectbox/lib/data_layer/db/object_box/schema/db_nip_01_event.dart +++ b/packages/objectbox/lib/data_layer/db/object_box/schema/db_nip_01_event.dart @@ -133,11 +133,12 @@ class DbNip01Event { createdAt: createdAt, kind: kind, tags: _tagsToList(tags), + id: nostrId, + sig: sig, + validSig: validSig, + sources: sources, ); - ndkE.id = nostrId; - ndkE.sig = sig; - ndkE.validSig = validSig; - ndkE.sources = sources; + return ndkE; } @@ -150,7 +151,7 @@ class DbNip01Event { dbTags: _listToTags(ndkE.tags).map((tag) => tag.toString()).toList(), ); dbE.nostrId = ndkE.id; - dbE.sig = ndkE.sig; + dbE.sig = ndkE.sig!; dbE.validSig = ndkE.validSig; dbE.sources = ndkE.sources; return dbE; diff --git a/packages/rust_verifier/lib/data_layer/repositories/verifiers/rust_event_verifier.dart b/packages/rust_verifier/lib/data_layer/repositories/verifiers/rust_event_verifier.dart index b2a2d5404..c15b91fb8 100644 --- a/packages/rust_verifier/lib/data_layer/repositories/verifiers/rust_event_verifier.dart +++ b/packages/rust_verifier/lib/data_layer/repositories/verifiers/rust_event_verifier.dart @@ -48,6 +48,9 @@ class RustEventVerifier implements EventVerifier { @override Future verify(Nip01Event event) async { await _isInitialized.future; + if (event.sig == null) { + return false; + } return verifyNostrEvent( eventIdHex: event.id, @@ -56,7 +59,7 @@ class RustEventVerifier implements EventVerifier { kind: event.kind, tags: event.tags, content: event.content, - signatureHex: event.sig, + signatureHex: event.sig!, ); } } diff --git a/packages/sample-app/lib/amber_page.dart b/packages/sample-app/lib/amber_page.dart index 2802e2f15..0e96a84d2 100644 --- a/packages/sample-app/lib/amber_page.dart +++ b/packages/sample-app/lib/amber_page.dart @@ -91,7 +91,7 @@ class _AmberPageState extends State { ); EventVerifier eventVerifier = RustEventVerifier(); eventVerifier - .verify(Nip01Event.fromJson(json.decode(value['event']))) + .verify(Nip01EventModel.fromJson(json.decode(value['event']))) .then((valid) { setState(() { _text = valid ? "✅ Valid" : "❌ Invalid"; diff --git a/packages/sample-app/lib/verifiers_performance.dart b/packages/sample-app/lib/verifiers_performance.dart index e6bd5fdc7..5d848d26f 100644 --- a/packages/sample-app/lib/verifiers_performance.dart +++ b/packages/sample-app/lib/verifiers_performance.dart @@ -43,10 +43,11 @@ class _VerifiersPerformancePageState extends State { ); }); + final List signedList = []; for (final event in list) { - await widget.ndk.accounts.sign(event); + signedList.add(await widget.ndk.accounts.sign(event)); } - return list; + return signedList; } _verifyEventsWaiting({required EventVerifier verifier}) async { diff --git a/packages/sembast_cache_manager/lib/src/ndk_extensions.dart b/packages/sembast_cache_manager/lib/src/ndk_extensions.dart index 9203a6739..9672cc05e 100644 --- a/packages/sembast_cache_manager/lib/src/ndk_extensions.dart +++ b/packages/sembast_cache_manager/lib/src/ndk_extensions.dart @@ -21,18 +21,16 @@ extension Nip01EventExtension on Nip01Event { } static Nip01Event fromJsonStorage(Map json) { - final event = Nip01Event.fromJson(json); - // Restore additional properties not handled by fromJson - if (json['validSig'] != null) { - event.validSig = json['validSig'] as bool?; - } - if (json['sources'] != null) { - event.sources = List.from(json['sources'] as List); - } + final event = Nip01EventModel.fromJson(json); - return event; + return event.copyWith( + validSig: json['validSig'] as bool?, + sources: json['sources'] != null + ? List.from(json['sources'] as List) + : const [], + ); } } diff --git a/packages/sembast_cache_manager/test/sembast_cache_manager_test.dart b/packages/sembast_cache_manager/test/sembast_cache_manager_test.dart index 7a6f2370e..d9c54d461 100644 --- a/packages/sembast_cache_manager/test/sembast_cache_manager_test.dart +++ b/packages/sembast_cache_manager/test/sembast_cache_manager_test.dart @@ -237,9 +237,12 @@ void main() { test('removeAllEventsByPubKey', () async { final events = [ - Nip01Event(pubKey: 'pubkey1', kind: 1, tags: [], content: 'Event 1'), - Nip01Event(pubKey: 'pubkey1', kind: 1, tags: [], content: 'Event 2'), - Nip01Event(pubKey: 'pubkey2', kind: 1, tags: [], content: 'Event 3'), + Nip01Event( + pubKey: 'pubkey1', kind: 1, tags: [], content: 'Event 1'), + Nip01Event( + pubKey: 'pubkey1', kind: 1, tags: [], content: 'Event 2'), + Nip01Event( + pubKey: 'pubkey2', kind: 1, tags: [], content: 'Event 3'), ]; await cacheManager.saveEvents(events);