Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 38 additions & 38 deletions conformance/htlc-handshake-vectors.json

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions scripts/_dust_swap_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,7 @@ def covenant_fund_height(height: ChainHeight) -> ChainHeight:
return height


async def scan_covenant_fund_height(
client: Any, *, covenant_spk: bytes, expected_photons: int
) -> ChainHeight:
async def scan_covenant_fund_height(client: Any, *, covenant_spk: bytes, expected_photons: int) -> ChainHeight:
"""The anchor for paths that locked the asset WITHOUT :func:`wait_for_covenant_funding` — the
NFT and FT variants, which lock by SPENDING into the covenant rather than by waiting on an
operator payment. Same conversion, same fail-closed rules, one scan.
Expand Down
15 changes: 9 additions & 6 deletions scripts/btc_swap_two_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,8 +851,9 @@ def run_self_check() -> None:
terms, cov = _terms_from_public(
hashlock=h,
btc_sats=100_000,
t_rxd_blocks=20,
t_btc_blocks=60, # t_btc - t_rxd = 40 >= margin 36
# INVERTED (#482): the maker holds p and LOCKS the Radiant leg, so t_rxd is the LONGER.
t_rxd_blocks=60,
t_btc_blocks=20, # t_rxd - t_btc = 40 >= margin 36
taker_pkh=taker_pkh,
maker_pkh=maker_pkh,
btc_claim_xonly=claim_xonly,
Expand Down Expand Up @@ -909,12 +910,14 @@ def run_self_check() -> None:
assert_timelock_margin(terms2.t_btc, terms2.t_rxd, policy)
print(" [ok] taker's INDEPENDENT timelock-margin check passes for honest terms")

# ...and REFUSES a hostile too-tight envelope (t_btc - t_rxd < margin).
# ...and REFUSES a hostile too-tight envelope (t_rxd - t_btc < margin).
# The pair inverted with #482: t_rxd=50/t_btc=60 no longer constructs at all, so this check
# would have passed on the ordering guard's exception rather than the margin check's.
hostile, _ = _terms_from_public(
hashlock=h,
btc_sats=100_000,
t_rxd_blocks=50,
t_btc_blocks=60, # gap 10 < margin 36
t_rxd_blocks=60,
t_btc_blocks=50, # gap 10 < margin 36
taker_pkh=taker_pkh,
maker_pkh=maker_pkh,
btc_claim_xonly=claim_xonly,
Expand All @@ -924,7 +927,7 @@ def run_self_check() -> None:
assert_timelock_margin(hostile.t_btc, hostile.t_rxd, policy)
raise AssertionError("FAIL: the margin check did not reject a too-tight hostile envelope")
except ValidationError:
print(" [ok] taker REFUSES a hostile too-tight envelope (t_btc - t_rxd < margin)")
print(" [ok] taker REFUSES a hostile too-tight envelope (t_rxd - t_btc < margin)")

# --- MAKER side: the expected HTLC SPK is deterministically re-derivable from public terms. ---
htlc = bt.build_htlc(
Expand Down
5 changes: 4 additions & 1 deletion scripts/dust_swap_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ async def run_dust_swap(args: argparse.Namespace) -> None:
h = hashlib.sha256(p).digest()
margin_blocks = policy.margin.normalize_to(bt.TimeUnit.BLOCKS, block_interval_s=policy.block_interval_s).value
t_rxd = bt.Timelock(args.t_rxd_blocks, bt.TimeUnit.BLOCKS)
t_btc = bt.Timelock(args.t_rxd_blocks + margin_blocks + 4, bt.TimeUnit.BLOCKS) # > t_rxd + margin
# INVERTED (#482): the maker holds p and LOCKS the Radiant leg, so t_rxd carries the LONGER
# timeout and the BTC leg it CLAIMS is the shorter one. This built t_rxd + margin + 4 — the
# layout where the maker can refund RXD and still claim BTC with p.
t_btc = bt.Timelock(args.t_rxd_blocks - margin_blocks - 4, bt.TimeUnit.BLOCKS) # < t_rxd - margin

maker_btc = coincurve.PrivateKey(os.urandom(32))
taker_btc_kp = generate_keypair(btc_network)
Expand Down
209 changes: 132 additions & 77 deletions scripts/eth_swap_run.py

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions scripts/eth_swap_two_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,9 @@ def _terms_from_public(
identical covenant SPK + dest hashes — that mutual re-derivation is the trust anchor."""
t_rxd = bt.Timelock(t_rxd_blocks, bt.TimeUnit.BLOCKS)
# t_btc is decorative for an ETH swap (the real ETH deadline is eth_timeout_unix_s), but it must
# stay > t_rxd so the same-unit ordering guard in NegotiatedTerms passes; keep it well clear.
t_btc = bt.Timelock(t_rxd_blocks + margin_blocks + 4, bt.TimeUnit.BLOCKS)
# stay BELOW t_rxd so the same-unit ordering guard in NegotiatedTerms passes; keep it well
# clear. It was t_rxd + margin + 4 before #482 inverted the relation.
t_btc = bt.Timelock(t_rxd_blocks - margin_blocks - 4, bt.TimeUnit.BLOCKS)
cov = build_htlc_covenant_rxd(
amount=rxd_photons,
taker_pkh=bytes(Hex20(taker_pkh)),
Expand Down Expand Up @@ -957,13 +958,17 @@ def run_self_check() -> None:
assert_timelock_margin(terms2.t_btc, terms2.t_rxd, policy)
print(" [ok] taker's INDEPENDENT timelock-margin check passes for honest terms")

# ...and REFUSES a hostile too-tight envelope (t_btc - t_rxd < margin).
# ...and REFUSES a hostile too-tight envelope (t_rxd - t_btc < margin).
#
# The pair is the other way round since #482. Written as t_btc=61/t_rxd=60 it no longer even
# CONSTRUCTS — the ordering guard rejects it first — so `assert_timelock_margin` never runs and
# this check would pass on an exception raised by the wrong thing entirely.
hostile = NegotiatedTerms(
hashlock=h,
btc_sats=1000,
radiant_amount=1000,
t_btc=bt.Timelock(61, bt.TimeUnit.BLOCKS), # only 1 block over t_rxd — far below margin 36
t_rxd=bt.Timelock(60, bt.TimeUnit.BLOCKS),
t_btc=bt.Timelock(60, bt.TimeUnit.BLOCKS),
t_rxd=bt.Timelock(61, bt.TimeUnit.BLOCKS), # only 1 block over t_btc — far below margin 36
asset_variant="rxd",
genesis_ref=b"",
taker_dest_hash=cov.expected_taker_hash,
Expand Down
10 changes: 6 additions & 4 deletions scripts/watchtower_dust_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,10 @@ def cmd_setup(args: argparse.Namespace) -> int:
"--refund-spk must be a standard spendable scriptPubKey (P2WPKH/P2TR/P2WSH/P2PKH/P2SH); "
"derive it from your checksum-validated refund address"
)
if args.t_btc <= args.t_rxd:
raise SystemExit(f"--t-btc ({args.t_btc}) must be > --t-rxd ({args.t_rxd}) (BTC is the longer leg)")
# INVERTED (#482): the maker holds p and LOCKS the Radiant leg, so the RADIANT leg carries the
# longer refund and the BTC leg it claims is the shorter one.
if args.t_rxd <= args.t_btc:
raise SystemExit(f"--t-rxd ({args.t_rxd}) must be > --t-btc ({args.t_btc}) (Radiant is the longer leg)")

# Keys: taker refund key (generated, persisted 0600) + a maker claim PUBKEY (we never claim, so its
# private half is discarded — only the x-only pubkey is needed to reconstruct the taptree).
Expand Down Expand Up @@ -408,8 +410,8 @@ def _parse_args(argv=None) -> argparse.Namespace:
s.add_argument("--swap-id", default="dust1", help="swap id (== the SwapRecord/sidecar file stem)")
s.add_argument("--network", default="bc", help="bc | bcrt | tb | signet")
s.add_argument("--btc-sats", type=int, required=True, help="exact sats to fund the HTLC with")
s.add_argument("--t-btc", type=int, default=2, help="BTC refund CSV in blocks (the longer leg)")
s.add_argument("--t-rxd", type=int, default=1, help="RXD refund CSV in blocks (must be < --t-btc)")
s.add_argument("--t-btc", type=int, default=1, help="BTC refund CSV in blocks (the shorter leg)")
s.add_argument("--t-rxd", type=int, default=2, help="RXD refund CSV in blocks (must be > --t-btc)")
s.add_argument("--refund-spk", required=True, help="hex scriptPubKey the refund must pay (YOUR address)")
s.add_argument("--refund-address", help="the refund address, for display only")
s.add_argument("--force", action="store_true", help="overwrite an existing state file")
Expand Down
Loading
Loading