following up on #1597 nLockTime axis nLockTime is a per-tx fingerprint, matching the largest crowd (Core / Electrum) keeps payjoin txs in the same anonymity set.
| wallet |
nLockTime now |
converges? |
| Core / Electrum |
tip + ~10% backdate |
|
| Liana |
tip + ~10% backdate |
|
| payjoin-cli / ldk-node / BBM |
exact-tip (no backdate) |
target |
| Cake |
0 |
|
| Boltz |
claim 0 / refund = swap timeout |
|
payjoin-cli sets an explicit locktime = get_block_count() (wallet.rs#L54-L59). walletcreatefundedpsbt copies that into coin_control.m_locktime (wallet/spend.cpp#L1501), and a set m_locktime turns off Core's anti-fee-sniping wallet/spend.cpp#L1313-L1316, so every payjoin-cli tx lands on the exact tip, with no backdating.
both wallets we'd want to blend into backdate ~10% of the time: Core's DiscourageFeeSniping (wallet/spend.cpp#L1029-L1030: randrange(10)==0 → nLockTime -= randrange(100)) and Electrum (wallet.py#L224-L226). Always landing on the tip separates payjoin-cli (with ldk-node and BBM) from the Core/Electrum crowd over a few transactions. The code comment cites "anti-fee-sniping best practices", exact-tip keeps the anti-snipe protection but drops the randomized backdate Core/Electrum apply for privacy, and that skip is what makes it stand out.
proposed: drop the explicit locktime and let Core's DiscourageFeeSniping run. payjoin-cli then matches Core/Electrum exactly, same anonymity set, no new coupling.
other integrations:
following up on #1597 nLockTime axis nLockTime is a per-tx fingerprint, matching the largest crowd (Core / Electrum) keeps payjoin txs in the same anonymity set.
00/ refund = swap timeoutpayjoin-clisets an explicitlocktime = get_block_count()(wallet.rs#L54-L59).walletcreatefundedpsbtcopies that intocoin_control.m_locktime(wallet/spend.cpp#L1501), and a setm_locktimeturns off Core's anti-fee-sniping wallet/spend.cpp#L1313-L1316, so everypayjoin-clitx lands on the exact tip, with no backdating.both wallets we'd want to blend into backdate ~10% of the time: Core's
DiscourageFeeSniping(wallet/spend.cpp#L1029-L1030:randrange(10)==0 → nLockTime -= randrange(100)) and Electrum (wallet.py#L224-L226). Always landing on the tip separates payjoin-cli (with ldk-node and BBM) from the Core/Electrum crowd over a few transactions. The code comment cites "anti-fee-sniping best practices", exact-tip keeps the anti-snipe protection but drops the randomized backdate Core/Electrum apply for privacy, and that skip is what makes it stand out.proposed: drop the explicit locktime and let Core's
DiscourageFeeSnipingrun.payjoin-clithen matchesCore/Electrumexactly, same anonymity set, no new coupling.other integrations:
Liana- already converges:anti_fee_sniping_locktime(spend.rs#L485-L516) is used increate_spend(lianad/src/commands/mod.rs#L746).ldk-node/BBM- exact-tip: their sends build without.nlocktime(ldk mod.rs#L743, BBM bdk_wallet_datasource.dart#L168-L204), so BDK's no-locktime arm applies, current height, no backdate (bdk_wallet mod.rs#L1439). A backdate added upstream in BDK covers both.Cake- always0(constant.dart#L148); would need anti-fee-sniping addedBoltz- refund can't: itsnLockTimemust equal the swap timeout to satisfyOP_CHECKLOCKTIMEVERIFYin the swap script (SwapScript.ts#L29-L30, Refund.ts#L12-L37). The success claim isn't CLTV-bound and could converge inboltz-core: Claim.ts#L79 setslockTimefrom the optional timeout, the claim path passes none so it stays0, and its input is non-final (Claim.ts#L96) so a tip locktime would be valid.