Skip to content

Commit

Permalink
NIP46 Client added with examples both server and client
Browse files Browse the repository at this point in the history
  • Loading branch information
monty committed Jun 13, 2024
1 parent e8b038d commit b941215
Show file tree
Hide file tree
Showing 8 changed files with 436 additions and 334 deletions.
19 changes: 18 additions & 1 deletion examples/nip46_client_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from monstr.event.event import Event

# url to relay used for talking to the signer
RELAY = 'ws://localhost:8080'
RELAY = 'ws://localhost:8081'


async def do_post(url, text):
Expand All @@ -22,10 +22,27 @@ async def do_post(url, text):

# from here it's just a signer interface same as if we were using BasicKeySigner
async with Client(url) as c:
# plain text
n_msg = await my_signer.ready_post(Event(kind=Event.KIND_TEXT_NOTE,
content=text))
c.publish(n_msg)

# nip4 encrypted to our self
enc_event = Event(kind=Event.KIND_TEXT_NOTE,
content=text+' - encrypted nip4')
enc_event = await my_signer.nip4_encrypt_event(enc_event,
to_pub_k=await my_signer.get_public_key())
await my_signer.sign_event(enc_event)
c.publish(enc_event)

# nip44 encrypted to our self
enc_event = Event(kind=Event.KIND_TEXT_NOTE,
content=text + ' - encrypted nip44')
enc_event = await my_signer.nip44_encrypt_event(enc_event,
to_pub_k=await my_signer.get_public_key())
await my_signer.sign_event(enc_event)
c.publish(enc_event)


if __name__ == "__main__":
logging.getLogger().setLevel(logging.DEBUG)
Expand Down
2 changes: 1 addition & 1 deletion examples/nip46_signer_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from monstr.signing.signing import BasicKeySigner

# url to relay used for talking to the signer
RELAY = 'ws://localhost:8080'
RELAY = 'ws://localhost:8081'


async def run_signer():
Expand Down
97 changes: 71 additions & 26 deletions src/monstr/encrypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,66 +213,111 @@ def __str__(self):
return '\n'.join(ret)


class Encrypter(ABC):
class DecryptionException(Exception):
pass

def __init__(self, key: Keys | str):
if isinstance(key, str):
key = Keys(priv_k=key)
if key.private_key_hex() is None:
raise ValueError(f'{self.__class__.__name__}::__init__ a key that can sign is required')

self._key = key
self._priv_k = secp256k1.PrivateKey(bytes.fromhex(self._key.private_key_hex()))
class Encrypter(ABC):

@abstractmethod
def encrypt(self, plain_text: str, to_pub_k: str) -> str:
raise NotImplementedError

@abstractmethod
def decrypt(self, payload: str, for_pub_k: str) -> str:
raise NotImplementedError

def public_key_hex(self) -> str:
raise NotImplementedError

async def aencrypt(self, plain_text: str, to_pub_k: str) -> str:
raise NotImplementedError

async def adecrypt(self, payload: str, for_pub_k: str) -> str:
raise NotImplementedError

async def apublic_key_hex(self) -> str:
raise NotImplementedError

"""
util methods for basic nostr messaging if encrypt and decrypt functions have been declared
(for basic 2 way event mesasge i.e use the p_tags)
both return new event:
encrypt_event, content encrypted and p_tags set (append your own p_tags after)
decrypt_event, content decrypted based on the p_tags
"""
def encrypt_event(self, evt: Event, to_pub_k: str | Keys) -> Event:
if isinstance(to_pub_k, Keys):
to_pub_k = to_pub_k.public_key_hex()
elif not Keys.is_valid_key(to_pub_k):
raise ValueError(f'{self.__class__.__name__}::encrypt_event invalid to_pub_k - {to_pub_k}')

ret = Event.load(evt.data())
def _get_to_pub_key_hex(self, to_pub_k: str) -> str:
ret = to_pub_k
if isinstance(ret, Keys):
ret = to_pub_k.public_key_hex()
elif not Keys.is_valid_key(ret):
raise ValueError(f'{self.__class__.__name__}::encrypt_event invalid to_pub_k - {ret}')
return ret

def _make_encrypt_event(self, src_event: Event, to_k: str) -> Event:
to_k_hex = self._get_to_pub_key_hex(to_k)

# copy the event
ret = Event.load(src_event.data())
ret.tags = [['p', to_k_hex]]
return ret

def encrypt_event(self, evt: Event, to_pub_k: str | Keys) -> Event:
ret = self._make_encrypt_event(evt, to_pub_k)
# the pub_k author must be us
ret.pub_key = self._key.public_key_hex()
ret.pub_key = self.public_key_hex()
# change content to cipher_text
ret.content = self.encrypt(plain_text=evt.content,
to_pub_k=to_pub_k)

ret.tags = [['p', to_pub_k]]
ret.content = self.encrypt(plain_text=ret.content,
to_pub_k=ret.tags.get_tag_value_pos('p'))
return ret

async def aencrypt_event(self, evt: Event, to_pub_k: str | Keys) -> Event:
ret = self._make_encrypt_event(evt, to_pub_k)
# the pub_k author must be us
ret.pub_key = await self.apublic_key_hex()
# change content to cipher_text
ret.content = await self.aencrypt(plain_text=ret.content,
to_pub_k=ret.tags.get_tag_value_pos('p'))
return ret

def decrypt_event(self, evt: Event) -> Event:
pub_k = evt.pub_key
if pub_k == self._key.public_key_hex():
if pub_k == self.public_key_hex():
pub_k = evt.p_tags[0]

ret = Event.load(evt.data())
ret.content = self.decrypt(payload=evt.content,
for_pub_k=pub_k)
return ret

async def adecrypt_event(self, evt: Event) -> Event:
pub_k = evt.pub_key
if pub_k == await self.apublic_key_hex():
pub_k = evt.p_tags[0]

# always a copy
ret = Event.load(evt.data())

class DecryptionException(Exception):
pass
ret.content = await self.adecrypt(payload=evt.content,
for_pub_k=pub_k)
return ret


class KeyEncrypter(Encrypter):

def __init__(self, key: Keys | str):
if isinstance(key, str):
key = Keys(priv_k=key)
if key.private_key_hex() is None:
raise ValueError(f'{self.__class__.__name__}::__init__ a key that can sign is required')

self._key = key
self._priv_k = secp256k1.PrivateKey(bytes.fromhex(self._key.private_key_hex()))

def public_key_hex(self) -> str:
return self._key.public_key_hex()


class NIP4Encrypt(Encrypter):
class NIP4Encrypt(KeyEncrypter):

def __init__(self, key: Keys | str):
super().__init__(key)
Expand Down Expand Up @@ -342,7 +387,7 @@ def decrypt(self, payload: str, for_pub_k: str) -> str:
for_pub_k=for_pub_k).decode('utf8')


class NIP44Encrypt(Encrypter):
class NIP44Encrypt(KeyEncrypter):
"""
base functionality for implementing NIP44
https://github.com/paulmillr/nip44
Expand Down
8 changes: 8 additions & 0 deletions src/monstr/event/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,10 @@ def id(self):
self._get_id()
return self._id

@id.setter
def id(self, id):
self._id = id

@property
def short_id(self):
# shorter version of id for display, note id doesn't until signing
Expand Down Expand Up @@ -605,6 +609,10 @@ def content(self, content):
def sig(self):
return self._sig

@sig.setter
def sig(self, sig):
self._sig = sig

def __str__(self):
ret = super(Event, self).__str__()
# hopefully id is set but it might not be if the event is being prepeped
Expand Down
Loading

0 comments on commit b941215

Please sign in to comment.