Merge pull request #4467 from spesmilo/merge_openchannel_htlc_state_machine

ln: Merge OpenChannel and HTLCStateMachine
This commit is contained in:
ThomasV 2018-06-29 12:15:08 +02:00 committed by GitHub
commit fbe54d8a81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1027 additions and 971 deletions

View File

@ -4,13 +4,13 @@ from PyQt5.QtWidgets import *
from electrum.util import inv_dict, bh2u, bfh
from electrum.i18n import _
from electrum.lnbase import OpenChannel
from electrum.lnhtlc import HTLCStateMachine
from .util import MyTreeWidget, SortableTreeWidgetItem, WindowModalDialog, Buttons, OkButton, CancelButton
from .amountedit import BTCAmountEdit
class ChannelsList(MyTreeWidget):
update_rows = QtCore.pyqtSignal()
update_single_row = QtCore.pyqtSignal(OpenChannel)
update_single_row = QtCore.pyqtSignal(HTLCStateMachine)
def __init__(self, parent):
MyTreeWidget.__init__(self, parent, self.create_menu, [_('Node ID'), _('Balance'), _('Remote'), _('Status')], 0)
@ -33,11 +33,12 @@ class ChannelsList(MyTreeWidget):
channel_id = self.currentItem().data(0, QtCore.Qt.UserRole)
print('ID', bh2u(channel_id))
def close():
self.parent.wallet.lnworker.close_channel(channel_id)
suc, msg = self.parent.wallet.lnworker.close_channel(channel_id)
assert suc # TODO show error message in dialog
menu.addAction(_("Close channel"), close)
menu.exec_(self.viewport().mapToGlobal(position))
@QtCore.pyqtSlot(OpenChannel)
@QtCore.pyqtSlot(HTLCStateMachine)
def do_update_single_row(self, chan):
for i in range(self.topLevelItemCount()):
item = self.topLevelItem(i)

View File

@ -684,7 +684,7 @@ class Commands:
# lightning network commands
@command('wpn')
def open_channel(self, node_id, amount, channel_push=0, password=None):
f = self.wallet.lnworker.open_channel(node_id, satoshis(amount), satoshis(channel_push), password)
f = self.wallet.lnworker.open_channel(bytes.fromhex(node_id), satoshis(amount), satoshis(channel_push), password)
return f.result()
@command('wn')

View File

@ -4,12 +4,16 @@
Derived from https://gist.github.com/AdamISZ/046d05c156aaeb56cc897f85eecb3eb8
"""
from collections import namedtuple, defaultdict, OrderedDict, defaultdict
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
from collections import OrderedDict, defaultdict
import asyncio
from concurrent.futures import FIRST_COMPLETED
import os
@ -17,19 +21,8 @@ import time
import binascii
import hashlib
import hmac
from typing import Sequence, Union, Tuple
from collections import namedtuple, defaultdict
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
@ -42,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
@ -249,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)
@ -274,17 +265,6 @@ def create_ephemeral_key(privkey):
pub = privkey_to_pubkey(privkey)
return (privkey[:32], pub)
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"])
def aiosafe(f):
async def f2(*args, **kwargs):
@ -299,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):
@ -835,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,
@ -887,14 +583,14 @@ class Peer(PrintError):
# remote commitment transaction
channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_index)
their_revocation_store = RevocationStore()
chan = OpenChannel(
node_id=self.pubkey,
channel_id=channel_id,
short_channel_id=None,
funding_outpoint=Outpoint(funding_txid, funding_index),
local_config=local_config,
remote_config=remote_config,
remote_state=RemoteState(
chan = {
"node_id": self.pubkey,
"channel_id": channel_id,
"short_channel_id": None,
"funding_outpoint": Outpoint(funding_txid, funding_index),
"local_config": local_config,
"remote_config": remote_config,
"remote_state": RemoteState(
ctn = -1,
next_per_commitment_point=remote_per_commitment_point,
current_per_commitment_point=None,
@ -902,7 +598,7 @@ class Peer(PrintError):
revocation_store=their_revocation_store,
next_htlc_id = 0
),
local_state=LocalState(
"local_state": LocalState(
ctn = -1,
per_commitment_secret_seed=per_commitment_secret_seed,
amount_msat=local_amount,
@ -911,8 +607,8 @@ class Peer(PrintError):
was_announced = False,
current_commitment_signature = None
),
constraints=ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth)
)
"constraints": ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth)
}
m = HTLCStateMachine(chan)
sig_64, _ = m.sign_next_commitment()
self.send_message(gen_msg("funding_created",
@ -927,7 +623,9 @@ class Peer(PrintError):
# broadcast funding tx
success, _txid = self.network.broadcast_transaction(funding_tx)
assert success, success
return chan._replace(remote_state=chan.remote_state._replace(ctn=0),local_state=chan.local_state._replace(ctn=0, current_commitment_signature=remote_sig))
m.remote_state = m.remote_state._replace(ctn=0)
m.local_state = m.local_state._replace(ctn=0, current_commitment_signature=remote_sig)
return m
@aiosafe
async def reestablish_channel(self, chan):
@ -987,7 +685,8 @@ class Peer(PrintError):
their_next_point = payload["next_per_commitment_point"]
new_remote_state = chan.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point)
new_local_state = chan.local_state._replace(funding_locked_received = True)
chan = chan._replace(remote_state=new_remote_state, local_state=new_local_state)
chan.remote_state=new_remote_state
chan.local_state=new_local_state
self.lnworker.save_channel(chan)
if chan.short_channel_id:
self.mark_open(chan)
@ -999,7 +698,7 @@ class Peer(PrintError):
Runs on the Network thread.
"""
if not chan.local_state.was_announced and funding_tx_depth >= 6:
chan = chan._replace(local_state=chan.local_state._replace(was_announced=True))
chan.local_state=chan.local_state._replace(was_announced=True)
coro = self.handle_announcements(chan)
self.lnworker.save_channel(chan)
asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
@ -1033,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],
@ -1075,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],
@ -1146,69 +845,68 @@ class Peer(PrintError):
self.send_message(gen_msg("update_add_htlc", channel_id=chan.channel_id, id=chan.local_state.next_htlc_id, cltv_expiry=final_cltv_expiry_with_deltas, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes()))
m = HTLCStateMachine(chan)
m.add_htlc(htlc)
chan.add_htlc(htlc)
self.attempted_route[(chan.channel_id, htlc.htlc_id)] = route
sig_64, htlc_sigs = m.sign_next_commitment()
sig_64, htlc_sigs = chan.sign_next_commitment()
htlc_sig = htlc_sigs[0]
self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig))
await self.receive_revoke(m)
await self.receive_revoke(chan)
self.revoke(m)
self.revoke(chan)
fulfill_coro = asyncio.ensure_future(self.update_fulfill_htlc[chan.channel_id].get())
failure_coro = asyncio.ensure_future(self.update_fail_htlc[chan.channel_id].get())
done, pending = await asyncio.wait([fulfill_coro, failure_coro], return_when=FIRST_COMPLETED)
if failure_coro.done():
sig_64, htlc_sigs = m.sign_next_commitment()
sig_64, htlc_sigs = chan.sign_next_commitment()
self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sigs[0]))
while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"":
self.revoke(m)
await self.receive_revoke(m)
m.fail_htlc(htlc)
sig_64, htlc_sigs = m.sign_next_commitment()
self.revoke(chan)
await self.receive_revoke(chan)
chan.fail_htlc(htlc)
sig_64, htlc_sigs = chan.sign_next_commitment()
self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=0))
await self.receive_revoke(m)
await self.receive_revoke(chan)
fulfill_coro.cancel()
self.lnworker.save_channel(m.state)
self.lnworker.save_channel(chan)
return failure_coro.result()
if fulfill_coro.done():
failure_coro.cancel()
update_fulfill_htlc_msg = fulfill_coro.result()
m.receive_htlc_settle(update_fulfill_htlc_msg["payment_preimage"], int.from_bytes(update_fulfill_htlc_msg["id"], "big"))
chan.receive_htlc_settle(update_fulfill_htlc_msg["payment_preimage"], int.from_bytes(update_fulfill_htlc_msg["id"], "big"))
while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"":
self.revoke(m)
self.revoke(chan)
# TODO process above commitment transactions
bare_ctx = make_commitment_using_open_channel(m.state, m.state.remote_state.ctn + 1, False, m.state.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)
self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=0))
await self.receive_revoke(m)
await self.receive_revoke(chan)
self.lnworker.save_channel(m.state)
self.lnworker.save_channel(chan)
async def receive_revoke(self, m):
revoke_and_ack_msg = await self.revoke_and_ack[m.state.channel_id].get()
revoke_and_ack_msg = await self.revoke_and_ack[m.channel_id].get()
m.receive_revocation(RevokeAndAck(revoke_and_ack_msg["per_commitment_secret"], revoke_and_ack_msg["next_per_commitment_point"]))
def revoke(self, m):
rev, _ = m.revoke_current_commitment()
self.send_message(gen_msg("revoke_and_ack",
channel_id=m.state.channel_id,
channel_id=m.channel_id,
per_commitment_secret=rev.per_commitment_secret,
next_per_commitment_point=rev.next_per_commitment_point))
async def receive_commitment(self, m):
commitment_signed_msg = await self.commitment_signed[m.state.channel_id].get()
commitment_signed_msg = await self.commitment_signed[m.channel_id].get()
data = commitment_signed_msg["htlc_signature"]
htlc_sigs = [data[i:i+64] for i in range(0, len(data), 64)]
m.receive_new_commitment(commitment_signed_msg["signature"], htlc_sigs)
@ -1218,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)
@ -1232,42 +930,41 @@ class Peer(PrintError):
htlc = UpdateAddHtlc(amount_msat, payment_hash, cltv_expiry, 0)
m = HTLCStateMachine(chan)
chan.receive_htlc(htlc)
m.receive_htlc(htlc)
assert (await self.receive_commitment(chan)) == 1
assert (await self.receive_commitment(m)) == 1
self.revoke(chan)
self.revoke(m)
sig_64, htlc_sigs = m.sign_next_commitment()
sig_64, htlc_sigs = chan.sign_next_commitment()
htlc_sig = htlc_sigs[0]
self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig))
await self.receive_revoke(m)
await self.receive_revoke(chan)
m.settle_htlc(payment_preimage, htlc_id)
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.state, m.state.remote_state.ctn + 1, False, m.state.remote_state.next_per_commitment_point,
m.state.remote_state.amount_msat - expected_received_msat, m.state.local_state.amount_msat + expected_received_msat)
sig_64 = sign_and_get_sig_string(bare_ctx, m.state.local_config, m.state.remote_config)
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))
await self.receive_revoke(m)
await self.receive_revoke(chan)
assert (await self.receive_commitment(m)) == 0
assert (await self.receive_commitment(chan)) == 0
self.revoke(m)
self.revoke(chan)
self.lnworker.save_channel(m.state)
self.lnworker.save_channel(chan)
def on_commitment_signed(self, payload):
self.print_error("commitment_signed", payload)
channel_id = payload['channel_id']
chan = self.channels[channel_id]
self.lnworker.save_channel(chan._replace(local_state=chan.local_state._replace(current_commitment_signature=payload['signature'])))
chan.local_state=chan.local_state._replace(current_commitment_signature=payload['signature'])
self.lnworker.save_channel(chan)
self.commitment_signed[channel_id].put_nowait(payload)
def on_update_fulfill_htlc(self, payload):
@ -1298,43 +995,9 @@ class Peer(PrintError):
channel_id = payload["channel_id"]
self.revoke_and_ack[channel_id].put_nowait(payload)
def on_update_fee(self, payload):
channel_id = payload["channel_id"]
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) + ")"
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))

View File

@ -1,14 +1,17 @@
# ported from lnd 42de4400bff5105352d0552155f73589166d162b
from ecdsa.util import sigencode_string_canonize, sigdecode_der
from collections import namedtuple
import binascii
import json
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
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"])
@ -38,6 +41,24 @@ class UpdateAddHtlc:
def __repr__(self):
return "UpdateAddHtlc" + str(self.as_tuple())
is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key")
def maybeDecode(k, v):
assert type(v) is not list
if k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature"] and v is not None:
return binascii.unhexlify(v)
return v
def decodeAll(v):
return {i: maybeDecode(i, j) for i, j in v.items()} if isinstance(v, dict) else v
def typeWrap(k, v, local):
if is_key(k):
if local:
return Keypair(**v)
else:
return OnlyPubkeyKeypair(**v)
return v
class HTLCStateMachine(PrintError):
def lookup_htlc(self, log, htlc_id):
@ -52,7 +73,33 @@ class HTLCStateMachine(PrintError):
return str(self.name)
def __init__(self, state, name = None):
self.state = state
self.local_config = state["local_config"]
if type(self.local_config) is not ChannelConfig:
new_local_config = {k: typeWrap(k, decodeAll(v), True) for k, v in self.local_config.items()}
self.local_config = ChannelConfig(**new_local_config)
self.remote_config = state["remote_config"]
if type(self.remote_config) is not ChannelConfig:
new_remote_config = {k: typeWrap(k, decodeAll(v), False) for k, v in self.remote_config.items()}
self.remote_config = ChannelConfig(**new_remote_config)
self.local_state = state["local_state"]
if type(self.local_state) is not LocalState:
self.local_state = LocalState(**decodeAll(self.local_state))
self.remote_state = state["remote_state"]
if type(self.remote_state) is not RemoteState:
self.remote_state = RemoteState(**decodeAll(self.remote_state))
if type(self.remote_state.revocation_store) is not RevocationStore:
self.remote_state = self.remote_state._replace(revocation_store = RevocationStore.from_json_obj(self.remote_state.revocation_store))
self.channel_id = maybeDecode("channel_id", state["channel_id"]) if type(state["channel_id"]) is not bytes else state["channel_id"]
self.constraints = ChannelConstraints(**decodeAll(state["constraints"])) if type(state["constraints"]) is not ChannelConstraints else state["constraints"]
self.funding_outpoint = Outpoint(**decodeAll(state["funding_outpoint"])) if type(state["funding_outpoint"]) is not Outpoint else state["funding_outpoint"]
self.node_id = maybeDecode("node_id", state["node_id"]) if type(state["node_id"]) is not bytes else state["node_id"]
self.short_channel_id = maybeDecode("short_channel_id", state["short_channel_id"]) if type(state["short_channel_id"]) is not bytes else state["short_channel_id"]
self.local_update_log = []
self.remote_update_log = []
@ -60,6 +107,7 @@ class HTLCStateMachine(PrintError):
self.total_msat_sent = 0
self.total_msat_received = 0
self.pending_feerate = None
def add_htlc(self, htlc):
"""
@ -69,8 +117,8 @@ class HTLCStateMachine(PrintError):
assert type(htlc) is UpdateAddHtlc
self.local_update_log.append(htlc)
self.print_error("add_htlc")
htlc_id = self.state.local_state.next_htlc_id
self.state = self.state._replace(local_state=self.state.local_state._replace(next_htlc_id=htlc_id + 1))
htlc_id = self.local_state.next_htlc_id
self.local_state=self.local_state._replace(next_htlc_id=htlc_id + 1)
htlc.htlc_id = htlc_id
return htlc_id
@ -83,8 +131,8 @@ class HTLCStateMachine(PrintError):
self.print_error("receive_htlc")
assert type(htlc) is UpdateAddHtlc
self.remote_update_log.append(htlc)
htlc_id = self.state.remote_state.next_htlc_id
self.state = self.state._replace(remote_state=self.state.remote_state._replace(next_htlc_id=htlc_id + 1))
htlc_id = self.remote_state.next_htlc_id
self.remote_state=self.remote_state._replace(next_htlc_id=htlc_id + 1)
htlc.htlc_id = htlc_id
return htlc_id
@ -101,17 +149,16 @@ 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.state.local_state.ctn
if htlc.l_locked_in is None: htlc.l_locked_in = self.local_state.ctn
self.print_error("sign_next_commitment")
sig_64 = sign_and_get_sig_string(self.remote_commitment, self.state.local_config, self.state.remote_config)
sig_64 = sign_and_get_sig_string(self.remote_commitment, self.local_config, self.remote_config)
their_remote_htlc_privkey_number = derive_privkey(
int.from_bytes(self.state.local_config.htlc_basepoint.privkey, 'big'),
self.state.remote_state.next_per_commitment_point)
int.from_bytes(self.local_config.htlc_basepoint.privkey, 'big'),
self.remote_state.next_per_commitment_point)
their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
for_us = False
@ -120,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
if htlc.amount_msat // 1000 - weight * (self.state.constraints.feerate // 1000) < self.state.remote_config.dust_limit_sat:
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.state.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.state, *args)
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
@ -144,17 +190,16 @@ 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:
if not type(htlc) is UpdateAddHtlc: continue
if htlc.r_locked_in is None: htlc.r_locked_in = self.state.remote_state.ctn
if htlc.r_locked_in is None: htlc.r_locked_in = self.remote_state.ctn
assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
preimage_hex = self.local_commitment.serialize_preimage(0)
pre_hash = Hash(bfh(preimage_hex))
if not ecc.verify_signature(self.state.remote_config.multisig_key.pubkey, sig, pre_hash):
if not ecc.verify_signature(self.remote_config.multisig_key.pubkey, sig, pre_hash):
raise Exception('failed verifying signature of our updated commitment transaction: ' + str(sig))
_, this_point, _ = self.points
@ -165,9 +210,9 @@ class HTLCStateMachine(PrintError):
payment_hash = self.htlcs_in_remote[0].payment_hash
amount_msat = self.htlcs_in_remote[0].amount_msat
cltv_expiry = self.htlcs_in_remote[0].cltv_expiry
htlc_tx = make_htlc_tx_with_open_channel(self.state, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.local_commitment, 0)
htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.local_commitment, 0)
pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
remote_htlc_pubkey = derive_pubkey(self.state.remote_config.htlc_basepoint.pubkey, this_point)
remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
if not ecc.verify_signature(remote_htlc_pubkey, htlc_sigs[0], pre_hash):
raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
@ -188,25 +233,29 @@ class HTLCStateMachine(PrintError):
last_secret, this_point, next_point = self.points
self.state = self.state._replace(
local_state=self.state.local_state._replace(
ctn=self.state.local_state.ctn + 1
)
if self.pending_feerate is not None:
new_feerate = self.pending_feerate
else:
new_feerate = self.constraints.feerate
self.local_state=self.local_state._replace(
ctn=self.local_state.ctn + 1
)
self.constraints=self.constraints._replace(
feerate=new_feerate
)
return RevokeAndAck(last_secret, next_point), "current htlcs"
@property
def points(self):
from .lnbase import get_per_commitment_secret_from_seed, secret_to_pubkey
chan = self.state
last_small_num = chan.local_state.ctn
last_small_num = self.local_state.ctn
next_small_num = last_small_num + 2
this_small_num = last_small_num + 1
last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1)
this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1)
last_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, 2**48-last_small_num-1)
this_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, 2**48-this_small_num-1)
this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big'))
next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1)
next_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, 2**48-next_small_num-1)
next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big'))
return last_secret, this_point, next_point
@ -263,22 +312,20 @@ class HTLCStateMachine(PrintError):
received_fees = sum(x.total_fee for x in to_remove)
self.state.remote_state.revocation_store.add_next_entry(revocation.per_commitment_secret)
self.remote_state.revocation_store.add_next_entry(revocation.per_commitment_secret)
next_point = self.state.remote_state.next_per_commitment_point
next_point = self.remote_state.next_per_commitment_point
print("RECEIVED", received_this_batch)
print("SENT", sent_this_batch)
self.state = self.state._replace(
remote_state=self.state.remote_state._replace(
ctn=self.state.remote_state.ctn + 1,
current_per_commitment_point=next_point,
next_per_commitment_point=revocation.next_per_commitment_point,
amount_msat=self.state.remote_state.amount_msat + (sent_this_batch - received_this_batch) + sent_fees - received_fees
),
local_state=self.state.local_state._replace(
amount_msat = self.state.local_state.amount_msat + (received_this_batch - sent_this_batch) - sent_fees + received_fees
)
self.remote_state=self.remote_state._replace(
ctn=self.remote_state.ctn + 1,
current_per_commitment_point=next_point,
next_per_commitment_point=revocation.next_per_commitment_point,
amount_msat=self.remote_state.amount_msat + (sent_this_batch - received_this_batch) + sent_fees - received_fees
)
self.local_state=self.local_state._replace(
amount_msat = self.local_state.amount_msat + (received_this_batch - sent_this_batch) - sent_fees + received_fees
)
@staticmethod
@ -297,30 +344,29 @@ class HTLCStateMachine(PrintError):
htlc_value_remote, total_fee_remote = self.htlcsum(self.htlcs_in_remote)
total_fee_local += local_settled_fee
total_fee_remote += remote_settled_fee
local_msat = self.state.local_state.amount_msat -\
local_msat = self.local_state.amount_msat -\
htlc_value_local + remote_settled_value - local_settled_value
remote_msat = self.state.remote_state.amount_msat -\
remote_msat = self.remote_state.amount_msat -\
htlc_value_remote + local_settled_value - remote_settled_value
return remote_msat, total_fee_remote, local_msat, total_fee_local
@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
this_point = self.state.remote_state.next_per_commitment_point
this_point = self.remote_state.next_per_commitment_point
remote_htlc_pubkey = derive_pubkey(self.state.remote_config.htlc_basepoint.pubkey, this_point)
local_htlc_pubkey = derive_pubkey(self.state.local_config.htlc_basepoint.pubkey, this_point)
local_revocation_pubkey = derive_blinded_pubkey(self.state.local_config.revocation_basepoint.pubkey, this_point)
remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
local_htlc_pubkey = derive_pubkey(self.local_config.htlc_basepoint.pubkey, this_point)
local_revocation_pubkey = derive_blinded_pubkey(self.local_config.revocation_basepoint.pubkey, this_point)
trimmed = 0
htlcs_in_local = []
for htlc in self.htlcs_in_local:
if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.state.constraints.feerate // 1000) < self.state.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(
@ -328,35 +374,34 @@ class HTLCStateMachine(PrintError):
htlcs_in_remote = []
for htlc in self.htlcs_in_remote:
if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.state.constraints.feerate // 1000) < self.state.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.state, self.state.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
_, this_point, _ = self.points
remote_htlc_pubkey = derive_pubkey(self.state.remote_config.htlc_basepoint.pubkey, this_point)
local_htlc_pubkey = derive_pubkey(self.state.local_config.htlc_basepoint.pubkey, this_point)
remote_revocation_pubkey = derive_blinded_pubkey(self.state.remote_config.revocation_basepoint.pubkey, this_point)
remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
local_htlc_pubkey = derive_pubkey(self.local_config.htlc_basepoint.pubkey, this_point)
remote_revocation_pubkey = derive_blinded_pubkey(self.remote_config.revocation_basepoint.pubkey, this_point)
trimmed = 0
htlcs_in_local = []
for htlc in self.htlcs_in_local:
if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.state.constraints.feerate // 1000) < self.state.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(
@ -364,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.state.constraints.feerate // 1000) < self.state.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.state, self.state.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
@ -383,7 +428,7 @@ class HTLCStateMachine(PrintError):
for htlc in update_log:
if type(htlc) is not UpdateAddHtlc:
continue
height = (self.state.local_state.ctn if subject == "remote" else self.state.remote_state.ctn)
height = (self.local_state.ctn if subject == "remote" else self.remote_state.ctn)
locked_in = (htlc.r_locked_in if subject == "remote" else htlc.l_locked_in)
if locked_in is None or just_unsettled == (SettleHtlc(htlc.htlc_id) in other_log):
@ -423,12 +468,78 @@ class HTLCStateMachine(PrintError):
@property
def l_current_height(self):
return self.state.local_state.ctn
return self.local_state.ctn
@property
def r_current_height(self):
return self.state.remote_state.ctn
return self.remote_state.ctn
@property
def local_commit_fee(self):
return self.state.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs())
return self.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs())
def update_fee(self, fee):
self.pending_feerate = fee
def receive_update_fee(self, fee):
self.pending_feerate = fee
def to_save(self):
return {
"local_config": self.local_config,
"remote_config": self.remote_config,
"local_state": self.local_state,
"remote_state": self.remote_state,
"channel_id": self.channel_id,
"short_channel_id": self.short_channel_id,
"constraints": self.constraints,
"funding_outpoint": self.funding_outpoint,
"node_id": self.node_id,
"channel_id": self.channel_id
}
def serialize(self):
namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()}
serialized_channel = {k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in self.to_save().items()}
class MyJsonEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, bytes):
return binascii.hexlify(o).decode("ascii")
if isinstance(o, RevocationStore):
return o.serialize()
return super(MyJsonEncoder, self)
dumped = MyJsonEncoder().encode(serialized_channel)
roundtripped = json.loads(dumped)
reconstructed = HTLCStateMachine(roundtripped)
if reconstructed.to_save() != self.to_save():
raise Exception("Channels did not roundtrip serialization without changes:\n" + repr(reconstructed.to_save()) + "\n" + repr(self.to_save()))
return roundtripped
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)

View File

@ -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):
@ -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")
@ -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)
@ -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"

315
lib/lnutil.py Normal file
View File

@ -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, 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 = secret_to_pubkey(secret)
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())

View File

@ -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):

View File

@ -10,66 +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, Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, OpenChannel, ChannelConstraints, RevocationStore, 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
is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key")
def maybeDecode(k, v):
if k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature"] and v is not None:
return binascii.unhexlify(v)
return v
def decodeAll(v):
return {i: maybeDecode(i, j) for i, j in v.items()} if isinstance(v, dict) else v
def typeWrap(k, v, local):
if is_key(k):
if local:
return Keypair(**v)
else:
return OnlyPubkeyKeypair(**v)
return v
def reconstruct_namedtuples(openingchannel):
openingchannel = decodeAll(openingchannel)
openingchannel=OpenChannel(**openingchannel)
openingchannel = openingchannel._replace(funding_outpoint=Outpoint(**openingchannel.funding_outpoint))
new_local_config = {k: typeWrap(k, decodeAll(v), True) for k, v in openingchannel.local_config.items()}
openingchannel = openingchannel._replace(local_config=ChannelConfig(**new_local_config))
new_remote_config = {k: typeWrap(k, decodeAll(v), False) for k, v in openingchannel.remote_config.items()}
openingchannel = openingchannel._replace(remote_config=ChannelConfig(**new_remote_config))
new_local_state = decodeAll(openingchannel.local_state)
openingchannel = openingchannel._replace(local_state=LocalState(**new_local_state))
new_remote_state = decodeAll(openingchannel.remote_state)
new_remote_state["revocation_store"] = RevocationStore.from_json_obj(new_remote_state["revocation_store"])
openingchannel = openingchannel._replace(remote_state=RemoteState(**new_remote_state))
openingchannel = openingchannel._replace(constraints=ChannelConstraints(**openingchannel.constraints))
return openingchannel
def serialize_channels(channels_dict):
serialized_channels = []
for chan in channels_dict.values():
namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()}
serialized_channels.append({k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in chan._asdict().items()})
class MyJsonEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, bytes):
return binascii.hexlify(o).decode("ascii")
if isinstance(o, RevocationStore):
return o.serialize()
return super(MyJsonEncoder, self)
dumped = MyJsonEncoder().encode(serialized_channels)
roundtripped = json.loads(dumped)
reconstructed = set(reconstruct_namedtuples(x) for x in roundtripped)
if reconstructed != set(channels_dict.values()):
raise Exception("Channels did not roundtrip serialization without changes:\n" + repr(reconstructed) + "\n" + repr(channels))
return roundtripped
from .lnhtlc import HTLCStateMachine
from .lnutil import Outpoint, calc_short_channel_id
# hardcoded nodes
node_list = [
@ -87,10 +33,10 @@ 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(reconstruct_namedtuples, wallet.storage.get("channels", []))}
self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))}
self.invoices = wallet.storage.get('lightning_invoices', {})
peer_list = network.config.get('lightning_peers', node_list)
self.channel_state = {chan.channel_id: "DISCONNECTED" for chan in self.channels.values()}
@ -113,6 +59,7 @@ class LNWorker(PrintError):
self.lock = threading.Lock()
def save_channel(self, openchannel):
assert type(openchannel) is HTLCStateMachine
if openchannel.channel_id not in self.channel_state:
self.channel_state[openchannel.channel_id] = "OPENING"
self.channels[openchannel.channel_id] = openchannel
@ -120,7 +67,7 @@ class LNWorker(PrintError):
peer.channels = self.channels_for_peer(node_id)
if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.current_per_commitment_point:
raise Exception("Tried to save channel with next_point == current_point, this should not happen")
dumped = serialize_channels(self.channels)
dumped = [x.serialize() for x in self.channels.values()]
self.wallet.storage.put("channels", dumped)
self.wallet.storage.write()
self.network.trigger_callback('channel', openchannel)
@ -139,11 +86,11 @@ class LNWorker(PrintError):
if tx_pos == -1:
self.print_error('funding tx is not yet SPV verified.. but there are '
'already enough confirmations (currently {})'.format(conf))
return None
chan = chan._replace(short_channel_id = calc_short_channel_id(block_height, tx_pos, chan.funding_outpoint.output_index))
return False
chan.short_channel_id = calc_short_channel_id(block_height, tx_pos, chan.funding_outpoint.output_index)
self.save_channel(chan)
return chan
return None
return True
return False
def on_channel_utxos(self, chan, utxos):
outpoints = [Outpoint(x["tx_hash"], x["tx_pos"]) for x in utxos]
@ -158,8 +105,8 @@ class LNWorker(PrintError):
for chan in self.channels.values():
peer = self.peers[chan.node_id]
if self.channel_state[chan.channel_id] == "OPENING":
chan = self.save_short_chan_id(chan)
if not chan:
res = self.save_short_chan_id(chan)
if not res:
self.print_error("network update but funding tx is still not at sufficient depth")
continue
# this results in the channel being marked OPEN
@ -172,7 +119,7 @@ class LNWorker(PrintError):
if node_id not in self.peers:
node = self.network.lightning_nodes.get(node_id)
if node is None:
return False
return "node not found, peers available are: " + str(self.network.lightning_nodes.keys())
host, port = node['addresses'][0]
self.add_peer(host, port, node_id)
peer = self.peers[node_id]
@ -206,13 +153,10 @@ class LNWorker(PrintError):
return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
def add_invoice(self, amount_sat, message):
is_open = lambda chan: self.channel_state[chan.channel_id] == "OPEN"
payment_preimage = os.urandom(32)
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()
@ -227,14 +171,16 @@ class LNWorker(PrintError):
self.wallet.storage.write()
def list_channels(self):
return serialize_channels(self.channels)
return [str(x) for x in self.channels]
def close_channel(self, chan_id):
from .lnhtlc import HTLCStateMachine
chan = self.channels[chan_id]
# local_commitment always gives back the next expected local_commitment,
# but in this case, we want the current one. So substract one ctn number
tx = HTLCStateMachine(chan._replace(local_state=chan.local_state._replace(ctn=chan.local_state.ctn - 1))).local_commitment
old_local_state = chan.local_state
chan.local_state=chan.local_state._replace(ctn=chan.local_state.ctn - 1)
tx = chan.local_commitment
chan.local_state = old_local_state
tx.sign({bh2u(chan.local_config.multisig_key.pubkey): (chan.local_config.multisig_key.privkey, True)})
remote_sig = chan.local_state.current_commitment_signature
remote_sig = der_sig_from_sig_string(remote_sig) + b"\x01"

View File

@ -4,6 +4,7 @@ import unittest
import lib.bitcoin as bitcoin
import lib.lnbase as lnbase
import lib.lnhtlc as lnhtlc
import lib.lnutil as lnutil
import lib.util as util
import os
import binascii
@ -36,13 +37,13 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
max_accepted_htlcs=5
)
return lnbase.OpenChannel(
channel_id=channel_id,
short_channel_id=channel_id[:8],
funding_outpoint=lnbase.Outpoint(funding_txid, funding_index),
local_config=local_config,
remote_config=remote_config,
remote_state=lnbase.RemoteState(
return {
"channel_id":channel_id,
"short_channel_id":channel_id[:8],
"funding_outpoint":lnbase.Outpoint(funding_txid, funding_index),
"local_config":local_config,
"remote_config":remote_config,
"remote_state":lnbase.RemoteState(
ctn = 0,
next_per_commitment_point=nex,
current_per_commitment_point=cur,
@ -50,7 +51,7 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
revocation_store=their_revocation_store,
next_htlc_id = 0
),
local_state=lnbase.LocalState(
"local_state":lnbase.LocalState(
ctn = 0,
per_commitment_secret_seed=seed,
amount_msat=local_amount,
@ -59,9 +60,9 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
was_announced=False,
current_commitment_signature=None
),
constraints=lnbase.ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=is_initiator, funding_txn_minimum_depth=3),
node_id=other_node_id
)
"constraints":lnbase.ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=is_initiator, funding_txn_minimum_depth=3),
"node_id":other_node_id
}
def bip32(sequence):
xprv, xpub = bitcoin.bip32_root(b"9dk", 'standard')
@ -87,10 +88,10 @@ def create_test_channels():
alice_seed = os.urandom(32)
bob_seed = os.urandom(32)
alice_cur = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 1), "big"))
alice_next = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 2), "big"))
bob_cur = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 1), "big"))
bob_next = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 2), "big"))
alice_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 1), "big"))
alice_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 2), "big"))
bob_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 1), "big"))
bob_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 2), "big"))
return \
lnhtlc.HTLCStateMachine(
@ -184,8 +185,8 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
self.assertEqual(alice_channel.total_msat_received, bobSent, "alice has incorrect milli-satoshis received")
self.assertEqual(bob_channel.total_msat_sent, bobSent, "bob has incorrect milli-satoshis sent")
self.assertEqual(bob_channel.total_msat_received, aliceSent, "bob has incorrect milli-satoshis received")
self.assertEqual(bob_channel.state.local_state.ctn, 1, "bob has incorrect commitment height")
self.assertEqual(alice_channel.state.local_state.ctn, 1, "alice has incorrect commitment height")
self.assertEqual(bob_channel.local_state.ctn, 1, "bob has incorrect commitment height")
self.assertEqual(alice_channel.local_state.ctn, 1, "alice has incorrect commitment height")
# Both commitment transactions should have three outputs, and one of
# them should be exactly the amount of the HTLC.
@ -238,9 +239,9 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
paymentPreimage = b"\x01" * 32
paymentHash = bitcoin.sha256(paymentPreimage)
fee_per_kw = alice_channel.state.constraints.feerate
fee_per_kw = alice_channel.constraints.feerate
self.assertEqual(fee_per_kw, 6000)
htlcAmt = 500 + lnbase.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
self.assertEqual(htlcAmt, 4478)
htlc = lnhtlc.UpdateAddHtlc(
payment_hash = paymentHash,
@ -250,28 +251,44 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
)
aliceHtlcIndex = alice_channel.add_htlc(htlc)
bobHtlcIndex = bob_channel.receive_htlc(htlc)
force_state_transition(alice_channel, bob_channel)
self.assertEqual(len(alice_channel.local_commitment.outputs()), 3)
self.assertEqual(len(bob_channel.local_commitment.outputs()), 2)
default_fee = calc_static_fee(0)
self.assertEqual(bob_channel.local_commit_fee, default_fee)
bob_channel.settle_htlc(paymentPreimage, htlc.htlc_id)
alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
force_state_transition(bob_channel, alice_channel)
self.assertEqual(len(alice_channel.local_commitment.outputs()), 2)
self.assertEqual(alice_channel.total_msat_sent // 1000, htlcAmt)
def test_UpdateFeeSenderCommits(self):
alice_channel, bob_channel = create_test_channels()
paymentPreimage = b"\x01" * 32
paymentHash = bitcoin.sha256(paymentPreimage)
htlc = lnhtlc.UpdateAddHtlc(
payment_hash = paymentHash,
amount_msat = one_bitcoin_in_msat,
cltv_expiry = 5, # also in create_test_channels
total_fee = 0
)
aliceHtlcIndex = alice_channel.add_htlc(htlc)
bobHtlcIndex = bob_channel.receive_htlc(htlc)
fee = 111
alice_channel.update_fee(fee)
bob_channel.receive_update_fee(fee)
alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
self.assertNotEqual(fee, alice_channel.constraints.feerate)
rev, _ = alice_channel.revoke_current_commitment()
self.assertEqual(fee, alice_channel.constraints.feerate)
bob_channel.receive_revocation(rev)
def force_state_transition(chanA, chanB):
chanB.receive_new_commitment(*chanA.sign_next_commitment())
rev, _ = chanB.revoke_current_commitment()

150
lib/tests/test_lnrouter.py Normal file
View File

@ -0,0 +1,150 @@
import unittest
from lib.util import bh2u, bfh
from lib.lnbase import Peer
from lib.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop
from lib import bitcoin, lnrouter
class Test_LNRouter(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_find_path_for_payment(self):
class fake_network:
channel_db = lnrouter.ChannelDB()
class fake_ln_worker:
path_finder = lnrouter.LNPathFinder(fake_network.channel_db)
privkey = bitcoin.sha256('privkeyseed')
network = fake_network
channel_state = {}
channels = []
invoices = {}
p = Peer(fake_ln_worker, '', 0, 'a')
p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'c', 'short_channel_id': bfh('0000000000000001')})
p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000002')})
p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'b', 'short_channel_id': bfh('0000000000000003')})
p.on_channel_announcement({'node_id_1': b'c', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000004')})
p.on_channel_announcement({'node_id_1': b'd', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000005')})
p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000006')})
o = lambda i: i.to_bytes(8, "big")
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x00', 'cltv_expiry_delta': o(99), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(999)})
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(99999999)})
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
self.assertNotEqual(None, fake_ln_worker.path_finder.find_path_for_payment(b'a', b'e', 100000))
def test_new_onion_packet(self):
# test vector from bolt-04
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(OnionPerHop(
bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004')
)),
]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf'),
packet.to_bytes())
def test_process_onion_packet(self):
# this test is not from bolt-04, but is based on the one there;
# except here we have the privkeys for these pubkeys
payment_path_pubkeys = [
bfh('03d75c0ee70f68d73d7d13aeb6261d8ace11416800860c7e59407afe4e2e2d42bb'),
bfh('03960a0b830c7b8e76de745b819f252c62508346196b916f5e813cdb0773283cce'),
bfh('0385620e0a571cbc3552620f8bf1bdcdab2d1a4a59c36fa10b8249114ccbdda40d'),
bfh('02ee242cf6c38b7285f0152c33804ff777f5c51fd352ca8132e845e2cf23b3d8ba'),
bfh('025c585fd2e174bf8245b2b4a119e52a417688904228643ea3edaa1728bf2a258e'),
]
payment_path_privkeys = [
bfh('3463a278617b3dd83f79bda7f97673f12609c54386e1f0d2b67b1c6354fda14e'),
bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d'),
bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23'),
bfh('3d885f374d79a5e777459b083f7818cdc9493e5c4994ac9c7b843de8b70be661'),
bfh('dd72ab44729527b7942e195e7a835e7c71f9c0ff61844eb21274d9c26166a8f8'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(OnionPerHop(
bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004')
)),
]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368661954176cd9869da33d713aa219fcef1e5c806fef11e696bcc66844de8271c27974a0fd57c2dbcb2c6dd4e8ef35d96db28d5a0e49b6ab3d6de31af65950723b8cddc108390bebf8d149002e31bdc283056477ba27c8054c248ad7306de31663a7c99ec659da15d0f6fbc7e1687485b39e9be0ec3b70164cb3618a9b546317e7c2d62ae9f0f840704535729262d30c6132d1b390f073edec8fa057176c6268b6ad06a82ff0d16d4c662194873e8b4ecf46eb2c9d4d58d2ee2021adb19840605ac5afd8bd942dd71e8244c83e28b2ed5a3b09e9e7df5c8c747e5765ba366a4f7407a6c6b0a32f74bc5e428f7fa4c3cf70e13ed91563177d94190d5149aa4b9c96d00e40d2ac35ab9c4a621ce0f6f5df7d64a9c8d435db19de192d9db522c7f7b4e201fc1b61a9bd3efd062ae24455d463818b01e2756c7d0691bc3ac4c017be34c9a8b2913bb1b937e31e0ae40f650a7cd820bcb4996825b1cbad1ff7ccc2b513b1104524c34f6573e1b59201c005a632ee5dccd3711a32e3ba1ff00fcffbe636e4b3a84bbe491b836a57ccec138b8cc2ec733846904d872f305d538d51db8e56232ec6e07877075328874cb7b09c7e799100a9ff085dead253886b174fc408a0ea7b48bce2c5d8992285011960af088f7e006ef60089d46ac9aa15acfac6c87c3cf6904764dd785419292fbafa9cca09c8ade24a6cd63f12d1cfc83fa35cf2f1cf503c39cbf78293f06c68a3cece7177169cd872bb49bf69d933a27a887dd9daefa9239fca9f0c3e309ec61d9df947211da98cf11a6e0fb77252629cdf9f2226dd69ca73fa51be4df224592f8d471b69a1aebbdaa2f3a798b3581253d97feb0a12e6606043ca0fc5efc0f49b8061d6796eff31cd8638499e2f25ffb96eec32837438ed7ebebbe587886648f63e35d80f41869f4c308f2e6970bd65fead5e8544e3239a6acc9d996b08d1546455bcafbe88ed3ed547714841946fe2e77180e4d7bf1452414e4b1745a7897184a2c4cbc3ac46f83342a55a48e29dc8f17cf595dd28f51e297ba89fd25ed0dbd1c0081a810beaab09758a36fbfd16fbdc3daa9fe05c8a73195f244ef2743a5df761f01ee6e693eb6c7f1a7834fab3671391e5ddebf611e119a2ae4456e2cee7a6d4f27a2246cdb1f8ef35f0b3d7044b3799d8d0ed0a6470557fd807c065d6d83acba07e96e10770ada8c0b4d4921522944188d5f30086a6ee0a4795331273f32beaaa43363fc58208a257e5c5c434c7325b583642219d81c7d67b908d5263b42ac1991edc69a777da60f38eff138c844af9e549374e8b29b166211bfded24587a29394e33828b784da7e7b62ab7e49ea2693fcdd17fa96186a5ef11ef1a8adffa50f93a3119e95e6c09014f3e3b0709183fa08a826ced6deb4608b7d986ebbcf99ad58e25451d4d9d38d0059734d8501467b97182cd11e0c07c91ca50f61cc31255a3147ade654976a5989097281892aafd8df595c63bd14f1e03f5955a9398d2dd6368bbcae833ae1cc2df31eb0980b4817dfd130020ffb275743fcc01df40e3ecda1c5988e8e1bde965353b0b1bf34ea05f095000c45b6249618d275905a24d3eb58c600aeab4fb552fbf1ccdb2a5c80ace220310f89829d7e53f78c126037b6d8d500220c7a118d9621b4d6bd5379edd7e24bcf540e87aba6b88862db16fa4ee00b009fda80577be67ab94910fd8a7807dfe4ebe66b8fdcd040aa2dc17ec22639298be56b2a2c9d8940647b75f2f6d81746df16e1cb2f05e23397a8c63baea0803441ff4b7d517ff172980a056726235e2f6af85e8aa9b91ba85f14532272d6170df3166b91169dc09d4f4a251610f57ff0885a93364cfaf650bdf436c89795efed5ca934bc7ffc0a4'),
packet.to_bytes())
for i, privkey in enumerate(payment_path_privkeys):
processed_packet = lnrouter.process_onion_packet(packet, associated_data, privkey)
self.assertEqual(hops_data[i].per_hop.to_bytes(), processed_packet.hop_data.per_hop.to_bytes())
packet = processed_packet.next_packet
def test_decode_onion_error(self):
# test vector from bolt-04
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
error_packet_for_node_0 = bfh('9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d')
decoded_error, index_of_sender = lnrouter._decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key)
self.assertEqual(bfh('4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'),
decoded_error)
self.assertEqual(4, index_of_sender)

View File

@ -1,19 +1,8 @@
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
import json
from lib import bitcoin
import ecdsa.ellipticcurve
from ecdsa.curves import SECP256k1
from lib.util import bfh
from lib import bitcoin, lnbase, lnrouter
from lib.lnutil import RevocationStore, get_per_commitment_secret_from_seed, make_offered_htlc, make_received_htlc, make_commitment, make_htlc_tx_witness, make_htlc_tx_output, make_htlc_tx_inputs, secret_to_pubkey, derive_blinded_pubkey, derive_privkey, derive_pubkey, make_htlc_tx
from lib.util import bh2u, bfh
funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be'
funding_output_index = 0
@ -24,7 +13,7 @@ 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)
# 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')
@ -35,377 +24,7 @@ local_delayedpubkey = bytes.fromhex('03fd5960528dc152014952efdb702a88f71e3c1653b
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
output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
htlc0_msat = 1000 * 1000
htlc2_msat = 2000 * 1000
htlc3_msat = 3000 * 1000
htlc1_msat = 2000 * 1000
htlc4_msat = 4000 * 1000
htlcs = [(htlc2, htlc2_msat), (htlc3, htlc3_msat), (htlc0, htlc0_msat), (htlc1, htlc1_msat), (htlc4, htlc4_msat)]
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=htlcs)
self.sign_and_insert_remote_sig(our_commit_tx, remote_funding_pubkey, remote_signature, local_funding_pubkey, local_funding_privkey)
self.assertEqual(str(our_commit_tx), output_commit_tx)
# (HTLC 0)
signature_for_output_remote_htlc_0 = "304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6"
# (HTLC 2)
signature_for_output_remote_htlc_2 = "3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b"
# (HTLC 1)
signature_for_output_remote_htlc_1 = "304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202"
# (HTLC 3)
signature_for_output_remote_htlc_3 = "3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554"
# (HTLC 4)
signature_for_output_remote_htlc_4 = "304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d"
local_signature_htlc0 = "304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5" # derive ourselves
output_htlc_success_tx_0 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219700000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a60147304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000"
local_signature_htlc2 = "3045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be5"
output_htlc_timeout_tx_2 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000"
local_signature_htlc1 = "3045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da"
output_htlc_success_tx_1 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219702000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f20201483045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000"
local_signature_htlc3 = "30440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac08727"
output_htlc_timeout_tx_3 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219703000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554014730440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac0872701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000"
local_signature_htlc4 = "30440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e"
output_htlc_success_tx_4 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219704000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d014730440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000"
def test_htlc_tx(htlc, htlc_output_index, amount_msat, ref_local_sig, htlc_payment_preimage, remote_htlc_sig, ref_tx, success, cltv_timeout):
our_htlc_tx_output = make_htlc_tx_output(
amount_msat=amount_msat,
local_feerate=local_feerate_per_kw,
revocationpubkey=local_revocation_pubkey,
local_delayedpubkey=local_delayedpubkey,
success=success,
to_self_delay=local_delay)
our_htlc_tx_inputs = make_htlc_tx_inputs(
htlc_output_txid=our_commit_tx.txid(),
htlc_output_index=htlc_output_index,
revocationpubkey=local_revocation_pubkey,
local_delayedpubkey=local_delayedpubkey,
amount_msat=amount_msat,
witness_script=bh2u(htlc))
our_htlc_tx = make_htlc_tx(cltv_timeout,
inputs=our_htlc_tx_inputs,
output=our_htlc_tx_output)
local_sig = our_htlc_tx.sign_txin(0, local_privkey[:-1])
#self.assertEqual(ref_local_sig + "01", local_sig) # commented out as it is sufficient to compare the serialized txn
our_htlc_tx_witness = make_htlc_tx_witness( # FIXME only correct for success=True
remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL
localhtlcsig=bfh(local_sig),
payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout
witness_script=htlc)
our_htlc_tx._inputs[0]['witness'] = bh2u(our_htlc_tx_witness)
self.assertEqual(ref_tx, str(our_htlc_tx))
test_htlc_tx(htlc=htlc0, htlc_output_index=0,
amount_msat=htlc0_msat,
ref_local_sig=local_signature_htlc0,
htlc_payment_preimage=htlc0_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_0,
ref_tx=output_htlc_success_tx_0,
success=True, cltv_timeout=0)
test_htlc_tx(htlc=htlc1, htlc_output_index=2,
amount_msat=htlc1_msat,
ref_local_sig=local_signature_htlc1,
htlc_payment_preimage=htlc1_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_1,
ref_tx=output_htlc_success_tx_1,
success=True, cltv_timeout=0)
test_htlc_tx(htlc=htlc2, htlc_output_index=1,
amount_msat=htlc2_msat,
ref_local_sig=local_signature_htlc2,
htlc_payment_preimage=htlc2_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_2,
ref_tx=output_htlc_timeout_tx_2,
success=False, cltv_timeout=htlc2_cltv_timeout)
test_htlc_tx(htlc=htlc3, htlc_output_index=3,
amount_msat=htlc3_msat,
ref_local_sig=local_signature_htlc3,
htlc_payment_preimage=htlc3_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_3,
ref_tx=output_htlc_timeout_tx_3,
success=False, cltv_timeout=htlc3_cltv_timeout)
test_htlc_tx(htlc=htlc4, htlc_output_index=4,
amount_msat=htlc4_msat,
ref_local_sig=local_signature_htlc4,
htlc_payment_preimage=htlc4_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_4,
ref_tx=output_htlc_success_tx_4,
success=True, cltv_timeout=0)
def test_find_path_for_payment(self):
class fake_ln_worker:
channel_db = lnrouter.ChannelDB()
path_finder = lnrouter.LNPathFinder(channel_db)
privkey = bitcoin.sha256('privkeyseed')
network = None
channel_state = {}
channels = []
invoices = {}
p = Peer(fake_ln_worker, '', 0, 'a')
p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'c', 'short_channel_id': bfh('0000000000000001')})
p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000002')})
p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'b', 'short_channel_id': bfh('0000000000000003')})
p.on_channel_announcement({'node_id_1': b'c', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000004')})
p.on_channel_announcement({'node_id_1': b'd', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000005')})
p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000006')})
o = lambda i: i.to_bytes(8, "big")
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x00', 'cltv_expiry_delta': o(99), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(999)})
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(99999999)})
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
self.assertNotEqual(None, fake_ln_worker.path_finder.find_path_for_payment(b'a', b'e', 100000))
def test_key_derivation(self):
# BOLT3, Appendix E
base_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100
revocation_basepoint_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
base_point = secret_to_pubkey(base_secret)
self.assertEqual(base_point, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2'))
per_commitment_point = secret_to_pubkey(per_commitment_secret)
self.assertEqual(per_commitment_point, bfh('025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486'))
localpubkey = derive_pubkey(base_point, per_commitment_point)
self.assertEqual(localpubkey, bfh('0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5'))
localprivkey = derive_privkey(base_secret, per_commitment_point)
self.assertEqual(localprivkey, 0xcbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f)
revocation_basepoint = secret_to_pubkey(revocation_basepoint_secret)
self.assertEqual(revocation_basepoint, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2'))
revocationpubkey = derive_blinded_pubkey(revocation_basepoint, per_commitment_point)
self.assertEqual(revocationpubkey, bfh('02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0'))
def test_per_commitment_secret_from_seed(self):
self.assertEqual(0x02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0x0000000000000000000000000000000000000000000000000000000000000000.to_bytes(byteorder="big", length=32), 281474976710655))
self.assertEqual(0x7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 281474976710655))
self.assertEqual(0x56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0xaaaaaaaaaaa))
self.assertEqual(0x9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0x555555555555))
self.assertEqual(0x915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0x0101010101010101010101010101010101010101010101010101010101010101.to_bytes(byteorder="big", length=32), 1))
def test_new_onion_packet(self):
# test vector from bolt-04
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(OnionPerHop(
bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004')
)),
]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf'),
packet.to_bytes())
def test_process_onion_packet(self):
# this test is not from bolt-04, but is based on the one there;
# except here we have the privkeys for these pubkeys
payment_path_pubkeys = [
bfh('03d75c0ee70f68d73d7d13aeb6261d8ace11416800860c7e59407afe4e2e2d42bb'),
bfh('03960a0b830c7b8e76de745b819f252c62508346196b916f5e813cdb0773283cce'),
bfh('0385620e0a571cbc3552620f8bf1bdcdab2d1a4a59c36fa10b8249114ccbdda40d'),
bfh('02ee242cf6c38b7285f0152c33804ff777f5c51fd352ca8132e845e2cf23b3d8ba'),
bfh('025c585fd2e174bf8245b2b4a119e52a417688904228643ea3edaa1728bf2a258e'),
]
payment_path_privkeys = [
bfh('3463a278617b3dd83f79bda7f97673f12609c54386e1f0d2b67b1c6354fda14e'),
bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d'),
bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23'),
bfh('3d885f374d79a5e777459b083f7818cdc9493e5c4994ac9c7b843de8b70be661'),
bfh('dd72ab44729527b7942e195e7a835e7c71f9c0ff61844eb21274d9c26166a8f8'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(OnionPerHop(
bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003')
)),
OnionHopsDataSingle(OnionPerHop(
bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004')
)),
]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368661954176cd9869da33d713aa219fcef1e5c806fef11e696bcc66844de8271c27974a0fd57c2dbcb2c6dd4e8ef35d96db28d5a0e49b6ab3d6de31af65950723b8cddc108390bebf8d149002e31bdc283056477ba27c8054c248ad7306de31663a7c99ec659da15d0f6fbc7e1687485b39e9be0ec3b70164cb3618a9b546317e7c2d62ae9f0f840704535729262d30c6132d1b390f073edec8fa057176c6268b6ad06a82ff0d16d4c662194873e8b4ecf46eb2c9d4d58d2ee2021adb19840605ac5afd8bd942dd71e8244c83e28b2ed5a3b09e9e7df5c8c747e5765ba366a4f7407a6c6b0a32f74bc5e428f7fa4c3cf70e13ed91563177d94190d5149aa4b9c96d00e40d2ac35ab9c4a621ce0f6f5df7d64a9c8d435db19de192d9db522c7f7b4e201fc1b61a9bd3efd062ae24455d463818b01e2756c7d0691bc3ac4c017be34c9a8b2913bb1b937e31e0ae40f650a7cd820bcb4996825b1cbad1ff7ccc2b513b1104524c34f6573e1b59201c005a632ee5dccd3711a32e3ba1ff00fcffbe636e4b3a84bbe491b836a57ccec138b8cc2ec733846904d872f305d538d51db8e56232ec6e07877075328874cb7b09c7e799100a9ff085dead253886b174fc408a0ea7b48bce2c5d8992285011960af088f7e006ef60089d46ac9aa15acfac6c87c3cf6904764dd785419292fbafa9cca09c8ade24a6cd63f12d1cfc83fa35cf2f1cf503c39cbf78293f06c68a3cece7177169cd872bb49bf69d933a27a887dd9daefa9239fca9f0c3e309ec61d9df947211da98cf11a6e0fb77252629cdf9f2226dd69ca73fa51be4df224592f8d471b69a1aebbdaa2f3a798b3581253d97feb0a12e6606043ca0fc5efc0f49b8061d6796eff31cd8638499e2f25ffb96eec32837438ed7ebebbe587886648f63e35d80f41869f4c308f2e6970bd65fead5e8544e3239a6acc9d996b08d1546455bcafbe88ed3ed547714841946fe2e77180e4d7bf1452414e4b1745a7897184a2c4cbc3ac46f83342a55a48e29dc8f17cf595dd28f51e297ba89fd25ed0dbd1c0081a810beaab09758a36fbfd16fbdc3daa9fe05c8a73195f244ef2743a5df761f01ee6e693eb6c7f1a7834fab3671391e5ddebf611e119a2ae4456e2cee7a6d4f27a2246cdb1f8ef35f0b3d7044b3799d8d0ed0a6470557fd807c065d6d83acba07e96e10770ada8c0b4d4921522944188d5f30086a6ee0a4795331273f32beaaa43363fc58208a257e5c5c434c7325b583642219d81c7d67b908d5263b42ac1991edc69a777da60f38eff138c844af9e549374e8b29b166211bfded24587a29394e33828b784da7e7b62ab7e49ea2693fcdd17fa96186a5ef11ef1a8adffa50f93a3119e95e6c09014f3e3b0709183fa08a826ced6deb4608b7d986ebbcf99ad58e25451d4d9d38d0059734d8501467b97182cd11e0c07c91ca50f61cc31255a3147ade654976a5989097281892aafd8df595c63bd14f1e03f5955a9398d2dd6368bbcae833ae1cc2df31eb0980b4817dfd130020ffb275743fcc01df40e3ecda1c5988e8e1bde965353b0b1bf34ea05f095000c45b6249618d275905a24d3eb58c600aeab4fb552fbf1ccdb2a5c80ace220310f89829d7e53f78c126037b6d8d500220c7a118d9621b4d6bd5379edd7e24bcf540e87aba6b88862db16fa4ee00b009fda80577be67ab94910fd8a7807dfe4ebe66b8fdcd040aa2dc17ec22639298be56b2a2c9d8940647b75f2f6d81746df16e1cb2f05e23397a8c63baea0803441ff4b7d517ff172980a056726235e2f6af85e8aa9b91ba85f14532272d6170df3166b91169dc09d4f4a251610f57ff0885a93364cfaf650bdf436c89795efed5ca934bc7ffc0a4'),
packet.to_bytes())
for i, privkey in enumerate(payment_path_privkeys):
processed_packet = lnrouter.process_onion_packet(packet, associated_data, privkey)
self.assertEqual(hops_data[i].per_hop.to_bytes(), processed_packet.hop_data.per_hop.to_bytes())
packet = processed_packet.next_packet
def test_decode_onion_error(self):
# test vector from bolt-04
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
error_packet_for_node_0 = bfh('9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d')
decoded_error, index_of_sender = lnrouter._decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key)
self.assertEqual(bfh('4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'),
decoded_error)
self.assertEqual(4, index_of_sender)
class TestLNUtil(unittest.TestCase):
def test_shachain_store(self):
tests = [
{
@ -817,3 +436,235 @@ class Test_LNBase(unittest.TestCase):
except Exception as e:
raise Exception("iteration " + str(i) + ": " + str(e))
if i % 1000 == 0: self.assertEqual(consumer.serialize(), RevocationStore.from_json_obj(json.loads(json.dumps(consumer.serialize()))).serialize())
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 = secret_to_pubkey(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
output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
htlc0_msat = 1000 * 1000
htlc2_msat = 2000 * 1000
htlc3_msat = 3000 * 1000
htlc1_msat = 2000 * 1000
htlc4_msat = 4000 * 1000
htlcs = [(htlc2, htlc2_msat), (htlc3, htlc3_msat), (htlc0, htlc0_msat), (htlc1, htlc1_msat), (htlc4, htlc4_msat)]
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=htlcs)
self.sign_and_insert_remote_sig(our_commit_tx, remote_funding_pubkey, remote_signature, local_funding_pubkey, local_funding_privkey)
self.assertEqual(str(our_commit_tx), output_commit_tx)
# (HTLC 0)
signature_for_output_remote_htlc_0 = "304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6"
# (HTLC 2)
signature_for_output_remote_htlc_2 = "3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b"
# (HTLC 1)
signature_for_output_remote_htlc_1 = "304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202"
# (HTLC 3)
signature_for_output_remote_htlc_3 = "3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554"
# (HTLC 4)
signature_for_output_remote_htlc_4 = "304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d"
local_signature_htlc0 = "304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5" # derive ourselves
output_htlc_success_tx_0 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219700000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a60147304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000"
local_signature_htlc2 = "3045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be5"
output_htlc_timeout_tx_2 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000"
local_signature_htlc1 = "3045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da"
output_htlc_success_tx_1 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219702000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f20201483045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000"
local_signature_htlc3 = "30440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac08727"
output_htlc_timeout_tx_3 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219703000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554014730440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac0872701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000"
local_signature_htlc4 = "30440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e"
output_htlc_success_tx_4 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219704000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d014730440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000"
def test_htlc_tx(htlc, htlc_output_index, amount_msat, ref_local_sig, htlc_payment_preimage, remote_htlc_sig, ref_tx, success, cltv_timeout):
our_htlc_tx_output = make_htlc_tx_output(
amount_msat=amount_msat,
local_feerate=local_feerate_per_kw,
revocationpubkey=local_revocation_pubkey,
local_delayedpubkey=local_delayedpubkey,
success=success,
to_self_delay=local_delay)
our_htlc_tx_inputs = make_htlc_tx_inputs(
htlc_output_txid=our_commit_tx.txid(),
htlc_output_index=htlc_output_index,
revocationpubkey=local_revocation_pubkey,
local_delayedpubkey=local_delayedpubkey,
amount_msat=amount_msat,
witness_script=bh2u(htlc))
our_htlc_tx = make_htlc_tx(cltv_timeout,
inputs=our_htlc_tx_inputs,
output=our_htlc_tx_output)
local_sig = our_htlc_tx.sign_txin(0, local_privkey[:-1])
#self.assertEqual(ref_local_sig + "01", local_sig) # commented out as it is sufficient to compare the serialized txn
our_htlc_tx_witness = make_htlc_tx_witness( # FIXME only correct for success=True
remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL
localhtlcsig=bfh(local_sig),
payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout
witness_script=htlc)
our_htlc_tx._inputs[0]['witness'] = bh2u(our_htlc_tx_witness)
self.assertEqual(ref_tx, str(our_htlc_tx))
test_htlc_tx(htlc=htlc0, htlc_output_index=0,
amount_msat=htlc0_msat,
ref_local_sig=local_signature_htlc0,
htlc_payment_preimage=htlc0_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_0,
ref_tx=output_htlc_success_tx_0,
success=True, cltv_timeout=0)
test_htlc_tx(htlc=htlc1, htlc_output_index=2,
amount_msat=htlc1_msat,
ref_local_sig=local_signature_htlc1,
htlc_payment_preimage=htlc1_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_1,
ref_tx=output_htlc_success_tx_1,
success=True, cltv_timeout=0)
test_htlc_tx(htlc=htlc2, htlc_output_index=1,
amount_msat=htlc2_msat,
ref_local_sig=local_signature_htlc2,
htlc_payment_preimage=htlc2_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_2,
ref_tx=output_htlc_timeout_tx_2,
success=False, cltv_timeout=htlc2_cltv_timeout)
test_htlc_tx(htlc=htlc3, htlc_output_index=3,
amount_msat=htlc3_msat,
ref_local_sig=local_signature_htlc3,
htlc_payment_preimage=htlc3_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_3,
ref_tx=output_htlc_timeout_tx_3,
success=False, cltv_timeout=htlc3_cltv_timeout)
test_htlc_tx(htlc=htlc4, htlc_output_index=4,
amount_msat=htlc4_msat,
ref_local_sig=local_signature_htlc4,
htlc_payment_preimage=htlc4_payment_preimage,
remote_htlc_sig=signature_for_output_remote_htlc_4,
ref_tx=output_htlc_success_tx_4,
success=True, cltv_timeout=0)
def test_per_commitment_secret_from_seed(self):
self.assertEqual(0x02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0x0000000000000000000000000000000000000000000000000000000000000000.to_bytes(byteorder="big", length=32), 281474976710655))
self.assertEqual(0x7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 281474976710655))
self.assertEqual(0x56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0xaaaaaaaaaaa))
self.assertEqual(0x9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0x555555555555))
self.assertEqual(0x915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c.to_bytes(byteorder="big", length=32),
get_per_commitment_secret_from_seed(0x0101010101010101010101010101010101010101010101010101010101010101.to_bytes(byteorder="big", length=32), 1))
def test_key_derivation(self):
# BOLT3, Appendix E
base_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100
revocation_basepoint_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
base_point = secret_to_pubkey(base_secret)
self.assertEqual(base_point, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2'))
per_commitment_point = secret_to_pubkey(per_commitment_secret)
self.assertEqual(per_commitment_point, bfh('025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486'))
localpubkey = derive_pubkey(base_point, per_commitment_point)
self.assertEqual(localpubkey, bfh('0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5'))
localprivkey = derive_privkey(base_secret, per_commitment_point)
self.assertEqual(localprivkey, 0xcbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f)
revocation_basepoint = secret_to_pubkey(revocation_basepoint_secret)
self.assertEqual(revocation_basepoint, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2'))
revocationpubkey = derive_blinded_pubkey(revocation_basepoint, per_commitment_point)
self.assertEqual(revocationpubkey, bfh('02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0'))
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