DIJKSTRA KEYSTONE

Deterministic decimal arithmetic

Arithmetic that agrees with itself.

Binary floating point cannot represent 0.1, and its results depend on the order operations happen to run in. For systems that move money, that is a defect. Keystone gives the same answer every time, on every platform.

Read the documentationcargo add precision-core
live · your browserieee-754 binary64

Type a number. See what your machine actually stores.

you wrote
0.1
f64 stores
0.1000000000000000055511151231257827021181583404541015625
Decimal stores
0.1exact
round to 2dp
f64 0.10·Decimal 0.10

You wrote 3 characters. Your machine stores 57. The difference is already there before you compute anything, and every operation inherits it.

The defect

Two kinds of wrong, and only one of them is obvious.

The first is representation. A double stores a binary fraction, and one tenth has no finite binary expansion, so 0.1 is already an approximation before you do anything with it. That error is identical on every conforming machine, which is precisely why it survives testing.

The second is divergence, and it is the one that costs money. Because rounding happens at every step, addition stops being associative: regrouping the same operands changes the result, as above. Compilers exploit that freedom with reassociation, fused multiply-add and extended-precision intermediates. On top of that, exp, ln and pow come from a libm that differs across platforms and JavaScript engines. A validator, a laptop and a browser can each compute a liquidation price and disagree.

Keystone stores a 96-bit integer and a decimal scale, so tenths are exact. It computes transcendentals in decimal instead of calling libm. Nothing is left to the platform.

live · computed in your browserieee-754 double vs decimal

(0.1 + 0.2) + 0.3

f640.6000000000000001error
Decimal0.6exact

0.1 + (0.2 + 0.3)

f640.6exact
Decimal0.6exact

Same operands. Same machine. Different result. f64 addition is not associative, so grouping changes the answer. Decimal returns 0.6 either way.

28
significant digits
7
rounding modes
17
kani proof harnesses
0
unsafe blocks
…the purpose of abstracting is not to be vague, but to create a new semantic level in which one can be absolutely precise.Edsger W. Dijkstra · EWD340 · The Humble Programmer · 1972

The stack

Five crates, one arithmetic.

Every crate is #![no_std] and #![forbid(unsafe_code)], dual-licensed MIT and Apache 2.0.

precision-core
128-bit decimal, seven rounding modes, checked arithmetic, transcendentals computed in decimal rather than libm.
17 kani proofs
financial-calc
Interest and time value, Black–Scholes and Greeks, AMM and concentrated liquidity, perpetuals, term structures, root solvers.
no_std
risk-metrics
Health factors, liquidation thresholds and prices, loan-to-value, utilisation.
no_std
keystone-defi
One import over the three libraries above, organised by protocol domain.
sdk
keystone-wasm
The same arithmetic in the browser and Node, with identical results.
65 kb

Evidence

Check it yourself.

The same core compiles to Arbitrum Stylus. These three contracts are live on Arbitrum One.

determinismtest vectors run in CI on Linux, macOS and Windows
installcargo add precision-core · npm i @dijkstra-keystone/keystone-wasm

Contract source is not yet published to the explorer. Build from examples/ to reproduce the deployed bytecode.