Building decentralised web experiences with Concords Ledger.

Sam Ternent
4 min readApr 14, 2021

This post assumes a small knowledge of the data structure behind a blockchain. If you’re not familiar with how a Blockchain works, Adil Haris does a wonderful job of explaining it simply, but in depth over on hackernoon.

Blockchain — A Short and Simple Explanation with Pictures

Concords Ledger is a runtime Javascript module that exports a simple interface for creating bespoke runtime blockchains in the browser, leveraging the Web Crypto API for hashing data to form the secure block links and providing identity to transactions on the chain.

It doesn’t require any server API, remote DB, public blockchain or crypto assets to run. Everything that happens, happens directly in the users browser.

Installation

https://unpkg.com/@concords/ledger@1.5.2/dist/concords-ledger.min.js

$ npm install @concords/ledger --save
$ yarn add @concords/ledger

Usage

A Ledger is a living representation of transactional state, secured by cryptographic hashes and signatures.

Let’s look at a headless Todo App implementation using @concords/ledger.

For the purpose of this walkthrough we won’t touch too much on Identity*, I’ve talked to the implementation details in a previous post to build Offline Authentication in the Browser.

However, a ledger must be loaded with a valid key-pair to add new records, which can be done as we instantiate a ledger using the onAuth or onReady plugin hooks.

*Identity is not currently published to NPM, but the source is available for download on Github.

import ledger from '@concords/ledger';const { create } ledger({
...user,
plugins: {
onReady() {
// set ready flag
}
},
});
create();

Or via promises using the methods returned by the Ledger API.

import ledger from '@concords/ledger';const { create, auth, add } ledger();async function runTodoApp() {
await auth(user);
await create();
add({
id: 0,
title: '',
completed: false,
created_at: Date.now()
});
}
runTodoApp();

Plugins

As Concords data is unidirectional and actions are asynchronous, the ledger uses as plugin system to communicate changes back to the application.

import ledger from '@concords/ledger';const todos = [];const { create, auth, add } ledger({
plugins: [{
onAdd(record) {
const index = todos.find((item) => item.id === record.id);
if (index) {
todos[index] = { ...todos[index], ...record };
} else {
todos.push(record);
}
},
onUpdate({ ledger }) {
// Update UI state
},
}]
});
async function runTodoApp() {
await auth(user);
await create();
add({
id: 0,
title: 'My first Todo!',
completed: false,
created_at: Date.now()
});
}
runTodoApp();

The available plugin hooks can be found in the source and TypeDocs.

Committing Transactions

When records are first entered onto the ledger, they are softly added to a pending records list. Each transaction is signed against it’s data using the ECDSA key-pair, making the record tamper-proof.

To also make the ledger tamper-proof, we must commit records to the ledger by creating a record block. A chunk of individually signed records that are then put through a Proof of Work algorithm (again, still in your browser) to generate a hash value of the transactional data, timestamp, identity and last hash value of the ledger.

const { create, auth, add } ledger({
plugins: [{
onCommit({ ledger }) {
// Save raw ledger file as JSON.
},
}]
});
async function runTodoApp() {
await auth(user);
await create();
await add({
id: 0,
title: 'My first Todo!',
completed: false,
created_at: Date.now()
});
commit();
}
runTodoApp();

In our commit hook we can then take a full copy of our cryptographically secured ledger as a JSON document and store it anywhere.

Sharing and Verifying Documents

A Ledger is a static JSON object, designed to be shared across the web. It can be dowloaded, uploaded to a private server, uploaded to cloud storage, Git, Beaker Browser, DAT protocol, Solid PODs, Airdrop, Chat client… the list goes on.

We can then load it back into the web app through the Ledger API in the same way we created it and continue work through adding new records.

import ledger from '@concords/ledger';const { load } ledger({
...user,
plugins: {
onReady() {
// set ready flag
}
},
});
load(RawLedgerJSON);

Tamper-proof

Due to transaction signing and chained hashed in the ledger, any attempts to modify committed records on a ledger should be flagged as invalid. A blocks hash is calculated through the data of all transactions, and each transaction is signed with the value of its data. Meaning even the slightest modification will flag corruption in the document.

Full document invalidation through tampered transaction
Identify the corrupt transaction through cryptographic signing

If you want to check it out in person, the full copy of this document is stored in a personal Github repository and can be accessed through a prototype web app running a Ledger and can be accessed HERE.

Conclusion

By eliminating servers and cloud DBs in favour of proven cryptographic patterns, we’re able to offer a full web app experience completely in the browser. Powerful enough for creating rich data-driven applications, in a decentralised fashion, with data that can be distributed and owned by the user and not vulnerable to attacks or leaks on shared servers.

I’ve built three demo applications using the technology, which is now open source and can be found at Github 👉 Concords/Ledger.

https://open.concords.app/
https://demo.concords.app/
https://alpha.concords.app/

--

--