check dust limits
* on channel opening we verify that the peer's dust limit is above 354 sat, the limit for unknown segwit versions * we constrain the allowed scriptpubkey types for channel closing * we check that the remote's output is above the relay dust limit for the collaborative close case
This commit is contained in:
@@ -349,8 +349,12 @@ def relayfee(network: 'Network' = None) -> int:
|
|||||||
|
|
||||||
|
|
||||||
# see https://github.com/bitcoin/bitcoin/blob/a62f0ed64f8bbbdfe6467ac5ce92ef5b5222d1bd/src/policy/policy.cpp#L14
|
# see https://github.com/bitcoin/bitcoin/blob/a62f0ed64f8bbbdfe6467ac5ce92ef5b5222d1bd/src/policy/policy.cpp#L14
|
||||||
DUST_LIMIT_DEFAULT_SAT_LEGACY = 546
|
# and https://github.com/lightningnetwork/lightning-rfc/blob/7e3dce42cbe4fa4592320db6a4e06c26bb99122b/03-transactions.md#dust-limits
|
||||||
DUST_LIMIT_DEFAULT_SAT_SEGWIT = 294
|
DUST_LIMIT_P2PKH = 546
|
||||||
|
DUST_LIMIT_P2SH = 540
|
||||||
|
DUST_LIMIT_UNKNOWN_SEGWIT = 354
|
||||||
|
DUST_LIMIT_P2WSH = 330
|
||||||
|
DUST_LIMIT_P2WPKH = 294
|
||||||
|
|
||||||
|
|
||||||
def dust_threshold(network: 'Network' = None) -> int:
|
def dust_threshold(network: 'Network' = None) -> int:
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ class Peer(Logger):
|
|||||||
static_remotekey = bfh(wallet.get_public_key(addr))
|
static_remotekey = bfh(wallet.get_public_key(addr))
|
||||||
else:
|
else:
|
||||||
static_remotekey = None
|
static_remotekey = None
|
||||||
dust_limit_sat = bitcoin.DUST_LIMIT_DEFAULT_SAT_LEGACY
|
dust_limit_sat = bitcoin.DUST_LIMIT_P2PKH
|
||||||
reserve_sat = max(funding_sat // 100, dust_limit_sat)
|
reserve_sat = max(funding_sat // 100, dust_limit_sat)
|
||||||
# for comparison of defaults, see
|
# for comparison of defaults, see
|
||||||
# https://github.com/ACINQ/eclair/blob/afa378fbb73c265da44856b4ad0f2128a88ae6c6/eclair-core/src/main/resources/reference.conf#L66
|
# https://github.com/ACINQ/eclair/blob/afa378fbb73c265da44856b4ad0f2128a88ae6c6/eclair-core/src/main/resources/reference.conf#L66
|
||||||
@@ -1697,12 +1697,7 @@ class Peer(Logger):
|
|||||||
raise UpfrontShutdownScriptViolation("remote didn't use upfront shutdown script it commited to in channel opening")
|
raise UpfrontShutdownScriptViolation("remote didn't use upfront shutdown script it commited to in channel opening")
|
||||||
else:
|
else:
|
||||||
# BOLT-02 restrict the scriptpubkey to some templates:
|
# BOLT-02 restrict the scriptpubkey to some templates:
|
||||||
# order by decreasing dust limit
|
if self.is_shutdown_anysegwit() and match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT):
|
||||||
if match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_P2PKH):
|
|
||||||
pass
|
|
||||||
elif match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_P2SH):
|
|
||||||
pass
|
|
||||||
elif self.is_shutdown_anysegwit() and match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT):
|
|
||||||
pass
|
pass
|
||||||
elif match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0):
|
elif match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0):
|
||||||
pass
|
pass
|
||||||
@@ -1765,9 +1760,9 @@ class Peer(Logger):
|
|||||||
# BOLT2: The sending node MUST set fee less than or equal to the base fee of the final ctx
|
# BOLT2: The sending node MUST set fee less than or equal to the base fee of the final ctx
|
||||||
max_fee = chan.get_latest_fee(LOCAL if is_local else REMOTE)
|
max_fee = chan.get_latest_fee(LOCAL if is_local else REMOTE)
|
||||||
our_fee = min(our_fee, max_fee)
|
our_fee = min(our_fee, max_fee)
|
||||||
drop_remote = False
|
drop_to_remote = False
|
||||||
def send_closing_signed():
|
def send_closing_signed():
|
||||||
our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=our_fee, drop_remote=drop_remote)
|
our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=our_fee, drop_remote=drop_to_remote)
|
||||||
self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig)
|
self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig)
|
||||||
def verify_signature(tx, sig):
|
def verify_signature(tx, sig):
|
||||||
their_pubkey = chan.config[REMOTE].multisig_key.pubkey
|
their_pubkey = chan.config[REMOTE].multisig_key.pubkey
|
||||||
@@ -1788,13 +1783,22 @@ class Peer(Logger):
|
|||||||
# verify their sig: they might have dropped their output
|
# verify their sig: they might have dropped their output
|
||||||
our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=False)
|
our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=False)
|
||||||
if verify_signature(closing_tx, their_sig):
|
if verify_signature(closing_tx, their_sig):
|
||||||
drop_remote = False
|
drop_to_remote = False
|
||||||
else:
|
else:
|
||||||
our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=True)
|
our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=True)
|
||||||
if verify_signature(closing_tx, their_sig):
|
if verify_signature(closing_tx, their_sig):
|
||||||
drop_remote = True
|
drop_to_remote = True
|
||||||
else:
|
else:
|
||||||
|
# this can happen if we consider our output too valuable to drop,
|
||||||
|
# but the remote drops it because it violates their dust limit
|
||||||
raise Exception('failed to verify their signature')
|
raise Exception('failed to verify their signature')
|
||||||
|
# at this point we know how the closing tx looks like
|
||||||
|
# check that their output is above their scriptpubkey's network dust limit
|
||||||
|
if not drop_to_remote:
|
||||||
|
to_remote_idx = closing_tx.get_output_idxs_from_scriptpubkey(their_scriptpubkey.hex()).pop()
|
||||||
|
to_remote_amount = closing_tx.outputs()[to_remote_idx].value
|
||||||
|
transaction.check_scriptpubkey_template_and_dust(their_scriptpubkey, to_remote_amount)
|
||||||
|
|
||||||
# Agree if difference is lower or equal to one (see below)
|
# Agree if difference is lower or equal to one (see below)
|
||||||
if abs(our_fee - their_fee) < 2:
|
if abs(our_fee - their_fee) < 2:
|
||||||
our_fee = their_fee
|
our_fee = their_fee
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ COMMITMENT_TX_WEIGHT = 724
|
|||||||
HTLC_OUTPUT_WEIGHT = 172
|
HTLC_OUTPUT_WEIGHT = 172
|
||||||
|
|
||||||
LN_MAX_FUNDING_SAT = pow(2, 24) - 1
|
LN_MAX_FUNDING_SAT = pow(2, 24) - 1
|
||||||
|
DUST_LIMIT_MAX = 1000
|
||||||
|
|
||||||
# dummy address for fee estimation of funding tx
|
# dummy address for fee estimation of funding tx
|
||||||
def ln_dummy_address():
|
def ln_dummy_address():
|
||||||
@@ -103,10 +104,10 @@ class ChannelConfig(StoredObject):
|
|||||||
raise Exception(f"{conf_name}. insane initial_msat={self.initial_msat}. (funding_sat={funding_sat})")
|
raise Exception(f"{conf_name}. insane initial_msat={self.initial_msat}. (funding_sat={funding_sat})")
|
||||||
if self.reserve_sat < self.dust_limit_sat:
|
if self.reserve_sat < self.dust_limit_sat:
|
||||||
raise Exception(f"{conf_name}. MUST set channel_reserve_satoshis greater than or equal to dust_limit_satoshis")
|
raise Exception(f"{conf_name}. MUST set channel_reserve_satoshis greater than or equal to dust_limit_satoshis")
|
||||||
# technically this could be using the lower DUST_LIMIT_DEFAULT_SAT_SEGWIT
|
if self.dust_limit_sat < bitcoin.DUST_LIMIT_UNKNOWN_SEGWIT:
|
||||||
# but other implementations are checking against this value too; also let's be conservative
|
|
||||||
if self.dust_limit_sat < bitcoin.DUST_LIMIT_DEFAULT_SAT_LEGACY:
|
|
||||||
raise Exception(f"{conf_name}. dust limit too low: {self.dust_limit_sat} sat")
|
raise Exception(f"{conf_name}. dust limit too low: {self.dust_limit_sat} sat")
|
||||||
|
if self.dust_limit_sat > DUST_LIMIT_MAX:
|
||||||
|
raise Exception(f"{conf_name}. dust limit too high: {self.dust_limit_sat} sat")
|
||||||
if self.reserve_sat > funding_sat // 100:
|
if self.reserve_sat > funding_sat // 100:
|
||||||
raise Exception(f"{conf_name}. reserve too high: {self.reserve_sat}, funding_sat: {funding_sat}")
|
raise Exception(f"{conf_name}. reserve too high: {self.reserve_sat}, funding_sat: {funding_sat}")
|
||||||
if self.htlc_minimum_msat > 1_000:
|
if self.htlc_minimum_msat > 1_000:
|
||||||
|
|||||||
@@ -459,6 +459,21 @@ SCRIPTPUBKEY_TEMPLATE_P2WSH = [opcodes.OP_0, OPPushDataGeneric(lambda x: x == 32
|
|||||||
SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT = [OP_ANYSEGWIT_VERSION, OPPushDataGeneric(lambda x: x in list(range(2, 40 + 1)))]
|
SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT = [OP_ANYSEGWIT_VERSION, OPPushDataGeneric(lambda x: x in list(range(2, 40 + 1)))]
|
||||||
|
|
||||||
|
|
||||||
|
def check_scriptpubkey_template_and_dust(scriptpubkey, amount: Optional[int]):
|
||||||
|
if match_script_against_template(scriptpubkey, SCRIPTPUBKEY_TEMPLATE_P2PKH):
|
||||||
|
dust_limit = bitcoin.DUST_LIMIT_P2PKH
|
||||||
|
elif match_script_against_template(scriptpubkey, SCRIPTPUBKEY_TEMPLATE_P2SH):
|
||||||
|
dust_limit = bitcoin.DUST_LIMIT_P2SH
|
||||||
|
elif match_script_against_template(scriptpubkey, SCRIPTPUBKEY_TEMPLATE_P2WSH):
|
||||||
|
dust_limit = bitcoin.DUST_LIMIT_P2WSH
|
||||||
|
elif match_script_against_template(scriptpubkey, SCRIPTPUBKEY_TEMPLATE_P2WPKH):
|
||||||
|
dust_limit = bitcoin.DUST_LIMIT_P2WPKH
|
||||||
|
else:
|
||||||
|
raise Exception(f'scriptpubkey does not conform to any template: {scriptpubkey.hex()}')
|
||||||
|
if amount < dust_limit:
|
||||||
|
raise Exception(f'amount ({amount}) is below dust limit for scriptpubkey type ({dust_limit})')
|
||||||
|
|
||||||
|
|
||||||
def match_script_against_template(script, template) -> bool:
|
def match_script_against_template(script, template) -> bool:
|
||||||
"""Returns whether 'script' matches 'template'."""
|
"""Returns whether 'script' matches 'template'."""
|
||||||
if script is None:
|
if script is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user