π doubleentry
An immutable, tamper-evident double-entry bookkeeping engine.
A ledger is one entity's books, kept as one append-only RFC 6962 SHA-256 Merkle log of typed
records: genesis, currencies, accounts, policies, periods, entries, clearings and seals. One
pure function, the rule core, decides every write: it returns the records to append or one
Rejection with a stable code. Two faΓ§ades call it with the same methods:
Journal (sync, in
memory) and Ledger<S>
(async, over SQLite, PostgreSQL or your own Store).
Why: a ledger should not leave its invariants to the application, and it should be able to answer an auditor's question β how do I know this record was not changed afterwards? β with a proof instead of a promise.
π Documentation Β· π¦ API reference Β· π Changelog
π Quick start
use *;
let mut books = new;
books.declare_currency?;
books.define_months?;
books.open?;
books.open?;
let rec = books.record?;
let inclusion = books.prove_entry?; // an RFC 6962 inclusion proof
assert_eq!;
// Seal March (and the months before it), then prove one closing balance.
let archived = books.head;
books.seal_through?;
let proof = books.prove_balance?;
let proven = proof.verify?;
assert_eq!;
books.prove_consistency?.verify?;
# Ok::
The idempotency key ("invoice-2026-0001") is the entry's identity: recording the same draft
again writes nothing and returns the original receipt with replayed == true.
Over SQLite (feature sqlite) the same calls are async:
#
#
#
#
SqlStore::postgres(url) is the same for PostgreSQL. More in
Getting started and the
programs in examples/.
π Guarantees
| Guarantee | How |
|---|---|
| Every entry balances per (currency, layer) | The rule core refuses an unbalanced draft inside record, in the store's write transaction; there is no other way to write an entry. |
| Exact money | Amount<P> is an i128 scaled by 10^P. Checked arithmetic returning Result, no floats, explicit Rounding; excess decimals are refused. |
| Deterministic identity | EntryId is SHA-256 over the ledger id and the idempotency key, so a retry replays instead of booking twice. The rule core reads no clock and no randomness. |
| Tamper evidence | Every record is a leaf of one RFC 6962 tree. Inclusion and consistency proofs verify against a TreeHead; records are re-hashed on read (Error::Integrity). |
| Provable balances | A seal commits, per (currency, layer), to a tree of account balances; prove_balance proves one, named, against a head alone. |
| Witnessable | checkpoint signs a C2SP tlog-checkpoint; WitnessSession speaks tlog-witness to stock witnesses; Policy and Verifier check cosignatures. Tested against the Go reference implementations. |
| Full audit | audit() replays every record through the rule core and compares every table the store keeps, row by row. |
| One behaviour | Journal, MemoryStore, SQLite and PostgreSQL return the same Rejection for the same input, checked by a public conformance suite. |
It does not prevent a database superuser from editing tables β it detects it: on read, in
audit(), and, for a history rewritten from scratch, against a head someone else kept (an
auditor's archive or a witness cosignature). There is no FX conversion, no chart of
accounts, no HTTP client, no clock and no migrations. See
Design.
π¦ Features
All off by default.
| Feature | Enables |
|---|---|
serde |
Serialize / Deserialize on public types (transport only; hashing never uses serde) |
sqlite |
SqlStore<Sqlite> (sqlx, bundled SQLite) |
postgres |
SqlStore<Postgres> (sqlx, no TLS stack) |
witness |
Ed25519 signing and verification for transparency |
testing |
The store conformance suite and, with witness, TestWitness |
tracing |
tracing spans; never an amount, memo or account path |
For PostgreSQL over TLS, enable a TLS feature on your own sqlx dependency, e.g.
sqlx = { version = "0.9", features = ["tls-rustls-aws-lc-rs"] }.
π¦ MSRV
Rust 1.94 (required by sqlx 0.9), declared as rust-version and checked in CI. It is never
raised in a patch release, and only to a stable release at least six months old.
π License
Licensed under either of Apache-2.0 or MIT at your option.