Skip to content

TypeScript Verifier

Note

The TypeScript verifier supports CSMT proofs only. MPF proof verification is not yet available in TypeScript.

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.