tln: avoid recursive dependencies, make new lnutil - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit fe973a5137ecb1ed9a8b63e042363b0c7792edef DIR parent 7a3551b5dfbc5643bb26add87cca8b0634d3dfb8 HTML Author: Janus <ysangkok@gmail.com> Date: Thu, 28 Jun 2018 15:50:45 +0200 ln: avoid recursive dependencies, make new lnutil Diffstat: M lib/lnbase.py | 366 +------------------------------ M lib/lnhtlc.py | 62 ++++++++++++++++++++----------- M lib/lnrouter.py | 10 ++++++---- A lib/lnutil.py | 315 +++++++++++++++++++++++++++++++ M lib/lnwatcher.py | 2 +- M lib/lnworker.py | 10 ++++------ D lib/tests/test_lnbase.py | 819 ------------------------------- M lib/tests/test_lnhtlc.py | 11 ++++++----- A lib/tests/test_lnrouter.py | 150 +++++++++++++++++++++++++++++++ A lib/tests/test_lnutil.py | 670 +++++++++++++++++++++++++++++++ 10 files changed, 1205 insertions(+), 1210 deletions(-) --- DIR diff --git a/lib/lnbase.py b/lib/lnbase.py t@@ -5,49 +5,12 @@ """ from collections import namedtuple, defaultdict, OrderedDict, defaultdict -Keypair = namedtuple("Keypair", ["pubkey", "privkey"]) -Outpoint = namedtuple("Outpoint", ["txid", "output_index"]) -ChannelConfig = namedtuple("ChannelConfig", [ - "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", - "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) -OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) -RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"]) -LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"]) -ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"]) -#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"]) - -class RevocationStore: - """ taken from lnd """ - def __init__(self): - self.buckets = [None] * 48 - self.index = 2**48 - 1 - def add_next_entry(self, hsh): - new_element = ShachainElement(index=self.index, secret=hsh) - bucket = count_trailing_zeros(self.index) - for i in range(0, bucket): - this_bucket = self.buckets[i] - e = shachain_derive(new_element, this_bucket.index) - - if e != this_bucket: - raise Exception("hash is not derivable: {} {} {}".format(bh2u(e.secret), bh2u(this_bucket.secret), this_bucket.index)) - self.buckets[bucket] = new_element - self.index -= 1 - def serialize(self): - return {"index": self.index, "buckets": [[bh2u(k.secret), k.index] if k is not None else None for k in self.buckets]} - @staticmethod - def from_json_obj(decoded_json_obj): - store = RevocationStore() - decode = lambda to_decode: ShachainElement(bfh(to_decode[0]), int(to_decode[1])) - store.buckets = [k if k is None else decode(k) for k in decoded_json_obj["buckets"]] - store.index = decoded_json_obj["index"] - return store - def __eq__(self, o): - return type(o) is RevocationStore and self.serialize() == o.serialize() - def __hash__(self): - return hash(json.dumps(self.serialize(), sort_keys=True)) +from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore +from .lnutil import sign_and_get_sig_string, funding_output_script, get_ecdh, get_per_commitment_secret_from_seed +from .lnutil import secret_to_pubkey +from .bitcoin import COIN from ecdsa.util import sigdecode_der, sigencode_string_canonize, sigdecode_string -from ecdsa.curves import SECP256k1 import queue import traceback import json t@@ -58,18 +21,8 @@ import time import binascii import hashlib import hmac -from typing import Sequence, Union, Tuple import cryptography.hazmat.primitives.ciphers.aead as AEAD -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms -from cryptography.hazmat.backends import default_backend -HTLC_TIMEOUT_WEIGHT = 663 -HTLC_SUCCESS_WEIGHT = 703 - -from .ecc import ser_to_point, point_to_ser, string_to_number -from .bitcoin import (deserialize_privkey, rev_hex, int_to_hex, - push_script, script_num_to_hex, - add_number_to_script, var_int, COIN) from . import bitcoin from . import ecc from . import crypto t@@ -82,6 +35,8 @@ from .lnrouter import new_onion_packet, OnionHopsDataSingle, OnionPerHop, decode from .lightning_payencode.lnaddr import lndecode from .lnhtlc import UpdateAddHtlc, HTLCStateMachine, RevokeAndAck, SettleHtlc +REV_GENESIS = bytes.fromhex(bitcoin.rev_hex(constants.net.GENESIS)) + def channel_id_from_funding_tx(funding_txid, funding_index): funding_txid_bytes = bytes.fromhex(funding_txid)[::-1] i = int.from_bytes(funding_txid_bytes, 'big') ^ funding_index t@@ -289,10 +244,6 @@ def get_bolt8_hkdf(salt, ikm): assert len(T1 + T2) == 64 return T1, T2 -def get_ecdh(priv: bytes, pub: bytes) -> bytes: - pt = ecc.ECPubkey(pub) * string_to_number(priv) - return sha256(pt.get_public_key_bytes()) - def act1_initiator_message(hs, my_privkey): #Get a new ephemeral key epriv, epub = create_ephemeral_key(my_privkey) t@@ -328,290 +279,6 @@ def aiosafe(f): traceback.print_exc() return f2 -def get_obscured_ctn(ctn, local, remote): - mask = int.from_bytes(sha256(local + remote)[-6:], 'big') - return ctn ^ mask - -def secret_to_pubkey(secret): - assert type(secret) is int - return point_to_ser(SECP256k1.generator * secret) - -def derive_pubkey(basepoint, per_commitment_point): - p = ecc.ECPubkey(basepoint) + ecc.generator() * ecc.string_to_number(sha256(per_commitment_point + basepoint)) - return p.get_public_key_bytes() - -def derive_privkey(secret, per_commitment_point): - assert type(secret) is int - basepoint = point_to_ser(SECP256k1.generator * secret) - basepoint = secret + ecc.string_to_number(sha256(per_commitment_point + basepoint)) - basepoint %= SECP256k1.order - return basepoint - -def derive_blinded_pubkey(basepoint, per_commitment_point): - k1 = ecc.ECPubkey(basepoint) * ecc.string_to_number(sha256(basepoint + per_commitment_point)) - k2 = ecc.ECPubkey(per_commitment_point) * ecc.string_to_number(sha256(per_commitment_point + basepoint)) - return (k1 + k2).get_public_key_bytes() - -def shachain_derive(element, toIndex): - return ShachainElement(get_per_commitment_secret_from_seed(element.secret, toIndex, count_trailing_zeros(element.index)), toIndex) - - -def get_per_commitment_secret_from_seed(seed: bytes, i: int, bits: int = 48) -> bytes: - """Generate per commitment secret.""" - per_commitment_secret = bytearray(seed) - for bitindex in range(bits - 1, -1, -1): - mask = 1 << bitindex - if i & mask: - per_commitment_secret[bitindex // 8] ^= 1 << (bitindex % 8) - per_commitment_secret = bytearray(sha256(per_commitment_secret)) - bajts = bytes(per_commitment_secret) - return bajts - - -def overall_weight(num_htlc): - return 500 + 172 * num_htlc + 224 - -def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay): - assert type(amount_msat) is int - assert type(local_feerate) is int - assert type(revocationpubkey) is bytes - assert type(local_delayedpubkey) is bytes - script = bytes([opcodes.OP_IF]) \ - + bfh(push_script(bh2u(revocationpubkey))) \ - + bytes([opcodes.OP_ELSE]) \ - + bitcoin.add_number_to_script(to_self_delay) \ - + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) \ - + bfh(push_script(bh2u(local_delayedpubkey))) \ - + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) - - p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script)) - weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT - fee = local_feerate * weight - final_amount_sat = (amount_msat - fee) // 1000 - assert final_amount_sat > 0, final_amount_sat - output = (bitcoin.TYPE_ADDRESS, p2wsh, final_amount_sat) - return output - -def make_htlc_tx_witness(remotehtlcsig, localhtlcsig, payment_preimage, witness_script): - assert type(remotehtlcsig) is bytes - assert type(localhtlcsig) is bytes - assert type(payment_preimage) is bytes - assert type(witness_script) is bytes - return bfh(transaction.construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])) - -def make_htlc_tx_inputs(htlc_output_txid, htlc_output_index, revocationpubkey, local_delayedpubkey, amount_msat, witness_script): - assert type(htlc_output_txid) is str - assert type(htlc_output_index) is int - assert type(revocationpubkey) is bytes - assert type(local_delayedpubkey) is bytes - assert type(amount_msat) is int - assert type(witness_script) is str - c_inputs = [{ - 'scriptSig': '', - 'type': 'p2wsh', - 'signatures': [], - 'num_sig': 0, - 'prevout_n': htlc_output_index, - 'prevout_hash': htlc_output_txid, - 'value': amount_msat // 1000, - 'coinbase': False, - 'sequence': 0x0, - 'preimage_script': witness_script, - }] - return c_inputs - -def make_htlc_tx(cltv_timeout, inputs, output): - assert type(cltv_timeout) is int - c_outputs = [output] - tx = Transaction.from_io(inputs, c_outputs, locktime=cltv_timeout, version=2) - tx.BIP_LI01_sort() - return tx - -def make_offered_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash): - assert type(revocation_pubkey) is bytes - assert type(remote_htlcpubkey) is bytes - assert type(local_htlcpubkey) is bytes - assert type(payment_hash) is bytes - return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey))))\ - + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ - + bfh(push_script(bh2u(remote_htlcpubkey)))\ - + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) + bitcoin.add_number_to_script(32) + bytes([opcodes.OP_EQUAL, opcodes.OP_NOTIF, opcodes.OP_DROP])\ - + bitcoin.add_number_to_script(2) + bytes([opcodes.OP_SWAP]) + bfh(push_script(bh2u(local_htlcpubkey))) + bitcoin.add_number_to_script(2)\ - + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\ - + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) - -def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash, cltv_expiry): - for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]: - assert type(i) is bytes - assert type(cltv_expiry) is int - - return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) \ - + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey)))) \ - + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ - + bfh(push_script(bh2u(remote_htlcpubkey))) \ - + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) \ - + bitcoin.add_number_to_script(32) \ - + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_HASH160]) \ - + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) \ - + bytes([opcodes.OP_EQUALVERIFY]) \ - + bitcoin.add_number_to_script(2) \ - + bytes([opcodes.OP_SWAP]) \ - + bfh(push_script(bh2u(local_htlcpubkey))) \ - + bitcoin.add_number_to_script(2) \ - + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP]) \ - + bitcoin.add_number_to_script(cltv_expiry) \ - + bytes([opcodes.OP_CLTV, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) - -def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, cltv_expiry, payment_hash, commit, original_htlc_output_index): - conf = chan.local_config if for_us else chan.remote_config - other_conf = chan.local_config if not for_us else chan.remote_config - - revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) - delayedpubkey = derive_pubkey(conf.delayed_basepoint.pubkey, pcp) - other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) - other_htlc_pubkey = derive_pubkey(other_conf.htlc_basepoint.pubkey, pcp) - htlc_pubkey = derive_pubkey(conf.htlc_basepoint.pubkey, pcp) - # HTLC-success for the HTLC spending from a received HTLC output - # if we do not receive, and the commitment tx is not for us, they receive, so it is also an HTLC-success - is_htlc_success = for_us == we_receive - htlc_tx_output = make_htlc_tx_output( - amount_msat = amount_msat, - local_feerate = chan.constraints.feerate, - revocationpubkey=revocation_pubkey, - local_delayedpubkey=delayedpubkey, - success = is_htlc_success, - to_self_delay = other_conf.to_self_delay) - if is_htlc_success: - preimage_script = make_received_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash, cltv_expiry) - else: - preimage_script = make_offered_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash) - htlc_tx_inputs = make_htlc_tx_inputs( - commit.txid(), commit.htlc_output_indices[original_htlc_output_index], - revocationpubkey=revocation_pubkey, - local_delayedpubkey=delayedpubkey, - amount_msat=amount_msat, - witness_script=bh2u(preimage_script)) - if is_htlc_success: - cltv_expiry = 0 - htlc_tx = make_htlc_tx(cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output) - return htlc_tx - -def make_commitment_using_open_channel(chan, ctn, for_us, pcp, local_msat, remote_msat, htlcs=[], trimmed=0): - conf = chan.local_config if for_us else chan.remote_config - other_conf = chan.local_config if not for_us else chan.remote_config - payment_pubkey = derive_pubkey(other_conf.payment_basepoint.pubkey, pcp) - remote_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) - return make_commitment( - ctn, - conf.multisig_key.pubkey, - other_conf.multisig_key.pubkey, - payment_pubkey, - chan.local_config.payment_basepoint.pubkey, - chan.remote_config.payment_basepoint.pubkey, - remote_revocation_pubkey, - derive_pubkey(conf.delayed_basepoint.pubkey, pcp), - other_conf.to_self_delay, - *chan.funding_outpoint, - chan.constraints.capacity, - local_msat, - remote_msat, - chan.local_config.dust_limit_sat, - chan.constraints.feerate, - for_us, - chan.constraints.is_initiator, - htlcs=htlcs, - trimmed=trimmed) - -def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey, - remote_payment_pubkey, payment_basepoint, - remote_payment_basepoint, revocation_pubkey, - delayed_pubkey, to_self_delay, funding_txid, - funding_pos, funding_sat, local_amount, remote_amount, - dust_limit_sat, local_feerate, for_us, we_are_initiator, - htlcs, trimmed=0): - - pubkeys = sorted([bh2u(local_funding_pubkey), bh2u(remote_funding_pubkey)]) - payments = [payment_basepoint, remote_payment_basepoint] - if not we_are_initiator: - payments.reverse() - obs = get_obscured_ctn(ctn, *payments) - locktime = (0x20 << 24) + (obs & 0xffffff) - sequence = (0x80 << 24) + (obs >> 24) - print_error('locktime', locktime, hex(locktime)) - # commitment tx input - c_inputs = [{ - 'type': 'p2wsh', - 'x_pubkeys': pubkeys, - 'signatures': [None, None], - 'num_sig': 2, - 'prevout_n': funding_pos, - 'prevout_hash': funding_txid, - 'value': funding_sat, - 'coinbase': False, - 'sequence': sequence - }] - # commitment tx outputs - local_script = bytes([opcodes.OP_IF]) + bfh(push_script(bh2u(revocation_pubkey))) + bytes([opcodes.OP_ELSE]) + add_number_to_script(to_self_delay) \ - + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) + bfh(push_script(bh2u(delayed_pubkey))) + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) - local_address = bitcoin.redeem_script_to_address('p2wsh', bh2u(local_script)) - remote_address = bitcoin.pubkey_to_address('p2wpkh', bh2u(remote_payment_pubkey)) - # TODO trim htlc outputs here while also considering 2nd stage htlc transactions - fee = local_feerate * overall_weight(len(htlcs)) - fee -= trimmed * 1000 - assert type(fee) is int - we_pay_fee = for_us == we_are_initiator - to_local_amt = local_amount - (fee if we_pay_fee else 0) - assert type(to_local_amt) is int - to_local = (bitcoin.TYPE_ADDRESS, local_address, to_local_amt // 1000) - to_remote_amt = remote_amount - (fee if not we_pay_fee else 0) - assert type(to_remote_amt) is int - to_remote = (bitcoin.TYPE_ADDRESS, remote_address, to_remote_amt // 1000) - c_outputs = [to_local, to_remote] - for script, msat_amount in htlcs: - c_outputs += [(bitcoin.TYPE_ADDRESS, bitcoin.redeem_script_to_address('p2wsh', bh2u(script)), msat_amount // 1000)] - - # trim outputs - c_outputs_filtered = list(filter(lambda x:x[2]>= dust_limit_sat, c_outputs)) - assert sum(x[2] for x in c_outputs) <= funding_sat - - # create commitment tx - tx = Transaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2) - tx.BIP_LI01_sort() - - tx.htlc_output_indices = {} - for idx, output in enumerate(c_outputs): - if output in tx.outputs(): - # minus the first two outputs (to_local, to_remote) - tx.htlc_output_indices[idx - 2] = tx.outputs().index(output) - - return tx - - -def calc_short_channel_id(block_height: int, tx_pos_in_block: int, output_index: int) -> bytes: - bh = block_height.to_bytes(3, byteorder='big') - tpos = tx_pos_in_block.to_bytes(3, byteorder='big') - oi = output_index.to_bytes(2, byteorder='big') - return bh + tpos + oi - - -def sign_and_get_sig_string(tx, local_config, remote_config): - pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) - tx.sign({bh2u(local_config.multisig_key.pubkey): (local_config.multisig_key.privkey, True)}) - sig_index = pubkeys.index(bh2u(local_config.multisig_key.pubkey)) - sig = bytes.fromhex(tx.inputs()[0]["signatures"][sig_index]) - r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order()) - sig_64 = sigencode_string_canonize(r, s, SECP256k1.generator.order()) - return sig_64 - -def is_synced(network): - local_height, server_height = network.get_status_value("updated") - synced = server_height != 0 and network.is_up_to_date() and local_height >= server_height - return synced - -def funding_output_script(local_config, remote_config): - pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) - return transaction.multisig_script(pubkeys, 2) - class Peer(PrintError): def __init__(self, lnworker, host, port, pubkey, request_initial_sync=False): t@@ -864,7 +531,7 @@ class Peer(PrintError): msg = gen_msg( "open_channel", temporary_channel_id=temp_channel_id, - chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), + chain_hash=REV_GENESIS, funding_satoshis=funding_sat, push_msat=push_msat, dust_limit_satoshis=local_config.dust_limit_sat, t@@ -1065,7 +732,7 @@ class Peer(PrintError): bitcoin_signature_2=bitcoin_sigs[1], len=0, #features not set (defaults to zeros) - chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), + chain_hash=REV_GENESIS, short_channel_id=chan.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], t@@ -1107,7 +774,7 @@ class Peer(PrintError): chan_ann = gen_msg("channel_announcement", len=0, #features not set (defaults to zeros) - chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), + chain_hash=REV_GENESIS, short_channel_id=chan.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], t@@ -1217,7 +884,7 @@ class Peer(PrintError): self.revoke(chan) # TODO process above commitment transactions - bare_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, + bare_ctx = chan.make_commitment(chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, msat_remote, msat_local) sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) t@@ -1249,7 +916,7 @@ class Peer(PrintError): async def receive_commitment_revoke_ack(self, htlc, decoded, payment_preimage): chan = self.channels[htlc['channel_id']] channel_id = chan.channel_id - expected_received_msat = int(decoded.amount * COIN * 1000) + expected_received_msat = int(decoded.amount * bitcoin.COIN * 1000) htlc_id = int.from_bytes(htlc["id"], 'big') assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id) t@@ -1279,7 +946,7 @@ class Peer(PrintError): self.send_message(gen_msg("update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=payment_preimage)) # remote commitment transaction without htlcs - bare_ctx = make_commitment_using_open_channel(m, m.remote_state.ctn + 1, False, m.remote_state.next_per_commitment_point, + bare_ctx = chan.make_commitment(m.remote_state.ctn + 1, False, m.remote_state.next_per_commitment_point, m.remote_state.amount_msat - expected_received_msat, m.local_state.amount_msat + expected_received_msat) sig_64 = sign_and_get_sig_string(bare_ctx, m.local_config, m.remote_config) self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0)) t@@ -1333,13 +1000,4 @@ class Peer(PrintError): self.channels[channel_id].update_fee(int.from_bytes(payload["feerate_per_kw"], "big")) -def count_trailing_zeros(index): - """ BOLT-03 (where_to_put_secret) """ - try: - return list(reversed(bin(index)[2:])).index("1") - except ValueError: - return 48 - -ShachainElement = namedtuple("ShachainElement", ["secret", "index"]) -ShachainElement.__str__ = lambda self: "ShachainElement(" + bh2u(self.secret) + "," + str(self.index) + ")" DIR diff --git a/lib/lnhtlc.py b/lib/lnhtlc.py t@@ -1,17 +1,17 @@ # ported from lnd 42de4400bff5105352d0552155f73589166d162b +from collections import namedtuple import binascii import json -from ecdsa.util import sigencode_string_canonize, sigdecode_der from .util import bfh, PrintError from .bitcoin import Hash -from collections import namedtuple -from ecdsa.curves import SECP256k1 from .crypto import sha256 from . import ecc -from . import lnbase -from .lnbase import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore -HTLC_TIMEOUT_WEIGHT = lnbase.HTLC_TIMEOUT_WEIGHT -HTLC_SUCCESS_WEIGHT = lnbase.HTLC_SUCCESS_WEIGHT +from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore +from .lnutil import get_per_commitment_secret_from_seed +from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey +from .lnutil import sign_and_get_sig_string +from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc +from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT SettleHtlc = namedtuple("SettleHtlc", ["htlc_id"]) RevokeAndAck = namedtuple("RevokeAndAck", ["per_commitment_secret", "next_per_commitment_point"]) t@@ -149,7 +149,6 @@ class HTLCStateMachine(PrintError): any). The HTLC signatures are sorted according to the BIP 69 order of the HTLC's on the commitment transaction. """ - from .lnbase import sign_and_get_sig_string, derive_privkey, make_htlc_tx_with_open_channel for htlc in self.local_update_log: if not type(htlc) is UpdateAddHtlc: continue if htlc.l_locked_in is None: htlc.l_locked_in = self.local_state.ctn t@@ -168,15 +167,14 @@ class HTLCStateMachine(PrintError): for we_receive, htlcs in zip([True, False], [self.htlcs_in_remote, self.htlcs_in_local]): assert len(htlcs) <= 1 for htlc in htlcs: - weight = lnbase.HTLC_SUCCESS_WEIGHT if we_receive else lnbase.HTLC_TIMEOUT_WEIGHT + weight = HTLC_SUCCESS_WEIGHT if we_receive else HTLC_TIMEOUT_WEIGHT if htlc.amount_msat // 1000 - weight * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: continue original_htlc_output_index = 0 args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.remote_commitment, original_htlc_output_index] htlc_tx = make_htlc_tx_with_open_channel(self, *args) sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) - r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order()) - htlc_sig = sigencode_string_canonize(r, s, SECP256k1.generator.order()) + htlc_sig = ecc.sig_string_from_der_sig(sig[:-1]) htlcsigs.append(htlc_sig) return sig_64, htlcsigs t@@ -192,7 +190,6 @@ class HTLCStateMachine(PrintError): state, then this newly added commitment becomes our current accepted channel state. """ - from .lnbase import make_htlc_tx_with_open_channel , derive_pubkey self.print_error("receive_new_commitment") for htlc in self.remote_update_log: t@@ -252,7 +249,6 @@ class HTLCStateMachine(PrintError): @property def points(self): - from .lnbase import get_per_commitment_secret_from_seed, secret_to_pubkey last_small_num = self.local_state.ctn next_small_num = last_small_num + 2 this_small_num = last_small_num + 1 t@@ -356,7 +352,6 @@ class HTLCStateMachine(PrintError): @property def remote_commitment(self): - from .lnbase import make_commitment_using_open_channel, make_received_htlc, make_offered_htlc, derive_pubkey, derive_blinded_pubkey remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts() assert local_msat >= 0 assert remote_msat >= 0 t@@ -371,7 +366,7 @@ class HTLCStateMachine(PrintError): htlcs_in_local = [] for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_local.append( t@@ -379,20 +374,19 @@ class HTLCStateMachine(PrintError): htlcs_in_remote = [] for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_remote.append( ( make_offered_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee)) - commit = make_commitment_using_open_channel(self, self.remote_state.ctn + 1, + commit = self.make_commitment(self.remote_state.ctn + 1, False, this_point, remote_msat - total_fee_remote, local_msat - total_fee_local, htlcs_in_local + htlcs_in_remote, trimmed) return commit @property def local_commitment(self): - from .lnbase import make_commitment_using_open_channel, make_received_htlc, make_offered_htlc, derive_pubkey, derive_blinded_pubkey, get_per_commitment_secret_from_seed, secret_to_pubkey remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts() assert local_msat >= 0 assert remote_msat >= 0 t@@ -407,7 +401,7 @@ class HTLCStateMachine(PrintError): htlcs_in_local = [] for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_local.append( t@@ -415,13 +409,13 @@ class HTLCStateMachine(PrintError): htlcs_in_remote = [] for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_remote.append( ( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee)) - commit = make_commitment_using_open_channel(self, self.local_state.ctn + 1, + commit = self.make_commitment(self.local_state.ctn + 1, True, this_point, local_msat - total_fee_local, remote_msat - total_fee_remote, htlcs_in_local + htlcs_in_remote, trimmed) return commit t@@ -523,3 +517,29 @@ class HTLCStateMachine(PrintError): def __str__(self): return self.serialize() + + def make_commitment(chan, ctn, for_us, pcp, local_msat, remote_msat, htlcs=[], trimmed=0): + conf = chan.local_config if for_us else chan.remote_config + other_conf = chan.local_config if not for_us else chan.remote_config + payment_pubkey = derive_pubkey(other_conf.payment_basepoint.pubkey, pcp) + remote_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) + return make_commitment( + ctn, + conf.multisig_key.pubkey, + other_conf.multisig_key.pubkey, + payment_pubkey, + chan.local_config.payment_basepoint.pubkey, + chan.remote_config.payment_basepoint.pubkey, + remote_revocation_pubkey, + derive_pubkey(conf.delayed_basepoint.pubkey, pcp), + other_conf.to_self_delay, + *chan.funding_outpoint, + chan.constraints.capacity, + local_msat, + remote_msat, + chan.local_config.dust_limit_sat, + chan.constraints.feerate, + for_us, + chan.constraints.is_initiator, + htlcs=htlcs, + trimmed=trimmed) DIR diff --git a/lib/lnrouter.py b/lib/lnrouter.py t@@ -40,8 +40,8 @@ from . import bitcoin from . import ecc from . import crypto from .crypto import sha256 -from .util import PrintError, bh2u, print_error, bfh, profiler, xor_bytes -from . import lnbase +from .util import PrintError, bh2u, profiler, xor_bytes +from .lnutil import get_ecdh class ChannelInfo(PrintError): t@@ -368,7 +368,7 @@ def get_shared_secrets_along_route(payment_path_pubkeys: Sequence[bytes], ephemeral_key = session_key # compute shared key for each hop for i in range(0, num_hops): - hop_shared_secrets[i] = lnbase.get_ecdh(ephemeral_key, payment_path_pubkeys[i]) + hop_shared_secrets[i] = get_ecdh(ephemeral_key, payment_path_pubkeys[i]) ephemeral_pubkey = ecc.ECPrivkey(ephemeral_key).get_public_key_bytes() blinding_factor = sha256(ephemeral_pubkey + hop_shared_secrets[i]) blinding_factor_int = int.from_bytes(blinding_factor, byteorder="big") t@@ -435,7 +435,7 @@ ProcessedOnionPacket = namedtuple("ProcessedOnionPacket", ["are_we_final", "hop_ # TODO replay protection def process_onion_packet(onion_packet: OnionPacket, associated_data: bytes, our_onion_private_key: bytes) -> ProcessedOnionPacket: - shared_secret = lnbase.get_ecdh(our_onion_private_key, onion_packet.public_key) + shared_secret = get_ecdh(our_onion_private_key, onion_packet.public_key) # check message integrity mu_key = get_bolt04_onion_key(b'mu', shared_secret) t@@ -519,5 +519,7 @@ def get_failure_msg_from_onion_error(decrypted_error_packet: bytes) -> OnionRout return OnionRoutingFailureMessage(failure_code, failure_data) + + # <----- bolt 04, "onion" DIR diff --git a/lib/lnutil.py b/lib/lnutil.py t@@ -0,0 +1,315 @@ +from .util import bfh, bh2u +from .crypto import sha256 +import json +from collections import namedtuple +from .transaction import Transaction +from .ecc import CURVE_ORDER, generator, sig_string_from_der_sig, ECPubkey, string_to_number +from . import ecc, bitcoin, crypto, transaction +from .transaction import opcodes +from .bitcoin import push_script + +HTLC_TIMEOUT_WEIGHT = 663 +HTLC_SUCCESS_WEIGHT = 703 + +Keypair = namedtuple("Keypair", ["pubkey", "privkey"]) +Outpoint = namedtuple("Outpoint", ["txid", "output_index"]) +ChannelConfig = namedtuple("ChannelConfig", [ + "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", + "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) +OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) +RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"]) +LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"]) +ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"]) +#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"]) + +class RevocationStore: + """ taken from lnd """ + def __init__(self): + self.buckets = [None] * 48 + self.index = 2**48 - 1 + def add_next_entry(self, hsh): + new_element = ShachainElement(index=self.index, secret=hsh) + bucket = count_trailing_zeros(self.index) + for i in range(0, bucket): + this_bucket = self.buckets[i] + e = shachain_derive(new_element, this_bucket.index) + + if e != this_bucket: + raise Exception("hash is not derivable: {} {} {}".format(bh2u(e.secret), bh2u(this_bucket.secret), this_bucket.index)) + self.buckets[bucket] = new_element + self.index -= 1 + def serialize(self): + return {"index": self.index, "buckets": [[bh2u(k.secret), k.index] if k is not None else None for k in self.buckets]} + @staticmethod + def from_json_obj(decoded_json_obj): + store = RevocationStore() + decode = lambda to_decode: ShachainElement(bfh(to_decode[0]), int(to_decode[1])) + store.buckets = [k if k is None else decode(k) for k in decoded_json_obj["buckets"]] + store.index = decoded_json_obj["index"] + return store + def __eq__(self, o): + return type(o) is RevocationStore and self.serialize() == o.serialize() + def __hash__(self): + return hash(json.dumps(self.serialize(), sort_keys=True)) + +def count_trailing_zeros(index): + """ BOLT-03 (where_to_put_secret) """ + try: + return list(reversed(bin(index)[2:])).index("1") + except ValueError: + return 48 + +def shachain_derive(element, toIndex): + return ShachainElement(get_per_commitment_secret_from_seed(element.secret, toIndex, count_trailing_zeros(element.index)), toIndex) + +ShachainElement = namedtuple("ShachainElement", ["secret", "index"]) +ShachainElement.__str__ = lambda self: "ShachainElement(" + bh2u(self.secret) + "," + str(self.index) + ")" + +def get_per_commitment_secret_from_seed(seed: bytes, i: int, bits: int = 48) -> bytes: + """Generate per commitment secret.""" + per_commitment_secret = bytearray(seed) + for bitindex in range(bits - 1, -1, -1): + mask = 1 << bitindex + if i & mask: + per_commitment_secret[bitindex // 8] ^= 1 << (bitindex % 8) + per_commitment_secret = bytearray(sha256(per_commitment_secret)) + bajts = bytes(per_commitment_secret) + return bajts + +def secret_to_pubkey(secret): + assert type(secret) is int + return (ecc.generator() * secret).get_public_key_bytes() + +def derive_pubkey(basepoint, per_commitment_point): + p = ecc.ECPubkey(basepoint) + ecc.generator() * ecc.string_to_number(sha256(per_commitment_point + basepoint)) + return p.get_public_key_bytes() + +def derive_privkey(secret, per_commitment_point): + assert type(secret) is int + basepoint = (ecc.generator() * secret).get_public_key_bytes() + basepoint = secret + ecc.string_to_number(sha256(per_commitment_point + basepoint)) + basepoint %= CURVE_ORDER + return basepoint + +def derive_blinded_pubkey(basepoint, per_commitment_point): + k1 = ecc.ECPubkey(basepoint) * ecc.string_to_number(sha256(basepoint + per_commitment_point)) + k2 = ecc.ECPubkey(per_commitment_point) * ecc.string_to_number(sha256(per_commitment_point + basepoint)) + return (k1 + k2).get_public_key_bytes() + +def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay): + assert type(amount_msat) is int + assert type(local_feerate) is int + assert type(revocationpubkey) is bytes + assert type(local_delayedpubkey) is bytes + script = bytes([opcodes.OP_IF]) \ + + bfh(push_script(bh2u(revocationpubkey))) \ + + bytes([opcodes.OP_ELSE]) \ + + bitcoin.add_number_to_script(to_self_delay) \ + + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) \ + + bfh(push_script(bh2u(local_delayedpubkey))) \ + + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) + + p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script)) + weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT + fee = local_feerate * weight + final_amount_sat = (amount_msat - fee) // 1000 + assert final_amount_sat > 0, final_amount_sat + output = (bitcoin.TYPE_ADDRESS, p2wsh, final_amount_sat) + return output + +def make_htlc_tx_witness(remotehtlcsig, localhtlcsig, payment_preimage, witness_script): + assert type(remotehtlcsig) is bytes + assert type(localhtlcsig) is bytes + assert type(payment_preimage) is bytes + assert type(witness_script) is bytes + return bfh(transaction.construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])) + +def make_htlc_tx_inputs(htlc_output_txid, htlc_output_index, revocationpubkey, local_delayedpubkey, amount_msat, witness_script): + assert type(htlc_output_txid) is str + assert type(htlc_output_index) is int + assert type(revocationpubkey) is bytes + assert type(local_delayedpubkey) is bytes + assert type(amount_msat) is int + assert type(witness_script) is str + c_inputs = [{ + 'scriptSig': '', + 'type': 'p2wsh', + 'signatures': [], + 'num_sig': 0, + 'prevout_n': htlc_output_index, + 'prevout_hash': htlc_output_txid, + 'value': amount_msat // 1000, + 'coinbase': False, + 'sequence': 0x0, + 'preimage_script': witness_script, + }] + return c_inputs + +def make_htlc_tx(cltv_timeout, inputs, output): + assert type(cltv_timeout) is int + c_outputs = [output] + tx = Transaction.from_io(inputs, c_outputs, locktime=cltv_timeout, version=2) + tx.BIP_LI01_sort() + return tx + +def make_offered_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash): + assert type(revocation_pubkey) is bytes + assert type(remote_htlcpubkey) is bytes + assert type(local_htlcpubkey) is bytes + assert type(payment_hash) is bytes + return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey))))\ + + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ + + bfh(push_script(bh2u(remote_htlcpubkey)))\ + + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) + bitcoin.add_number_to_script(32) + bytes([opcodes.OP_EQUAL, opcodes.OP_NOTIF, opcodes.OP_DROP])\ + + bitcoin.add_number_to_script(2) + bytes([opcodes.OP_SWAP]) + bfh(push_script(bh2u(local_htlcpubkey))) + bitcoin.add_number_to_script(2)\ + + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\ + + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) + +def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash, cltv_expiry): + for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]: + assert type(i) is bytes + assert type(cltv_expiry) is int + + return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) \ + + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey)))) \ + + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ + + bfh(push_script(bh2u(remote_htlcpubkey))) \ + + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) \ + + bitcoin.add_number_to_script(32) \ + + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_HASH160]) \ + + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) \ + + bytes([opcodes.OP_EQUALVERIFY]) \ + + bitcoin.add_number_to_script(2) \ + + bytes([opcodes.OP_SWAP]) \ + + bfh(push_script(bh2u(local_htlcpubkey))) \ + + bitcoin.add_number_to_script(2) \ + + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP]) \ + + bitcoin.add_number_to_script(cltv_expiry) \ + + bytes([opcodes.OP_CLTV, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) + +def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, cltv_expiry, payment_hash, commit, original_htlc_output_index): + conf = chan.local_config if for_us else chan.remote_config + other_conf = chan.local_config if not for_us else chan.remote_config + + revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) + delayedpubkey = derive_pubkey(conf.delayed_basepoint.pubkey, pcp) + other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) + other_htlc_pubkey = derive_pubkey(other_conf.htlc_basepoint.pubkey, pcp) + htlc_pubkey = derive_pubkey(conf.htlc_basepoint.pubkey, pcp) + # HTLC-success for the HTLC spending from a received HTLC output + # if we do not receive, and the commitment tx is not for us, they receive, so it is also an HTLC-success + is_htlc_success = for_us == we_receive + htlc_tx_output = make_htlc_tx_output( + amount_msat = amount_msat, + local_feerate = chan.constraints.feerate, + revocationpubkey=revocation_pubkey, + local_delayedpubkey=delayedpubkey, + success = is_htlc_success, + to_self_delay = other_conf.to_self_delay) + if is_htlc_success: + preimage_script = make_received_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash, cltv_expiry) + else: + preimage_script = make_offered_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash) + htlc_tx_inputs = make_htlc_tx_inputs( + commit.txid(), commit.htlc_output_indices[original_htlc_output_index], + revocationpubkey=revocation_pubkey, + local_delayedpubkey=delayedpubkey, + amount_msat=amount_msat, + witness_script=bh2u(preimage_script)) + if is_htlc_success: + cltv_expiry = 0 + htlc_tx = make_htlc_tx(cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output) + return htlc_tx + + +def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey, + remote_payment_pubkey, payment_basepoint, + remote_payment_basepoint, revocation_pubkey, + delayed_pubkey, to_self_delay, funding_txid, + funding_pos, funding_sat, local_amount, remote_amount, + dust_limit_sat, local_feerate, for_us, we_are_initiator, + htlcs, trimmed=0): + + pubkeys = sorted([bh2u(local_funding_pubkey), bh2u(remote_funding_pubkey)]) + payments = [payment_basepoint, remote_payment_basepoint] + if not we_are_initiator: + payments.reverse() + obs = get_obscured_ctn(ctn, *payments) + locktime = (0x20 << 24) + (obs & 0xffffff) + sequence = (0x80 << 24) + (obs >> 24) + # commitment tx input + c_inputs = [{ + 'type': 'p2wsh', + 'x_pubkeys': pubkeys, + 'signatures': [None, None], + 'num_sig': 2, + 'prevout_n': funding_pos, + 'prevout_hash': funding_txid, + 'value': funding_sat, + 'coinbase': False, + 'sequence': sequence + }] + # commitment tx outputs + local_script = bytes([opcodes.OP_IF]) + bfh(push_script(bh2u(revocation_pubkey))) + bytes([opcodes.OP_ELSE]) + bitcoin.add_number_to_script(to_self_delay) \ + + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) + bfh(push_script(bh2u(delayed_pubkey))) + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) + local_address = bitcoin.redeem_script_to_address('p2wsh', bh2u(local_script)) + remote_address = bitcoin.pubkey_to_address('p2wpkh', bh2u(remote_payment_pubkey)) + # TODO trim htlc outputs here while also considering 2nd stage htlc transactions + fee = local_feerate * overall_weight(len(htlcs)) + fee -= trimmed * 1000 + assert type(fee) is int + we_pay_fee = for_us == we_are_initiator + to_local_amt = local_amount - (fee if we_pay_fee else 0) + assert type(to_local_amt) is int + to_local = (bitcoin.TYPE_ADDRESS, local_address, to_local_amt // 1000) + to_remote_amt = remote_amount - (fee if not we_pay_fee else 0) + assert type(to_remote_amt) is int + to_remote = (bitcoin.TYPE_ADDRESS, remote_address, to_remote_amt // 1000) + c_outputs = [to_local, to_remote] + for script, msat_amount in htlcs: + c_outputs += [(bitcoin.TYPE_ADDRESS, bitcoin.redeem_script_to_address('p2wsh', bh2u(script)), msat_amount // 1000)] + + # trim outputs + c_outputs_filtered = list(filter(lambda x:x[2]>= dust_limit_sat, c_outputs)) + assert sum(x[2] for x in c_outputs) <= funding_sat + + # create commitment tx + tx = Transaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2) + tx.BIP_LI01_sort() + + tx.htlc_output_indices = {} + for idx, output in enumerate(c_outputs): + if output in tx.outputs(): + # minus the first two outputs (to_local, to_remote) + tx.htlc_output_indices[idx - 2] = tx.outputs().index(output) + + return tx + +def sign_and_get_sig_string(tx, local_config, remote_config): + pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) + tx.sign({bh2u(local_config.multisig_key.pubkey): (local_config.multisig_key.privkey, True)}) + sig_index = pubkeys.index(bh2u(local_config.multisig_key.pubkey)) + sig = bytes.fromhex(tx.inputs()[0]["signatures"][sig_index]) + sig_64 = sig_string_from_der_sig(sig[:-1]) + return sig_64 + +def funding_output_script(local_config, remote_config): + pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) + return transaction.multisig_script(pubkeys, 2) + +def calc_short_channel_id(block_height: int, tx_pos_in_block: int, output_index: int) -> bytes: + bh = block_height.to_bytes(3, byteorder='big') + tpos = tx_pos_in_block.to_bytes(3, byteorder='big') + oi = output_index.to_bytes(2, byteorder='big') + return bh + tpos + oi + +def get_obscured_ctn(ctn, local, remote): + mask = int.from_bytes(sha256(local + remote)[-6:], 'big') + return ctn ^ mask + +def overall_weight(num_htlc): + return 500 + 172 * num_htlc + 224 + +def get_ecdh(priv: bytes, pub: bytes) -> bytes: + pt = ECPubkey(pub) * string_to_number(priv) + return sha256(pt.get_public_key_bytes()) DIR diff --git a/lib/lnwatcher.py b/lib/lnwatcher.py t@@ -1,5 +1,5 @@ from .util import PrintError -from .lnbase import funding_output_script +from .lnutil import funding_output_script from .bitcoin import redeem_script_to_address class LNWatcher(PrintError): DIR diff --git a/lib/lnworker.py b/lib/lnworker.py t@@ -10,12 +10,12 @@ from . import constants from .bitcoin import sha256, COIN from .util import bh2u, bfh, PrintError from .constants import set_testnet, set_simnet -from .lnbase import Peer, calc_short_channel_id, privkey_to_pubkey +from .lnbase import Peer, privkey_to_pubkey from .lightning_payencode.lnaddr import lnencode, LnAddr, lndecode -from .ecc import ECPrivkey, CURVE_ORDER, der_sig_from_sig_string +from .ecc import der_sig_from_sig_string from .transaction import Transaction from .lnhtlc import HTLCStateMachine -from .lnbase import Outpoint +from .lnutil import Outpoint, calc_short_channel_id # hardcoded nodes node_list = [ t@@ -33,7 +33,7 @@ class LNWorker(PrintError): wallet.storage.put('lightning_privkey', pk) wallet.storage.write() self.privkey = bfh(pk) - self.pubkey = ECPrivkey(self.privkey).get_public_key_bytes() + self.pubkey = privkey_to_pubkey(self.privkey) self.config = network.config self.peers = {} self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))} t@@ -157,8 +157,6 @@ class LNWorker(PrintError): RHASH = sha256(payment_preimage) amount_btc = amount_sat/Decimal(COIN) if amount_sat else None pay_req = lnencode(LnAddr(RHASH, amount_btc, tags=[('d', message)]), self.privkey) - decoded = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP) - assert decoded.pubkey.serialize() == privkey_to_pubkey(self.privkey) self.invoices[bh2u(payment_preimage)] = pay_req self.wallet.storage.put('lightning_invoices', self.invoices) self.wallet.storage.write() DIR diff --git a/lib/tests/test_lnbase.py b/lib/tests/test_lnbase.py t@@ -1,819 +0,0 @@ -import binascii -import json -import unittest - -from lib.util import bh2u, bfh -from lib.lnbase import make_commitment, get_obscured_ctn, Peer, make_offered_htlc, make_received_htlc, make_htlc_tx -from lib.lnbase import secret_to_pubkey, derive_pubkey, derive_privkey, derive_blinded_pubkey, overall_weight -from lib.lnbase import make_htlc_tx_output, make_htlc_tx_inputs, get_per_commitment_secret_from_seed, make_htlc_tx_witness -from lib.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop -from lib.lnbase import RevocationStore -from lib.transaction import Transaction -from lib import bitcoin -import ecdsa.ellipticcurve -from ecdsa.curves import SECP256k1 -from lib.util import bfh -from lib import bitcoin, lnbase, lnrouter - -funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be' -funding_output_index = 0 -funding_amount_satoshi = 10000000 -commitment_number = 42 -local_delay = 144 -local_dust_limit_satoshi = 546 - -local_payment_basepoint = bytes.fromhex('034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa') -remote_payment_basepoint = bytes.fromhex('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991') -obs = get_obscured_ctn(42, local_payment_basepoint, remote_payment_basepoint) -local_funding_privkey = bytes.fromhex('30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901') -local_funding_pubkey = bytes.fromhex('023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb') -remote_funding_pubkey = bytes.fromhex('030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1') -local_privkey = bytes.fromhex('bb13b121cdc357cd2e608b0aea294afca36e2b34cf958e2e6451a2f27469449101') -localpubkey = bytes.fromhex('030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e7') -remotepubkey = bytes.fromhex('0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b') -local_delayedpubkey = bytes.fromhex('03fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c') -local_revocation_pubkey = bytes.fromhex('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19') -# funding wscript = 5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae - -class Test_LNBase(unittest.TestCase): - - @staticmethod - def parse_witness_list(witness_bytes): - amount_witnesses = witness_bytes[0] - witness_bytes = witness_bytes[1:] - res = [] - for i in range(amount_witnesses): - witness_length = witness_bytes[0] - this_witness = witness_bytes[1:witness_length+1] - assert len(this_witness) == witness_length - witness_bytes = witness_bytes[witness_length+1:] - res += [bytes(this_witness)] - assert witness_bytes == b"", witness_bytes - return res - - def test_simple_commitment_tx_with_no_HTLCs(self): - to_local_msat = 7000000000 - to_remote_msat = 3000000000 - local_feerate_per_kw = 15000 - # base commitment transaction fee = 10860 - # actual commitment transaction fee = 10860 - # to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = "3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0" - # local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939 - htlcs=[] - our_commit_tx = make_commitment( - commitment_number, - local_funding_pubkey, remote_funding_pubkey, remotepubkey, - local_payment_basepoint, remote_payment_basepoint, - local_revocation_pubkey, local_delayedpubkey, local_delay, - funding_tx_id, funding_output_index, funding_amount_satoshi, - to_local_msat, to_remote_msat, local_dust_limit_satoshi, - local_feerate_per_kw, True, we_are_initiator=True, htlcs=[]) - self.sign_and_insert_remote_sig(our_commit_tx, remote_funding_pubkey, remote_signature, local_funding_pubkey, local_funding_privkey) - ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220' - self.assertEqual(str(our_commit_tx), ref_commit_tx_str) - - def sign_and_insert_remote_sig(self, tx, remote_pubkey, remote_signature, pubkey, privkey): - assert type(remote_pubkey) is bytes - assert len(remote_pubkey) == 33 - assert type(remote_signature) is str - assert type(pubkey) is bytes - assert type(privkey) is bytes - assert len(pubkey) == 33 - assert len(privkey) == 33 - tx.sign({bh2u(pubkey): (privkey[:-1], True)}) - pubkeys, _x_pubkeys = tx.get_sorted_pubkeys(tx.inputs()[0]) - index_of_pubkey = pubkeys.index(bh2u(remote_pubkey)) - tx._inputs[0]["signatures"][index_of_pubkey] = remote_signature + "01" - tx.raw = None - - def test_commitment_tx_with_all_five_HTLCs_untrimmed_minimum_feerate(self): - to_local_msat = 6988000000 - to_remote_msat = 3000000000 - local_feerate_per_kw = 0 - # base commitment transaction fee = 0 - # actual commitment transaction fee = 0 - - per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100 - per_commitment_point = SECP256k1.generator * per_commitment_secret - - remote_htlcpubkey = remotepubkey - local_htlcpubkey = localpubkey - - htlc2_cltv_timeout = 502 - htlc2_payment_preimage = b"\x02" * 32 - htlc2 = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc2_payment_preimage)) - # HTLC 2 offered amount 2000 - ref_htlc2_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868" - self.assertEqual(htlc2, bfh(ref_htlc2_wscript)) - - htlc3_cltv_timeout = 503 - htlc3_payment_preimage = b"\x03" * 32 - htlc3 = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc3_payment_preimage)) - # HTLC 3 offered amount 3000 - ref_htlc3_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868" - self.assertEqual(htlc3, bfh(ref_htlc3_wscript)) - - htlc0_cltv_timeout = 500 - htlc0_payment_preimage = b"\x00" * 32 - htlc0 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc0_payment_preimage), htlc0_cltv_timeout) - # HTLC 0 received amount 1000 - ref_htlc0_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6868" - self.assertEqual(htlc0, bfh(ref_htlc0_wscript)) - - htlc1_cltv_timeout = 501 - htlc1_payment_preimage = b"\x01" * 32 - htlc1 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc1_payment_preimage), htlc1_cltv_timeout) - # HTLC 1 received amount 2000 - ref_htlc1_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868" - self.assertEqual(htlc1, bfh(ref_htlc1_wscript)) - - htlc4_cltv_timeout = 504 - htlc4_payment_preimage = b"\x04" * 32 - htlc4 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc4_payment_preimage), htlc4_cltv_timeout) - # HTLC 4 received amount 4000 - ref_htlc4_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868" - self.assertEqual(htlc4, bfh(ref_htlc4_wscript)) - - # to_local amount 6988000 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" - # local_signature = 30440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f06 parazyd.org:70 /git/electrum/commit/fe973a5137ecb1ed9a8b63e042363b0c7792edef.gph:1187: line too long