BIP Draft: Anti-Fee-Sniping with LockTime #2266

pull nervana21 wants to merge 2 commits into bitcoin:master from nervana21:anti-fee-snipe changing 2 files +506 −0
  1. nervana21 commented at 8:42 PM on August 21, 2026: contributor

    Specifies the nLockTime anti-fee-sniping behavior that Bitcoin Core and Electrum already implement. BIP326 assumes these rules as the baseline and uses nSequence instead for some taproot spends. The nLockTime path itself was never specified.

    Discussed on bitcoindev https://gnusha.org/pi/bitcoindev/wWGWjMw9TI22vp4VzCm4xJJS3IGA1UhndMKynUkB04BqeoRjhe-QbDbJU-GMQq0nnOnXuB__u8KcdIcxU5i8Cy9pbTbtJ7Hi583FzLVojek=@pm.me/

  2. nervana21 force-pushed on Aug 21, 2026
  3. nervana21 force-pushed on Aug 21, 2026
  4. nervana21 force-pushed on Aug 21, 2026
  5. in bip-anti-fee-sniping-with-locktime.md:18 in 0af9cb51e9
      13 | +
      14 | +This BIP specifies wallet behavior that discourages fee sniping by setting nLockTime. Bitcoin Core already implements this behavior. BIP326 assumes these nLockTime rules as the baseline and uses nSequence instead for some taproot spends.
      15 | +
      16 | +## Motivation
      17 | +
      18 | +Anti-fee-sniping behavior has existed in Bitcoin Core since [2014](https://github.com/bitcoin/bitcoin/commit/ba7fcc8de06602576ab6a5911879d3d8df80d36a) and in Electrum since [2017](https://github.com/spesmilo/electrum/commit/de85b56e0aeac52463530dab3e54f1a35128ee3b). BIP326 assumes this nLockTime behavior but never defined it. This BIP specifies those rules.
    


    murchandamus commented at 8:30 PM on September 21, 2026:

    While it is good to describe why the BIP is being written, the Motivation section should also describe what issue the proposal addresses and how adopting the proposed idea improves the situation. Some of the information below seems like it should be moved into the Motivation section.


    nervana21 commented at 11:45 PM on September 29, 2026:

    Taken. I've moved the fee-sniping description into Motivation and used your wording as below. Thanks!

  6. in bip-anti-fee-sniping-with-locktime.md:24 in 0af9cb51e9
      19 | +
      20 | +## Background
      21 | +
      22 | +### Fee sniping
      23 | +
      24 | +Fee sniping is an incentive-misaligned miner strategy. The miner orphans the best block to capture fees from its transactions and from the mempool instead of extending the tip. With nLockTime anti-fee-sniping, many of those transactions cannot enter the remined first block and must wait for the second, which weakens the attack and favors extending the chain instead.
    


    murchandamus commented at 8:33 PM on September 21, 2026:

    I think this paragraph belongs in the Motivation, and I have a few suggestions regarding the phrasing. How about:

    Fee sniping is an incentive-misaligned miner strategy. The miner orphans the best block to capture fees from its transactions and from the mempool instead of extending the tip. With nLockTime anti-fee-sniping, new transactions submitted to the mempool cannot be included in the remined first block and must wait for the second, which reduces the potential fee revenue of the attack and incentivizes extending the chain instead.
    

    nervana21 commented at 11:46 PM on September 29, 2026:

    Done. Thanks!

  7. in bip-anti-fee-sniping-with-locktime.md:42 in 0af9cb51e9
      37 | +
      38 | +If the tip is not current, such as during initial block download, or when the current time minus the tip timestamp is greater than 8 hours (28_800 seconds), set nLockTime to 0 instead. A tip-relative locktime while far behind the network does not help against fee sniping and can fingerprint the wallet.
      39 | +
      40 | +Do not apply this BIP when the user or a higher-level protocol sets nLockTime, or when any input already has a preset nSequence (for example a PSBT this wallet only funds, or a contract template). Leave those fields unmodified. Presigned transactions meant to stay valid across a wide height range should document their own locktime policy.
      41 | +
      42 | +A child's nLockTime does not protect an unconfirmed parent. Finality is checked per transaction.
    


    murchandamus commented at 8:40 PM on September 21, 2026:

    I don’t understand why this paragraph is in the specification. This seems descriptive rather than prescriptive.


    nervana21 commented at 12:17 AM on September 30, 2026:

    Okay, I've moved it to Rationale under "Unconfirmed parents and child transactions". I've also clarified that package wide parent floors are out of scope. This BIP only floors replacements of the same transaction.

  8. in bip-anti-fee-sniping-with-locktime.md:9 in 0af9cb51e9 outdated
       0 | @@ -0,0 +1,304 @@
       1 | +```
       2 | +  BIP: ?
       3 | +  Layer: Applications
       4 | +  Title: Anti-Fee-Sniping with LockTime
       5 | +  Authors: nervana21 <nervana21@pm.me>
       6 | +  Status: Draft
       7 | +  Type: Informational
       8 | +  Assigned: ?
       9 | +  License: CC0-1.0
    


    murchandamus commented at 8:44 PM on September 21, 2026:

    Please add a link to the discussion on the mailing list. Since this proposal supersedes BIP326 in one aspect, please add the corresponding “Replaces” header:

      License: CC0-1.0
      Discussion: link to mailing list thread
      Replaces: 326
    

    nervana21 commented at 12:35 AM on September 30, 2026:

    Okay, I've added the mailing list discussion link.

    IIUC correctly, "Replaces: 326" would mean that this BIP "succeeds, supersedes, or renders obsolete" BIP326, but isn't BIP326 is still needed to refer to the nSequence-based anti-fee-sniping?

  9. in bip-anti-fee-sniping-with-locktime.md:50 in 0af9cb51e9
      45 | +
      46 | +### Pseudocode
      47 | +
      48 | +```
      49 | +def apply_nlocktime_anti_fee_sniping(transaction):
      50 | +    if transaction.has_preset_nlocktime() or any(input.has_preset_nsequence() for input in transaction.inputs):
    


    murchandamus commented at 8:47 PM on September 21, 2026:

    Should this also check that the locktime of any child transaction is equal or higher than the locktime of every parent transaction? I.e., if a transaction had two parent transactions that were locked to 100 and 110, the child should not have a locktime lower than 110.


    nervana21 commented at 12:17 AM on September 30, 2026:

    Done as above.

  10. in bip-anti-fee-sniping-with-locktime.md:7 in 0af9cb51e9
       0 | @@ -0,0 +1,304 @@
       1 | +```
       2 | +  BIP: ?
       3 | +  Layer: Applications
       4 | +  Title: Anti-Fee-Sniping with LockTime
       5 | +  Authors: nervana21 <nervana21@pm.me>
       6 | +  Status: Draft
       7 | +  Type: Informational
    


    murchandamus commented at 8:55 PM on September 21, 2026:

    I think this should be a Specification BIP

      Type: Specification
    
  11. in bip-anti-fee-sniping-with-locktime.md:256 in 0af9cb51e9
     251 | +
     252 | +## Rationale
     253 | +
     254 | +### Fee bumping and replacements
     255 | +
     256 | +Re-applying the 10% privacy branch when fee-bumping can set a lower nLockTime on the replacement than on the transaction being replaced. That fingerprints Core or Electrum style wallets ([bitcoin#26526](https://github.com/bitcoin/bitcoin/issues/26526)). Changing locktime type across a replacement (for example height-based to time-based) is related ([bitcoin#35628](https://github.com/bitcoin/bitcoin/issues/35628)). Implementations SHOULD document replacement locktime policy, including locktime type. When re-running the privacy branch on a height-based previous locktime, floor at that height. Do not go older unless that policy is intentional and documented.
    


    murchandamus commented at 9:00 PM on September 21, 2026:

    The Rationale section describes related work, engages with criticism, which alternate designs were considered, and why decisions should be made. The implementation instructions in this section belong in the Specification section.


    nervana21 commented at 12:38 AM on September 30, 2026:

    Done.

  12. in bip-anti-fee-sniping-with-locktime.md:53 in 0af9cb51e9
      48 | +```
      49 | +def apply_nlocktime_anti_fee_sniping(transaction):
      50 | +    if transaction.has_preset_nlocktime() or any(input.has_preset_nsequence() for input in transaction.inputs):
      51 | +        return
      52 | +    for input in transaction.inputs:
      53 | +        input.nsequence = 2**32 - 2  # Conventional
    


    murchandamus commented at 9:19 PM on September 21, 2026:

    This sets all nSequence fields to non-final, but the above rules specify that at least one input has to use a non-final sequence. Perhaps this option should be recommended in the text?


    nervana21 commented at 12:40 AM on September 30, 2026:

    Done.

  13. in bip-anti-fee-sniping-with-locktime.md:34 in 0af9cb51e9
      29 | +
      30 | +If every input has nSequence equal to 0xffffffff, nLockTime is ignored.
      31 | +
      32 | +## Specification
      33 | +
      34 | +Set nLockTime to the current tip block height. With probability 10%, subtract a uniform random integer in 0..99 from that height (clamped at 0). The random branch improves privacy when signing is delayed (for example in high-latency mix networks).
    


    murchandamus commented at 9:20 PM on September 21, 2026:

    The sentence starting with “The random branch improves…” should be in the Rationale. The Specification section should focus on describing the exact syntax and semantics of the proposed feature.


    murchandamus commented at 9:38 PM on September 21, 2026:

    “current tip block height” feels imprecise, especially given that you define below that anti-fee-sniping is used if the node’s view of the chaintip is within eight hours of wall-time.


    nervana21 commented at 12:41 AM on September 30, 2026:

    Done.


    nervana21 commented at 12:44 AM on September 30, 2026:

    Updated.

  14. in bip-anti-fee-sniping-with-locktime.md:270 in 0af9cb51e9
     265 | +
     266 | +## Backward Compatibility
     267 | +
     268 | +This BIP requires no consensus changes and wallets may adopt it unilaterally.
     269 | +
     270 | +Hardware signers and PSBT cosigners should accept tip-relative anti-fee-sniping nLockTime values and should not require nLockTime=0 by default. Wallet software that funds PSBTs should apply this BIP when locktime and sequences are still wallet-controlled. Creator-chosen values stay untouched.
    


    murchandamus commented at 9:21 PM on September 21, 2026:

    If these are rules for implementers to follow, these should be in the Specification section.


    nervana21 commented at 2:22 PM on September 30, 2026:

    Done.

  15. in bip-anti-fee-sniping-with-locktime.md:40 in 0af9cb51e9
      35 | +
      36 | +Ensure at least one input has nSequence below 0xffffffff so nLockTime takes effect. The pseudocode sets every wallet-controlled input to `2**32 - 2` as one conventional choice.
      37 | +
      38 | +If the tip is not current, such as during initial block download, or when the current time minus the tip timestamp is greater than 8 hours (28_800 seconds), set nLockTime to 0 instead. A tip-relative locktime while far behind the network does not help against fee sniping and can fingerprint the wallet.
      39 | +
      40 | +Do not apply this BIP when the user or a higher-level protocol sets nLockTime, or when any input already has a preset nSequence (for example a PSBT this wallet only funds, or a contract template). Leave those fields unmodified. Presigned transactions meant to stay valid across a wide height range should document their own locktime policy.
    


    murchandamus commented at 9:24 PM on September 21, 2026:

    The Specification should describe how the anti-fee-sniping should work, when it is applied. Perhaps you could extract an “Applicability” section or otherwise separate the information about when to use anti-fee-sniping from how to implement anti-fee-sniping.


    nervana21 commented at 2:25 PM on September 30, 2026:

    Done.

  16. in bip-anti-fee-sniping-with-locktime.md:28 in 0af9cb51e9
      23 | +
      24 | +Fee sniping is an incentive-misaligned miner strategy. The miner orphans the best block to capture fees from its transactions and from the mempool instead of extending the tip. With nLockTime anti-fee-sniping, many of those transactions cannot enter the remined first block and must wait for the second, which weakens the attack and favors extending the chain instead.
      25 | +
      26 | +### Absolute locktime
      27 | +
      28 | +nLockTime is an absolute lock. Setting aside nSequence effects, a transaction with an nLockTime set to block height N may only be included in block height N + 1 or greater. A transaction with an nLockTime set to 0 is always final. nLockTime values less than 500_000_000 are block heights. The 10% privacy branch uses an older height, which can still enter a remine.
    


    murchandamus commented at 9:35 PM on September 21, 2026:

    “A transaction with an nLockTime set to 0 is always final.” Is that true? I think that a transaction with a locktime of 0 can included in any block, but I thought that finality was orthogonal, in the sense that it was indicated by whether all nSequence fields are set to the maximal value.

    How about:

    nLockTime is an absolute lock. A non-final transaction with an nLockTime set to block height N may only be included in block height N + 1 or greater. A transaction with max value in all nSequence fields is final. nLockTime values less than 500_000_000 are block heights. The 10% privacy branch uses an older height, which can still enter a remine.
    

    Regarding or with an nLockTime set to 0 is


    nervana21 commented at 12:52 AM on September 30, 2026:

    Good catch. Updated.

  17. murchandamus commented at 9:41 PM on September 21, 2026: member

    Thanks for your submission. I like the idea to document Anti-Fee-Sniping as a BIP. This document is a good start, but it still feels a bit rough on the edges.

    In some places you give advice that could be interpreted as implementation instructions in other sections than the Specification. Please make it clear whether these are implementation instructions or optional suggestions. If they are normative, they should appear in the Specification. If they are optional suggestions they should be more clearly introduced as such.

    It’s a bit unclear whether you are going for an Informational BIP that describes a design issue, or a Specification BIP that tries to describe a feature in a way that it can be implemented. At times you describe behavior exhibited by existing implementations, but then you also seem to try to comprehensively describe how to implement the feature. If you are going for an Informational BIP, you may focus on the benefit of anti-fee-sniping, suggest that wallet implementers lock transactions to the chaintip and cut the specification back to some general implementation advice. If you are going for a Specification BIP, you would double-down on your Specification and Pseudocode by making sure that the Specification is comprehensive and precise enough that projects can implement the feature from reading the Specification section.

  18. nervana21 commented at 10:04 PM on September 21, 2026: contributor

    Thanks for the feedback @murchandamus! I will address your concerns and post an updated draft asap

  19. murchandamus added the label New BIP on Sep 21, 2026
  20. murchandamus added the label PR Author action required on Sep 21, 2026
  21. nervana21 commented at 11:36 PM on September 29, 2026: contributor

    If you are going for a Specification BIP, you would double-down on your Specification and Pseudocode by making sure that the Specification is comprehensive and precise enough that projects can implement the feature from reading the Specification section.

    Ok, I think it's best to be a Specification as opposed to Informational BIP. I've updated the style to reflect that everywhere appropriate.

  22. doc: Allow nervana in typos word list eba3b85cdf
  23. nervana21 force-pushed on Sep 30, 2026
  24. doc: Anti-fee-sniping with locktime 456b1a3720
  25. nervana21 force-pushed on Sep 30, 2026
  26. nervana21 commented at 5:20 PM on September 30, 2026: contributor

    Rebased off master and updated draft to address detailed feedback from @murchandamus

  27. in bip-anti-fee-sniping-with-locktime.md:15 in 456b1a3720
      10 | +  Discussion: 2026-08-18: https://gnusha.org/pi/bitcoindev/wWGWjMw9TI22vp4VzCm4xJJS3IGA1UhndMKynUkB04BqeoRjhe-QbDbJU-GMQq0nnOnXuB__u8KcdIcxU5i8Cy9pbTbtJ7Hi583FzLVojek=@pm.me/
      11 | +```
      12 | +
      13 | +## Abstract
      14 | +
      15 | +This BIP specifies wallet behavior that discourages fee sniping by setting a height-based `nLockTime` relative to a wallet's local chain tip. BIP326 treats existing `nLockTime` anti-fee-sniping as the baseline and, for some taproot spends, uses `nSequence`-based anti-fee-sniping instead. This BIP defines the `nLockTime` path that BIP326 left unspecified.
    


    danielabrozzoni commented at 10:21 AM on October 29, 2026:

    nit: "BIP326" should be a link to the bip

  28. in bip-anti-fee-sniping-with-locktime.md:29 in 456b1a3720
      24 | +
      25 | +
      26 | +
      27 | +### Absolute locktime
      28 | +
      29 | +`nLockTime` is an absolute lock. A non-final transaction with an `nLockTime` set to block height `N` may only be included in block height `N + 1` or greater. A transaction with the maximum value in all `nSequence` fields is final and ignores `nLockTime`. `nLockTime` values less than 500_000_000 are block heights. Values at or above 500_000_000 are Unix timestamps. The 10% privacy branch uses an older height, which can still enter a remine of the tip.
    


    danielabrozzoni commented at 10:51 AM on October 29, 2026:

    This part:

    The 10% privacy branch uses an older height, which can still enter a remine of the tip.

    doesn't belong in the background section:

    • it assumes that the reader already knows what the privacy branch is, but it hasn't been explained yet
    • the rest of the paragraph explains what nlocktime is and how it works, while this sentence explains a consequence of the rules in this BIP
  29. in bip-anti-fee-sniping-with-locktime.md:77 in 456b1a3720
      72 | +#### Current tip
      73 | +
      74 | +1. Set `nLockTime` to `height`.
      75 | +2. With probability 10%, if `nLockTime > floor`, draw an integer `back` uniformly from `0 .. bound - 1` where `bound = min(100, height - floor + 1)`, and set `nLockTime = height - back`.
      76 | +
      77 | +`randrange(n)` returns an integer in `0 .. n - 1`. The inclusive window `[floor, height]` has length `height - floor + 1`. Cap that length at 100. The same cap applies for every `floor`, including the default of 0.
    


    danielabrozzoni commented at 11:07 AM on October 29, 2026:

    The whole "Locktime selection" section, but especially this part, reads quite weird: it's written in a sort of pseudocode instead of plain english, which doesn't add much since there's already a pseudocode section down below.

    I would completely rewrite it in plain english, for example:

    Set nLockTime to height, so the transaction cannot be mined in a remine of the current tip.

    With probability 10%, instead set nLockTime to a height drawn uniformly from the window [max(floor, height - 99), height]. This hides the tip height observed at creation time when signing is delayed.

  30. in bip-anti-fee-sniping-with-locktime.md:453 in 456b1a3720
     448 | +
     449 | +A stale or IBD tip makes a fresh tip-relative locktime encode an unreliable local height and stand out. *The Specification reuses* `floor` *when* `floor <= height`*,* and otherwise sets `nLockTime` to 0.
     450 | +
     451 | +### Enforcing locktime
     452 | +
     453 | +Tip-relative `nLockTime` with all-final `nSequence` is a known fingerprint ([locktime-stairs observation](https://b10c.me/observations/01-locktime-stairs/), [unenforced locktime charts](https://mainnet.observer/charts/transactions-not-enforced-locktime/)). Setting every wallet-controlled input to `2**32 - 2` is the conventional defense.
    


    danielabrozzoni commented at 12:02 PM on October 29, 2026:

    This is very dense and hard to understand.

    You are mixing two problems together:

    • setting the locktime to the current tip is a fingerprint
    • if you don't set the nsequence to a non-final value, the locktime is ignored

    The first problem should go in the "Anonymity set" section, along with the locktime stairs observation article. The way this sentence is structured, you're making it sound like setting nSequence on the wallet inputs fixes it, which it doesn't.

    The second problem should be stated more clearly. You should repeat what you already said in the Background section - if all the sequences are final, the locktime is ignored - so this section still makes sense to someone who missed it. Also, you're saying that the locktime being ignored is a fingerprint. It is, but more importantly it makes the anti-fee-sniping ineffective, which seems like the thing to lead with.

    Also:

    Setting every wallet-controlled input to 2**32 - 2 is the conventional defense.

    Should be "every wallet-controlled input's nSequence"

  31. in bip-anti-fee-sniping-with-locktime.md:79 in 456b1a3720
      74 | +1. Set `nLockTime` to `height`.
      75 | +2. With probability 10%, if `nLockTime > floor`, draw an integer `back` uniformly from `0 .. bound - 1` where `bound = min(100, height - floor + 1)`, and set `nLockTime = height - back`.
      76 | +
      77 | +`randrange(n)` returns an integer in `0 .. n - 1`. The inclusive window `[floor, height]` has length `height - floor + 1`. Cap that length at 100. The same cap applies for every `floor`, including the default of 0.
      78 | +
      79 | +The resulting `nLockTime` is never older than `floor` ([bitcoin#26526](https://github.com/bitcoin/bitcoin/issues/26526)).
    


    danielabrozzoni commented at 12:12 PM on October 29, 2026:

    You often link to Bitcoin Core PRs and issues, but I think you should give some context around the links. It's not helpful to end a sentence with a bare (bitcoin#XXXXX) - I read the sentence, open the link, and then have to work out why it was put there in the first place.

    A couple of extra words would fix it: "this was introduced in XXX", "for additional details see YYY", etc. If that makes the sentences too long, it can go in footnotes instead.

  32. danielabrozzoni commented at 12:28 PM on October 29, 2026: member

    Thanks for working on this, I think it's useful to specify the anti-fee-sniping behavior. For example, when I found myself implementing anti-fee-sniping in bdk, I didn't know about the 10% privacy branch and completely left it out :) Having a spec to guide me would have been helpful there.

    That being said, I found the BIP hard to read in its current state. The specification is written in a sort of semi-pseudocode, which I found confusing - I'd rather read plain English explaining what to do and why the choices were made. There are also a lot of links dropped into sentences without any context, which made the BIP harder to follow.

  33. nervana21 commented at 12:46 AM on October 30, 2026: contributor

    Hi @danielabrozzoni! Thanks for taking the time to review :) I will address your feedback and post an updated draft asap


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bips. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-11-03 19:10 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me