A production-ready TypeScript implementation of Verkle Tries (Vector Commitment Trees) with KZG polynomial commitments. Supports efficient multi-proof generation and verification, designed for compatibility with Solidity verifiers.
Verkle Tries are an advanced data structure that combines the benefits of Merkle trees with polynomial commitments (KZG). This implementation provides:
- Efficient Multi-Proofs: Prove multiple key-value pairs with a single fixed-size KZG proof
- Path Sharing: Multiple leaves sharing internal nodes are deduplicated in proofs
- Solidity-Compatible: Proof format matches Solidity verifier expectations
- Production-Ready: Comprehensive test coverage, security checks, and JSDoc documentation
npm install verkle-trie-tsOr with yarn:
yarn add verkle-trie-tsimport { VerkleTrie, VerkleTrieVerifier } from 'verkle-trie-ts';
import { encodeAbiParameters, keccak256 } from 'viem';
const FIELD_SIZE = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617');
// Create a trie with segmentBits=8 (2^8 = 256 slots)
const trie = new VerkleTrie(8);
// Insert data values directly (they will be hashed internally)
const data1 = BigInt(42);
trie.insert(data1);
// Verkelize the trie (compute all KZG commitments)
// REQUIRED: Must call verkelize() after all insertions
trie.verkelize(trie.root);
// Generate proof
const rootCommitment = trie.getRootCommitment();
const [D, pi, uniqueCommitments, internalYvals, zvals, rootCommitmentIndex, commitmentIndices] =
trie.getProof([data1]);
// Verify proof
const verifier = new VerkleTrieVerifier(rootCommitment!);
const encodedData = [encodeAbiParameters([{ type: 'uint256' }], [data1])];
const isValid = verifier.verifyMultiProof(
D, pi, uniqueCommitments, internalYvals, zvals, encodedData, rootCommitmentIndex, commitmentIndices
);For structured data, use TrieJSONBuilder which handles ABI encoding and hashing:
import { loadTrieFromJSON, generateProofForEntry } from 'verkle-trie-ts';
// Load trie from JSON file
// Note: loadTrieFromJSON still uses branchingFactor for backward compatibility
const { trie, entryHashes } = loadTrieFromJSON('./data.json', 256);
// Generate proof for an entry
const entry = {
types: [{ name: 'address', type: 'address' }, { name: 'amount', type: 'uint256' }],
values: ['0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 1000000000000000000n]
};
const proof = generateProofForEntry(trie, entry);
// Verify
const verifier = new VerkleTrieVerifier(trie.getRootCommitment()!);
const isValid = verifier.verifyMultiProof(
proof.D, proof.pi, proof.uniqueCommitments, proof.yvals, proof.zvals,
[proof.encodedData], proof.rootCommitmentIndex, proof.commitmentIndices
);
## API Documentation
### VerkleTrie
Main class for managing the Verkle Trie structure.
#### Constructor
```typescript
constructor(segmentBits: number)
segmentBits: Number of bits per segment (exponent for branching factor: 2^segmentBits)segmentBits=8→ 256 slots (2^8)segmentBits=6→ 64 slots (2^6)- Must be between 1 and 16 (branching factors from 2 to 65536)
Inserts a value into the trie. This is the recommended method for inserting data. Creates a leaf node automatically and inserts it into the trie.
Important: After inserting all values, you MUST call verkelize() before generating proofs.
Advanced method for inserting a pre-constructed node. If verkelize is true, computes KZG commitments along the insertion path (for incremental updates).
Note: For most use cases, use insert() instead, which handles node creation automatically.
Computes KZG commitments for a node and all its descendants. Must be called after all insertions before generating proofs.
getProof(data: bigint[]): [Commitment, Commitment, Commitment[], bigint[], bigint[], number, \0x${string}`]`
Generates a cryptographic proof for the given data values.
Returns:
D: G1Point commitment to aggregated polynomialpi: G1Point proofuniqueCommitments: Array of unique commitments (deduplicated to reduce proof size)internalYvals: Array of y-values for internal nodes only (leaf yvals are computed from encodedData in verifier)zvals: Array of all z-values (child indices, includes both leaves and internal nodes)rootCommitmentIndex: Index of root commitment in uniqueCommitments arraycommitmentIndices: Packed uint16 array (as hex string) mapping each zval/yval position to its index in uniqueCommitments
Note: Commitments are deduplicated to reduce proof size. The commitmentIndices array (packed as uint16 values) allows the verifier to reconstruct the full commitment list when computing challenges.
Returns the root commitment if the trie has been verkelized.
Verifies cryptographic proofs for Verkle Trie membership.
constructor(storedRootCommitment: Commitment)verifyMultiProof(D, pi, uniqueCommitments, internalYvals, zvals, encodedData, rootCommitmentIndex, commitmentIndices): boolean
Verifies a Verkle multi-proof with security checks matching Solidity verifier.
Parameters:
D: G1Point commitment to aggregated polynomialpi: G1Point proofuniqueCommitments: Array of unique commitments (deduplicated)internalYvals: Array of y-values for internal nodes only (leaves excluded)zvals: Array of all z-values (child indices, leaves + internal nodes)encodedData: Array of ABI-encoded data entries (as hex strings) for leaf nodesrootCommitmentIndex: Index of root commitment in uniqueCommitments arraycommitmentIndices: Packed uint16 array (as hex string) mapping each zval/yval position to its index in uniqueCommitments
Security Checks:
- Root commitment matches stored root
- Leaf yvals are computed from encodedData and verified against stored hashes
- Yval indices are valid, unique, and data hashes match
- Pairing equation verification
Utilities for working with ABI-encoded entries.
Loads a Verkle trie from a JSON file with ABI-encoded entries.
Note: This function uses branchingFactor for backward compatibility. The branching factor must be a power of 2 (e.g., 256 = 2^8). For new code, prefer creating a VerkleTrie directly with segmentBits (e.g., new VerkleTrie(8) for 256 slots).
Generates a proof for a specific ABI-encoded entry.
See the examples/ directory for complete examples:
examples/main.ts: Basic usage example
Run the comprehensive test suite:
npm testThe test suite covers:
- ✅ Single element trie
- ✅ Multiple elements
- ✅ Empty trie edge cases
- ✅ Security checks (tampered proofs, invalid indices)
- ✅ Path sharing verification
- ✅ Different segmentBits values (branching factors)
- VerkleTrie: Main trie structure with radix tree insertion (core operations only)
- VerkleTrieNode: Individual nodes (internal or leaf)
- VerkleTrieVerifier: Proof verification with security checks
- KZG Module: Polynomial commitment operations (commit, prove, verify)
- TrieJSONBuilder: Utilities for ABI-encoded data
- VerkleTrieUtils: Debugging and analysis utilities (printing, validation, statistics)
The proof follows the Verkle tree multiproof structure:
- Challenge
r = H(C_0,...,C_m-1, y_0,...,y_m-1, z_0,...,z_m-1)where commitments are reconstructed fromuniqueCommitmentsusingcommitmentIndices - Challenge
t = H(r, D[0], D[1]) - Aggregated polynomial
g(X) = Σ r^i * (pi(X) - yi)/(X - zi) - Proof
π = [(h(s) - g(s) - y)/(s - t)]_1
Commitment Deduplication: To reduce proof size, commitments are deduplicated. The commitmentIndices array (packed as uint16 values) maps each position in the proof to the corresponding unique commitment. This significantly reduces calldata costs when multiple paths share common internal nodes.
- Fiat-Shamir Heuristic: Challenges
randtare computed from hashes - Root Commitment Verification: Prevents proofs from different tries
- Data Hash Verification: Ensures encoded data matches stored values
- Index Validation: Prevents duplicate or out-of-bounds indices
Commitment: G1Point (elliptic curve point, represented as[bigint, bigint]at runtime)Proof: G1PointCoefficient:bigintABIEncodedEntry: Interface for ABI-encoded entries
- Segment Bits: The constructor takes
segmentBits(exponent) notbranchingFactor. UsesegmentBits=8for 256 slots (2^8),segmentBits=6for 64 slots (2^6), etc. - Insert Method: Use
insert(value)for simple insertion. The value will be hashed internally to generate the key. For ABI-encoded data (e.g., for Solidity verification), useinsertNode()with a node created from an ABI-encoded hash. - Verkelization: Must call
verkelize(trie.root)after all insertions before generating proofs - Proof Yvals: The
internalYvalsreturned fromgetProof()contains only internal node yvals. Leaf yvals are computed fromencodedDatain the verifier to reduce proof size. - Commitment Deduplication: Commitments are automatically deduplicated in proofs to reduce size. The
commitmentIndicesarray (packed as uint16 values) maps each position to the corresponding unique commitment. - Single Element: Special handling for single-element tries (zero polynomial cases)
This TypeScript implementation is designed to work seamlessly with the Solidity verifier. The proof format matches exactly what the Solidity contract expects:
- Commitment Deduplication: Commitments are deduplicated and
commitmentIndicesare packed as uint16 values to minimize calldata - Leaf Yvals: Leaf yvals are computed from
encodedDatain the verifier (not included in proof) to reduce size - ABI Encoding: Data must be ABI-encoded (using
encodeAbiParametersfromviem) for Solidity compatibility
See the Solidity verifier repository for the on-chain verification contract.
MIT