Skip to content

Commit

Permalink
Merge branch 'master' into lnurl-zaps
Browse files Browse the repository at this point in the history
  • Loading branch information
frnandu authored Dec 25, 2024
2 parents 5942dae + 85f4b0c commit c84c4df
Show file tree
Hide file tree
Showing 10 changed files with 398 additions and 15 deletions.
1 change: 1 addition & 0 deletions packages/ndk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ await for (final event in response.stream) {
- [x] Relay List Metadata ([NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md))
- [x] Wallet Connect API ([NIP-47](https://github.com/nostr-protocol/nips/blob/master/47.md))
- [X] Zaps ([NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md))
- [x] Authentication of clients to relays ([NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md))
- [ ] Bech Encoding support ([NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md))
- [ ] Badges ([NIP-58](https://github.com/nostr-protocol/nips/blob/master/58.md))

Expand Down
14 changes: 11 additions & 3 deletions packages/ndk/lib/domain_layer/entities/filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ class Filter {
/// List of replaceable event tags to filter by.
List<String>? aTags;

List<String>? dTags; // d tags
/// ...
List<String>? dTags;

/// other tags
List<String>? mTags;

/// Unix timestamp to filter events created after this time.
int? since;
Expand All @@ -49,6 +53,7 @@ class Filter {
this.tTags,
this.aTags,
this.dTags,
this.mTags,
this.since,
this.until,
this.limit,
Expand All @@ -63,6 +68,7 @@ class Filter {
tTags = map['#t'] == null ? null : List<String>.from(map['#t']);
aTags = map['#a'] == null ? null : List<String>.from(map['#a']);
dTags = map['#d'] == null ? null : List<String>.from(map['#d']);
mTags = map['#m'] == null ? null : List<String>.from(map['#m']);
search = map['search'];
since = map['since'];
until = map['until'];
Expand All @@ -72,13 +78,14 @@ class Filter {
Map<String, dynamic> toMap() {
var body = {
"ids": ids,
"authors": authors,
"authors": authors!=null && authors!.isNotEmpty? authors : null,
"kinds": kinds,
"#e": eTags,
"#p": pTags,
"#t": tTags,
"#d": dTags,
"#a": aTags,
"#m": mTags,
"since": since,
"until": until,
"search": search,
Expand Down Expand Up @@ -138,14 +145,15 @@ class Filter {
Filter clone() {
return Filter(
ids: ids != null ? List<String>.from(ids!) : null,
authors: authors != null ? List<String>.from(authors!) : null,
authors: authors != null && authors!.isNotEmpty ? List<String>.from(authors!) : null,
kinds: kinds != null ? List<int>.from(kinds!) : null,
search: search,
eTags: eTags != null ? List<String>.from(eTags!) : null,
pTags: pTags != null ? List<String>.from(pTags!) : null,
tTags: tTags != null ? List<String>.from(tTags!) : null,
aTags: aTags != null ? List<String>.from(aTags!) : null,
dTags: dTags != null ? List<String>.from(dTags!) : null,
mTags: mTags != null ? List<String>.from(mTags!) : null,
since: since,
until: until,
limit: limit,
Expand Down
14 changes: 14 additions & 0 deletions packages/ndk/lib/domain_layer/usecases/nip42/auth_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:ndk/domain_layer/entities/nip_01_event.dart';

/// auth event to send to relays
class AuthEvent extends Nip01Event {
/// auth kind
// ignore: constant_identifier_names
static const int KIND = 22242;

/// Zap Request
AuthEvent({
required super.pubKey,
required super.tags,
}) : super(kind: KIND, content: '');
}
42 changes: 30 additions & 12 deletions packages/ndk/lib/domain_layer/usecases/relay_manager.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer' as developer;

import '../../config/bootstrap_relays.dart';
import '../../config/relay_defaults.dart';
Expand All @@ -16,8 +17,10 @@ import '../entities/relay_connectivity.dart';
import '../entities/relay_info.dart';
import '../entities/request_state.dart';
import '../entities/tuple.dart';
import '../repositories/event_signer.dart';
import '../repositories/nostr_transport.dart';
import 'engines/network_engine.dart';
import 'nip42/auth_event.dart';

/// relay manager, responsible for lifecycle of relays, sending messages, \
/// and help with tracking of requests
Expand All @@ -30,6 +33,9 @@ class RelayManager<T> {
/// global state obj
GlobalState globalState;

/// signer for nip-42 AUTH challenges from relays
EventSigner? signer;

/// nostr transport factory, to create new transports (usually websocket)
final NostrTransportFactory nostrTransportFactory;

Expand All @@ -43,6 +49,7 @@ class RelayManager<T> {
RelayManager(
{required this.globalState,
required this.nostrTransportFactory,
this.signer,
this.engineAdditionalDataFactory,
List<String>? bootstrapRelays,
allowReconnect = true}) {
Expand All @@ -54,7 +61,7 @@ class RelayManager<T> {
bool get allowReconnectRelays => _allowReconnectRelays;

/// sets allowed to reconnectRelays
set allowReconnectRelays(bool b) {
void set allowReconnectRelays(bool b) {
_allowReconnectRelays = b;
}

Expand Down Expand Up @@ -101,7 +108,7 @@ class RelayManager<T> {

/// checks if a relay is connecting
bool isRelayConnecting(String url) {
Relay? relay = globalState.relays[url]?.relay;
Relay? relay = globalState.relays[url]?.relay ?? null;
return relay != null && relay.connecting;
}

Expand Down Expand Up @@ -138,6 +145,7 @@ class RelayManager<T> {
);
globalState.relays[url] = relayConnectivity;
}
;
relayConnectivity.relay.tryingToConnect();

/// TO BE REMOVED, ONCE WE FIND A WAY OF AVOIDING PROBLEM WHEN CONNECTING TO THIS
Expand All @@ -159,7 +167,7 @@ class RelayManager<T> {

_startListeningToSocket(relayConnectivity);

Logger.log.t("connected to relay: $url");
developer.log("connected to relay: $url");
relayConnectivity.relay.succeededToConnect();
relayConnectivity.stats.connections++;
getRelayInfo(url).then((info) {
Expand All @@ -183,7 +191,7 @@ class RelayManager<T> {
await relayConnectivity.relayTransport!.ready
.timeout(Duration(seconds: DEFAULT_WEB_SOCKET_CONNECT_TIMEOUT))
.onError((error, stackTrace) {
Logger.log.e("error connecting to relay $url: $error");
Logger.log.e("error connecting to relay ${url}: $error");
return []; // Return an empty list in case of error
});
}
Expand Down Expand Up @@ -301,7 +309,7 @@ class RelayManager<T> {
} else {
// do not overwrite
Logger.log.w(
"registerRelayBroadcast: relay broadcast already registered for ${eventToPublish.id} $relayUrl, skipping");
"registerRelayBroadcast: relay broadcast already registered for ${eventToPublish.id} ${relayUrl}, skipping");
}
}

Expand Down Expand Up @@ -373,12 +381,22 @@ class RelayManager<T> {
" CLOSED subscription url: ${relayConnectivity.url} id: ${eventJson[1]} msg: ${eventJson.length > 2 ? eventJson[2] : ''}");
globalState.inFlightRequests.remove(eventJson[1]);
}
// TODO
// if (eventJson[0] == 'AUTH') {
// log("AUTH: ${eventJson[1]}");
// // nip 42 used to send authentication challenges
// return;
// }
if (eventJson[0] == ClientMsgType.AUTH) {
// nip 42 used to send authentication challenges
final challenge = eventJson[1];
Logger.log.d("AUTH: $challenge");
if (signer != null && signer!.canSign()) {
final auth = AuthEvent(pubKey: signer!.getPublicKey(), tags: [
["relay", relayConnectivity.url],
["challenge", challenge]
]);
signer!.sign(auth);
send(relayConnectivity, ClientMsg(ClientMsgType.AUTH, event: auth));
} else {
Logger.log.w("Received an AUTH challenge but don't have a signer configured");
}
return;
}
//
// if (eventJson[0] == 'COUNT') {
// log("COUNT: ${eventJson[1]}");
Expand All @@ -402,7 +420,7 @@ class RelayManager<T> {
if (state != null) {
RelayRequestState? request = state.requests[url];
if (request == null) {
Logger.log.w("No RelayRequestState found for id $id");
Logger.log.w("No RelayRequestState found for id ${id}");
return;
}
event.sources.add(url);
Expand Down
1 change: 1 addition & 0 deletions packages/ndk/lib/presentation_layer/init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Initialization {
case NdkEngine.RELAY_SETS:
relayManager = RelayManager(
globalState: _globalState,
signer: _ndkConfig.eventSigner,
nostrTransportFactory: _webSocketNostrTransportFactory,
bootstrapRelays: _ndkConfig.bootstrapRelays,
);
Expand Down
8 changes: 8 additions & 0 deletions packages/ndk/lib/shared/nips/nip01/client_msg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class ClientMsg {
if (type == ClientMsgType.COUNT) {
throw Exception("COUNT is not implemented yet");
}
if (type == ClientMsgType.AUTH) {
if (event == null) {
throw Exception("event is required for type AUTH");
}
}
}

_eventToJson() {
Expand All @@ -74,6 +79,8 @@ class ClientMsg {
return _reqToJson();
case ClientMsgType.CLOSE:
return _closeToJson();
case ClientMsgType.AUTH:
return _eventToJson();
case ClientMsgType.COUNT:
throw Exception("COUNT is not implemented yet");
}
Expand All @@ -90,4 +97,5 @@ class ClientMsgType {
static const String CLOSE = "CLOSE";
static const String EVENT = "EVENT";
static const String COUNT = "COUNT";
static const String AUTH = "AUTH";
}
29 changes: 29 additions & 0 deletions packages/ndk/test/mocks/mock_relay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import 'dart:convert';
import 'dart:developer';
import 'dart:io';

import 'package:bip340/bip340.dart';
import 'package:ndk/data_layer/repositories/verifiers/bip340_event_verifier.dart';
import 'package:ndk/domain_layer/repositories/event_verifier.dart';
import 'package:ndk/entities.dart';
import 'package:ndk/shared/nips/nip01/helpers.dart';
import 'package:ndk/shared/nips/nip01/key_pair.dart';

class MockRelay {
Expand All @@ -14,6 +18,7 @@ class MockRelay {
Map<KeyPair, Nip65>? nip65s;
Map<KeyPair, Nip01Event>? textNotes;
bool signEvents;
bool requireAuthForRequests;

static int startPort = 4040;

Expand All @@ -23,6 +28,7 @@ class MockRelay {
required this.name,
this.nip65s,
this.signEvents = true,
this.requireAuthForRequests = false,
int? explicitPort,
}) {
if (explicitPort != null) {
Expand Down Expand Up @@ -52,14 +58,37 @@ class MockRelay {

var stream = server.transform(WebSocketTransformer());

String challenge='';

bool signedChallenge=false;
stream.listen((webSocket) {
this.webSocket = webSocket;
if (requireAuthForRequests && !signedChallenge) {
challenge = Helpers.getRandomString(10);
webSocket.add(jsonEncode(["AUTH", challenge]));
}
webSocket.listen((message) {
if (message == "ping") {
webSocket.add("pong");
return;
}
var eventJson = json.decode(message);
if (eventJson[0] == "AUTH") {
Nip01Event event = Nip01Event.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) {
signedChallenge = true;
}
}
webSocket.add(jsonEncode(["OK", event.id, signedChallenge, signedChallenge?"":"auth-required: we can't serve requests to unauthenticated users"]));
return;
}
if (requireAuthForRequests && !signedChallenge) {
webSocket.add(jsonEncode(["CLOSED", "sub_1","auth-required: we can't serve requests to unauthenticated users"]));
return;
}
if (eventJson[0] == "REQ") {
String requestId = eventJson[1];
log('Received: $eventJson');
Expand Down
Loading

0 comments on commit c84c4df

Please sign in to comment.