Skip to content
LedgerDock

Blog · How it is built

How do you stop an accounting ledger from changing after it is approved?

With two pieces. A hash chain seals the record of each approved batch, its totals, its list of rows and its evidence, so that record cannot be rewritten without breaking every later hash. A database trigger on the ledger rows refuses deletes, and refuses updates to the date, the accounts and the amounts, once the batch is sealed. Corrections are new entries.

What property are we after?

An accounting ledger has one property that matters more than speed or features: after a period is approved, the numbers in it must not change quietly. Here is how LedgerDock gets that with two small pieces in PostgreSQL.

Why is the unit a commit and not a row?

Nothing is written to the ledger one row at a time. Approved work is grouped into a commit: one bank statement, one import, or one journal entry. A person reviews it and finalizes it. Only then are its rows written, each carrying the id of its commit.

Piece one: what does the hash chain do?

When a commit is finalized, the application computes a SHA-256 hash over the commit: its totals, the ids of the ledger rows it wrote, its documents, its decision trail, the time it was sealed, and the hash of the commit before it. Each commit stores its own hash and the previous one.

Change a sealed commit’s totals, its list of rows, its documents or its decision trail, and its hash no longer matches. Because the next commit’s hash includes it, every later hash is wrong too. A verifier can walk the chain and recompute each one.

The hash does not cover each row’s own date, account and amount. Those are guarded by the second piece.

One practical lesson: hash the exact strings the database gives back. A total stored as numeric(15,2) comes back as 0.00, not 0. Hash the number in one place and the string in another, and the chain fails to verify with nothing wrong in the data.

Piece two: what does the trigger do?

The hash shows that a sealed record was changed. It does not stop a change, and it does not look inside the rows. That is the trigger’s job. Before any update or delete on a ledger row, it looks up the row’s commit:

  • if the commit is finalized, a delete is refused;
  • if the commit is finalized, an update is refused when it changes the commit, the date, an account, the debit or the credit;
  • anything else on the row, such as a memo or a class, can still be edited.

The last line is deliberate. The money identity of a row is frozen. Its labels are not.

How is a mistake corrected, then?

You do not edit a sealed row. You make a new commit that adjusts it, with your name and the date on it. The old row, the new row and the reason all stay in the books. See fixing a mistake in a closed period.

Why both pieces?

The trigger alone can be dropped by someone with enough access. The hash chain alone tells you only after the fact, and only about the sealed record. Together, the trigger blocks the ordinary mistake, and the chain keeps a sealed set of totals for every batch that the rows can be checked against.

For the other half of trusting the books, the numbers going in, see the footing check.

Questions people also ask

Is a hash chain the same as a blockchain?

No. A hash chain is one of the ideas inside a blockchain: each record's hash includes the hash of the record before it. There is no network, no mining and no token. It is a column in an ordinary database.

Can a database administrator still change a sealed ledger row?

Someone with enough access can remove a trigger. That is why there are two pieces. The trigger stops ordinary mistakes. The hash chain seals each batch's totals and row count, so the rows can still be added up and compared with what was sealed, and the sealed record itself cannot be rewritten quietly.

Why can a memo still be edited on a sealed row?

Because a memo is a label, not money. The trigger freezes the date, the accounts and the amounts. Letting labels be corrected avoids breaking the chain for a typing error.

All articles

An accountant's close tool. Month-end, year-end, closed right.