import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:nostr_sdk/client_utils/keys.dart'; import 'package:nostr_sdk/event.dart'; import 'package:nostr_sdk/event_kind.dart'; import 'package:nostr_sdk/filter.dart'; import 'package:nostr_sdk/nip46/nostr_remote_request.dart'; import 'package:nostr_sdk/nip46/nostr_remote_response.dart'; import 'package:nostr_sdk/relay/relay.dart'; import 'package:nostr_sdk/relay/relay_isolate.dart'; import 'package:nostr_sdk/relay/relay_status.dart'; import 'package:nostr_sdk/signer/local_nostr_signer.dart'; import 'package:nostr_sdk/signer/nostr_signer.dart'; import 'package:nostr_sdk/utils/string_util.dart'; import 'package:nowser/const/auth_type.dart'; import 'package:nowser/data/app.dart'; import 'package:nowser/provider/permission_check_mixin.dart'; import '../data/remote_signing_info.dart'; import '../data/remote_signing_info_db.dart'; import '../main.dart'; class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { BuildContext? context; void updateContext(BuildContext _context) { context = _context; } // localPubkey - Relay Map> relayMap = {}; // localPubkey - RemoteSigningInfo Map remoteSigningInfoMap = {}; // localPubkey - App Map appMap = {}; Future reload() async { relayMap = {}; remoteSigningInfoMap = {}; appMap = {}; } Future load() async { var remoteAppList = appProvider.remoteAppList(); for (var remoteApp in remoteAppList) { await add(remoteApp); } } Future add(App remoteApp) async { var remoteSigningInfo = await RemoteSigningInfoDB.getByAppId(remoteApp.id!); if (remoteSigningInfo != null && StringUtil.isNotBlank(remoteSigningInfo.remoteSignerKey) && StringUtil.isNotBlank(remoteSigningInfo.localPubkey) && StringUtil.isNotBlank(remoteSigningInfo.relays)) { var localPubkey = remoteSigningInfo.localPubkey!; var pubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!); var relayAddrs = remoteSigningInfo.relays!.split(","); List relays = []; for (var relayAddr in relayAddrs) { // use pubkey relace with var relay = RelayIsolate(relayAddr, RelayStatus(localPubkey)); var filter = Filter(p: [pubkey]); relay.pendingAuthedMessages .add(["REQ", StringUtil.rndNameStr(10), filter.toJson()]); relay.onMessage = _onEvent; relay.connect(); relays.add(relay); } relayMap[localPubkey] = relays; remoteSigningInfoMap[localPubkey] = remoteSigningInfo; } } Future onRequest( Relay relay, NostrRemoteRequest request, RemoteSigningInfo remoteSigningInfo, App app, ) async { String localPubkey = remoteSigningInfo.localPubkey!; NostrSigner signer = LocalNostrSigner(remoteSigningInfo.remoteSignerKey!); var remoteSignerPubkey = await signer.getPublicKey(); var appType = app.appType!; var code = app.code!; NostrRemoteResponse? response; if (request.method == "ping") { response = NostrRemoteResponse(request.id, "pong"); sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!); } else { int? eventKind; String? authDetail; int authType = AuthType.GET_PUBLIC_KEY; dynamic eventObj; if (request.method == "sign_event") { authType = AuthType.SIGN_EVENT; authDetail = request.params[0]; eventObj = jsonDecode(authDetail); eventKind = eventObj["kind"]; } else if (request.method == "get_relays") { authType = AuthType.GET_RELAYS; } else if (request.method == "get_public_key") { authType = AuthType.GET_PUBLIC_KEY; } else if (request.method == "nip04_encrypt") { authType = AuthType.NIP04_ENCRYPT; authDetail = request.params[1]; } else if (request.method == "nip04_decrypt") { authType = AuthType.NIP04_DECRYPT; authDetail = request.params[1]; } else if (request.method == "nip44_encrypt") { authType = AuthType.NIP44_ENCRYPT; authDetail = request.params[1]; } else if (request.method == "nip44_decrypt") { authType = AuthType.NIP44_DECRYPT; authDetail = request.params[1]; } checkPermission(context!, appType, code, authType, eventKind: eventKind, authDetail: authDetail, (app) { response = NostrRemoteResponse(request.id, "", error: "forbid"); sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!); }, (app, signer) async { if (request.method == "sign_event") { var tags = eventObj["tags"]; Event? event = Event( app.pubkey!, eventObj["kind"], tags ?? [], eventObj["content"], createdAt: eventObj["created_at"]); log(jsonEncode(event.toJson())); event = await signer.signEvent(event); if (event == null) { log("sign event fail"); return; } response = NostrRemoteResponse(request.id, jsonEncode(event.toJson())); } else if (request.method == "get_relays") { // TODO } else if (request.method == "get_public_key") { var pubkey = await signer.getPublicKey(); response = NostrRemoteResponse(request.id, pubkey!); } else if (request.method == "nip04_encrypt") { var text = await signer.encrypt(localPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } else if (request.method == "nip04_decrypt") { var text = await signer.decrypt(localPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } else if (request.method == "nip44_encrypt") { var text = await signer.nip44Encrypt(localPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } else if (request.method == "nip44_decrypt") { var text = await signer.nip44Decrypt(localPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!); }); } } Future sendResponse(Relay relay, NostrRemoteResponse? response, NostrSigner signer, String localPubkey, String remoteSignerPubkey) async { if (response != null) { var result = await response.encrypt(signer, localPubkey); var event = Event( remoteSignerPubkey!, EventKind.NOSTR_REMOTE_SIGNING, [ ["p", localPubkey] ], result!); relay.send(["event", event.toJson()]); } } Future _onEvent(Relay relay, List json) async { var localPubkey = relay.relayStatus.addr; var remoteSigningInfo = remoteSigningInfoMap[localPubkey]; if (remoteSigningInfo == null) { print("remoteSigningInfo is null"); return; } var remotePubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!); var nostrSigner = LocalNostrSigner(remoteSigningInfo.remoteSignerKey!); final messageType = json[0]; if (messageType == 'EVENT') { try { final event = Event.fromJson(json[2]); // add some statistics relay.relayStatus.noteReceive(); event.sources.add(relay.url); if (event.kind == EventKind.NOSTR_REMOTE_SIGNING) { var request = await NostrRemoteRequest.decrypt( event.content, nostrSigner, localPubkey); if (request != null) {} } } catch (err) { log(err.toString()); } } else if (messageType == 'EOSE') { } else if (messageType == "NOTICE") { } else if (messageType == "AUTH") { // auth needed if (json.length < 2) { log("AUTH result not right."); return; } final challenge = json[1] as String; var tags = [ ["relay", relay.url], ["challenge", challenge] ]; Event? event = Event(remotePubkey, EventKind.AUTHENTICATION, tags, ""); event = await nostrSigner.signEvent(event); if (event != null) { relay.send(["AUTH", event.toJson()], forceSend: true); relay.relayStatus.authed = true; if (relay.pendingAuthedMessages.isNotEmpty) { Future.delayed(const Duration(seconds: 1), () { for (var message in relay.pendingAuthedMessages) { relay.send(message); } relay.pendingAuthedMessages.clear(); }); } } } } List _penddingRemoteApps = []; List get penddingRemoteApps => _penddingRemoteApps; void addRemoteSigningInfo(RemoteSigningInfo remoteSigningInfo) { RemoteSigningInfoDB.insert(remoteSigningInfo); reloadPenddingRemoteApps(); notifyListeners(); } Future reloadPenddingRemoteApps() async { var list = await RemoteSigningInfoDB.penddingRemoteSigningInfo(); _penddingRemoteApps = list; notifyListeners(); } /** * The below code is design for relay n - remoteSignerKey n */ // Map relayStatusMap = {}; // // relayAddr - Relay // Map relayMap = {}; // // remoteSignerPubkey - RemoteSigningInfo // Map remoteSigningInfoMap = {}; // Map> relayToRemoteSignerPubkeys = {}; // Map> remoteSignerPubkeyToRelays = {}; // /// relay - remoteSignerKey > n - n // /// ==> // /// relay - remoteSignerKey > 1 - n // /// remoteSigner - relay > 1 - n // Future load() async { // var remoteAppList = appProvider.remoteAppList(); // for (var remoteApp in remoteAppList) { // var remoteSigningInfo = // await RemoteSigningInfoDB.getByAppId(remoteApp.id!); // if (remoteSigningInfo != null && // StringUtil.isNotBlank(remoteSigningInfo.remoteSignerKey) && // StringUtil.isNotBlank(remoteSigningInfo.localPubkey) && // StringUtil.isNotBlank(remoteSigningInfo.relays)) { // var pubkey = getPublicKey(remoteSigningInfo.secret!); // var relays = remoteSigningInfo.relays!.split(","); // remoteSignerPubkeyToRelays[pubkey] = relays; // remoteSigningInfoMap[pubkey] = remoteSigningInfo; // for (var relay in relays) { // relay = RelayAddrUtil.handle(relay); // var pubkeys = relayToRemoteSignerPubkeys[relay]; // if (pubkeys == null) { // pubkeys = []; // relayToRemoteSignerPubkeys[relay] = pubkeys; // } // if (!pubkeys.contains(pubkey)) { // pubkeys.add(pubkey); // } // } // } // } // // data handle complete, begin to init relays // for (var entry in relayToRemoteSignerPubkeys.entries) { // var relayAddr = entry.key; // var pubkeys = entry.value; // var relayStatus = RelayStatus(relayAddr); // relayStatusMap[relayAddr] = relayStatus; // var relay = RelayIsolate(relayAddr, relayStatus); // relayMap[relayAddr] = relay; // // var filter = Filter( // // p: pubkeys, since: DateTime.now().millisecondsSinceEpoch ~/ 1000); // } // } }