1
0

swaps: clean-up onchain script construction

This commit is contained in:
SomberNight
2025-08-22 16:25:10 +00:00
parent 8d8d1dba0f
commit fc362826e8

View File

@@ -79,33 +79,34 @@ assert MAX_LOCKTIME_DELTA < MIN_FINAL_CLTV_DELTA_FOR_CLIENT
WITNESS_TEMPLATE_REVERSE_SWAP = [
opcodes.OP_SIZE,
OPPushDataGeneric(None),
OPPushDataGeneric(None), # idx 1. length of preimage
opcodes.OP_EQUAL,
opcodes.OP_IF,
opcodes.OP_HASH160,
OPPushDataGeneric(lambda x: x == 20),
OPPushDataGeneric(lambda x: x == 20), # idx 5. payment_hash
opcodes.OP_EQUALVERIFY,
OPPushDataPubkey,
OPPushDataPubkey, # idx 7. claim_pubkey
opcodes.OP_ELSE,
opcodes.OP_DROP,
OPPushDataGeneric(None),
OPPushDataGeneric(None), # idx 10. locktime
opcodes.OP_CHECKLOCKTIMEVERIFY,
opcodes.OP_DROP,
OPPushDataPubkey,
OPPushDataPubkey, # idx 13. refund_pubkey
opcodes.OP_ENDIF,
opcodes.OP_CHECKSIG
]
def check_reverse_redeem_script(
def _check_swap_scriptcode(
*,
redeem_script: bytes,
lockup_address: str,
payment_hash: bytes,
locktime: int,
refund_pubkey: bytes = None,
claim_pubkey: bytes = None,
refund_pubkey: Optional[bytes], # note: We don't need to check the counterparty's key.
claim_pubkey: Optional[bytes], # Can use None in that case.
) -> None:
assert (refund_pubkey is not None) or (claim_pubkey is not None), "at least one pubkey must be set"
parsed_script = [x for x in script_GetOp(redeem_script)]
if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP):
raise Exception("rswap check failed: scriptcode does not match template")
@@ -113,12 +114,38 @@ def check_reverse_redeem_script(
raise Exception("rswap check failed: inconsistent scriptcode and address")
if ripemd(payment_hash) != parsed_script[5][1]:
raise Exception("rswap check failed: our preimage not in script")
if claim_pubkey and claim_pubkey != parsed_script[7][1]:
claim_pubkey = claim_pubkey or parsed_script[7][1]
if claim_pubkey != parsed_script[7][1]:
raise Exception("rswap check failed: our pubkey not in script")
if refund_pubkey and refund_pubkey != parsed_script[13][1]:
refund_pubkey = refund_pubkey or parsed_script[13][1]
if refund_pubkey != parsed_script[13][1]:
raise Exception("rswap check failed: our pubkey not in script")
if locktime != int.from_bytes(parsed_script[10][1], byteorder='little'):
raise Exception("rswap check failed: inconsistent locktime and script")
# let's just rebuild the full script from scratch...
if redeem_script != _construct_swap_scriptcode(
payment_hash=payment_hash,
locktime=locktime,
refund_pubkey=refund_pubkey,
claim_pubkey=claim_pubkey,
):
raise Exception("failed to rebuild swap script from scratch")
def _construct_swap_scriptcode(
payment_hash: bytes,
locktime: int,
refund_pubkey: bytes,
claim_pubkey: bytes,
) -> bytes:
assert isinstance(payment_hash, bytes) and len(payment_hash) == 32
assert isinstance(locktime, int) and (0 <= locktime <= bitcoin.NLOCKTIME_BLOCKHEIGHT_MAX)
assert isinstance(refund_pubkey, bytes) and len(refund_pubkey) == 33
assert isinstance(claim_pubkey, bytes) and len(claim_pubkey) == 33
return construct_script(
WITNESS_TEMPLATE_REVERSE_SWAP,
values={1: 32, 5: ripemd(payment_hash), 7: claim_pubkey, 10: locktime, 13: refund_pubkey}
)
class SwapServerError(Exception):
@@ -571,9 +598,11 @@ class SwapManager(Logger):
onchain_amount_sat = self._get_recv_amount(lightning_amount_sat, is_reverse=True) # what the client is going to receive
if not onchain_amount_sat:
raise Exception("no onchain amount")
redeem_script = construct_script(
WITNESS_TEMPLATE_REVERSE_SWAP,
values={1:32, 5:ripemd(payment_hash), 7:their_pubkey, 10:locktime, 13:our_pubkey}
redeem_script = _construct_swap_scriptcode(
payment_hash=payment_hash,
locktime=locktime,
refund_pubkey=our_pubkey,
claim_pubkey=their_pubkey,
)
swap, invoice, prepay_invoice = self.add_normal_swap(
redeem_script=redeem_script,
@@ -670,9 +699,11 @@ class SwapManager(Logger):
raise Exception("no onchain amount")
preimage = os.urandom(32)
payment_hash = sha256(preimage)
redeem_script = construct_script(
WITNESS_TEMPLATE_REVERSE_SWAP,
values={1:32, 5:ripemd(payment_hash), 7:our_pubkey, 10:locktime, 13:their_pubkey}
redeem_script = _construct_swap_scriptcode(
payment_hash=payment_hash,
locktime=locktime,
refund_pubkey=their_pubkey,
claim_pubkey=our_pubkey,
)
swap = self.add_reverse_swap(
redeem_script=redeem_script,
@@ -812,12 +843,13 @@ class SwapManager(Logger):
raise SwapServerError("failed to parse response from swapserver for createnormalswap") from e
del data # parsing done
# verify redeem_script is built with our pubkey and preimage
check_reverse_redeem_script(
_check_swap_scriptcode(
redeem_script=redeem_script,
lockup_address=lockup_address,
payment_hash=payment_hash,
locktime=locktime,
refund_pubkey=refund_pubkey,
claim_pubkey=None,
)
# check that onchain_amount is not more than what we estimated
@@ -995,7 +1027,7 @@ class SwapManager(Logger):
del data # parsing done
self.logger.debug(f'rswap: {response_id=}')
# verify redeem_script is built with our pubkey and preimage
check_reverse_redeem_script(
_check_swap_scriptcode(
redeem_script=redeem_script,
lockup_address=lockup_address,
payment_hash=payment_hash,