BIP proposal: Anti-fee-sniping protection with nSequence in taproot transactions to improve privacy for off-chain protocols

BIP proposal: Anti-fee-sniping protection with nSequence in taproot transactions to improve privacy for off-chain protocols
2021-07-22 14:44 ` Chris Belcher
  
From: Chris Belcher @ 2021-06-10 12:56 UTC (permalink / raw)
  To: bitcoin-dev


See
https://gist.github.com/chris-belcher/903feab321bf41055c91eaec46581e89
for the latest version of this BIP.

<pre>
  BIP: TBD
  Layer: Applications
  Title: Anti-fee-sniping protection with nSequence in taproot
transactions to improve privacy for off-chain protocols
  Author: Chris Belcher <belcher at riseup dot net>
  Status: Draft
  Type: Standards Track
  Created: 2021-06-10
  License: PD
</pre>

== Abstract ==

This document proposes a certain type of wallet behaviour which uses
BIP341 taproot[1]. It provides a greater anonymity set for off-chain
protocols which will make use of point-time-locked contracts (PTLCs)
such as CoinSwap, Lightning and Discrete Log Contracts.

== Motivation ==

With taproot most likely to be added to bitcoin very soon, and wallet
software about to implement taproot wallets, we are in a unique position
to improve the privacy of off-chain protocols if we act soon.

Taproot allows for point-time-locked contracts (PTLC) as a more private
replacement for hash-time-locked contracts (HTLCs). If an off-chain
contract (for example a Lightning channel) is closed using a PTLC
instead of an HTLC, then the blockchain will just see a regular taproot
script instead of a hash value and preimage. However, if a contract is
closed using the timelock path, then the blockchain will either see a
OP_CHECKSEQUENCEVERIFY opcode or a nSequence value in the transaction,
neither of which are very common today, and this would mark the closing
transaction as something special and unusual.

This BIP proposes to improve the privacy and fungibility of off-chain
protocols by having on-chain wallets like Bitcoin Core also set the
nSequence field in their taproot transactions as in BIP68. This would be
in place of their regular nLockTime anti-fee-sniping protection. The end
result is that, if an observer of the blockchain sees a taproot spend
with an nSequence value, then that could be either: a regular spend from
a wallet, or an off-chain settlement transaction spent with a timelock.
The two cases would be indistinguishable, and this could greatly improve
the privacy and fungibility of bitcoin. The community and wallet
developers should act now to implement this so that the anonymity set of
nSequence transactions starts to be built up as soon as taproot itself
becomes adopted by wallets.


== Background ==

=== Fee sniping ===

Fee sniping is a hypothetical outcome of bad incentives to bitcoin
mining in the low-inflation future. For a large miner the value of the
transactions in the best block and the mempool can be exceeded by the
cost of deliberately attempting to mine two blocks to orphan the best
block. However with anti-fee-sniping protection using nLockTime or
nSequence the bad miner will soon run out of transactions that can be
put in the first block, which means they now need to go in the second.
Anti-fee-sniping adds to the incentive to move the blockchain forward.

The nLockTime field is being used this way today. It is implemented in
Bitcoin Core[2] and Electrum[3], and adopted by approximately 20% of all
recent transactions[4].

== Absolute vs relative locktime ==

nLockTime is an absolute lock time, it allows the transaction to only be
mined after a certain block height or unix time. The widespread adoption
of it might have provided a good anonymity set for off-chain protocols.
Unfortunately those protocols also commonly use relative lock times,
because it allows contracts (for example Lightning payment channels or
CoinSwaps) to remain open indefinitely as the countdown clock only
starts ticking when the closing transaction is confirmed.

Absolute locktimes are also still used, so we should keep using
nLockTime, but also often use nSequence.

== Specifications ==

When wallets create transactions spending UTXOs protected by BIP341
taproot, they should set either an nLockTime value or nSequence values
to discourage fee sniping, by allowing the transaction to only be mined
in the next block after the tip, not the current block. This BIP
suggests 50% probability for using nLockTime and 50% for nSequence. If
nSequence is set it should apply only to the first input of the
transaction, if it has multiple inputs.

Wallets should also have a second random branch which sets the nLockTime
or nSequence value even further back, so that transactions that are
delayed after signing for whatever reason (e.g. high-latency mix
networks) have better privacy. Existing behaviour is that with a
probability of 10%, choose a random number between 0 and 99, and
subtract it from the current block height. See the Bitcoin Core and
Electrum source codes linked in the references for an example.

nSequence can only encode up to a max of 65535 for the block distance,
see BIP68[5], so if the UTXOs being spent have more confirmations than
that then the wallet should use nLockTime instead.


== Compatibility ==

This BIP doesnt need any consensus changes. It can be adopted
unilaterally and gradually by wallets. Although for greater privacy it
would be good for software to adopt it as soon as possible. Ideally
during the process of developers implementing their taproot wallets, so
that when taproot starts to be used it will already include the
nSequence code.

All wallet software already keeps track of how many confirmations its
UTXOs have, so the information required to set the nSequence field is
already available.

== Acknowledgements ==

Originally suggested by David Harding[6] and mentioned to me by ZmnSCPxj.

==Copyright==

This document is placed in the public domain.

== References ==
[1] https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
[2] https://github.com/bitcoin/bitcoin/pull/2340
[3]
https://github.com/spesmilo/electrum/blob/7e6d65ec11c0dccfc24478471c5951d3ae586937/electrum/wallet.py#L211-L224
[4]
https://txstats.com/dashboard/db/blocks-statistics?panelId=4&fullscreen&orgId=1
[5] https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
[6]
https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-January/002412.html



 permalink raw reply	[|nested]


  2021-06-29  9:25   ` Chris Belcher
  
From: Ben Carman @ 2021-06-28 10:55 UTC (permalink / raw)
  To: bitcoin-dev

[-- Attachment #1: Type: text/plain, Size: 579 bytes --]

> If nSequence is set it should apply only to the first input of the
transaction, if it has multiple inputs.

This could have complications with DLCs and dual funded lightning. In both protocols the ordering of the inputs is not know until both parties have revealed all of their inputs, and during the reveal the nSequence is given.  If we want DLCs and dual funded lightning to be compatible it would be better to have it define it as “at least one of the inputs of the transaction” instead of “it should apply only to the first input of the transaction”

benthecarman


[-- Attachment #2: Type: text/html, Size: 1786 bytes --]

 permalink raw reply	[|nested]
2021-06-28 10:55 ` Ben Carman

  
From: Chris Belcher @ 2021-06-29  9:25 UTC (permalink / raw)
  To: bitcoin-dev

Good thinking. Your point also applies to CoinJoins (both equal-output
and payjoin), and to any transaction where multiple parties contribute
inputs.

The BIP should say "at least one of the inputs of the transaction" with
a suggestion that on-chain wallets just randomly pick an input.

On 28/06/2021 11:55, Ben Carman via bitcoin-dev wrote:
>> If nSequence is set it should apply only to the first input of the
> transaction, if it has multiple inputs.
> 
> This could have complications with DLCs and dual funded lightning. In both protocols the ordering of the inputs is not know until both parties have revealed all of their inputs, and during the reveal the nSequence is given.  If we want DLCs and dual funded lightning to be compatible it would be better to have it define it as “at least one of the inputs of the transaction” instead of “it should apply only to the first input of the transaction”
> 
> benthecarman
> 
> 
> 
> _______________________________________________
> bitcoin-dev mailing list
> bitcoin-dev@lists•linuxfoundation.org
> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
> 


 permalink raw reply	[|nested]
2021-06-10 12:56 [bitcoin-dev] BIP proposal: Anti-fee-sniping protection with nSequence in taproot transactions to improve privacy for off-chain protocols Chris Belcher

  
From: Chris Belcher @ 2021-07-22 14:44 UTC (permalink / raw)
  To: bitcoin-dev

Hello list,

Someone reviewing my taproot privacy BIP proposal suggested
clarification on the spec, so I've written some python-like pseudocode.
It implements the suggestion of choosing a random input instead of the
first one.

Some wallet teams are already working on implementing taproot for their
on-chain app. I urge wallet developers to include this BIP as well, so
that their user's spends will improve the privacy and fungibility of
off-chain protocols. Also, and admittedly a less urgently,
anti-fee-sniping will improve the incentives for miners in the
low-inflation future of bitcoin.

As before find the latest version of this BIP here:
https://gist.github.com/chris-belcher/903feab321bf41055c91eaec46581e89


def apply_anti_fee_sniping_fields(transaction):
    # bip68 requires v=2
    transaction.version = 2
    # always set nlocktime if any of the transaction inputs have more
    # confirmations than 65535 or are taproot inputs
    # otherwise choose either nlocktime or nsequence with 50% odds
    if any(map(lambda input: input.confirmations() > 65535
	    || input.is_taproot(), transaction.inputs))\
	    || randint(2) == 0:
	transaction.nlocktime = blockchain.height()
	if randint(10) == 0:
	    transaction.nlocktime = max(0, transaction.nlocktime
	    - randint(0, 99))
    else:
	input_index = randint(len(transaction.inputs))
	transaction.inputs[input_index].nsequence = transaction.inputs\
	    [input_index].confirmations()
	if randint(10) == 0:
	    transaction.inputs[input_index].nsequence = max(0,
		transaction.inputs[input_index].nsequence
                - randint(0, 99))


 permalink raw reply	[|nested]