mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
pyln: Split pylightning into multiple pyln modules
This is the first step to transition to a better organized python module structure. Sadly we can't reuse the `pylightning` module as a namespace module since having importable things in the top level of the namespace is not allowed in any of the namespace variants [1], hence we just switch over to the `pyln` namespace. The code the was under `lightning` will now be reachable under `pyln.client` and we add the `pyln.proto` module for all the things that are independent of talking to lightningd and can be used for protocol testing. [1] https://packaging.python.org/guides/packaging-namespace-packages/ Signed-off-by: Christian Decker <decker.christian@gmail.com>
This commit is contained in:
parent
4e0c3f098c
commit
3418e59d76
3
.gitignore
vendored
3
.gitignore
vendored
@ -39,4 +39,7 @@ tools/headerversions
|
||||
contrib/pylightning/build/
|
||||
contrib/pylightning/dist/
|
||||
contrib/pylightning/pylightning.egg-info/
|
||||
contrib/pyln-*/build/
|
||||
contrib/pyln-*/dist/
|
||||
contrib/pyln-*/pyln_*.egg-info/
|
||||
devtools/create-gossipstore
|
||||
|
@ -1,2 +0,0 @@
|
||||
cryptography==2.7
|
||||
coincurve==12.0.0
|
101
contrib/pyln-client/README.md
Normal file
101
contrib/pyln-client/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
# pyln-client: A python client library for lightningd
|
||||
|
||||
This package implements the Unix socket based JSON-RPC protocol that
|
||||
`lightningd` exposes to the rest of the world. It can be used to call
|
||||
arbitrary functions on the RPC interface, and serves as a basis for plugins
|
||||
written in python.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
`pyln-client` is available on `pip`:
|
||||
|
||||
```
|
||||
pip install pyln-client
|
||||
```
|
||||
|
||||
Alternatively you can also install the development version to get access to
|
||||
currently unreleased features by checking out the c-lightning source code and
|
||||
installing into your python3 environment:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ElementsProject/lightning.git
|
||||
cd lightning/contrib/pyln-client
|
||||
python3 setup.py develop
|
||||
```
|
||||
|
||||
This will add links to the library into your environment so changing the
|
||||
checked out source code will also result in the environment picking up these
|
||||
changes. Notice however that unreleased versions may change API without
|
||||
warning, so test thoroughly with the released version.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
### Using the JSON-RPC client
|
||||
```py
|
||||
"""
|
||||
Generate invoice on one daemon and pay it on the other
|
||||
"""
|
||||
from pyln.client import LightningRpc
|
||||
import random
|
||||
|
||||
# Create two instances of the LightningRpc object using two different c-lightning daemons on your computer
|
||||
l1 = LightningRpc("/tmp/lightning1/lightning-rpc")
|
||||
l5 = LightningRpc("/tmp/lightning5/lightning-rpc")
|
||||
|
||||
info5 = l5.getinfo()
|
||||
print(info5)
|
||||
|
||||
# Create invoice for test payment
|
||||
invoice = l5.invoice(100, "lbl{}".format(random.random()), "testpayment")
|
||||
print(invoice)
|
||||
|
||||
# Get route to l1
|
||||
route = l1.getroute(info5['id'], 100, 1)
|
||||
print(route)
|
||||
|
||||
# Pay invoice
|
||||
print(l1.sendpay(route['route'], invoice['payment_hash']))
|
||||
```
|
||||
|
||||
### Writing a plugin
|
||||
|
||||
Plugins are programs that `lightningd` can be configured to execute alongside
|
||||
the main daemon. They allow advanced interactions with and customizations to
|
||||
the daemon.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
from pyln.client import Plugin
|
||||
|
||||
plugin = Plugin()
|
||||
|
||||
@plugin.method("hello")
|
||||
def hello(plugin, name="world"):
|
||||
"""This is the documentation string for the hello-function.
|
||||
|
||||
It gets reported as the description when registering the function
|
||||
as a method with `lightningd`.
|
||||
|
||||
"""
|
||||
greeting = plugin.get_option('greeting')
|
||||
s = '{} {}'.format(greeting, name)
|
||||
plugin.log(s)
|
||||
return s
|
||||
|
||||
|
||||
@plugin.init()
|
||||
def init(options, configuration, plugin):
|
||||
plugin.log("Plugin helloworld.py initialized")
|
||||
|
||||
|
||||
@plugin.subscribe("connect")
|
||||
def on_connect(plugin, id, address):
|
||||
plugin.log("Received connect event for peer {}".format(id))
|
||||
|
||||
|
||||
plugin.add_option('greeting', 'Hello', 'The greeting I should use.')
|
||||
plugin.run()
|
||||
|
||||
```
|
10
contrib/pyln-client/pyln/client/__init__.py
Normal file
10
contrib/pyln-client/pyln/client/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
from lightning import LightningRpc, Plugin, RpcError, Millisatoshi, __version__, monkey_patch
|
||||
|
||||
__all__ = [
|
||||
"LightningRpc",
|
||||
"Plugin",
|
||||
"RpcError",
|
||||
"Millisatoshi",
|
||||
"__version__",
|
||||
"monkey_patch"
|
||||
]
|
1
contrib/pyln-client/requirements.txt
Normal file
1
contrib/pyln-client/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
pylightning==0.0.7.3
|
24
contrib/pyln-client/setup.py
Normal file
24
contrib/pyln-client/setup.py
Normal file
@ -0,0 +1,24 @@
|
||||
from setuptools import setup
|
||||
from pyln import client
|
||||
import io
|
||||
|
||||
|
||||
with io.open('README.md', encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
with io.open('requirements.txt', encoding='utf-8') as f:
|
||||
requirements = [r for r in f.read().split('\n') if len(r)]
|
||||
|
||||
setup(name='pyln-client',
|
||||
version=client.__version__,
|
||||
description='Client library for lightningd',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='http://github.com/ElementsProject/lightning',
|
||||
author='Christian Decker',
|
||||
author_email='decker.christian@gmail.com',
|
||||
license='MIT',
|
||||
packages=['pyln.client'],
|
||||
scripts=[],
|
||||
zip_safe=True,
|
||||
install_requires=requirements)
|
30
contrib/pyln-proto/README.md
Normal file
30
contrib/pyln-proto/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# pyln-proto: Lightning Network protocol implementation
|
||||
|
||||
This package implements some of the Lightning Network protocol in pure
|
||||
python. It is intended for protocol testing and some minor tooling only. It is
|
||||
not deemed secure enough to handle any amount of real funds (you have been
|
||||
warned!).
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
`pyln-proto` is available on `pip`:
|
||||
|
||||
```
|
||||
pip install pyln-proto
|
||||
```
|
||||
|
||||
Alternatively you can also install the development version to get access to
|
||||
currently unreleased features by checking out the c-lightning source code and
|
||||
installing into your python3 environment:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ElementsProject/lightning.git
|
||||
cd lightning/contrib/pyln-proto
|
||||
python3 setup.py develop
|
||||
```
|
||||
|
||||
This will add links to the library into your environment so changing the
|
||||
checked out source code will also result in the environment picking up these
|
||||
changes. Notice however that unreleased versions may change API without
|
||||
warning, so test thoroughly with the released version.
|
27
contrib/pyln-proto/examples/connect.py
Normal file
27
contrib/pyln-proto/examples/connect.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Simple connect and read test
|
||||
|
||||
Connects to a peer, performs handshake and then just prints all the messages
|
||||
it gets.
|
||||
|
||||
"""
|
||||
|
||||
from pyln.proto.wire import connect, PrivateKey, PublicKey
|
||||
from binascii import unhexlify, hexlify
|
||||
|
||||
ls_privkey = PrivateKey(unhexlify(
|
||||
b'1111111111111111111111111111111111111111111111111111111111111111'
|
||||
))
|
||||
remote_pubkey = PublicKey(unhexlify(
|
||||
b'03b31e5bbf2cdbe115b485a2b480e70a1ef3951a0dc6df4b1232e0e56f3dce18d6'
|
||||
))
|
||||
|
||||
lc = connect(ls_privkey, remote_pubkey, '127.0.0.1', 9375)
|
||||
|
||||
# Send an init message, with no global features, and 0b10101010 as local
|
||||
# features.
|
||||
lc.send_message(b'\x00\x10\x00\x00\x00\x01\xaa')
|
||||
|
||||
# Now just read whatever our peer decides to send us
|
||||
while True:
|
||||
print(hexlify(lc.read_message()).decode('ASCII'))
|
47
contrib/pyln-proto/examples/listen.py
Normal file
47
contrib/pyln-proto/examples/listen.py
Normal file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""A simple handshake and encryption test.
|
||||
|
||||
This script will listen on port 9736 for incoming Lightning Network protocol
|
||||
connections, perform the cryptographic handshake, send 10k small pings, and
|
||||
then exit, closing the connection. This is useful to check the correct
|
||||
rotation of send- and receive-keys in the implementation.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from pyln.proto.wire import LightningServerSocket, PrivateKey
|
||||
from binascii import hexlify, unhexlify
|
||||
import time
|
||||
import threading
|
||||
|
||||
ls_privkey = PrivateKey(unhexlify(
|
||||
b'1111111111111111111111111111111111111111111111111111111111111111'
|
||||
))
|
||||
listener = LightningServerSocket(ls_privkey)
|
||||
print("Node ID: {}".format(ls_privkey.public_key()))
|
||||
|
||||
listener.bind(('0.0.0.0', 9735))
|
||||
listener.listen()
|
||||
c, a = listener.accept()
|
||||
|
||||
c.send_message(b'\x00\x10\x00\x00\x00\x01\xaa')
|
||||
print(c.read_message())
|
||||
|
||||
num_pings = 10000
|
||||
|
||||
|
||||
def read_loop(c):
|
||||
for i in range(num_pings):
|
||||
print("Recv", i, hexlify(c.read_message()))
|
||||
|
||||
|
||||
t = threading.Thread(target=read_loop, args=(c,))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
for i in range(num_pings):
|
||||
m = b'\x00\x12\x00\x01\x00\x01\x00'
|
||||
c.send_message(m)
|
||||
print("Sent", i, hexlify(m))
|
||||
time.sleep(0.01)
|
||||
|
||||
t.join()
|
1
contrib/pyln-proto/pyln/proto/__init__.py
Normal file
1
contrib/pyln-proto/pyln/proto/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__version__ = '0.0.1'
|
394
contrib/pyln-proto/pyln/proto/wire.py
Normal file
394
contrib/pyln-proto/pyln/proto/wire.py
Normal file
@ -0,0 +1,394 @@
|
||||
from binascii import hexlify
|
||||
from cryptography.exceptions import InvalidTag
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from hashlib import sha256
|
||||
import coincurve
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
|
||||
|
||||
__all__ = [
|
||||
'PrivateKey',
|
||||
'PublicKey',
|
||||
'Secret',
|
||||
'LightningConnection',
|
||||
'LightningServerSocket',
|
||||
'connect'
|
||||
]
|
||||
|
||||
|
||||
def hkdf(ikm, salt=b"", info=b""):
|
||||
hkdf = HKDF(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=64,
|
||||
salt=salt,
|
||||
info=info,
|
||||
backend=default_backend())
|
||||
|
||||
return hkdf.derive(ikm)
|
||||
|
||||
|
||||
def hkdf_two_keys(ikm, salt):
|
||||
t = hkdf(ikm, salt)
|
||||
return t[:32], t[32:]
|
||||
|
||||
|
||||
def ecdh(k, rk):
|
||||
k = coincurve.PrivateKey(secret=k.rawkey)
|
||||
rk = coincurve.PublicKey(data=rk.serializeCompressed())
|
||||
a = k.ecdh(rk.public_key)
|
||||
return Secret(a)
|
||||
|
||||
|
||||
def encryptWithAD(k, n, ad, plaintext):
|
||||
chacha = ChaCha20Poly1305(k)
|
||||
return chacha.encrypt(n, plaintext, ad)
|
||||
|
||||
|
||||
def decryptWithAD(k, n, ad, ciphertext):
|
||||
chacha = ChaCha20Poly1305(k)
|
||||
return chacha.decrypt(n, ciphertext, ad)
|
||||
|
||||
|
||||
class PrivateKey(object):
|
||||
def __init__(self, rawkey):
|
||||
assert len(rawkey) == 32 and isinstance(rawkey, bytes)
|
||||
self.rawkey = rawkey
|
||||
rawkey = int(hexlify(rawkey), base=16)
|
||||
self.key = ec.derive_private_key(rawkey, ec.SECP256K1(),
|
||||
default_backend())
|
||||
|
||||
def serializeCompressed(self):
|
||||
return self.key.private_bytes(serialization.Encoding.Raw,
|
||||
serialization.PrivateFormat.Raw, None)
|
||||
|
||||
def public_key(self):
|
||||
return PublicKey(self.key.public_key())
|
||||
|
||||
|
||||
class Secret(object):
|
||||
def __init__(self, raw):
|
||||
assert(len(raw) == 32)
|
||||
self.raw = raw
|
||||
|
||||
def __str__(self):
|
||||
return "Secret[0x{}]".format(hexlify(self.raw).decode('ASCII'))
|
||||
|
||||
|
||||
class PublicKey(object):
|
||||
def __init__(self, innerkey):
|
||||
# We accept either 33-bytes raw keys, or an EC PublicKey as returned
|
||||
# by cryptography.io
|
||||
if isinstance(innerkey, bytes):
|
||||
innerkey = ec.EllipticCurvePublicKey.from_encoded_point(
|
||||
ec.SECP256K1(), innerkey
|
||||
)
|
||||
|
||||
elif not isinstance(innerkey, ec.EllipticCurvePublicKey):
|
||||
raise ValueError(
|
||||
"Key must either be bytes or ec.EllipticCurvePublicKey"
|
||||
)
|
||||
self.key = innerkey
|
||||
|
||||
def serializeCompressed(self):
|
||||
raw = self.key.public_bytes(
|
||||
serialization.Encoding.X962,
|
||||
serialization.PublicFormat.CompressedPoint
|
||||
)
|
||||
return raw
|
||||
|
||||
def __str__(self):
|
||||
return "PublicKey[0x{}]".format(
|
||||
hexlify(self.serializeCompressed()).decode('ASCII')
|
||||
)
|
||||
|
||||
|
||||
def Keypair(object):
|
||||
def __init__(self, priv, pub):
|
||||
self.priv, self.pub = priv, pub
|
||||
|
||||
|
||||
class Sha256Mixer(object):
|
||||
def __init__(self, base):
|
||||
self.hash = sha256(base).digest()
|
||||
|
||||
def update(self, data):
|
||||
h = sha256(self.hash)
|
||||
h.update(data)
|
||||
self.hash = h.digest()
|
||||
return self.hash
|
||||
|
||||
def digest(self):
|
||||
return self.hash
|
||||
|
||||
def __str__(self):
|
||||
return "Sha256Mixer[0x{}]".format(hexlify(self.hash).decode('ASCII'))
|
||||
|
||||
|
||||
class LightningConnection(object):
|
||||
def __init__(self, connection, remote_pubkey, local_privkey, is_initiator):
|
||||
self.connection = connection
|
||||
self.chaining_key = None
|
||||
self.handshake_hash = None
|
||||
self.local_privkey = local_privkey
|
||||
self.local_pubkey = self.local_privkey.public_key()
|
||||
self.remote_pubkey = remote_pubkey
|
||||
self.is_initiator = is_initiator
|
||||
self.init_handshake()
|
||||
self.rn, self.sn = 0, 0
|
||||
self.send_lock, self.recv_lock = threading.Lock(), threading.Lock()
|
||||
|
||||
@classmethod
|
||||
def nonce(cls, n):
|
||||
"""Transforms a numeric nonce into a byte formatted one
|
||||
|
||||
Nonce n encoded as 32 zero bits, followed by a little-endian 64-bit
|
||||
value. Note: this follows the Noise Protocol convention, rather than
|
||||
our normal endian.
|
||||
"""
|
||||
return b'\x00' * 4 + struct.pack("<Q", n)
|
||||
|
||||
def init_handshake(self):
|
||||
h = sha256(b'Noise_XK_secp256k1_ChaChaPoly_SHA256').digest()
|
||||
self.chaining_key = h
|
||||
h = sha256(h + b'lightning').digest()
|
||||
|
||||
if self.is_initiator:
|
||||
responder_pubkey = self.remote_pubkey
|
||||
else:
|
||||
responder_pubkey = self.local_pubkey
|
||||
h = sha256(h + responder_pubkey.serializeCompressed()).digest()
|
||||
|
||||
self.handshake = {
|
||||
'h': h,
|
||||
'e': PrivateKey(os.urandom(32)),
|
||||
}
|
||||
|
||||
def handshake_act_one_initiator(self):
|
||||
h = Sha256Mixer(b'')
|
||||
h.hash = self.handshake['h']
|
||||
h.update(self.handshake['e'].public_key().serializeCompressed())
|
||||
es = ecdh(self.handshake['e'], self.remote_pubkey)
|
||||
t = hkdf(salt=self.chaining_key, ikm=es.raw, info=b'')
|
||||
assert(len(t) == 64)
|
||||
self.chaining_key, temp_k1 = t[:32], t[32:]
|
||||
c = encryptWithAD(temp_k1, self.nonce(0), h.digest(), b'')
|
||||
self.handshake['h'] = h.update(c)
|
||||
pk = self.handshake['e'].public_key().serializeCompressed()
|
||||
m = b'\x00' + pk + c
|
||||
return m
|
||||
|
||||
def handshake_act_one_responder(self, m):
|
||||
v, re, c = m[0], PublicKey(m[1:34]), m[34:]
|
||||
if v != 0:
|
||||
raise ValueError("Unsupported handshake version {}, only version "
|
||||
"0 is supported.".format(v))
|
||||
|
||||
h = Sha256Mixer(b'')
|
||||
h.hash = self.handshake['h']
|
||||
h.update(re.serializeCompressed())
|
||||
es = ecdh(self.local_privkey, re)
|
||||
self.handshake['re'] = re
|
||||
t = hkdf(salt=self.chaining_key, ikm=es.raw, info=b'')
|
||||
self.chaining_key, temp_k1 = t[:32], t[32:]
|
||||
|
||||
try:
|
||||
decryptWithAD(temp_k1, self.nonce(0), h.digest(), c)
|
||||
except InvalidTag:
|
||||
ValueError("Verification of tag failed, remote peer doesn't know "
|
||||
"our node ID.")
|
||||
h.update(c)
|
||||
self.handshake['h'] = h.digest()
|
||||
|
||||
def handshake_act_two_responder(self):
|
||||
h = Sha256Mixer(b'')
|
||||
h.hash = self.handshake['h']
|
||||
h.update(self.handshake['e'].public_key().serializeCompressed())
|
||||
ee = ecdh(self.handshake['e'], self.handshake['re'])
|
||||
t = hkdf(salt=self.chaining_key, ikm=ee.raw, info=b'')
|
||||
assert(len(t) == 64)
|
||||
self.chaining_key, self.temp_k2 = t[:32], t[32:]
|
||||
c = encryptWithAD(self.temp_k2, self.nonce(0), h.digest(), b'')
|
||||
h.update(c)
|
||||
self.handshake['h'] = h.digest()
|
||||
pk = self.handshake['e'].public_key().serializeCompressed()
|
||||
m = b'\x00' + pk + c
|
||||
return m
|
||||
|
||||
def handshake_act_two_initiator(self, m):
|
||||
v, re, c = m[0], PublicKey(m[1:34]), m[34:]
|
||||
if v != 0:
|
||||
raise ValueError("Unsupported handshake version {}, only version "
|
||||
"0 is supported.".format(v))
|
||||
self.re = re
|
||||
h = Sha256Mixer(b'')
|
||||
h.hash = self.handshake['h']
|
||||
h.update(re.serializeCompressed())
|
||||
ee = ecdh(self.handshake['e'], re)
|
||||
self.chaining_key, self.temp_k2 = hkdf_two_keys(
|
||||
salt=self.chaining_key, ikm=ee.raw
|
||||
)
|
||||
try:
|
||||
decryptWithAD(self.temp_k2, self.nonce(0), h.digest(), c)
|
||||
except InvalidTag:
|
||||
ValueError("Verification of tag failed.")
|
||||
h.update(c)
|
||||
self.handshake['h'] = h.digest()
|
||||
|
||||
def handshake_act_three_initiator(self):
|
||||
h = Sha256Mixer(b'')
|
||||
h.hash = self.handshake['h']
|
||||
pk = self.local_pubkey.serializeCompressed()
|
||||
c = encryptWithAD(self.temp_k2, self.nonce(1), h.digest(), pk)
|
||||
h.update(c)
|
||||
se = ecdh(self.local_privkey, self.re)
|
||||
|
||||
self.chaining_key, self.temp_k3 = hkdf_two_keys(
|
||||
salt=self.chaining_key, ikm=se.raw
|
||||
)
|
||||
t = encryptWithAD(self.temp_k3, self.nonce(0), h.digest(), b'')
|
||||
m = b'\x00' + c + t
|
||||
t = hkdf(salt=self.chaining_key, ikm=b'', info=b'')
|
||||
|
||||
self.sk, self.rk = hkdf_two_keys(salt=self.chaining_key, ikm=b'')
|
||||
self.rn, self.sn = 0, 0
|
||||
return m
|
||||
|
||||
def handshake_act_three_responder(self, m):
|
||||
h = Sha256Mixer(b'')
|
||||
h.hash = self.handshake['h']
|
||||
v, c, t = m[0], m[1:50], m[50:]
|
||||
if v != 0:
|
||||
raise ValueError("Unsupported handshake version {}, only version "
|
||||
"0 is supported.".format(v))
|
||||
rs = decryptWithAD(self.temp_k2, self.nonce(1), h.digest(), c)
|
||||
h.update(c)
|
||||
se = ecdh(self.handshake['e'], PublicKey(rs))
|
||||
|
||||
self.chaining_key, self.temp_k3 = hkdf_two_keys(
|
||||
se.raw, self.chaining_key
|
||||
)
|
||||
decryptWithAD(self.temp_k3, self.nonce(0), h.digest(), t)
|
||||
self.rn, self.sn = 0, 0
|
||||
|
||||
self.rk, self.sk = hkdf_two_keys(salt=self.chaining_key, ikm=b'')
|
||||
|
||||
def read_message(self):
|
||||
with self.recv_lock:
|
||||
lc = self.connection.recv(18)
|
||||
if len(lc) != 18:
|
||||
raise ValueError(
|
||||
"Short read reading the message length: 18 != {}".format(
|
||||
len(lc))
|
||||
)
|
||||
length = decryptWithAD(self.rk, self.nonce(self.rn), b'', lc)
|
||||
length, = struct.unpack("!H", length)
|
||||
self.rn += 1
|
||||
|
||||
mc = self.connection.recv(length + 16)
|
||||
if len(mc) < length + 16:
|
||||
raise ValueError(
|
||||
"Short read reading the message: {} != {}".format(
|
||||
length + 16, len(lc)
|
||||
)
|
||||
)
|
||||
m = decryptWithAD(self.rk, self.nonce(self.rn), b'', mc)
|
||||
self.rn += 1
|
||||
assert(self.rn % 2 == 0)
|
||||
self._maybe_rotate_keys()
|
||||
|
||||
return m
|
||||
|
||||
def send_message(self, m):
|
||||
length = struct.pack("!H", len(m))
|
||||
with self.send_lock:
|
||||
lc = encryptWithAD(self.sk, self.nonce(self.sn), b'', length)
|
||||
mc = encryptWithAD(self.sk, self.nonce(self.sn + 1), b'', m)
|
||||
self.sn += 2
|
||||
self.connection.send(lc)
|
||||
self.connection.send(mc)
|
||||
assert(self.sn % 2 == 0)
|
||||
self._maybe_rotate_keys()
|
||||
|
||||
def _maybe_rotate_keys(self):
|
||||
if self.sn == 1000:
|
||||
self.sck, self.sk = hkdf_two_keys(salt=self.sck, ikm=self.sk)
|
||||
self.sn = 0
|
||||
if self.rn == 1000:
|
||||
self.rck, self.rk = hkdf_two_keys(salt=self.rck, ikm=self.rk)
|
||||
self.rn = 0
|
||||
|
||||
def shake(self):
|
||||
if self.is_initiator:
|
||||
m = self.handshake_act_one_initiator()
|
||||
self.connection.send(m)
|
||||
m = self.connection.recv(50)
|
||||
if len(m) != 50:
|
||||
raise ValueError(
|
||||
"Short read from peer reading act2: 50 != {}".format(
|
||||
len(m))
|
||||
)
|
||||
self.handshake_act_two_initiator(m)
|
||||
m = self.handshake_act_three_initiator()
|
||||
self.connection.send(m)
|
||||
else:
|
||||
m = self.connection.recv(50)
|
||||
if len(m) != 50:
|
||||
raise ValueError(
|
||||
"Short read from peer reading act1: 50 != {}".format(
|
||||
len(m))
|
||||
)
|
||||
self.handshake_act_one_responder(m)
|
||||
m = self.handshake_act_two_responder()
|
||||
self.connection.send(m)
|
||||
m = self.connection.recv(66)
|
||||
if len(m) != 66:
|
||||
raise ValueError(
|
||||
"Short read from peer reading act3: 66 != {}".format(
|
||||
len(m))
|
||||
)
|
||||
self.handshake_act_three_responder(m)
|
||||
|
||||
self.sck = self.chaining_key
|
||||
self.rck = self.chaining_key
|
||||
|
||||
|
||||
class LightningServerSocket(socket.socket):
|
||||
def __init__(self, local_privkey):
|
||||
socket.socket.__init__(self)
|
||||
self.local_privkey = local_privkey
|
||||
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
def accept(self):
|
||||
conn, address = socket.socket.accept(self)
|
||||
lconn = LightningConnection(
|
||||
conn, remote_pubkey=None,
|
||||
local_privkey=self.local_privkey,
|
||||
is_initiator=False)
|
||||
lconn.shake()
|
||||
return (lconn, address)
|
||||
|
||||
|
||||
def connect(local_privkey, node_id, host, port=9735):
|
||||
if isinstance(node_id, bytes) and len(node_id) == 33:
|
||||
remote_pubkey = PublicKey(node_id)
|
||||
elif isinstance(node_id, ec.EllipticCurvePublicKey):
|
||||
remote_pubkey = PublicKey(node_id)
|
||||
elif isinstance(node_id, PublicKey):
|
||||
remote_pubkey = node_id
|
||||
else:
|
||||
raise ValueError(
|
||||
"node_id must be either a 33 byte array, or a PublicKey"
|
||||
)
|
||||
conn = socket.create_connection((host, port))
|
||||
lconn = LightningConnection(conn, remote_pubkey, local_privkey,
|
||||
is_initiator=True)
|
||||
lconn.shake()
|
||||
return lconn
|
2
contrib/pyln-proto/requirements.txt
Normal file
2
contrib/pyln-proto/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
cryptography==2.7
|
||||
coincurve==12.0.0
|
24
contrib/pyln-proto/setup.py
Normal file
24
contrib/pyln-proto/setup.py
Normal file
@ -0,0 +1,24 @@
|
||||
from setuptools import setup
|
||||
from pyln import proto
|
||||
import io
|
||||
|
||||
|
||||
with io.open('README.md', encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
with io.open('requirements.txt', encoding='utf-8') as f:
|
||||
requirements = [r for r in f.read().split('\n') if len(r)]
|
||||
|
||||
setup(name='pyln-proto',
|
||||
version=proto.__version__,
|
||||
description='Pure python implementation of the Lightning Network protocol',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='http://github.com/ElementsProject/lightning',
|
||||
author='Christian Decker',
|
||||
author_email='decker.christian@gmail.com',
|
||||
license='MIT',
|
||||
packages=['pyln.proto'],
|
||||
scripts=[],
|
||||
zip_safe=True,
|
||||
install_requires=requirements)
|
Loading…
Reference in New Issue
Block a user