Skip to content

TypeScript Verifier

Note

The TypeScript verifier supports CSMT proofs only. MPF proof verification is not yet available in TypeScript. For MPF in the browser today, use the pure-Haskell WASM flow documented in MPF WASM Write Demo.

A TypeScript library for verifying CSMT inclusion proofs client-side. This enables browser and Node.js applications to verify proofs without a Haskell runtime.

Installation

npm install @paolino/csmt-verify

Or with yarn:

yarn add @paolino/csmt-verify

Quick Start

import { parseProof, verifyInclusionProof } from '@paolino/csmt-verify';

// Parse CBOR-encoded proof bytes
const proof = parseProof(proofBytes);

// Verify the proof against a trusted root hash
if (verifyInclusionProof(trustedRootHash, proof)) {
    console.log('Proof verified against trusted root!');
}

API Reference

Types

type Direction = 0 | 1;  // L=0, R=1
type Key = Direction[];
type Hash = Uint8Array;  // 32 bytes (Blake2b-256)

interface Indirect {
    jump: Key;
    value: Hash;
}

interface ProofStep {
    stepConsumed: number;
    stepSibling: Indirect;
}

interface InclusionProof {
    proofKey: Key;
    proofValue: Hash;
    proofSteps: ProofStep[];
    proofRootJump: Key;
}

Functions

parseProof(bytes: Uint8Array): InclusionProof

Parse CBOR-encoded proof bytes into an InclusionProof object.

const proof = parseProof(cborBytes);
console.log('Key length:', proof.proofKey.length);
console.log('Steps:', proof.proofSteps.length);

Throws an error if the bytes are not valid CBOR or don't match the expected format.

verifyInclusionProof(trustedRoot: Hash, proof: InclusionProof): boolean

Verify a proof by recomputing the root hash and comparing it to the supplied trusted root hash.

const isValid = verifyInclusionProof(trustedRoot, proof);

Returns true if the computed root matches the trusted root.

computeRootHash(proof: InclusionProof): Hash

Compute the Merkle root hash from the proof data. Useful when you need the computed hash for comparison with multiple trusted roots.

const computed = computeRootHash(proof);

verifyProofBytes(trustedRoot: Hash, bytes: Uint8Array): boolean

Convenience function that parses and verifies against a trusted root in one call.

const isValid = verifyProofBytes(trustedRoot, cborBytes);

Advanced Functions

For custom implementations or debugging:

import {
    blake2b256,      // Compute Blake2b-256 hash
    rootHash,        // Hash an Indirect value
    combineHash,     // Combine two Indirect values
    serializeKey,    // Serialize Key to bytes
    serializeIndirect // Serialize Indirect to bytes
} from '@paolino/csmt-verify';

Usage Examples

Verify Against Trusted Root

import { parseProof, verifyInclusionProof } from '@paolino/csmt-verify';

async function verifyMembership(
    proofBytes: Uint8Array,
    trustedRoot: Uint8Array
): Promise<boolean> {
    const proof = parseProof(proofBytes);
    return verifyInclusionProof(trustedRoot, proof);
}

Browser Usage

The library works in browsers with no additional configuration:

<script type="module">
import { parseProof, verifyInclusionProof } from '@paolino/csmt-verify';

// Fetch proof from your API
const response = await fetch('/api/proof/mykey');
const proofBytes = new Uint8Array(await response.arrayBuffer());

const proof = parseProof(proofBytes);
const isValid = verifyInclusionProof(trustedRoot, proof);

document.getElementById('result').textContent =
    isValid ? 'Valid' : 'Invalid';
</script>

Extracting Proof Data

import { parseProof, L, R } from '@paolino/csmt-verify';

const proof = parseProof(proofBytes);

// Convert key to hex string
const keyHex = proof.proofKey
    .map(d => d === L ? '0' : '1')
    .join('');

// Get value hash as hex
const valueHex = Buffer.from(proof.proofValue).toString('hex');

console.log(`Key: ${keyHex}`);
console.log(`Value hash: ${valueHex}`);
console.log(`Proof steps: ${proof.proofSteps.length}`);

Integration with Haskell Backend

The TypeScript library verifies proofs generated by the Haskell CSMT library.

Workflow

sequenceDiagram
    participant Client as Browser/Node.js
    participant Server as Haskell Backend
    participant Chain as Blockchain

    Client->>Server: Request proof for key
    Server->>Server: generateInclusionProof
    Server->>Client: CBOR proof bytes

    Client->>Client: parseProof(bytes)

    Client->>Chain: Get trusted root
    Chain->>Client: root hash

    Client->>Client: verifyInclusionProof(trusted, proof)

Generating Proofs (Server)

-- Haskell backend
import CSMT.Hashes (generateInclusionProof, fromKVHashes)

handleProofRequest :: ByteString -> Transaction m cf d ops (Maybe ByteString)
handleProofRequest key = do
    result <- generateInclusionProof fromKVHashes kvCol csmtCol key
    pure $ fmap snd result  -- Return just the proof bytes

Verifying Proofs (Client)

// TypeScript client
const response = await fetch(`/proof/${key}`);
const proofBytes = new Uint8Array(await response.arrayBuffer());

const proof = parseProof(proofBytes);
if (verifyInclusionProof(trustedRoot, proof)) {
    // Proof is valid against trusted root
}

Development

The TypeScript package has its own Nix flake for development:

cd verifiers/typescript

# Enter development shell
nix develop

# Install dependencies
npm install

# Run tests
npm test

# Or run tests via Nix
nix run .#test

# Build
npm run build

Dependencies

Both work in Node.js and browsers.