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