Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relay first-double-spend transactions #3354

Closed

Conversation

@gavinandresen
Copy link
Contributor

gavinandresen commented Dec 4, 2013

Submitting this in the hopes somebody might pick it up and run with it (see the TODO list at end):

Instead of dropping double-spent transactions, relay them to peers. Why? To make it easier for merchants to quickly detect attempted fraud for low-value, in-person purchases.

A bloom filter is used so that only the first double-spend is relayed, to prevent an attacker from mounting a network denial-of-service attack by flooding it with double-spends of a transaction output they control.

Peter Todd pointed out a possible try-to-DoS-network-bandwidth attack that this enables that works like this:

  1. Attacker starts with an unspent transaction input.
  2. Attacker sends a small-in-size first spend with a small fee.
  3. Attacker double-spends the transaction input with a very large (e.g. 100K) transaction with a large fee.

Since miners will mine the first, small transaction (it might even pay a higher fee-per-kilobyte, but pay lower overall fee), an attacker can waste network bandwidth for a low cost. Whether or not attackers will actually do this isn't clear, they don't directly gain anything by wasting network bandwidth.

The right solution to that possible DoS attack is for nodes to have a bandwidth budget for 'tx' messages, and start dropping messages (perhaps starting with the largest double-spends) if that budget is exceeded.

Other things on the TODO:

  1. Bitcoin-Qt should warn the user if a zero-confirmation transaction is double-spent, and should have a notion of "dead" transactions (another spend made it into a block). NOTE: I found it is easy to create double spends running three local nodes in -regtest mode, and the raw transactions API (create/sign a pair of double-spending transactions, then sendrawtransaction them from two nodes at the same time).

  2. The RPC calls that give transaction information should report (somehow) double-spend attempts and "dead" transactions.

  3. Write up a test plan for all of the above.

Detects two double-spend conditions:
1) Receive a transaction that conflicts with a mempool transaction
2) New block contains a transaction that conflicts with a mempool transaction

... and fires a new signal. Future commits will use this signal handler
to note the double-spend attempt if the transactions are in our wallet,
and will relay first double-spends.
Instead of dropping double-spent transactions, relay them to peers.
Why? To make it easier for merchants to quickly detect attempted
fraud for low-value, in-person purchases.

A bloom filter is used so that only the first double-spend is
relayed, to prevent an attacker from mounting a network
denial-of-service attack by flooding it with double-spends of
a transaction output they control.
@BitcoinPullTester

This comment has been minimized.

Copy link

BitcoinPullTester commented Dec 4, 2013

Automatic sanity-testing: PASSED, see http://jenkins.bluematt.me/pull-tester/9344275de4207864678b1ebae526447eaee9bfc2 for binaries and test log.
This test script verifies pulls every time they are updated. It, however, dies sometimes and fails to test properly. If you are waiting on a test, please check timestamps to verify that the test.log is moving at http://jenkins.bluematt.me/pull-tester/current/
Contact BlueMatt on freenode if something looks broken.

@super3

This comment has been minimized.

Copy link
Contributor

super3 commented Dec 26, 2013

I quite like this. Adds much more user and merchant protection for reasonable zero-confirmation transactions (which I seem to get much grief about when explaining the protocol).

Since double spends should(hopefully) be very few, perhaps there should be two RPC calls. One to check the double spend status of a single tx, and another to detect the double spend status off all current zero-confirmation transactions.

Is there perhaps a way to trigger an external event instead of the merchants POS system constantly querying RPC for double spends?

@laanwj

This comment has been minimized.

Copy link
Member

laanwj commented Dec 27, 2013

@super3 Sure, there could be a -doublespendnotify that notifies when a transaction in the wallet is attempted double-spent.

@gavinandresen

This comment has been minimized.

Copy link
Contributor Author

gavinandresen commented Dec 28, 2013

I was thinking -walletnotify should notify for the second-spend, and then gettransaction would add a doublespend field (array of "other" txids that spend this txns inputs, default empty array).

I suppose a separate -doublespendnotify might be handy, but it seems like the "I got a new transaction" -walletnotify code would always be the best place to detect doublespends.

@luke-jr

This comment has been minimized.

Copy link
Member

luke-jr commented Feb 21, 2014

@gavinandresen Needs rebase

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 6, 2014

The case where local wallet learns of a remote double-spend AFTER seeing the expected transaction seems just as important, and demands immediate notification, no? Because that other transaction, though just having got here, may already be getting confirmed somewhere.

Also, is it necessary to check for a chain of txes in the memory pool, where the double spend is earlier in the chain?

@gmaxwell

This comment has been minimized.

Copy link
Contributor

gmaxwell commented Mar 21, 2014

Should this really be relaying functionally equivalent double spends? E.g. ones that pay the same amounts to the same outputs?

@gavinandresen

This comment has been minimized.

Copy link
Contributor Author

gavinandresen commented Mar 21, 2014

@gmaxwell : No. The point is to better support in-person face-to-face transactions, where the danger is customer buys a coffee with a hacked app that sends a spend to your point-of-sale system and, at the same time, broadcasts a send-to-self double-spend (different outputs) to the network.

This change makes it much easier for the PoS system to alert the cashier that the customer is doing something fishy (if the double-spend is sending to a different output; the store shouldn't care if the double-spend increases fees paid or is a mutated version of the transaction).

Without this change, the PoS system needs to try to be very strongly connected to the network to detect attempted real-time double spends. And I really don't think we want every PoS vendor in the world trying to connect to thousands of nodes...

Of course the customer could Finney attack the store, but if we're talking about a low-value purchases then that is almost certainly a risk the store can profitably take.

@gavinandresen

This comment has been minimized.

Copy link
Contributor Author

gavinandresen commented Mar 21, 2014

Wait: I just reread @gmaxwell's comment, and realize I might have misinterpreted it....

Not relaying functionally equivalent double spends is probably a good idea, given transaction malleability....

@gmaxwell

This comment has been minimized.

Copy link
Contributor

gmaxwell commented Mar 21, 2014

haha. Thanks. I was about to say!

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 21, 2014

Definitely no relay for a respend that could be constructed by a third party. Doesn't the IsStandardTx() check ensure this?

@gmaxwell

This comment has been minimized.

Copy link
Contributor

gmaxwell commented Mar 21, 2014

So what happens when an attacker constructs his doublespends using transaction patterns which are rejected by IsStandard but accepted by miners? There is currently a really significant portion of the hashrate which mines a definition somewhat wider than IsStandard.

@gavinandresen

This comment has been minimized.

Copy link
Contributor Author

gavinandresen commented Mar 21, 2014

@gmaxwell : that is the same as a Finney attack.

RE: hashrate accepting wider IsStandard: just Eligius? Or are there others?

@sipa

This comment has been minimized.

Copy link
Member

sipa commented Mar 21, 2014

Very random and not well-thought out idea: when relaying a double spend, clear one of the scriptSigs (or find another way to 'neuter' it), so that it cannot possibly confirm, but still proves someone signed a double spend.

@petertodd

This comment has been minimized.

Copy link
Contributor

petertodd commented Mar 22, 2014

@sipa Not likely to do any good, as malicious double-spends will just spend a single input. Of course, that leads to the next step of "why don't we just provide the signature and prove two signatures were signed for the same scriptPubKey" which then makes address re-use of any kind ever rather problematic, to say the least. Conveniently SHA256 midstates are no use here either, as the important bits - the COutPoints - are at the very start of a transaction.

Speaking of, if you're ever in a situation where transactions have to be re-issued false positives are a problem, e.g. bumping fees because your transaction won't mine and expired out of the mempool. Or for that matter if you ever participate in a ANYONECANPAY crowd-fund thing that fails. And then of course there's CoinJoin...

@gmaxwell Another good one is how do you handle floating fees? Do you relay the double-spend even though you wouldn't relay the transaction? Because if you don't, then it acts like a IsStandard() rule split and you can do things like relay a particularly low-fee tx first, let it propagate so some of the network, then relay the high-fee version and let it propagate to your target. Of course, then there's the issue that every time we change IsStandard() the double-spend relay stops working reliably. (to the extent it ever worked at all) It interacts badly with out-of-band miner payments too: suppose I have a zero-fee tx and get some miner OOB to mine it. The rest of the network won't see my tx, which lets me broadcast the fee-paying version. Meanwhile even if the miner is "honest" the best they can do is tell their peers about the double-spend, and those peers will just ignore it because it has no fee attached and they wouldn't accept it to their mempool.

@gmaxwell

This comment has been minimized.

Copy link
Contributor

gmaxwell commented Mar 22, 2014

@gavinandresen Indeed, but it doesn't require the attacker have hashpower themselves and may— at times— be easy to exploit. I think it's easy to exploit at the moment. I guess ultimately thats just yet-another argument to hurry up and get rid of most of IsStandard.

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 22, 2014

@sipa Sometimes what this node thinks is a respend is actually the one that will get confirmed. Respender will have tried to introduce the transactions in disparate parts of the network.

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 22, 2014

@petertodd I don't follow. You posit that one transaction doesn't relay, but then assume that both do. Who is "target"? The target of a respend is the merchant who is about to hand over a venti cappuccino. Can I currently pay miners OOB $.25 to allow me to get it for free? If so, then things are worse than I thought.

@luke-jr

This comment has been minimized.

Copy link
Member

luke-jr commented Mar 27, 2014

Does this relay double spends if they are non-standard? Should it?

@sipa

This comment has been minimized.

Copy link
Member

sipa commented Mar 27, 2014

@dgenr8 Yes, the network cannot guarantee any 0-confirmation security. If a miner is willing to mine a conflicting transaction for you, the original can't be confirmed anymore. Note that the "attacking" miner either needs a significant portion of the hashrate (to have a chance to be the one mining the next block), or needs to do a Finney attack (which is difficulty in PoS settings due to timing constraints).

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 27, 2014

@gmaxwell @petertodd @luke-jr How about a weaker !IsMutantTx function to allow one relay, rather than being subject to IsStandardTx

@luke-jr

This comment has been minimized.

Copy link
Member

luke-jr commented Mar 27, 2014

@dgenr8 The problem isn't that simple: If you don't relay non-standard non-mutants, then it's easy to make a double-spend that avoids announcement. But if you do relay non-standard non-mutants, then you effectively destroy the IsStandard rule entirely, since anyone can then get their non-standard transactions relayed by double-spending it with a standard transaction (which might still never confirm).

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 27, 2014

I agree that is worse. So this alert scheme will not be 100% effective until there is a consensus around what gets relayed. How about we have the reference client set the example though.

@luke-jr

This comment has been minimized.

Copy link
Member

luke-jr commented Mar 27, 2014

It will never be 100% effective. Unless my point is resolved, it won't even be 1% effective. Therefore, as it is today, this is IMO only a false sense of security.

@petertodd

This comment has been minimized.

Copy link
Contributor

petertodd commented Mar 28, 2014

@luke-jr IMO the reason to have IsStandard() these days is so that during a soft-fork non-upgraded miners will still mine valid blocks, and as a safety net for bugs. With Eligius mining them anyway, I'm much less worried about bugs; relaying them for double-spend notification doesn't mean you have to add them to your mempool and blocks.

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 31, 2014

@luke-jr It doesn't taste good worrying about you mining that non-standard double-spend. I have an idea that would require you to waste a whole block if you want to do that. Of course, it is not likely to happen, even if it works (obviously an eventual fork is involved).

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 31, 2014

@sipa 0-conf security < 1-conf security < 2-conf security... But does 0-conf security have to be zero? With all the payment protocol stuff happening I would think this question would be more popular.

@luke-jr

This comment has been minimized.

Copy link
Member

luke-jr commented Mar 31, 2014

@petertodd Without IsStandard, even relay nodes will execute the "non-standard" scripts. You can't skip that with double-spend-relay, or it's insecure and trivially DoS'd.

@dgenr8 Sounds like a 51% attack.

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented Mar 31, 2014

@luke-jr I was imprecise as usual. You would have to forego transactions worth .5 of the fees, on average, but keep the reward. I look forward to you blowing holes in it :)

@luke-jr

This comment has been minimized.

Copy link
Member

luke-jr commented Mar 31, 2014

@dgenr8 Whether a transaction IsStandard or not is not part of the Bitcoin protocol, changes all the time, and will eventually include everything possible. Making any protocol rules based on it (such as foregoing fees) would be a 51% attack or (if it had consensus) a softfork.

@laanwj

This comment has been minimized.

Copy link
Member

laanwj commented Mar 31, 2014

@dgenr8, others can you please take the basic bitcoin discussion somewhere else. Github isn't very suitable for forum-like discussions (everyone gets emails every time something is posted!) and this clutters the issue for people that want to actually comment on the working of the posted code.

@dgenr8

This comment has been minimized.

Copy link
Contributor

dgenr8 commented May 19, 2014

A patch like 2c65830 is needed to fix the bug in bloom filter clear.

@BitcoinPullTester

This comment has been minimized.

Copy link

BitcoinPullTester commented Jun 23, 2014

Automatic sanity-testing: FAILED MERGE, see http://jenkins.bluematt.me/pull-tester/p3354_9344275de4207864678b1ebae526447eaee9bfc2/ for test log.

This pull does not merge cleanly onto current master
This test script verifies pulls every time they are updated. It, however, dies sometimes and fails to test properly. If you are waiting on a test, please check timestamps to verify that the test.log is moving at http://jenkins.bluematt.me/pull-tester/current/
Contact BlueMatt on freenode if something looks broken.

@gavinandresen

This comment has been minimized.

Copy link
Contributor Author

gavinandresen commented Jul 4, 2014

Closing; @dgenr8 picked this up and ran with it, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.