1
0

lnchannel+lnutil: change htlc output, send new sig

* changes the htlc outputs' witness script to have a csv lock of 1
* send signatures for remote ctx with ANYONECANPAY|SINGLE
* refactor htlc weight (useful for zero-fee-htlc)
This commit is contained in:
bitromortac
2021-09-13 13:51:51 +02:00
committed by ThomasV
parent c8bf515953
commit 7907b9c05d
3 changed files with 109 additions and 42 deletions

View File

@@ -1140,6 +1140,10 @@ class Channel(AbstractChannel):
commit=pending_remote_commitment,
ctx_output_idx=ctx_output_idx,
htlc=htlc)
if self.has_anchors():
# we send a signature with the following sighash flags
# for the peer to be able to replace inputs and outputs
htlc_tx.inputs()[0].sighash = Sighash.ANYONECANPAY | Sighash.SINGLE
sig = htlc_tx.sign_txin(0, their_remote_htlc_privkey)
htlc_sig = ecc.ecdsa_sig64_from_der_sig(sig[:-1])
htlcsigs.append((ctx_output_idx, htlc_sig))
@@ -1208,6 +1212,9 @@ class Channel(AbstractChannel):
commit=ctx,
ctx_output_idx=ctx_output_idx,
htlc=htlc)
if self.has_anchors():
# peer sent us a signature for our ctx using anchor sighash flags
htlc_tx.inputs()[0].sighash = Sighash.ANYONECANPAY | Sighash.SINGLE
pre_hash = htlc_tx.serialize_preimage(0)
msg_hash = sha256d(pre_hash)
remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, pcp)
@@ -1227,7 +1234,8 @@ class Channel(AbstractChannel):
data = self.config[LOCAL].current_htlc_signatures
htlc_sigs = list(chunks(data, 64))
htlc_sig = htlc_sigs[htlc_relative_idx]
remote_htlc_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(htlc_sig) + Sighash.to_sigbytes(Sighash.ALL)
remote_sighash = Sighash.ALL if not self.has_anchors() else Sighash.ANYONECANPAY | Sighash.SINGLE
remote_htlc_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(htlc_sig) + remote_sighash.to_sigbytes(1, 'big')
return remote_htlc_sig
def revoke_current_commitment(self):
@@ -1358,7 +1366,7 @@ class Channel(AbstractChannel):
)
htlc_fee_msat = fee_for_htlc_output(feerate=feerate)
htlc_trim_func = received_htlc_trim_threshold_sat if ctx_owner == receiver else offered_htlc_trim_threshold_sat
htlc_trim_threshold_msat = htlc_trim_func(dust_limit_sat=self.config[ctx_owner].dust_limit_sat, feerate=feerate) * 1000
htlc_trim_threshold_msat = htlc_trim_func(dust_limit_sat=self.config[ctx_owner].dust_limit_sat, feerate=feerate, has_anchors=self.has_anchors()) * 1000
if sender == initiator == LOCAL: # see https://github.com/lightningnetwork/lightning-rfc/pull/740
fee_spike_buffer = calc_fees_for_commitment_tx(
num_htlcs=num_htlcs_in_ctx + int(not is_htlc_dust) + 1,
@@ -1396,7 +1404,7 @@ class Channel(AbstractChannel):
def included_htlcs(self, subject: HTLCOwner, direction: Direction, ctn: int = None, *,
feerate: int = None) -> Sequence[UpdateAddHtlc]:
feerate: int = None) -> List[UpdateAddHtlc]:
"""Returns list of non-dust HTLCs for subject's commitment tx at ctn,
filtered by direction (of HTLCs).
"""
@@ -1408,9 +1416,9 @@ class Channel(AbstractChannel):
feerate = self.get_feerate(subject, ctn=ctn)
conf = self.config[subject]
if direction == RECEIVED:
threshold_sat = received_htlc_trim_threshold_sat(dust_limit_sat=conf.dust_limit_sat, feerate=feerate)
threshold_sat = received_htlc_trim_threshold_sat(dust_limit_sat=conf.dust_limit_sat, feerate=feerate, has_anchors=self.has_anchors())
else:
threshold_sat = offered_htlc_trim_threshold_sat(dust_limit_sat=conf.dust_limit_sat, feerate=feerate)
threshold_sat = offered_htlc_trim_threshold_sat(dust_limit_sat=conf.dust_limit_sat, feerate=feerate, has_anchors=self.has_anchors())
htlcs = self.hm.htlcs_by_direction(subject, direction, ctn=ctn).values()
return list(filter(lambda htlc: htlc.amount_msat // 1000 >= threshold_sat, htlcs))
@@ -1591,7 +1599,8 @@ class Channel(AbstractChannel):
remote_htlc_pubkey=other_htlc_pubkey,
local_htlc_pubkey=this_htlc_pubkey,
payment_hash=htlc.payment_hash,
cltv_abs=htlc.cltv_abs), htlc))
cltv_abs=htlc.cltv_abs,
has_anchors=self.has_anchors()), htlc))
# note: maybe flip initiator here for fee purposes, we want LOCAL and REMOTE
# in the resulting dict to correspond to the to_local and to_remote *outputs* of the ctx
onchain_fees = calc_fees_for_commitment_tx(

View File

@@ -46,7 +46,9 @@ _logger = get_logger(__name__)
# defined in BOLT-03:
HTLC_TIMEOUT_WEIGHT = 663
HTLC_TIMEOUT_WEIGHT_ANCHORS = 666
HTLC_SUCCESS_WEIGHT = 703
HTLC_SUCCESS_WEIGHT_ANCHORS = 706
COMMITMENT_TX_WEIGHT = 724
COMMITMENT_TX_WEIGHT_ANCHORS = 1124
HTLC_OUTPUT_WEIGHT = 172
@@ -612,7 +614,13 @@ def derive_payment_basepoint(static_payment_secret: bytes, funding_pubkey: bytes
def make_htlc_tx_output(
amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay,
amount_msat,
local_feerate,
revocationpubkey,
local_delayedpubkey,
success,
to_self_delay,
has_anchors: bool
) -> Tuple[bytes, PartialTxOutput]:
assert type(amount_msat) is int
assert type(local_feerate) is int
@@ -623,7 +631,7 @@ def make_htlc_tx_output(
)
p2wsh = bitcoin.redeem_script_to_address('p2wsh', script)
weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
weight = effective_htlc_tx_weight(success=success, has_anchors=has_anchors)
fee = local_feerate * weight
fee = fee // 1000 * 1000
final_amount_sat = (amount_msat - fee) // 1000
@@ -665,12 +673,13 @@ def make_offered_htlc(
remote_htlcpubkey: bytes,
local_htlcpubkey: bytes,
payment_hash: bytes,
has_anchors: bool,
) -> bytes:
assert type(revocation_pubkey) is bytes
assert type(remote_htlcpubkey) is bytes
assert type(local_htlcpubkey) is bytes
assert type(payment_hash) is bytes
script = construct_script([
script_opcodes = [
opcodes.OP_DUP,
opcodes.OP_HASH160,
bitcoin.hash_160(revocation_pubkey),
@@ -696,8 +705,11 @@ def make_offered_htlc(
opcodes.OP_EQUALVERIFY,
opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF,
opcodes.OP_ENDIF,
])
]
if has_anchors:
script_opcodes.extend([1, opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP])
script_opcodes.append(opcodes.OP_ENDIF)
script = construct_script(script_opcodes)
return script
def make_received_htlc(
@@ -707,12 +719,13 @@ def make_received_htlc(
local_htlcpubkey: bytes,
payment_hash: bytes,
cltv_abs: int,
has_anchors: bool,
) -> bytes:
for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]:
assert type(i) is bytes
assert type(cltv_abs) is int
script = construct_script([
script_opcodes = [
opcodes.OP_DUP,
opcodes.OP_HASH160,
bitcoin.hash_160(revocation_pubkey),
@@ -741,8 +754,11 @@ def make_received_htlc(
opcodes.OP_DROP,
opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF,
opcodes.OP_ENDIF,
])
]
if has_anchors:
script_opcodes.extend([1, opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP])
script_opcodes.append(opcodes.OP_ENDIF)
script = construct_script(script_opcodes)
return script
WITNESS_TEMPLATE_OFFERED_HTLC = [
@@ -815,18 +831,25 @@ def make_htlc_output_witness_script(
local_htlc_pubkey: bytes,
payment_hash: bytes,
cltv_abs: Optional[int],
has_anchors: bool,
) -> bytes:
if is_received_htlc:
return make_received_htlc(revocation_pubkey=remote_revocation_pubkey,
remote_htlcpubkey=remote_htlc_pubkey,
local_htlcpubkey=local_htlc_pubkey,
payment_hash=payment_hash,
cltv_abs=cltv_abs)
return make_received_htlc(
revocation_pubkey=remote_revocation_pubkey,
remote_htlcpubkey=remote_htlc_pubkey,
local_htlcpubkey=local_htlc_pubkey,
payment_hash=payment_hash,
cltv_abs=cltv_abs,
has_anchors=has_anchors,
)
else:
return make_offered_htlc(revocation_pubkey=remote_revocation_pubkey,
remote_htlcpubkey=remote_htlc_pubkey,
local_htlcpubkey=local_htlc_pubkey,
payment_hash=payment_hash)
return make_offered_htlc(
revocation_pubkey=remote_revocation_pubkey,
remote_htlcpubkey=remote_htlc_pubkey,
local_htlcpubkey=local_htlc_pubkey,
payment_hash=payment_hash,
has_anchors=has_anchors,
)
def get_ordered_channel_configs(chan: 'AbstractChannel', for_us: bool) -> Tuple[Union[LocalConfig, RemoteConfig],
@@ -853,6 +876,7 @@ def possible_output_idxs_of_htlc_in_ctx(*, chan: 'Channel', pcp: bytes, subject:
local_htlc_pubkey=htlc_pubkey,
payment_hash=payment_hash,
cltv_abs=cltv_abs,
has_anchors=chan.has_anchors(),
)
htlc_address = redeem_script_to_address('p2wsh', witness_script)
candidates = ctx.get_output_idxs_from_address(htlc_address)
@@ -905,12 +929,14 @@ def make_htlc_tx_with_open_channel(*, chan: 'Channel', pcp: bytes, subject: 'HTL
# 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 = htlc_direction == RECEIVED
witness_script_of_htlc_tx_output, htlc_tx_output = make_htlc_tx_output(
amount_msat = amount_msat,
local_feerate = chan.get_feerate(subject, ctn=ctn),
amount_msat=amount_msat,
local_feerate=chan.get_feerate(subject, ctn=ctn),
revocationpubkey=other_revocation_pubkey,
local_delayedpubkey=delayedpubkey,
success = is_htlc_success,
to_self_delay = other_conf.to_self_delay)
success=is_htlc_success,
to_self_delay=other_conf.to_self_delay,
has_anchors=chan.has_anchors(),
)
witness_script_in = make_htlc_output_witness_script(
is_received_htlc=is_htlc_success,
remote_revocation_pubkey=other_revocation_pubkey,
@@ -918,11 +944,14 @@ def make_htlc_tx_with_open_channel(*, chan: 'Channel', pcp: bytes, subject: 'HTL
local_htlc_pubkey=htlc_pubkey,
payment_hash=payment_hash,
cltv_abs=cltv_abs,
has_anchors=chan.has_anchors(),
)
htlc_tx_inputs = make_htlc_tx_inputs(
commit.txid(), ctx_output_idx,
amount_msat=amount_msat,
witness_script=witness_script_in)
if chan.has_anchors():
htlc_tx_inputs[0].nsequence = 1
if is_htlc_success:
cltv_abs = 0
htlc_tx = make_htlc_tx(cltv_abs=cltv_abs, inputs=htlc_tx_inputs, output=htlc_tx_output)
@@ -985,19 +1014,30 @@ def make_commitment_outputs(*, fees_per_participant: Mapping[HTLCOwner, int], lo
return htlc_outputs, c_outputs_filtered
def offered_htlc_trim_threshold_sat(*, dust_limit_sat: int, feerate: int) -> int:
def effective_htlc_tx_weight(success: bool, has_anchors: bool):
# for anchors-zero-fee-htlc we set an effective weight of zero
# we only trim htlcs below dust, as in the anchors commitment format,
# the fees for the hltc transaction don't need to be subtracted from
# the htlc output, but fees are taken from extra attached inputs
if has_anchors:
return HTLC_SUCCESS_WEIGHT_ANCHORS if success else HTLC_TIMEOUT_WEIGHT_ANCHORS
else:
return HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
def offered_htlc_trim_threshold_sat(*, dust_limit_sat: int, feerate: int, has_anchors: bool) -> int:
# offered htlcs strictly below this amount will be trimmed (from ctx).
# feerate is in sat/kw
# returns value in sat
weight = HTLC_TIMEOUT_WEIGHT
weight = effective_htlc_tx_weight(success=False, has_anchors=has_anchors)
return dust_limit_sat + weight * feerate // 1000
def received_htlc_trim_threshold_sat(*, dust_limit_sat: int, feerate: int) -> int:
def received_htlc_trim_threshold_sat(*, dust_limit_sat: int, feerate: int, has_anchors: bool) -> int:
# received htlcs strictly below this amount will be trimmed (from ctx).
# feerate is in sat/kw
# returns value in sat
weight = HTLC_SUCCESS_WEIGHT
weight = effective_htlc_tx_weight(success=True, has_anchors=has_anchors)
return dust_limit_sat + weight * feerate // 1000

View File

@@ -12,6 +12,7 @@ from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_see
get_compressed_pubkey_from_bech32,
ScriptHtlc, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures,
ln_compare_features, IncompatibleLightningFeatures, ChannelType,
offered_htlc_trim_threshold_sat, received_htlc_trim_threshold_sat,
ImportedChannelBackupStorage)
from electrum.util import bfh, MyEncoder
from electrum.transaction import Transaction, PartialTransaction, Sighash
@@ -491,23 +492,23 @@ class TestLNUtil(ElectrumTestCase):
htlc_cltv_timeout[2] = 502
htlc_payment_preimage[2] = b"\x02" * 32
htlc[2] = make_offered_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[2]))
htlc[2] = make_offered_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[2]), has_anchors=False)
htlc_cltv_timeout[3] = 503
htlc_payment_preimage[3] = b"\x03" * 32
htlc[3] = make_offered_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[3]))
htlc[3] = make_offered_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[3]), has_anchors=False)
htlc_cltv_timeout[0] = 500
htlc_payment_preimage[0] = b"\x00" * 32
htlc[0] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[0]), cltv_abs=htlc_cltv_timeout[0])
htlc[0] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[0]), cltv_abs=htlc_cltv_timeout[0], has_anchors=False)
htlc_cltv_timeout[1] = 501
htlc_payment_preimage[1] = b"\x01" * 32
htlc[1] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[1]), cltv_abs=htlc_cltv_timeout[1])
htlc[1] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[1]), cltv_abs=htlc_cltv_timeout[1], has_anchors=False)
htlc_cltv_timeout[4] = 504
htlc_payment_preimage[4] = b"\x04" * 32
htlc[4] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[4]), cltv_abs=htlc_cltv_timeout[4])
htlc[4] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[4]), cltv_abs=htlc_cltv_timeout[4], has_anchors=False)
remote_signature = bfh("304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606")
output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
@@ -565,22 +566,33 @@ class TestLNUtil(ElectrumTestCase):
htlc_output_index = {0: 0, 1: 2, 2: 1, 3: 3, 4: 4}
for i in range(5):
self.assertEqual(output_htlc_tx[i][1], self.htlc_tx(htlc[i], htlc_output_index[i],
self.assertEqual(output_htlc_tx[i][1], self.htlc_tx(
htlc[i],
htlc_output_index[i],
htlcs[i].htlc.amount_msat,
htlc_payment_preimage[i],
signature_for_output_remote_htlc[i],
output_htlc_tx[i][0], htlc_cltv_timeout[i] if not output_htlc_tx[i][0] else 0,
output_htlc_tx[i][0],
htlc_cltv_timeout[i] if not output_htlc_tx[i][0] else 0,
local_feerate_per_kw,
our_commit_tx))
our_commit_tx,
False,
))
def htlc_tx(self, htlc, htlc_output_index, amount_msat, htlc_payment_preimage, remote_htlc_sig, success, cltv_abs, local_feerate_per_kw, our_commit_tx):
def htlc_tx(self, htlc: bytes, htlc_output_index: int, amount_msat: int,
htlc_payment_preimage: bytes, remote_htlc_sig: str,
success: bool, cltv_abs: int,
local_feerate_per_kw: int, our_commit_tx: PartialTransaction,
has_anchors: bool) -> str:
_script, 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)
to_self_delay=local_delay,
has_anchors=has_anchors
)
our_htlc_tx_inputs = make_htlc_tx_inputs(
htlc_output_txid=our_commit_tx.txid(),
htlc_output_index=htlc_output_index,
@@ -591,10 +603,16 @@ class TestLNUtil(ElectrumTestCase):
inputs=our_htlc_tx_inputs,
output=our_htlc_tx_output)
remote_sighash = Sighash.ALL
if has_anchors:
remote_sighash = Sighash.ANYONECANPAY | Sighash.SINGLE
our_htlc_tx.inputs()[0].nsequence = 1
our_htlc_tx.inputs()[0].sighash = Sighash.ALL
local_sig = our_htlc_tx.sign_txin(0, local_privkey[:-1])
our_htlc_tx_witness = make_htlc_tx_witness(
remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL
remotehtlcsig=bfh(remote_htlc_sig) + remote_sighash.to_bytes(1, 'big'),
localhtlcsig=local_sig,
payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout
witness_script=htlc)