Skip to content

Commit

Permalink
updates nip46 signer
Browse files Browse the repository at this point in the history
comm_k can be different to the signer pub_k
access to the underlying client to get the status of comms
  • Loading branch information
monty committed Jul 9, 2024
1 parent 092398f commit efdfa7b
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 17 deletions.
2 changes: 2 additions & 0 deletions examples/nip46_signer_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand Down
115 changes: 98 additions & 17 deletions src/monstr/signing/nip46.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
import datetime
import logging
import asyncio
import json
Expand All @@ -12,7 +13,6 @@
from monstr.util import util_funcs



class SignerException(Exception):
pass

Expand All @@ -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):

Expand All @@ -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

Expand Down Expand Up @@ -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}')

Expand All @@ -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:
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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):

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit efdfa7b

Please sign in to comment.