From efdfa7b1519c57f57d786e53a3cffe1b87e7cc44 Mon Sep 17 00:00:00 2001 From: monty Date: Tue, 9 Jul 2024 11:41:56 +0100 Subject: [PATCH] updates nip46 signer comm_k can be different to the signer pub_k access to the underlying client to get the status of comms --- examples/nip46_signer_service.py | 2 + src/monstr/signing/nip46.py | 115 ++++++++++++++++++++++++++----- 2 files changed, 100 insertions(+), 17 deletions(-) diff --git a/examples/nip46_signer_service.py b/examples/nip46_signer_service.py index 760398e..c9af448 100644 --- a/examples/nip46_signer_service.py +++ b/examples/nip46_signer_service.py @@ -17,9 +17,11 @@ async def run_signer(): # create the signing service my_signer = NIP46ServerConnection(signer=BasicKeySigner(n_keys), + same_signer_for_comm=True, relay=RELAY) # output info needed for client to connect to the signer + print(f'signing as {n_keys.public_key_hex()}') print(await my_signer.bunker_url) # wait forever... diff --git a/src/monstr/signing/nip46.py b/src/monstr/signing/nip46.py index c80de45..9fb35a3 100644 --- a/src/monstr/signing/nip46.py +++ b/src/monstr/signing/nip46.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +import datetime import logging import asyncio import json @@ -12,7 +13,6 @@ from monstr.util import util_funcs - class SignerException(Exception): pass @@ -22,6 +22,62 @@ class NIP46AuthoriseInterface(ABC): async def authorise(self, method: str, id: str, params: [str]) -> bool: pass +# some basic authorisers +class AuthoriseAll(NIP46AuthoriseInterface): + + def __init__(self, auth_info: callable = None): + self._auth_info = auth_info + + async def authorise(self, method: str, id: str, params: [str]) -> bool: + if self._auth_info: + await self._auth_info(method, id, params) + return True + + +class RequestAuthorise(NIP46AuthoriseInterface): + + def __init__(self, + request_auth: callable, + auth_info: callable = None): + + self._request_auth = request_auth + self._auth_info = auth_info + + async def authorise(self, method: str, id: str, params: [str]) -> bool: + if self._auth_info: + await self._auth_info(method, id, params) + + return await self._request_auth(method, id, params) + + +class TimedAuthorise(NIP46AuthoriseInterface): + + def __init__(self, + request_auth: callable, + auth_info: callable = None, + auth_mins = 10): + + self._auth_info = auth_info + self._do_request_auth = RequestAuthorise(request_auth=request_auth) + + self._last_auth_at = None + self._auth_delta = datetime.timedelta(minutes=auth_mins) + + async def authorise(self, method: str, id: str, params: [str]) -> bool: + now = datetime.datetime.now() + + # maybe we need to reauth? + if self._last_auth_at is None or (now - self._last_auth_at) > self._auth_delta: + ret = await self._do_request_auth.authorise(method, id, params) + if ret: + self._last_auth_at = now + else: + ret = True + if self._auth_delta: + await self._auth_info(method, id, params) + + return ret + class NIP46Comm(EventHandler, ABC): @@ -43,6 +99,11 @@ def __init__(self, if isinstance(relay, str): self._relay = [relay] + # maybe could execept client objs but just makes things cleaner if we only use our own client obj + for i in range(0, len(self._relay)): + if not isinstance(self._relay[i],str): + raise ValueError(f'NIP46Comm::__init__: should be str got {self._relay[i]}') + self._client = ClientPool(relay) self._run = False @@ -97,7 +158,6 @@ async def _my_event_consumer(self): try: args = await self._event_q.get() await self.ado_event(*args) - except Exception as e: logging.debug(f'NIP46Comm::_my_event_consumer: {e}') @@ -112,10 +172,8 @@ def do_event(self, the_client: Client, sub_id, evt: Event): ) async def ado_event(self, the_client: Client, sub_id, evt: Event): - # pull of events that were put on the queue bu do_event and deal with them decrypted_evt = await self._comm_signer.nip4_decrypt_event(evt) - try: cmd_dict = json.loads(decrypted_evt.content) if 'method' in cmd_dict and self._on_command: @@ -141,13 +199,11 @@ async def _do_response(self, id: str = None): if id is None: id = util_funcs.get_rnd_hex_str(8) - evt = await self._get_msg_event(json.dumps({ 'id': id, 'result': result, 'error': error }), to_k) - self._client.publish(evt) async def do_request(self, method: str, params: [str], to_k, id: str = None): @@ -168,7 +224,7 @@ async def do_request(self, method: str, params: [str], to_k, id: str = None): return id - def run(self): + def run(self, on_status=None) -> ClientPool: self._run = True # make client obj that will actually do the comm @@ -186,12 +242,19 @@ def on_connect(my_client: Client): asyncio.create_task(aconnect(my_client)) self._client.set_on_connect(on_connect) + self._client.set_on_status(on_status) asyncio.create_task(self._client.run()) + return self._client + def end(self): self._run = False self._client.end() + @property + def client(self) -> ClientPool: + return self._client + async def __aenter__(self): self.run() @@ -208,14 +271,21 @@ class NIP46ServerConnection: def __init__(self, signer: SignerInterface, relay: [str], - authoriser: NIP46AuthoriseInterface = None): + authoriser: NIP46AuthoriseInterface = None, + same_signer_for_comm: bool = True): # this is the key we'll sign as self._signer = signer + self._comm_sign = None + # it's probably better to use a rnd key for the signing comm but some services don't seem to + # like this + if same_signer_for_comm: + self._comm_sign = self._signer + # this is the chanel we sign over self._comm = NIP46Comm(relay=relay, - comm_signer=signer, + comm_signer=self._comm_sign, on_command=self._do_command) # called before we do any method @@ -380,16 +450,22 @@ async def _do_command(self, id: str, method: str, params: [str], evt: Event) -> return ret - async def run(self): + async def run(self, on_status: callable = None) -> ClientPool: self._run = True - self._comm.run() + comm_client = self._comm.run(on_status=on_status) while self._run is True: await asyncio.sleep(0.1) + return comm_client def end(self): self._run = False self._comm.end() + @property + def client(self) -> ClientPool: + return self._comm.client + + class NIP4SignerEncrypter(Encrypter): @@ -489,14 +565,14 @@ async def _wait_response(self, id:str, time_out: int = None): async def _do_method(self, method: str, args: list) -> str: logging.debug(f'NIP46Signer::_do_method: {method} - {args}') - to_k = self._sign_as_k - if method == 'connect' or to_k is None: - to_k = self._signer_k + # to_k = self._sign_as_k + # if method == 'connect' or to_k is None: + # to_k = self._signer_k # did we already get key to connect on? return await self._comm.do_request(method=method, params=args, - to_k=to_k) + to_k=self._signer_k) async def _get_connect(self): if self._sign_as_k is None: @@ -554,12 +630,17 @@ async def nip44_decrypt_event(self, evt: Event) -> Event: self._enc = NIP44SignerEncrypter(self) return await self._enc.adecrypt_event(evt) - def run(self): - self._comm.run() + def run(self, on_status: callable = None) -> ClientPool: + self._comm.run(on_status=on_status) + return self._comm.client def end(self): self._comm.end() + @property + def client(self) -> ClientPool: + return self._comm.client + async def __aenter__(self): self._comm.run() return self