
Round error issue - produce money for free on itBit bitcoin exchange - waffle_ss
https://hackerone.com/reports/176461
======
STRML
Disclosure: I'm CTO of BitMEX, a Bitcoin derivatives exchange built on kdb+/q.
We don't compete directly with itBit or any of the exchanges I mention below
(we don't do spot trading, they don't do derivatives) but we are in a similar
space.

Audits matter. Your books should always sum zero. Time and time again, we've
seen bugs like this across the space: there was the infamous Bitcoinica
"infinite leverage" bug [1], a rounding error in another's order sizes, and
it's theorized that a similar bug was leveraged in the downfall of MtGox to
create virtually infinite balances and use it to buy Bitcoin up to $1000 in
2013.

Whether it's in your engine or outside of it, whether it's a Bitcoin product
or any other financial product, make sure there is something - somewhere -
that is regularly summing up all accounts and ensuring that every cent or
satoshi is accounted for.

We run such a system at every execution. If the system doesn't sum zero, we
shut down. It's happened three times in two and a half years, each time due to
tiny mathematical errors in our liquidation process, each time for less than
10,000 satoshis (~$0.10). We sleep much better at night knowing it's there.

Audits would catch the error caused by float math. And of course, don't do
float math.

1\. I can't find the original reference to it, but it was originally possible
to simply edit the DOM to submit any leverage amount you pleased (such as
100,000x; never trust the client!) and then run your account massively into
profit - or bankruptcy.

~~~
SomeStupidPoint
Why would you ever use floats for finance?

That's like the industry that epitomizes the usecase for fixed point!

~~~
rhinoceraptor
It seems logical if you don't understand how floating points work under the
hood.

------
koolba
Those that do not understand why floating point math has no place in the world
of money are doomed to repeat this mistake. Over and over and over ...

The snippet of what I presume is their API response is interesting:

    
    
         {"currencyCode":"XBT","balance":100000.00000008,"available":100000.00000008,"onDeposit":0}
    

The amount fields are JSON numbers. That's fine for integers up to 2^52[1] but
can have issues with floating point math. They'd be better off representing
everything as satoshis and doing integer math on everything.

I bet their entire system is based on floating point math. Tisk tisk ...

[1]: _Yes probably not an issue unless your Bill Gates and convert your
networth to Zimbabwean dollars..._

~~~
Kenji
As someone who is trying to get deterministic simulations relying on floats -
the idea to use floats for money boggles my mind. Having a simple addition
give the exact same thing on different machines - bit for bit - is already
causing headaches.

~~~
mikeash
That's odd. IEEE 754 requires that basic operations produce the closest
possible result to the exact answer. (Those operations are simple arithmetic,
square roots, remainders, rounding, and various format conversions.) As long
as the rounding mode is the same on all machines, they should all give
identical results for something like addition unless you're using hardware
that doesn't do IEEE 754.

You will, of course, run into huge trouble if you perform more complex
operations. All bets are off once you start doing things like trigonometry or
logarithms, unless you implement it yourself.

~~~
Kenji
Even if you only have 2 additions in a row, the following things cause
problems: Rounding mode, distributivity (or rather, lack thereof),
intermediate precision, compiler optimisation, denormals, real IEEE754
conformity of the platform... The list goes on.

------
mikeash
Sounds like someone used floating point to store currency. Bad idea, unless
it's part of a macroeconomics model or something.

~~~
AtheistOfFail
This is why when doing currency, you store in the lowest denomination possible
and use whole number (if doing dollars, store cents in integers)

~~~
danbruc
That does not solve the problem in general, you still may end up with
fractional quantities if you, for example, calculate a 1% fee. The correct way
to do it is to use a decimal floating point format AND carefully pay attention
to rounding.

Also note the the way you round usually depends on the context, for some
calculations you want to round up, for some down, and for some in yet another
way. So you perform explicit rounding with an explicit rounding mode
everywhere it is required and don't assume that implicit rounding or the
default rounding mode will do the right thing because they won't in general.

EDIT: To expand a bit on the context. If you, for example, charge a percentage
fee you can choose between rounding up getting at least the fee you are asking
for or rounding down not charging the customer more than you are asking for.
Or you can try to be less biased and round either up or down depending on the
fractional part. This may still require tie-breaking, either in a direction of
your choice, or randomized, or by some rule like round half to even. Different
choices will obviously have different biases.

While this may almost seem somewhat pedantic, such things have been exploited,
see for example »Computer Capers: Tales of Electronic Thievery, Embezzlement &
Fraud«. [1][2] Also note that there may be even legal regulations prescribing
how you can and can not round, for example between the Euro and the replaced
national currencies. [3] I also have some faint memories of a case against
someone systematically exploiting rounding errors but I can not come up with a
reference and may even misremember this.

[1]
[http://www.snopes.com/business/bank/salami.asp](http://www.snopes.com/business/bank/salami.asp)

[2] [https://www.amazon.com/Computer-Capers-Electronic-
Thievery-E...](https://www.amazon.com/Computer-Capers-Electronic-Thievery-
Embezzlement/dp/069001743X)

[3]
[http://www.sysmod.com/eurofaq.htm#ROUNDING](http://www.sysmod.com/eurofaq.htm#ROUNDING)

~~~
mhluongo
That is _not_ the correct way. The correct way is to use fxed-decimal math,
and pay careful attention to rounding.

No floats. Not even once.

~~~
danbruc
_No floats. Not even once._

Do you mean no _decimal_ floating point numbers or did you misread my comment
as advocating _binary_ floating point numbers plus careful rounding?

Fixed point decimal numbers are of course also okay, it just changes what you
have to pay attention to. And often fixed point decimal numbers are in some
sense secretly floating point decimal numbers, they just do not automatically
adjust the decimal point [1] but require doing so explicitly.

And this may of course be useful because you will run into an overflow and not
accidentally lose precision when numbers become large. On the other hand you
may lose precision if numbers become small and you only have a fixed
precision.

[1] I am not sure what the important factor is for calling a number type fixed
point or floating point. Is it the ability to move the decimal point around or
is it doing so automatically?

~~~
mhluongo
Ah, sounds like a terminology issue. Sorry if this get pedantic, but I wanted
a solid response for anyone who stumbles on this thread down the road.

Fixed-point is a way of doing math, as well as a data representation. Think of
it as keeping a very large integer, plus another int that represents where the
decimal place should go. Hell, you can even think of it as a string of numbers
and an optional '.', removing the issue of overflow as well.

When you do fixed-point math, you have to be explicit about rounding and
precision changes, or set a default way to handle those situations. When two
numbers are added, do we inherit the precision of the more or less precise
number? What about division, where we run into necessarily repeating decimals?

Floating point math, on the other hand, uses a mantissa and necessarily loses
precision. As a floating point number is an approximation from 0 to infinity
using a fixed number of bits (often 32, 64, 128), there are a ton of amounts
that it can't exactly represent. But that's not an acceptable situation in
finance- we don't need infinity, but we do need countability within a range
with some minimum step size.

An example- let's say we're adding amounts of bitcoin in Python

    
    
      > 1000.1234 + 1000.1234
      20000.2468
      > 10000.1234 * 3
      30000.3702
    

Looks good, but now we have a whale, a high frequency trader, or some
HackerOne bounty hunter with API access

    
    
      > 10000.1234 * 50
      500006.17000000004
    

That calculation, at face value, just created .00000000004 bitcoin out of thin
air. That's less than a satoshi, but if you keep going with a strategy like
this on an automated platform, you can turn it into real money.

Instead, you want to use fixed-point.

    
    
      > from decimal import Decimal
      > Decimal('1000.1234') * Decimal('50')
      Decimal('50006.1700')
    

Note in this case the precision was expanded to the most precise in
multiplication, and no money was created out of thin air.

You can also set precision explicitly
(`Decimal('1000.1234').quantize('0.00000001')`), handle rounding per operation
or across calculations, etc.

------
tyingq
Reminds me of the penny shaving plot in Office Space.

[https://www.youtube.com/watch?v=6pX3OcPAHR8](https://www.youtube.com/watch?v=6pX3OcPAHR8)

------
Animats
All I get from that site, with some ad blocking on Firefox 51, is:

    
    
        11:34:40.979 SecurityError: The operation is insecure. 1 frontend.fe083880.js:15
    	e.exports<.initialize https://hackerone.com/assets/frontend.fe083880.js:15:31757
    	t.Model https://hackerone.com/assets/vendor.b247925b.js:31:4648
    	e.exports<.constructor https://hackerone.com/assets/frontend.fe083880.js:2:20109
    	q/r< https://hackerone.com/assets/vendor.b247925b.js:31:22455
    	<anonymous> https://hackerone.com/assets/frontend.fe083880.js:19:31012
    	t https://hackerone.com/assets/vendor.b247925b.js:1:96
    	n https://hackerone.com/assets/frontend.fe083880.js:26:14283
    	forEach self-hosted
    	<anonymous> https://hackerone.com/assets/frontend.fe083880.js:96:23586
    	t https://hackerone.com/assets/vendor.b247925b.js:1:96
    	window.webpackJsonp https://hackerone.com/assets/vendor.b247925b.js:1:418
    	<anonymous> https://hackerone.com/assets/frontend.fe083880.js:1:1
    

Fail, Hacker One.

------
xyzzy4
Isn't this the same as the premise of the movie Office Space?

~~~
teddyh
To quote from that movie:

“Yeah, they did it in _Superman III_.”

------
kchoudhu
I don't understand how you can run a financial exchange and not have break
analysis running periodically (if not every transaction). How else are you
going to catch mininteractions between complex systems on an on going basis?

Apparently Fintech startups have no need for the wisdom built up over the
course of 500 years of modern banking, because this stuff is Banking 101 for
any engineer at a bank.

------
magma17
It requires 3*10^8 transactions to get the amount of the bounty. Good deal.

~~~
pavel_lishin
It's a good thing nobody has access to machines that can rapidly perform
repetitive tasks.

~~~
magma17
Maybe people at NASA.

~~~
danbruc
Maybe people at NSA. FTFY

