Urkel Tree: Cryptographically Provable Key-Value Store

1.0.3 · active · verified Wed Apr 22

Urkel Tree is an optimized, cryptographically provable key-value store implemented as a base-2 merkelized trie, specifically designed for the Handshake protocol. It significantly outperforms alternatives like Ethereum's base-16 trie by operating as its own database, storing nodes in flat, append-only files for snapshotting and crash consistency. The current stable version is 1.0.3, with releases focusing on stability, minor bug fixes, and internal optimizations rather than rapid feature additions. Key differentiators include its architectural simplicity (only two node types), compact internal node size (76 bytes), and extremely small proof sizes (32 bytes per sibling node), which are crucial for maintaining proof sizes under 1KB even with millions of leaves. It offers fully transactional capabilities and inherently provides history independence and non-destruction properties. A critical usage requirement is that Urkel must be used with uniformly distributed keys, typically achieved through hashing. While compaction is available, it is currently inefficient and manual, with future C implementation planned for optimization.

Common errors

Warnings

Install

Imports

Quickstart

This example demonstrates creating an Urkel Tree, performing a batch of insertions within a transaction, committing the changes to get a new root, and then generating and verifying a cryptographic proof for a specific key-value pair.

const bcrypto = require('bcrypto');
const urkel = require('urkel');
const {BLAKE2b, randomBytes} = bcrypto;
const {Tree, Proof} = urkel;

async function runUrkelExample() {
  // Create a tree using blake2b-256 with a 256-bit key size.
  // Specify a unique prefix path for the database files.
  const tree = new Tree({
    hash: BLAKE2b,
    bits: 256,
    prefix: './urkel-db-example'
  });

  await tree.open();

  let keyToProve;

  // Transactions allow atomic operations on the tree.
  const txn = tree.transaction();

  // Insert 500 random key-value pairs.
  for (let i = 0; i < 500; i++) {
    const k = randomBytes(32); // 32 bytes for 256 bits
    const v = randomBytes(300);
    await txn.insert(k, v);
    if (i === 250) keyToProve = k; // Save one key for proof
  }

  // Commit the transaction and retrieve the new root hash.
  const root = await txn.commit();
  const snapshot = tree.snapshot(root);

  // Generate a cryptographic proof for the saved key.
  const proof = await snapshot.prove(keyToProve);

  // Verify the generated proof against the root, key, hash, and bits.
  const [code, value] = proof.verify(root, keyToProve, BLAKE2b, 256);

  if (code !== 0) {
    console.log('Could not verify proof: %s.', Proof.code(code));
  } else if (value) {
    console.log('Valid proof for %s, value: %s', keyToProve.toString('hex'), value.toString('hex'));
  } else {
    console.log('Absence proof for %s.', keyToProve.toString('hex'));
  }
  
  // Close the tree when done to release file handles.
  await tree.close();
}

runUrkelExample().catch(console.error);

view raw JSON →