diff --git a/noise/README.org b/noise/README.org index be05e1b..91945d8 100644 --- a/noise/README.org +++ b/noise/README.org @@ -3,7 +3,7 @@ The protocol was heavily inspired by the [[https://github.com/joostjager/whatsat | record type | length (bytes) | value | |-------------+----------------+-----------------------------------------------------------------| -| 5482373484 | 32 | key send preimage | +| 34349341 | 32 | key send preimage | | 34349334 | variable | chat message | | 34349335 | 65 | compressed signature + recovery id | | 34349339 | 33 | sender pubkey | diff --git a/noise/noise.py b/noise/noise.py index 866d897..f7440e3 100755 --- a/noise/noise.py +++ b/noise/noise.py @@ -12,10 +12,12 @@ from collections import namedtuple import shelve from pyln.proto.onion import OnionPayload import zbase32 +import hashlib +import os plugin = Plugin() -TLV_KEYSEND_PREIMAGE = 5482373484 +TLV_KEYSEND_PREIMAGE = 34349341 TLV_NOISE_MESSAGE = 34349334 TLV_NOISE_SIGNATURE = 34349335 @@ -73,13 +75,11 @@ def buildpath(plugin, node_id, payload, amt, exclusions): return first_hop, hops, route -def deliver(node_id, payload, amt, max_attempts=5, payment_hash=None): +def deliver(node_id, payload, amt, payment_hash, max_attempts=5): """Do your best to deliver `payload` to `node_id`. """ - if payment_hash is None: - payment_hash = ''.join(random.choice(string.hexdigits) for _ in range(64)).lower() - exclusions = [] + payment_hash = hexlify(payment_hash).decode('ASCII') for attempt in range(max_attempts): plugin.log("Starting attempt {} to deliver message to {}".format(attempt, node_id)) @@ -108,20 +108,33 @@ def deliver(node_id, payload, amt, max_attempts=5, payment_hash=None): @plugin.async_method('sendmsg') -def sendmsg(node_id, msg, plugin, request, amt=1000, **kwargs): +def sendmsg(node_id, msg, plugin, request, pay=None, **kwargs): payload = TlvPayload() payload.add_field(TLV_NOISE_MESSAGE, msg.encode('UTF-8')) + + payment_key = os.urandom(32) + payment_hash = hashlib.sha256(payment_key).digest() + + # If we don't want to tell the recipient how to claim the funds unset the + # payment_key + if pay is not None: + payload.add_field(TLV_KEYSEND_PREIMAGE, payment_key) + + # Signature generation always has to be last, otherwise we won't agree on + # the TLV payload and verification ends up with a bogus sender node_id. sigmsg = hexlify(payload.to_bytes()).decode('ASCII') - - # Sign the message: sig = plugin.rpc.signmessage(sigmsg) - sigcheck = plugin.rpc.checkmessage(sigmsg, sig['zbase']) sig = zbase32.decode(sig['zbase']) payload.add_field(TLV_NOISE_SIGNATURE, sig) - res = deliver(node_id, payload.to_bytes(), amt=amt) + res = deliver( + node_id, + payload.to_bytes(), + amt=pay if pay is not None else 10, + payment_hash=payment_hash + ) request.set_result(res) @@ -155,12 +168,21 @@ def on_htlc_accepted(onion, htlc, plugin, **kwargs): msg.sender = sigcheck['pubkey'] msg.verified = sigcheck['verified'] + preimage = payload.get(TLV_KEYSEND_PREIMAGE) + if preimage is not None: + res = { + 'result': 'resolve', + 'payment_key': hexlify(preimage.value).decode('ASCII') + } + else: + res = {'result': 'continue'} + plugin.messages.append(msg) for r in plugin.receive_waiters: r.set_result(msg.to_dict()) plugin.receive_waiters = [] - return {'result': 'continue'} + return res @plugin.init() diff --git a/noise/test_chat.py b/noise/test_chat.py index 2ba3172..6d2e447 100644 --- a/noise/test_chat.py +++ b/noise/test_chat.py @@ -64,3 +64,21 @@ def test_zbase32(): b = zbase32.decode(zb32) enc = zbase32.encode(b) assert(enc == zb32) + + +def test_msg_and_keysend(node_factory, executor): + opts = [{'plugin': plugin}, {}, {'plugin': plugin}] + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts=opts) + amt = 10000 + + # Check that l3 does not have funds initially + assert(l3.rpc.listpeers()['peers'][0]['channels'][0]['msatoshi_to_us'] == 0) + + l1.rpc.sendmsg(l3.info['id'], "Hello world!", amt) + m = l3.rpc.recvmsg(last_id=-1) + + assert(m['sender'] == l1.info['id']) + assert(m['verified'] == True) + + # Check that l3 actually got the funds I sent it + wait_for(lambda: l3.rpc.listpeers()['peers'][0]['channels'][0]['msatoshi_to_us'] == amt)