Bitcoin’s Double Spending Flaw Was Hush-Hush During Rollout

For a little while it was possible to spend Bitcoin twice. Think of it like a coin on a string, you put it into the vending machine to get a delicious snack, but if you pull the string quickly enough you could spend it again on some soda too. Except this coin is worth something like eighty-grand.

On September 20, the full details of the latest fix for the Bitcoin Core were published. This information came two days after the fix was actually released. Two vulnerabilities were involved; a Denial of Service vulnerability and a critical inflation vulnerability, both covered in CVE-2018-17144. These were originally reported to several developers working on Bitcoin Core, as well as projects supporting other cryptocurrencies, including ABC and Unlimited.

Let’s take a look at how this worked, and how the network was patched (while being kept quiet) to close up this vulnerability.

What is Bitcoin Core and why should I care?

Bitcoin Core is an open source project which maintains and releases Bitcoin client software called “Bitcoin Core”. It’s a direct descendant of the original Bitcoin software client released by Satoshi Nakamoto after he published the famous Bitcoin whitepaper. The software is both a full-node, validating the blockchain, and a bitcoin wallet. Bitcoin Core has a huge reach as it is a popular full-node and many other node software is forked from this project.

So when a vulnerability arises affecting Bitcoin Core it usually makes a lot of people nervous. That was just what happened. A vulnerability in the code appeared, mostly because of a speed up optimization, which ended up making the backbone nodes susceptible to a denial of service and it was quickly found that this caused the double spending bug.

How This Vulnerability Could Be Used

Part of the threat here is one of trust in the network. An attacker could use this bug to cause older nodes to crash (version 0.14.x nodes) by creating a special block and pushing it to other nodes, thus creating a denial of service situation. By targeting important nodes (or a large number of nodes) in the manner, an attacker could trigger negative publicity for the Bitcoin network and cryptocurrencies in general.

But perhaps more interesting is the ability to conjure up non-existent bitcoin. It was possible to craft a special kind of block that would trick core software from versions 0.15.0 to 0.16.2 to accepting an invalid block. That fake block inflates the supply, appearing like you have twice the amount available while in actuality you’d be spending the same amount twice. Like a magician, half the coins have appeared out of thin air.

Scrambling for a Fix While Keeping Things Quiet

The time line of this bug is pretty demonstrative of the potential seriousness of the flaw. On September 17, around 15h00, the bug was anonymously reported. Three hours later both the DoS flaw and the inflation by double spending had been identified. By 22h00, patches were out. Over the next two days, the message was spread across public forums and mailing lists urging people to upgrade — but without disclosing the complete details, only the DoS condition was mentioned. Then, on September 20th, the flaw was identified fully by an independent researcher. By then, the Bitcoin Core team release the full details:

“In order to encourage rapid upgrades, the decision was made to immediately patch and disclose the less serious Denial of Service vulnerability, concurrently with reaching out to miners, businesses, and other affected systems while delaying publication of the full issue to give times [sic] for systems to upgrade. On September 20th a post in a public forum reported the full impact and although it was quickly retracted the claim was further circulated.”

It seems like the details would have been held back even longer if the vulnerability hadn’t been fully identified by a third-party. Of course we don’t know how much longer, but lately any rumour seems to lead to widespread cryptopanic, so this stance is understandable. This doesn’t mean I agree, it seems highly debatable, but that’s what happened. Nevertheless, the patch was produced and circulated in a matter of hours after the bug was known and this is something really worth noting. Working in this field I can assure you that this happens around 0.001% of the time. Yes… it’s an optimistic figure.

Double Spending and the Patch That Stopped It

Double spending immediately got me curious, who doesn’t want free cryptocurrency created from (even more) thin air? So I headed out to Bitcoin Core website and downloaded both the patch and unpatched versions to diff them and try to make some sense of what went wrong.

Luckily there were not so many code changes and the main part of the fix seemed to be surprisingly simple:

I’m not going to pretend that I went through some painful ~500k lines of C++ code, I just went over the changes and read a bunch of functions. But for those who want a really deep dive, check out the very detailed explanation by Jimmy Song.

The little code I actually read quickly reminded me of my hate/love relation with C++ and my occasional wonder of why it doesn’t just die… I know, I know…

At first glance it seems the bug was introduced in a negligent way, just to gain some speed. But after reading the whole detailed explanation, the conclusion is that a mistake was made in thinking a check was redundant and that it could be optimized out. This conclusion was incorrect.

So… Did Anyone Get Free Coins?

It doesn’t seem realistic that anyone would have been able to get free coins from this exploit. The fact is that this flaw sounds way worse in theory than it is in practice. In order to actually trigger a DoS or double spending attack, there is a cost of create a malicious block with sufficient proof-of-work because that requires the same amount of energy/mining equipment as finding a valid block. We are talking about a minimum of 12.5 BTC (around $82500 at today’s rates) to implement the attack and even then the attack was going to be noticed by different parties involved in the Bitcoin network. You’ve got to spend money to make money, but here an attacker would most likely ended up losing coins. As for cryptopanic created, that’s hard to measure.

Update Early, and Update Often

At this time there are already over 33% of the nodes running patched versions that supposedly equate to over half of the Bitcoin hashrate, since the top mining pools and exchanges were alerted first, with most mining nodes patched within hours on the first day. As far as we know, there were no attempts to exploit this vulnerability in the wild.

As technologies and software mature, there are always going to be bugs. In a piece of critical software, the decisions made after knowing the existence of such bugs are of paramount importance to deter potential attacks and protect the final user. In the end, it seemed that the Bitcoin network had a really close call and the quick action by the developers solved the issue before it could become a problem, even if it was only bad PR.

On the other hand, the way the information was withhold makes me uncomfortable. What do you think about it, was this disclosure handled correctly?

15 thoughts on “Bitcoin’s Double Spending Flaw Was Hush-Hush During Rollout

  1. “It seems like the details would have been held back even longer if the vulnerability hadn’t been fully identified by a third-party. ”

    Reminds me of the handling of Spectre/Meltdown.

    1. Ha, I have to laugh at the deadpan of that sentence. Of fucking course they would have held back longer; they would have done like nearly everybody who has ever been hacked and just ignore it until somebody noticed and called them out. That’s the universal modus operandi unfortunately.

  2. “Working in this field I can assure you that this happens around 0.001% of the time. Yes… it’s an optimistic figure.” — What field is this?

    Many of us who use linux had a recent bug squashed in the same ‘number of hours’…. I guess with the number of patches in linux maybe it is only 0.001%….

    1. I work with Information Security. I’m a researcher and consultant. From my personal experience, most bugs discovered go through a not so fast process from the initial reporting to a proper solution and patch. Some times takes days, usually takes weeks, often months and it wouldn’t be the first time that I saw bugs taking more than a year getting fixed. Anyone working in the infosec industry is familiar with this reality and when a project is responsive (there are many, the Linux Kernel being one, if that is what you are referring to when you say linux) I think that’s always positive and worth mentioning.

      1. The response time for bugs even trivial depends on the target system, as ironically the more secure the code is intended to be the longer it takes to verify and certify the patch. Sometimes the feasibility study might suggest the patch might not cover the costs of the validation cycle so it is left until somebody blows the whistle. The equation of feasibility then changes as one must factor in the brand value and image losses if left untreated.

        Given the ever growing size complexity and non-determinism of the code base I have lost hope for things to improve at least in the short term.

  3. It sounds like this was handled fairly well. With an issue this big, it would be foolish for the Bitcoin Core guys to zero-day themselves. I am curious how long they would have waited to disclose the full issue, but I’m OK with pushing the patch and letting people apply it before full disclosure.

  4. Would need to see what is in CheckTransaction() to see if this was a simple coding error or a data type error.

    Unfortunately, in many languages TRUE and FALSE are data type INT rather than data type BOOL because there is no primitive data type BOOL, it’s just emulated with INT ie Zero (FALSE) or non-zero or ANYTHING else (TRUE). Non-zero being -1 or 2’s complement most commonly but can be anything even NULL in some cases.

    So things like this –

    If(outcome == FALSE)

    are actually and integer comparison by default but with loose typecasting the comparison will be typeOf(outcome)

    If outcome is the string ‘0’ then you have –
    if(‘0’ == 0) then the second 0 would be typecast as string and the result will be TRUE

      1. In many languages there is no option. It can’t be switched off.

        There are exceptions though.

        In PHP you can syntax if(a === b) to force type comparison as well.

        In some languages you can sort of fix the problem with

        if(is_equal(a, b)) …..

        function is_equal(arg1, arg2){
        if (typeOf(arg1) != typeOf(arg2)) return FALSE;
        if (arg1== arg2) return TRUE;
        return FALSE;
        }

        For anyone who is curious about their language –

        echo(true);
        echo(if(1==1);); // note the extra semicolon
        or print(true);
        etc

  5. “The little code I actually read quickly reminded me of my hate/love relation with C++ and my occasional wonder of why it doesn’t just die… I know, I know…”

    Wait, what? Any change you (reporter) could be more specific? C++ needs to die? Why?

    1. Hi there, this is me (reporter of some sort).
      Despite several known criticisms (https://en.wikipedia.org/wiki/C%2B%2B#Criticism), C++ does not need to die in my opinion, the expression was just a rhetorical device. C++ sure has its set of esoteric features (http://madebyevan.com/obscure-cpp-features/) but from Assembly to Lisp, from C to Java, from Fortran to Prolog, from Basic to C++, from Pascal to Ruby, from Ansi C to Python, I like all programming languages and all have their place in the world, even those I forgot to mention. Even Brainfuck…

    1. Not really. One of the ways to prevent double spending is by allowing enough time for enough clients to check a transaction to verify that it was a valid one through consensus. If you slightly alter a timestamp, it may be possible to verify a transaction late enough to satisfy that check. Now it can be added to the blockchain by proof of work as being “spent.” However, before doing so, you render many of those clients useless so that only your block is acceptable to other clients. The “spent” block is dropped as being invalid and the corrupted block is accepted as a new and valid transaction even though it should have been caught by the inconsistencies between most of the clients. This is why the DoS needed to be handled quick fast and in a hurry.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.