v0.1 · MIT · zero deps · WebCrypto · browser + Node

Prove your offline history
was not tampered with.

Local-first apps create data on offline devices, edited by different people, synced later in any order. @blocco/ledger gives each device a signed hash chain, merges them deterministically, and surfaces tampering, forks, and forged signatures as typed conflict flags, never silent failures.

$ npm install @blocco/ledger
1

You cannot prove a record was not altered after it was written.

2

When several devices each hold part of the truth, you cannot merge them into one history everyone can independently check.

CRDTs solve merging but trust their inputs. Signed audit logs solve authenticity but assume one writer. This does both, with one small idea.

Tamper-evident by hash chain

Every device keeps an append-only SHA-256 chain. Change one field and the recomputed hash diverges: the chain no longer verifies.

Signed, self-certifying identity

Each entry is signed with a non-extractable ECDSA P-256 key. The device id is the hash of its own public key, so identity cannot be spoofed.

Deterministic merge

Logs from any number of devices merge into one ordered log, identically regardless of arrival order. Commutative and associative over the set of entries.

Typed conflicts, no silent fixes

divergent_id, broken_chain, invalid_signature. The library gives you facts and where they happened; you decide the policy.

Zero deps, isomorphic

Nothing to audit but this. Runs the same in a browser and in Node ≥ 20, touching only globalThis.crypto.subtle. TypeScript strict, generic over your payload.

A substrate, not a framework

No storage, no network, no UI. You bring those; the library gives you the verifiable core, transport-agnostic and honest about scope.

How it fits together

Each device appends to its own chain and signs each entry. Merge unions and orders the logs deterministically; the crypto tier additionally verifies every chain and signature and reports what failed and where.

flowchart LR
  subgraph DA["Device A (offline)"]
    A0["entry a0"] --> A1["entry a1"]
  end
  subgraph DB["Device B (offline)"]
    B0["entry b0"]
  end
  A1 -- "signed chain" --> M{{"mergeLedgers"}}
  B0 -- "signed chain" --> M
  M --> O["one ordered log"]
  M --> V{{"verifyAndMergeLedgers"}}
  V --> OK["0 conflicts: clean"]
  V --> C["typed conflicts:<br/>divergent_id<br/>broken_chain<br/>invalid_signature"]
Signed per-device chains merge into one ordered log; verification reports typed conflicts.

The 60-second version

  1. Identify Each device generates a keypair with generateDeviceKey; its id is the SHA-256 of its public key.
  2. Append & sign appendEntry chains each record off the previous one; signEntry signs the same bytes the hash covers.
  3. Merge mergeLedgers unions every device's log, dedupes by id, and returns one deterministic order.
  4. Verify verifyAndMergeLedgers re-checks chains and signatures. Zero conflicts means everything checks out.
Animated demo: two devices sign entries, the logs merge with zero conflicts, then one flipped byte makes verification fail with typed conflicts naming the entry.

When NOT to use this

A narrow tool. Reach for something else when your problem is a different shape.

You needUse insteadWhy
Rich collaborative merge (text, trees) without authenticityAutomerge, YjsCRDTs merge concurrent edits field by field, but accept whatever a peer sends. No tamper-evidence.
A tamper-evident log with a single writerAn append-only signed audit logIf only one party ever writes, you do not need cross-device merge. Simpler.
A full peer-to-peer append-only stack (replication, discovery, storage)HypercoreHypercore is a whole P2P system. This library is just the verifiable core, transport-agnostic.
A server-side, publicly auditable transparency logTrillianMerkle-tree transparency logs assume a central, always-on server. This is offline-first and serverless.
Confidentiality (hiding contents)An encryption layerSignatures prove authorship, not secrecy. Payloads are plaintext.

If you want deterministic multi-writer merge and the ability to prove nobody rewrote history, and you are offline-first, this is the fit.

See a byte flip break verification.

Two devices, real WebCrypto, entirely in your browser. Sign, merge, then tamper.

@blocco/ledger Verifiable offline ledger: signed hash chains, deterministic merge, typed conflicts