Table of Contents
- Overview
- Prerequisites
- Installation
- Quick Start
- Reset / Starting Over
- CLI: vledger init
- CLI: vledger start
- CLI: vledger sql
- CLI: vledger audit-package
- CLI: vledger audit-proof
- CLI: vledger verify-audit-package
- CLI: vledger start-primary
- CLI: vledger start-replica
- CLI: vledger user
- PyHSM Key Backend
- Model 2 Setup Guide
- SQL Reference
- Compliance Reporting
- Cryptographic Audit Package
- Replication
- Replication Setup
- Client SDKs
- Licensing
- Production Checklist
- Testing & Verification
- Production Status
- Benchmarks
Overview
VectorLedger is a purpose-built, append-only financial ledger written entirely in Rust. Every journal entry is linked by a tamper-evident BLAKE3 hash chain, every page of data is encrypted at rest with AES-256-GCM, and every query result can carry a cryptographic Merkle proof that the returned data has not been modified since it was written.
Historical tampering is cryptographically detectable any modification to a past record invalidates every hash in the chain from that point to the present, provided that verification checkpoints are independently protected (which the HSM architecture is specifically designed to enforce).
Built by VectorGuard Labs.
Why VectorLedger?
Traditional relational databases treat audit trails as an afterthought: triggers that can be disabled, log tables that can be truncated, and backup files that can be silently replaced. For organizations operating under SOC 2, PCI-DSS, financial regulation, or internal zero-trust policies, this is not good enough.
- A row written five years ago cannot be changed without invalidating every hash in the chain from that point to the present.
- Every SELECT response optionally carries a Merkle proof that any client can independently verify.
- The audit log is WORM-append-only each event is hashed into the next, forming a second independent tamper-evident chain.
- The compliance engine generates machine-generated technical evidence supporting SOC 2 Type II and PCI-DSS v4 control assessments — not pre-written documentation. This evidence supports an auditor's work; it does not by itself make an organization compliant. Organizational compliance requires additional controls, policies, and independent auditor assessment beyond what any database engine can provide.
Deployment Architecture
VectorLedger supports two PyHSM deployment models. Model 1 is suitable for development and single-server production. Model 2 isolates key material on a dedicated host with no public IP, which is the recommended configuration for production.
VectorLedger
│
┌──────────┴──────────┐
│ │
Local PyHSM Remote PyHSM
(Model 1) (Model 2)
│ │
Unix socket TLS 1.3 + mTLS
/tmp/pyhsm.sock port 8443
│ │
▼ ▼
PyHSM PyHSM
same host separate host
(private subnet,
no public IP)
See Model 1 Local PyHSM and Model 2 Remote PyHSM for full setup instructions.
Prerequisites
| Requirement | Minimum Version | Notes |
|---|---|---|
| Rust toolchain | 1.80 | Install via rustup.rs |
| macOS, Linux, or Windows | macOS and Linux are fully supported; Windows 10/11 and Windows Server 2019/2022 (x86_64 and ARM64) are supported | |
| Git | Any recent | To clone the repository |
No other runtime dependencies are required. All cryptographic libraries are statically linked via Cargo.
Installation
Option 1 Install via curl (recommended)
The fastest way to install VectorLedger. The installer detects your OS and architecture, downloads the correct pre-built binary, verifies its SHA-256 checksum, and places vledger on your PATH.
curl --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/pavondunbar/VectorLedger/main/install.sh | bash
Install a specific version
curl --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/pavondunbar/VectorLedger/main/install.sh \
| VLEDGER_VERSION=v0.1.0 bash
Install to a custom directory
curl --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/pavondunbar/VectorLedger/main/install.sh \
| VLEDGER_INSTALL_DIR="$HOME/.local/bin" bash
Installer environment variables
| Variable | Default | Description |
|---|---|---|
VLEDGER_VERSION | latest | Release tag to install, e.g. v0.1.0 |
VLEDGER_INSTALL_DIR | /usr/local/bin | Directory to place the vledger binary |
VLEDGER_NO_MODIFY_PATH | 0 | Set to 1 to skip adding the install dir to your shell profile |
After installation, verify it works:
vledger --version
vledger self-test
Windows (PowerShell)
Run this in PowerShell 5.1+ or PowerShell 7+:
irm https://raw.githubusercontent.com/pavondunbar/VectorLedger/main/install.ps1 | iex
The installer detects your architecture (x86_64 or ARM64), downloads the signed release zip, verifies its SHA-256 checksum, installs vledger.exe to %LOCALAPPDATA%\vledger\bin, and adds it to your user PATH.
Windows installer parameters
| Parameter | Default | Description |
|---|---|---|
-Version | latest | Release tag, e.g. v0.1.0. Also reads $env:VLEDGER_VERSION. |
-InstallDir | %LOCALAPPDATA%\vledger\bin | Directory to install vledger.exe |
-NoPathUpdate | off | Skip adding the install dir to your user PATH |
$env:PYHSM_TCP_PORT = 7777 and pass --pyhsm-socket 127.0.0.1:7777 to vledger init. File permissions (0o600) are not enforced on Windows protect your data directory using NTFS ACLs. Graceful shutdown responds to CTRL-C; use Stop-Process or the service manager instead of SIGTERM.
Option 2 Build from source
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustc --version # should be 1.80 or newer
cargo --version
git clone https://github.com/pavondunbar/VectorLedger.git
cd VectorLedger
# Development build
cargo build
# Optimized production binary
cargo build --release
The binary is written to target/debug/vledger (dev) or target/release/vledger (release). Install to your PATH:
cargo install --path crates/vledger
# Or manually:
sudo cp target/release/vledger /usr/local/bin/vledger
./target/release/vledger self-test
./target/release/vledger self-test-phase3
Expected output:
── VectorLedger Phase 2 Self-Test ───────────────
[1/7] Hash chain ... ✓
[2/7] AES-256-GCM encryption ... ✓
[3/7] Merkle proofs ... ✓
[4/7] WAL-backed ledger ... ✓
[5/7] Page encryption ... ✓
[6/7] SQL engine ... ✓
[7/7] Verifiable query proof ... ✓
✓ All Phase 2 self-tests passed.
── VectorLedger Phase 3 Self-Test ───────────────
[1/7] Audit log (WORM + chain) ... ✓
[2/7] Four-eyes workflow ... ✓
[3/7] Compliance report (SOC 2) ... ✓
[4/7] Compliance report (PCI-DSS) ... ✓
[5/7] Backup & restore round-trip ... ✓
[6/7] PgWire message encoding ... ✓
[7/7] SQL optimizer (agg + window) ... ✓
✓ All Phase 3 self-tests passed.
Quick Start
VectorLedger uses PyHSM a VectorGuard Labs product as its default key management backend. PyHSM acts as a local software HSM: VectorLedger's master encryption key is sealed inside PyHSM's encrypted keystore and never touches disk in plaintext.
The steps below walk through the full setup from scratch. If you are not using PyHSM and prefer a different key backend, jump to Key Source Backends.
PyHSM ships as both a Python package and a TypeScript daemon. VectorLedger connects to the TypeScript daemon via a Unix domain socket.
Install Node.js (18+) if you don't have it
brew install node
# Or download from https://nodejs.org
Clone PyHSM and install dependencies
git clone https://github.com/pavondunbar/PyHSM.git ~/PyHSM
cd ~/PyHSM/pyhsm-ts
npm install
Generate a PyHSM master password
openssl rand -base64 32
Start the PyHSM daemon
cd ~/PyHSM
PYHSM_MASTER_PASSWORD="<your-password>" \
PYHSM_KEYSTORE_PATH="$HOME/PyHSM/pyhsm-keystore.enc" \
PYHSM_AUDIT_LOG_PATH="$HOME/PyHSM/pyhsm-audit.jsonl" \
npx tsx pyhsm-ts/process.ts
Expected output: [PyHSM] Listening on /tmp/pyhsm.sock
Confirm the socket is ready in a new terminal:
ls -la /tmp/pyhsm.sock
# srw------- 1 you wheel 0 ... /tmp/pyhsm.sock
vledger start is called, startup will fail with a clear error.cd /path/to/VectorLedger
vledger init --data-dir ./vledger-data --key-source pyhsm
Verify the backend was recorded correctly:
cat vledger-data/keys/key_source.json
# Must contain "backend": "py_hsm"
vledger init without --key-source pyhsm in production. The default without this flag uses an environment variable backend which stores key material in your shell environment.chmod 700 vledger-data/
chmod 700 vledger-data/keys/
chmod 700 vledger-data/catalog/
chmod 700 vledger-data/audit/
chmod 700 vledger-data/wal/
chmod 700 vledger-data/pages/
vledger start --data-dir ./vledger-data
On first start, credentials are written to ./vledger-data/catalog/.admin_initial_credentials (mode 0600) not printed to the terminal.
cat vledger-data/catalog/.admin_initial_credentials
vledger user set-password --username admin --data-dir ./vledger-data
rm vledger-data/catalog/.admin_initial_credentials
vledger verify --data-dir ./vledger-data
vledger sql --data-dir ./vledger-data --username admin
Once in the REPL, use \x to toggle expanded (vertical) display — useful for wide rows like ledger entries. Use \? to see all available meta-commands.
-- Create accounts
INSERT INTO accounts (code, name, account_type, currency, domain)
VALUES ('CASH', 'Cash - USD', 'asset', 'USD', 'main');
INSERT INTO accounts (code, name, account_type, currency, domain)
VALUES ('REVENUE', 'Revenue', 'income', 'USD', 'main');
-- Post a journal entry
INSERT INTO ledger (description, debit_account, credit_account, amount, currency, domain)
VALUES ('Customer payment', 'CASH', 'REVENUE', 100000, 'USD', 'main');
-- Check balance (amounts are in minor units — cents for USD)
SELECT BALANCE('CASH');
-- balance
-- -------
-- 100000
-- Verify the hash chain
SELECT VERIFY_CHAIN();
-- status | entries_verified
-- -------+-----------------
-- OK | 1
-- Query all entries (one row per entry)
SELECT * FROM ledger;
-- Query entries in traditional accounting format (one row per debit/credit line)
SELECT * FROM ledger_lines LIMIT 10;
-- date | sequence | description | account_id | dr_cr | amount | currency | domain
-- -----------+----------+------------------+------------+--------+--------+----------+-------
-- 2026-08-17 | 1 | Customer payment | <uuid> | Debit | 1.00 | USD | main
-- 2026-08-17 | 1 | Customer payment | <uuid> | Credit | 1.00 | USD | main
vledger start --data-dir ./vledger-data --pgwire
# Then connect with any PostgreSQL client:
psql "host=127.0.0.1 port=5432 user=admin sslmode=require"
Reset / Starting Over
If you lose the admin password, or need to wipe the database and start fresh, delete the data directory and re-initialise. Make sure PyHSM is running before you init.
# Stop the server first (Ctrl-C in the terminal where it is running)
rm -rf ./vledger-data
vledger init --data-dir ./vledger-data --key-source pyhsm
vledger start --data-dir ./vledger-data
The first start after a fresh init writes a new admin credential file. Read it, change the password, and delete the file before doing anything else.
vledger init
Initialise a new database.
vledger init [OPTIONS]
--data-dir <PATH> Data directory (default: ./vledger-data)
--force Reinitialise an existing database
--key-source <BACKEND> env | file | vault | aws_kms | pyhsm | remote-pyhsm
(default: pyhsm)
--vault-addr <URL> Vault server address
--vault-mount <MOUNT> Vault KV v2 mount path (default: secret)
--vault-path <PATH> Vault secret path (default: vledger/master_key)
--kms-key-id <ARN> AWS KMS key ARN or alias
--kms-region <REGION> AWS region (default: us-east-1)
# Model 1 local PyHSM (same server)
--pyhsm-socket <PATH> PyHSM Unix socket path (default: /tmp/pyhsm.sock)
--pyhsm-caller-id <ID> Caller ID written to PyHSM audit log (default: vledger)
# Model 2 remote PyHSM (same-region, separate server)
--pyhsm-endpoint <URL> Remote PyHSM HTTPS endpoint; selects remote-pyhsm
automatically when set
Example: https://pyhsm.internal.example.com:8443
Env: PYHSM_ENDPOINT
--pyhsm-ca-cert <PATH> CA certificate PEM to verify the PyHSM server
Env: PYHSM_CA_CERT
--pyhsm-client-cert <PATH> mTLS client certificate PEM (VectorLedger's identity)
Env: PYHSM_CLIENT_CERT
--pyhsm-client-key <PATH> mTLS client private key PEM
Env: PYHSM_CLIENT_KEY
--pyhsm-timeout-ms <MS> Per-request timeout in ms (default: 5000)
Env: PYHSM_TIMEOUT_MS
--pyhsm-max-retries <N> Max retries on transient errors (default: 3)
vledger start
Start the server. Responds to SIGTERM and CTRL-C with a graceful drain — in-flight connections are allowed to complete before the process exits.
vledger start [OPTIONS]
--data-dir <PATH> Data directory (default: ./vledger-data)
--bind <ADDR> Bind address (default: 127.0.0.1:5433)
--pgwire Also start the PostgreSQL wire-protocol listener on port 5432
--with-proofs Attach Merkle proofs to every SELECT response
--max-connections <N> Override the connection semaphore limit
--wal-sync-mode <MODE> group_commit | per_record | no_sync (default: group_commit)
--group-commit-delay-ms Flush interval in ms for group_commit mode (default: 2)
--tls-cert-path <PATH> Path to TLS certificate PEM (replaces self-signed)
--tls-key-path <PATH> Path to TLS private key PEM (replaces self-signed)
replication.json exists in the data directory with "role": "primary", vledger start automatically activates the WAL shipper alongside the SQL server — no separate start-primary process is required. Every committed journal entry is shipped to connected replicas automatically.
When started with --with-proofs, every SELECT response in the vledger sql REPL includes a Merkle proof and root hash.
WAL sync modes
# Default group commit (2 ms flush interval)
vledger start
# Per-record fsync (safest, lower TPS)
vledger start --wal-sync-mode per_record
# Tune the flush interval
vledger start --wal-sync-mode group_commit --group-commit-delay-ms 5
vledger sql
Interactive SQL REPL or single-statement execution. When vledger start is running, the CLI automatically detects it and connects over TLS. Use --server to point at a non-default address.
vledger sql [OPTIONS]
--data-dir <PATH> Data directory
--query <SQL> Run a single statement and exit (omit for interactive mode)
--username <USER> Username (or set VLEDGER_CLI_USER)
--password <PASS> Password (or set VLEDGER_CLI_PASSWORD, or enter interactively)
--server <ADDR> Connect to a running server at host:port
REPL meta-commands
| Command | Description |
|---|---|
\x | Toggle expanded (vertical) display. Useful for wide rows. |
\q or exit | Quit the REPL. |
\? or \help | Show available meta-commands. |
The prompt changes to vledger (expanded)> while expanded mode is active. Use \x again to toggle back.
vledger verify
Verify WAL integrity and the ledger hash chain.
vledger verify --data-dir <PATH>
vledger status
Show database version, WAL segment count, and active segment.
vledger status --data-dir <PATH>
vledger backup
Create a point-in-time backup archive with a BLAKE3 manifest. Private key material is excluded only public keys are archived.
vledger backup --data-dir <PATH> [--output <FILE.tar>]
vledger restore
Restore a backup archive. Verifies every file's BLAKE3 hash against the manifest before completing. --force is required to overwrite an existing data directory.
vledger restore --from <FILE.tar> [--target <PATH>] [--force]
vledger rotate-keys
Rotate all HSM key slots and record audit events. Old key versions are archived for decryption of existing data; new versions are used for all new writes.
vledger rotate-keys [OPTIONS]
--data-dir <PATH> Data directory (default: ./vledger-data)
--caller-id <ID> Caller ID written to audit log
# Model 1 local PyHSM
--hsm-socket <PATH> PyHSM Unix socket path (default: /tmp/pyhsm.sock)
# Model 2 remote PyHSM
--pyhsm-endpoint <URL> Remote PyHSM HTTPS endpoint
--pyhsm-ca-cert <PATH> CA certificate PEM
--pyhsm-client-cert <PATH> mTLS client certificate PEM
--pyhsm-client-key <PATH> mTLS client private key PEM
--pyhsm-timeout-ms <MS> Per-request timeout in ms (default: 5000)
--pyhsm-max-retries <N> Max retries on transient errors (default: 3)
vledger audit-export
Export the WORM audit log, optionally filtered by date range.
vledger audit-export --data-dir <PATH> [--format json|csv] [--output <FILE>]
[--from <RFC3339>] [--to <RFC3339>]
Audit log export is tier-gated:
| Tier | Date range allowed |
|---|---|
| Free | Last 30 days |
| Starter | Last 90 days |
| Growth / Enterprise | Unlimited |
vledger audit-package
Generate a portable, self-contained cryptographic audit evidence package. Default (commitment-only) mode computes the Merkle root over all entries in a single O(n) pass, signs it with the database Ed25519 key, and writes a compact JSON commitment. Completes in seconds at any scale.
vledger audit-package [OPTIONS]
--data-dir <PATH> Data directory (default: ./vledger-data)
--output <FILE> Output JSON file (default: ./vledger-audit-package-<ts>.json)
--tenant <NAME> Organisation name to embed in the package
Example: --tenant "Acme Financial"
--description <TEXT> Description of this audit package
Example: --description "Q3 2026 regulatory audit"
--period-start <DATE> Start of the reporting period (YYYY-MM-DD or RFC 3339)
--period-end <DATE> End of the reporting period (YYYY-MM-DD or RFC 3339)
--include-entries Also embed all entries and per-entry Merkle proofs
(only practical for ledgers with < ~10,000 entries)
vledger audit-package \
--data-dir ./vledger-data \
--tenant "Acme Financial" \
--description "Q3 2026 regulatory audit" \
--period-start 2026-07-01 \
--period-end 2026-09-30 \
--output audit-q3-2026.json
The output JSON meta block includes all provided fields:
{
"meta": {
"tenant": "Acme Financial",
"description": "Q3 2026 regulatory audit",
"period_start": "2026-07-01",
"period_end": "2026-09-30",
"entry_count": 1000000,
"merkle_root": "804efb54...",
"root_signature": "...",
"generated_at": "2026-09-30T23:59:59Z"
}
}
vledger audit-proof
Generate a single-entry inclusion proof against a commitment package. Produces a self-contained JSON file the auditor can verify without any database access.
vledger audit-proof [OPTIONS]
--data-dir <PATH> Data directory
--commitment <FILE> Commitment package JSON (from vledger audit-package)
--sequence <N> Sequence number of the entry to prove
--output <FILE> Output proof JSON (default: ./vledger-entry-proof-<N>.json)
vledger audit-proof \
--data-dir ./vledger-data \
--commitment audit.json \
--sequence 406340 \
--output entry-406340-proof.json
vledger verify-audit-package
Verify an audit package or entry proof. Requires no database access, no server, and no key files. Works on all three package types: commitment, entry_proof, and full.
vledger verify-audit-package --file <FILE>
| Package type | What is verified |
|---|---|
commitment | Ed25519 root signature |
entry_proof | Content hash + chain hash + Merkle inclusion proof |
full | All of the above + all entries |
Example output:
── VectorLedger Audit Verifier ─────────────────
Type : entry_proof
Entries : 1
[1/3] Content hash ✓
[2/3] Chain hash ✓
[3/3] Merkle proof ✓
✓ VERIFIED — 1 entries, all checks passed.
Merkle root : 804efb54ea31539a...
Auditors who cannot install software can download a pre-built binary from the GitHub release page (no Rust toolchain required):
curl --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/pavondunbar/VectorLedger/main/install.sh | bash
vledger verify-audit-package --file entry-406340-proof.json
vledger compliance-report
Generate a SOC 2 or PCI-DSS compliance evidence report. Reports are generated by running checks against real filesystem state not pre-written text.
vledger compliance-report --data-dir <PATH> [--standard soc2|pci-dss]
[--format markdown|json] [--output <FILE>]
# Example generate a PCI-DSS report as Markdown:
vledger compliance-report --data-dir ./vledger-data \
--standard pci-dss \
--format markdown \
--output pci-report.md
vledger license
Show the active license tier, features, and expiry. Place the signed license.json file provided by VectorGuard Labs into your data directory to unlock paid features. If no file is present, the engine runs in Free tier.
vledger license --data-dir <PATH>
# Example output (Growth tier):
# ── VectorLedger License ────────────────────────
# Tier : Growth
# Licensee : Acme Corp
# Expires : 2027-08-07
# Status : Active (365 days remaining)
vledger start-primary
Start a WAL replication primary listener. Requires a Growth or Enterprise license.
replication.json exists with "role": "primary", vledger start automatically activates the WAL shipper. Use start-primary only when you want a shipper running without a SQL server.
vledger start-primary [OPTIONS]
--data-dir <PATH> Data directory (default: ./vledger-data)
--bind <ADDR> Bind address for the WAL shipper
(overrides replication.json; default: 127.0.0.1:5434)
Reads <data-dir>/replication.json. If the file does not exist, a default config is written on first run. The primary auto-generates replication_secret.hex (mode 0o600) on first start — copy this file to every replica before starting them.
vledger start-replica
Start a WAL replication replica. Requires a Growth or Enterprise license. The replication_secret.hex file must already be present (copied from the primary) before this command will succeed.
vledger start-replica [OPTIONS]
--data-dir <PATH> Data directory (default: ./vledger-data)
--primary <ADDR> Primary host:port to connect to
(overrides replication.json)
The replica connects to the primary, performs the BLAKE3 HMAC handshake, and streams WAL records continuously. It reconnects automatically on disconnection with exponential backoff.
vledger user
Manage user accounts. All subcommands read the user store directly from the data directory the server does not need to be running.
| Subcommand | Description |
|---|---|
set-password | Change a user's password (revokes all active sessions) |
create | Create a new user account |
list | List all accounts |
set-enabled | Enable or disable an account |
delete | Delete an account |
# Change a password
vledger user set-password --data-dir <PATH> --username admin
# Create a user (roles: admin, operator, auditor, readonly)
vledger user create --data-dir <PATH> --username alice --role operator
# List users
vledger user list --data-dir <PATH>
# Disable an account
vledger user set-enabled --data-dir <PATH> --username alice --enabled false
# Delete an account
vledger user delete --data-dir <PATH> --username alice
Environment Variable
Best for containerised environments where secrets are injected as environment variables (Kubernetes Secrets, AWS ECS task definitions, etc.).
export VectorLedger_MASTER_KEY="$(openssl rand -hex 32)"
vledger init --key-source env
HashiCorp Vault KV v2
VAULT_TOKEN must be set in the environment before starting the server. VectorLedger checks the token's TTL at startup and logs a warning if it expires within 24 hours.
# One-time setup: write the key to Vault
vault kv put secret/vledger/master_key value="$(openssl rand -hex 32)"
# Initialise with Vault backend
export VAULT_TOKEN="<your-vault-token>"
vledger init \
--key-source vault \
--vault-addr http://127.0.0.1:8200 \
--vault-mount secret \
--vault-path vledger/master_key
AWS KMS
On first start, VectorLedger calls GenerateDataKey and caches the encrypted ciphertext blob locally (kms_data_key.enc). On subsequent restarts, it calls Decrypt against the cached blob. The cache file is protected with an HMAC-SHA256 integrity check a tampered blob is detected before any network call.
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
# AWS_SESSION_TOKEN is also supported for temporary credentials
vledger init \
--key-source aws_kms \
--kms-key-id "arn:aws:kms:us-east-1:123456789012:key/mrk-..." \
--kms-region us-east-1
File (development only)
Generates a random key and writes it to vledger-data/keys/master_key.hex with mode 0o600.
vledger init --key-source file
Key Backend Summary
| Backend | --key-source | Key never on disk | External dependency |
|---|---|---|---|
| PyHSM local Model 1 | pyhsm | ✓ | PyHSM daemon on same host |
| PyHSM remote Model 2 | remote-pyhsm | ✓ | PyHSM daemon on private subnet + TLS certs |
| Environment variable | env | ✗ (in env) | None |
| Disk file | file | ✗ | None |
| HashiCorp Vault | vault | ✓ | Vault server + token |
| AWS KMS | aws_kms | ✓ | AWS credentials + KMS key |
PyHSM (Recommended)
PyHSM is a VectorGuard Labs product and the default key backend for VectorLedger. The master encryption key is sealed inside PyHSM's AES-256-GCM-SIV encrypted keystore and never touches disk in plaintext.
Two ways to install PyHSM
Option A TypeScript daemon (required for VectorLedger integration)
This is what VectorLedger connects to. The daemon listens on a Unix domain socket and handles all key operations via IPC.
git clone https://github.com/pavondunbar/PyHSM.git ~/PyHSM
cd ~/PyHSM/pyhsm-ts
npm install
# Start the daemon:
PYHSM_MASTER_PASSWORD="<your-password>" \
PYHSM_KEYSTORE_PATH="$HOME/PyHSM/pyhsm-keystore.enc" \
PYHSM_AUDIT_LOG_PATH="$HOME/PyHSM/pyhsm-audit.jsonl" \
npx tsx ~/PyHSM/pyhsm-ts/process.ts
Option B Python CLI
The Python package installs the vectorguard-pyhsm command a key management CLI for generating, rotating, and inspecting keys. VectorLedger does not connect to it; use it to administer the keystore independently.
pip install vectorguard-pyhsm
# List keys in the keystore
vectorguard-pyhsm --store ~/PyHSM/pyhsm-keystore.enc list
# Rotate the VectorLedger wrapping key
vectorguard-pyhsm --store ~/PyHSM/pyhsm-keystore.enc rotate vledger.master-key
# Verify audit log integrity
vectorguard-pyhsm --store ~/PyHSM/pyhsm-keystore.enc audit --verify
PyHSM environment variables
| Variable | Default | Description |
|---|---|---|
PYHSM_MASTER_PASSWORD | Required. Password that unlocks the PyHSM keystore. | |
PYHSM_KEYSTORE_PATH | ./pyhsm-keystore.enc | Path where the encrypted keystore is stored. Set this to a persistent, backed-up location. |
PYHSM_AUDIT_LOG_PATH | <keystore>.audit.jsonl | Path for PyHSM's own tamper-evident audit log. |
PYHSM_SOCKET_PATH | /tmp/pyhsm.sock | Unix socket path the daemon listens on (Model 1). |
PYHSM_CALLER_SECRET | Optional shared secret for IPC caller authentication. | |
PYHSM_RATE_LIMIT | 100 | Max operations per rate window. |
PYHSM_RATE_WINDOW_MS | 60000 | Rate window in milliseconds. |
How VectorLedger uses PyHSM across restarts
| Event | What happens |
|---|---|
First vledger init --key-source pyhsm | VectorLedger generates a 32-byte master key in-process, asks PyHSM to encrypt it, stores only the encrypted blob at vledger-data/keys/pyhsm_master_key.enc with an HMAC-SHA256 integrity seal. |
Every vledger start | HMAC verified locally first, blob sent to PyHSM for decryption, plaintext key used briefly for key derivation then immediately zeroized from memory. |
| PyHSM daemon not running at startup | vledger start fails immediately with a clear error no data is touched. |
| Cache file tampered | HMAC check fails, startup aborts before any IPC call. |
| Remote PyHSM unreachable (Model 2) | Startup fails closed VectorLedger never falls back to a weaker key source. |
Model 1 Local PyHSM Same Server
PyHSM and VectorLedger run on the same host. Communication uses a Unix domain socket at /tmp/pyhsm.sock. Zero network overhead. Suitable for development, CI, and single-server production deployments.
┌──────────────────────────────┐
│ Server │
│ │
│ VectorLedger │
│ │ │
│ ▼ /tmp/pyhsm.sock │
│ PyHSM daemon │
└──────────────────────────────┘
vledger init --data-dir ./vledger-data --key-source pyhsm
Model 2 Remote PyHSM Separate Server
--key-source remote-pyhsm and all associated flags. However, the default PyHSM TypeScript daemon (pyhsm-ts/process.ts) currently binds only to the local Unix socket (/tmp/pyhsm.sock) and does not expose an HTTPS/TLS listener out of the box. To run Model 2 in production you must supply the TLS environment variables documented in the setup guide (PYHSM_TLS_CERT, PYHSM_TLS_KEY, PYHSM_TLS_CA, PYHSM_LISTEN) and verify that PyHSM is responding on port 8443 before calling vledger init --key-source remote-pyhsm. Do not label a Remote PyHSM deployment production-ready until you have tested the full mTLS handshake end-to-end.
PyHSM runs on a dedicated server in the same region's private subnet. VectorLedger connects over TLS 1.3 with mutual certificate authentication (mTLS) on port 8443. PyHSM's private key material is never accessible from the VectorLedger host. Recommended for production.
Private subnet (e.g. AWS VPC)
┌────────────────────┐ ┌────────────────────┐
│ Server A │ │ Server B │
│ VectorLedger │──TLS 1.3 ──▶│ PyHSM daemon │
│ encrypted data │ + mTLS │ port 8443 │
│ WAL / application │ │ no public IP │
└────────────────────┘ └────────────────────┘
Model 2 environment variables
| Variable | Description |
|---|---|
PYHSM_ENDPOINT | HTTPS endpoint of the remote PyHSM daemon |
PYHSM_CA_CERT | Path to the CA certificate PEM |
PYHSM_CLIENT_CERT | Path to the mTLS client certificate PEM |
PYHSM_CLIENT_KEY | Path to the mTLS client private key PEM |
PYHSM_TIMEOUT_MS | Per-request timeout in milliseconds |
Model 2 Step-by-Step Setup
This section walks through every step required to connect VectorLedger on Server A to PyHSM running on Server B, on the same-region private subnet.
openssl is available on whichever machine you generate certificates on.# 1a. Create the Certificate Authority
openssl genrsa -out ca.key 4096
openssl req -new -x509 -key ca.key -sha256 -days 3650 \
-subj "/CN=VectorLedger-PyHSM-CA" \
-out ca.crt
# 1b. Create the PyHSM server certificate
# Replace 10.0.1.50 with Server B's actual private IP.
openssl genrsa -out pyhsm-server.key 4096
openssl req -new -key pyhsm-server.key \
-subj "/CN=pyhsm.internal" \
-out pyhsm-server.csr
openssl x509 -req -in pyhsm-server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 3650 -sha256 \
-extfile <(printf "subjectAltName=IP:10.0.1.50,DNS:pyhsm.internal") \
-out pyhsm-server.crt
# 1c. Create the VectorLedger mTLS client certificate
openssl genrsa -out vledger-client.key 4096
openssl req -new -key vledger-client.key \
-subj "/CN=vledger-client" \
-out vledger-client.csr
openssl x509 -req -in vledger-client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 90 -sha256 \
-out vledger-client.crt
# On Server B (PyHSM server):
sudo mkdir -p /etc/pyhsm/tls
sudo cp ca.crt pyhsm-server.crt pyhsm-server.key /etc/pyhsm/tls/
sudo chmod 600 /etc/pyhsm/tls/pyhsm-server.key
# On Server A (VectorLedger server):
sudo mkdir -p /etc/vledger/pyhsm
sudo cp ca.crt vledger-client.crt vledger-client.key /etc/vledger/pyhsm/
sudo chmod 600 /etc/vledger/pyhsm/vledger-client.key
| Field | Value |
|---|---|
| Type | Custom TCP |
| Port | 8443 |
| Source | Server A's security group ID (or its private IP /32) |
No other inbound rule is needed on Server B. There must be no public IP on Server B and no 0.0.0.0/0 rule for port 8443.
PYHSM_MASTER_PASSWORD="<your-pyhsm-password>" \
PYHSM_KEYSTORE_PATH="/var/lib/pyhsm/pyhsm-keystore.enc" \
PYHSM_AUDIT_LOG_PATH="/var/log/pyhsm/pyhsm-audit.jsonl" \
PYHSM_TLS_CERT="/etc/pyhsm/tls/pyhsm-server.crt" \
PYHSM_TLS_KEY="/etc/pyhsm/tls/pyhsm-server.key" \
PYHSM_TLS_CA="/etc/pyhsm/tls/ca.crt" \
PYHSM_LISTEN="0.0.0.0:8443" \
npx tsx ~/PyHSM/pyhsm-ts/process.ts
Run on Server A. Replace 10.0.1.50 with Server B's private IP:
vledger init \
--data-dir ./vledger-data \
--key-source remote-pyhsm \
--pyhsm-endpoint https://10.0.1.50:8443 \
--pyhsm-ca-cert /etc/vledger/pyhsm/ca.crt \
--pyhsm-client-cert /etc/vledger/pyhsm/vledger-client.crt \
--pyhsm-client-key /etc/vledger/pyhsm/vledger-client.key
Verify key_source.json shows "backend": "remote_py_hsm". If it shows "env" or "file", the connection to PyHSM failed delete the data directory, confirm PyHSM is reachable, and re-run init.
vledger start --data-dir ./vledger-data
On every start, VectorLedger connects to the remote PyHSM, decrypts the master key blob, uses the key briefly for derivation, and immediately zeroizes it. If PyHSM is unreachable, startup fails closed it never falls back to a weaker key source.
Rotating the Client Certificate
The mTLS client certificate should be rotated before it expires (90-day validity recommended). No data migration is required only the transport credential changes.
# 1. Generate a new client cert signed by the same CA
openssl genrsa -out vledger-client-new.key 4096
openssl req -new -key vledger-client-new.key \
-subj "/CN=vledger-client" -out vledger-client-new.csr
openssl x509 -req -in vledger-client-new.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 90 -sha256 -out vledger-client-new.crt
# 2. Copy to Server A
sudo cp vledger-client-new.crt /etc/vledger/pyhsm/vledger-client.crt
sudo cp vledger-client-new.key /etc/vledger/pyhsm/vledger-client.key
sudo chmod 600 /etc/vledger/pyhsm/vledger-client.key
# 3. Restart VectorLedger to pick up the new certificate
# (no vledger init needed key_source.json paths are unchanged)
Replay-Attack Prevention (Model 2)
Every request sent to a remote PyHSM over TLS includes two additional fields that PyHSM should validate:
| Field | Value | Purpose |
|---|---|---|
requestId | UUID v4 | PyHSM rejects duplicate IDs within its replay window (recommended: 5 min) |
timestamp | RFC 3339 UTC | PyHSM rejects requests more than 2 minutes stale or in the future |
These fields are injected automatically by VectorLedger — no configuration required. They are not present on Model 1 (local socket) requests, where replay is not a meaningful threat.
requestId uniqueness within a 5-minute window and reject stale timestamp values. This is also a requirement in the Production Deployment Checklist.
PyHSM on Windows
PyHSM uses TCP instead of a Unix socket on Windows. Start the daemon with PYHSM_TCP_PORT and pass the TCP address to VectorLedger:
$env:PYHSM_MASTER_PASSWORD = "your-password"
$env:PYHSM_TCP_PORT = 7777
npx tsx ~/PyHSM/pyhsm-ts/process.ts
# Init with Model 1 TCP loopback:
vledger init --key-source pyhsm --pyhsm-socket 127.0.0.1:7777
Compliance Reporting
SOC 2 Type II controls covered
CC6.1, CC6.2, CC6.3, CC6.6, CC6.7, CC7.2, CC8.1, A1.1
PCI-DSS v4 controls covered
Req 2.2, 3.4, 3.5, 4.2, 7.1, 10.2, 10.3, 10.5, 11.5
Generate reports
# SOC 2 report as Markdown
vledger compliance-report --data-dir ./vledger-data \
--standard soc2 --format markdown --output soc2-report.md
# PCI-DSS report as JSON
vledger compliance-report --data-dir ./vledger-data \
--standard pci-dss --format json --output pci-report.json
Cryptographic Audit Package
VectorLedger can generate a portable, self-contained cryptographic audit evidence package that any third party can verify independently — no database access, no server, no credentials required.
Three-tier design:
Tier 1 — Commitment package (default, fast at any scale)
Computes the Merkle root over all entries in a single O(n) pass, signs it with the database Ed25519 key, and writes a compact JSON commitment. Completes in seconds regardless of ledger size.
vledger audit-package \
--data-dir ./vledger-data \
--tenant "Acme Financial" \
--description "Q3 2026 regulatory audit" \
--period-start 2026-07-01 \
--period-end 2026-09-30 \
--output audit-q3-2026.json
Tier 2 — On-demand entry proof (prove one specific entry)
Generates a single Merkle inclusion proof proving that a specific entry belongs to the committed root. The auditor receives a self-contained file they can verify without database access.
vledger audit-proof --data-dir ./vledger-data \
--commitment audit.json \
--sequence 406340 \
--output entry-proof.json
Tier 3 — Full export (small ledgers only)
Embeds all entries and per-entry proofs. Only practical for ledgers with fewer than ~10,000 entries.
vledger audit-package --data-dir ./vledger-data --include-entries
Verification (no database access required)
vledger verify-audit-package --file audit.json
vledger verify-audit-package --file entry-proof.json
Example output:
[1/3] Content hash ✓
[2/3] Chain hash ✓
[3/3] Merkle proof ✓
✓ VERIFIED — 1 entries, all checks passed.
Merkle root : 804efb54ea31539a...
Auditors who cannot install software can use the pre-built binary from the GitHub release (no Rust toolchain required):
curl --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/pavondunbar/VectorLedger/main/install.sh | bash
vledger verify-audit-package --file entry-proof.json
SQL Reference
VectorLedger supports a financial-ledger SQL dialect over both the native TLS connection (port 5433) and the PostgreSQL wire protocol (port 5432). It is PostgreSQL-compatible — not PostgreSQL — so standard PostgreSQL system catalog queries (\l, \dt, pg_catalog.*) are not supported.
Scan Safety — Default Row Cap
Unbounded full-table scans on a large ledger load all matching rows into memory before returning, which can exhaust server RAM. VectorLedger protects against this with an automatic 10,000-entry cap on non-point-lookup queries that have no explicit LIMIT.
| Query pattern | Behaviour |
|---|---|
SELECT * FROM ledger WHERE sequence = N | No cap — returns exactly 1 entry |
SELECT * FROM ledger WHERE external_ref = 'X' | No cap — point lookup |
SELECT * FROM ledger LIMIT 500 | Exactly 500 rows — explicit limit honoured |
SELECT * FROM ledger | Capped at 10,000 rows + pagination notice |
SELECT * FROM ledger WHERE domain = 'x' | Capped at 10,000 rows + pagination notice |
SELECT * FROM ledger WHERE status = 'Posted' | Capped at 10,000 rows + pagination notice |
When the cap fires you will see: 10000 rows (capped at 10000 — use LIMIT n or WHERE sequence = x to paginate)
To page through a large dataset, use sequence-based pagination:
-- Page 1: entries 1 – 10,000
SELECT * FROM ledger LIMIT 10000;
-- Page 2: entries 10,001 – 20,000
SELECT * FROM ledger WHERE sequence > 10000 LIMIT 10000;
-- Page 3: entries 20,001 – 30,000
SELECT * FROM ledger WHERE sequence > 20000 LIMIT 10000;
Tables
ledger — one row per journal entry
Columns: sequence, id, status, description, domain, effective_at, posted_at, external_ref, content_hash, chain_hash, lines
The lines column contains all debit and credit lines as a single semicolon-separated string. Use ledger_lines for the traditional accounting view.
-- Point lookups (no cap — always safe)
SELECT * FROM ledger WHERE sequence = 406340;
SELECT * FROM ledger WHERE external_ref = 'TXN-001';
-- Bounded queries (always safe)
SELECT * FROM ledger LIMIT 100;
SELECT * FROM ledger WHERE domain = 'main' LIMIT 500;
-- Full scans (auto-capped at 10,000 — paginate for more)
SELECT * FROM ledger;
SELECT * FROM ledger WHERE status = 'Posted';
ledger_lines — one row per debit/credit line
Returns each debit and credit as its own row — the standard double-entry accounting view that accountants expect.
Columns: date, sequence, entry_id, description, domain, account_id, dr_cr, amount, currency, status
Amounts are displayed as decimals (e.g. 1.00) rather than minor units. The cap applies to the number of entries before line expansion — a cap of 10,000 entries yields up to 20,000 rows for a standard two-line transaction.
-- Point lookups (no cap — always safe)
SELECT * FROM ledger_lines WHERE sequence = 406340;
-- Bounded queries (always safe)
SELECT * FROM ledger_lines LIMIT 100;
SELECT * FROM ledger_lines WHERE domain = 'main' LIMIT 500;
-- Full scans (auto-capped at 10,000 entries)
SELECT * FROM ledger_lines;
SELECT * FROM ledger_lines WHERE status = 'Posted';
Example output:
date | sequence | entry_id | description | domain | account_id | dr_cr | amount | currency | status
-----------+----------+----------+------------------+--------+------------+--------+--------+----------+-------
2026-08-17 | 1 | <uuid> | Customer payment | main | <uuid> | Debit | 1.00 | USD | Posted
2026-08-17 | 1 | <uuid> | Customer payment | main | <uuid> | Credit | 1.00 | USD | Posted
accounts — chart of accounts
Columns: id, code, name, account_type, currency, status, domain, balance
SELECT * FROM accounts;
SELECT * FROM accounts WHERE domain = 'main';
Write Commands
Post a journal entry
INSERT INTO ledger (description, debit_account, credit_account, amount, currency, domain)
VALUES ('Wire transfer', 'CASH', 'REVENUE', 100000, 'USD', 'main');
amountis in minor units (cents for USD — 100000 = $1,000.00)debit_accountandcredit_accountaccept either accountcodeor UUID- Optional fields:
external_ref,idempotency_key - Entries are append-only —
UPDATEandDELETEare not supported
Create an account
INSERT INTO accounts (code, name, account_type, currency, domain)
VALUES ('CASH', 'Cash - USD', 'asset', 'USD', 'main');
-- Account types: asset, liability, equity, income, expense
Post a correction (reversal)
Corrections are made by posting a new reversal entry, never by modifying the original:
INSERT INTO ledger (description, debit_account, credit_account, amount, currency, domain)
VALUES ('Reversal of TXN-001', 'REVENUE', 'CASH', 100000, 'USD', 'main');
Financial Functions
-- Account balance (returns minor units)
SELECT BALANCE('CASH');
SELECT BALANCE('account-uuid-here');
-- Verify the entire BLAKE3 hash chain
SELECT VERIFY_CHAIN();
-- Verify a range of entries
SELECT VERIFY_CHAIN(1, 100000);
SELECT VERIFY_CHAIN(1000000);
-- Verify a single entry's content and chain hashes
SELECT VERIFY_ENTRY(406340);
Aggregates & Window Functions
SELECT COUNT(sequence) FROM ledger;
SELECT SUM(amount) FROM ledger GROUP BY domain;
SELECT AVG(amount) FROM ledger;
SELECT MIN(sequence), MAX(sequence) FROM ledger;
SELECT ROW_NUMBER() OVER () AS rn FROM ledger;
SELECT RANK() OVER () FROM ledger;
Joins
Supports INNER JOIN and LEFT OUTER JOIN.
SELECT * FROM ledger JOIN accounts ON ledger.domain = accounts.domain;
Compatibility Queries
These queries are supported for ORM and connection pooler health checks:
SELECT 1;
SELECT version();
SELECT current_user();
SELECT current_database();
What Is Not Supported
| Operation | Why |
|---|---|
UPDATE | Append-only — entries are permanent |
DELETE | Append-only — entries are permanent |
CREATE TABLE / DROP TABLE | Schema is fixed |
pg_catalog.* system tables | Not PostgreSQL internally |
\l, \dt, \du psql meta-commands | Rely on pg_catalog |
| Multiple databases or schemas | Single-database engine |
| Stored procedures, triggers, sequences | Not implemented |
Replication
VectorLedger supports synchronous hot-standby WAL replication with three independent security layers: TLS 1.3, optional mTLS, and BLAKE3-keyed HMAC challenge-response inside TLS.
vledger start-primary or vledger start-replica on a Free or Starter license returns a feature-not-entitled error.Additional integrity guarantees:
- Replica verifies the BLAKE3 hash of every received WAL record before applying it — a tampered record is rejected and the connection closed.
- Exponential reconnect backoff (500 ms → 30 s) with faster escalation on auth failures (max 60 s).
- Replication secret generated from
OsRng, stored at mode0o600. - Divergence detection via periodic
DivergenceCheckpointmessages carrying a rolling BLAKE3 WAL chain hash — a mismatch means the replica must be re-seeded.
Replication Setup
# Option A — integrated mode (recommended):
# If replication.json is present with role=primary, vledger start
# automatically activates the WAL shipper alongside the SQL server.
vledger start --data-dir ./vledger-data
# Option B — standalone shipper only (no SQL server):
vledger start-primary --data-dir ./vledger-data
# Override the bind address:
vledger start-primary --data-dir ./vledger-data --bind 0.0.0.0:5434
The primary auto-generates a 32-byte HMAC secret at vledger-data/replication_secret.hex (mode 0o600) on first run. This secret must be copied to every replica.
scp vledger-data/replication_secret.hex replica-host:/path/to/vledger-data/replication_secret.hex
replication.json on the replica{
"role": "replica",
"replication_addr": "<primary-host>:5434",
"ack_timeout_ms": 5000,
"heartbeat_interval_ms": 1000,
"send_buffer_bytes": 67108864,
"tls": {
"enabled": true,
"server_hostname": "vledger-primary",
"ca_cert": "/path/to/replication-ca.pem"
}
}
"ca_cert": null and build with the dev-insecure-replication Cargo feature. Do not do this in production.# Start the replica (connects to primary and streams WAL continuously)
vledger start-replica --data-dir ./vledger-data
# Override the primary address
vledger start-replica --data-dir ./vledger-data --primary <primary-host>:5434
The replica connects to the primary, performs the HMAC handshake, and begins streaming WAL records. It reconnects automatically on disconnection.
replication.json Reference
| Field | Default | Description |
|---|---|---|
role | "primary" | "primary" or "replica" |
replication_addr | "127.0.0.1:5434" | Primary: bind address. Replica: primary's host:port |
ack_timeout_ms | 5000 | How long the primary waits for a replica ACK (ms) |
heartbeat_interval_ms | 1000 | Heartbeat frequency (ms) |
send_buffer_bytes | 67108864 | Max bytes buffered per replica connection (64 MiB) |
secret_path | null | Path to HMAC secret; null = <data_dir>/replication_secret.hex |
tls.enabled | true | Enable TLS on the replication channel |
tls.server_cert | null | Primary TLS cert PEM; null = auto-generate self-signed |
tls.server_key | null | Primary TLS key PEM; null = auto-generate |
tls.server_hostname | "vledger-primary" | SNI hostname used by replica to verify the primary's cert |
tls.ca_cert | null | Replica: CA cert PEM to verify the primary. Required in production |
tls.client_cert | null | Replica client cert PEM for mTLS (optional) |
tls.client_key | null | Replica client key PEM for mTLS (optional) |
Replication Limitations
These limitations do not affect the security properties of the WAL integrity or tamper-evidence guarantees.
Client SDKs
Native client libraries are included for three languages, all located under clients/ in the repository.
| Language | Location |
|---|---|
| Python | clients/python/ |
| TypeScript / Node.js | clients/typescript/ |
| Go | clients/go/ |
Any standard PostgreSQL client library can also connect to the PostgreSQL wire-protocol listener on port 5432 when the server is started with --pgwire (Starter tier and above).
Licensing
VectorLedger uses a tiered license model. The binary enforces feature availability at startup by verifying a signed license.json file in your data directory. If no license file is present, the engine runs in Free tier mode.
Licenses are issued by VectorGuard Labs. Purchase at vectorguardlabs.com/pricing — your license.json is generated automatically and delivered to the email on your account.
Pricing
| Tier | Price | Best for |
|---|---|---|
| Free | $0 / month | Development, evaluation, internal tools |
| Starter | $199 / month | Early-stage teams that need PostgreSQL client compatibility |
| Growth | $999 / month | Production fintechs and SaaS companies under SOC 2 or PCI-DSS |
| Enterprise | Contact Sales | Banks, payment processors, PCI-DSS Level 1, hardware HSM requirements |
Annual billing available on all paid tiers — pay for 10 months, get 12. Contact sales@vectorguardlabs.com for multi-instance or custom pricing.
Installing a license
cp acme-license.json ./vledger-data/license.json
vledger license --data-dir ./vledger-data
Feature availability by tier
| Feature | Free | Starter | Growth | Enterprise |
|---|---|---|---|---|
| Core ledger + SQL REPL | ✓ | ✓ | ✓ | ✓ |
| AES-256-GCM encryption at rest | ✓ | ✓ | ✓ | ✓ |
| BLAKE3 hash chain + Merkle proofs | ✓ | ✓ | ✓ | ✓ |
| Four-eyes dual-control workflow | ✓ | ✓ | ✓ | ✓ |
| WORM audit log + chain verification | ✓ | ✓ | ✓ | ✓ |
| Backup & restore | ✓ | ✓ | ✓ | ✓ |
| Audit log export (date range) | 30 days | 90 days | Unlimited | Unlimited |
PostgreSQL wire protocol (--pgwire) | ✗ | ✓ | ✓ | ✓ |
| WAL replication (hot standby) | ✗ | ✗ | ✓ | ✓ |
| Compliance reports (SOC 2 / PCI-DSS) | ✗ | ✗ | ✓ | ✓ |
| Hardware HSM PKCS#11 integration | ✗ | ✗ | ✗ | ✓ |
| Multi-node deployment | ✗ | ✗ | ✗ | ✓ |
Annual billing available on all paid tiers pay for 10 months, get 12. Contact sales@vectorguardlabs.com for multi-instance or custom pricing.
Production Deployment Checklist
Before putting VectorLedger in front of production traffic:
Core setup
- PyHSM daemon running with a persistent, backed-up keystore (
PYHSM_KEYSTORE_PATHpoints to a durable location) vledger initcompleted with--key-source pyhsm(Model 1) or--key-source remote-pyhsm(Model 2)key_source.jsonshows"backend": "py_hsm"or"backend": "remote_py_hsm"not"env"or"file"keys/MASTER_KEY_PLACEHOLDER.txtdeleted (its presence fails the PCI-DSS compliance check)- Admin credential file read, password changed, and
catalog/.admin_initial_credentialsdeleted - Data directory permissions locked (
chmod 700onvledger-data/and all subdirectories) - Volume encryption enabled on the disk hosting
vledger-data/ - Replace the self-signed TLS certificate with a CA-signed one pass
--tls-cert-pathand--tls-key-pathat startup - Configure replication with a secondary node (
replication.json) or document a backup-based HA strategy - Install a valid
license.jsonfor your paid tier (vledger license --data-dir ./vledger-datato confirm) - Test a full backup and restore drill:
vledger backup→vledger restore→vledger verify - Schedule regular
vledger backupruns (cron or your orchestrator) - Schedule regular
vledger verifyruns (recommended: after each backup) - Ship
audit/audit.logto an append-only off-host destination in real time - Run compliance reports and confirm zero FAIL items:
vledger compliance-report --standard pci-dss
Additional items for Model 2 (remote PyHSM)
- PyHSM server has no public IP accessible only via private subnet
- Security group / firewall allows VectorLedger → PyHSM (port 8443) only no other inbound
- CA certificate, client certificate, and client key stored at paths that survive reboots and are mode
0600 PYHSM_CA_CERT,PYHSM_CLIENT_CERT,PYHSM_CLIENT_KEYenvironment variables set (or paths baked intokey_source.json)- mTLS client certificate has a short validity period (90 days recommended) with a rotation schedule
- PyHSM configured to validate
requestId(reject duplicates within 5-minute window) and reject staletimestampvalues - Verified that
vledger startfails cleanly when PyHSM is unreachable never falls back silently
Testing & Verification
This section documents every testing mechanism available in VectorLedger. Run these commands on your own server to independently verify correctness, durability, and tamper-evidence before deploying to production.
Prerequisites
Start the server before running any tests that require a live connection:
export VectorLedger_MASTER_KEY=$(openssl rand -hex 32)
./target/release/vledger init --key-source env
cat vledger-data/catalog/.admin_initial_credentials # note the generated password
./target/release/vledger start --max-connections 200 --pgwire &
sleep 5
1. Benchmark Tests (TPS)
Measures transactions per second across INSERT, SELECT, and mixed workloads. Restart the server between each workload run to clear connection state.
# INSERT workload - write heavy
cargo run --release --package vledger-bench -- \
--username admin --password <password> \
--clients 50 --transactions 5000 --workload insert
# Restart between runs
pkill vledger && sleep 2 && ./target/release/vledger start --max-connections 200 --pgwire &
sleep 5
# SELECT workload - read heavy
cargo run --release --package vledger-bench -- \
--username admin --password <password> \
--clients 50 --transactions 5000 --workload select
# MIXED workload - 70% INSERT, 30% SELECT
cargo run --release --package vledger-bench -- \
--username admin --password <password> \
--clients 50 --transactions 5000 --workload mixed
c7g.xlarge (Graviton3, 4 vCPU, 8 GB RAM). Avoid t3/t4g burstable instances - CPU credit throttling produces misleading results.2. PostgreSQL Wire Protocol Compatibility
Verifies that VectorLedger accepts connections from standard PostgreSQL clients. Start the server with --pgwire and connect with psql:
psql "host=127.0.0.1 port=5432 user=admin dbname=vledger sslmode=require"
SELECT 1;
SELECT version();
SELECT current_user();
SELECT current_database();
SHOW server_version;
SHOW server_encoding;
SHOW TimeZone;
SELECT COUNT(*) FROM ledger;
SELECT * FROM ledger LIMIT 5;
SELECT * FROM accounts LIMIT 5;
SELECT VERIFY_CHAIN();
BEGIN;
SELECT COUNT(*) FROM ledger;
COMMIT;
3. Concurrent Transaction Test
Runs the benchmark in one terminal while querying live from another to confirm no torn reads, duplicate sequences, or chain failures under concurrent load.
Terminal 1
cargo run --release --package vledger-bench -- \
--username admin --password <password> \
--clients 10 --transactions 1000 --workload mixed &
Terminal 2 (while benchmark runs)
psql "host=127.0.0.1 port=5432 user=admin dbname=vledger sslmode=require"
SELECT COUNT(*) FROM ledger;
SELECT COUNT(DISTINCT sequence) FROM ledger; -- must equal COUNT(*)
SELECT VERIFY_CHAIN(); -- must return OK
4. WAL Corruption Test
Confirms that VectorLedger detects and rejects corrupted WAL data during recovery.
pkill vledger && sleep 2
# Corrupt a byte in the last WAL segment
python3 -c "
import os
wal_dir = 'vledger-data/wal'
segments = sorted(os.listdir(wal_dir))
target = os.path.join(wal_dir, segments[-1])
size = os.path.getsize(target)
mid = size // 2
with open(target, 'r+b') as f:
f.seek(mid)
b = f.read(1)
f.seek(-1, 1)
f.write(bytes([b[0] ^ 0xFF]))
print('WAL corruption written at offset', mid)
"
# Attempt restart - server will reject or truncate at the corrupt record
nohup ./target/release/vledger start --max-connections 200 --pgwire &
sleep 10
cat nohup.out | tail -10
# Restore the WAL (XOR with 0xFF again to flip back)
python3 -c "
import os
wal_dir = 'vledger-data/wal'
segments = sorted(os.listdir(wal_dir))
target = os.path.join(wal_dir, segments[-1])
size = os.path.getsize(target)
mid = size // 2
with open(target, 'r+b') as f:
f.seek(mid)
b = f.read(1)
f.seek(-1, 1)
f.write(bytes([b[0] ^ 0xFF]))
print('WAL restored at offset', mid)
"
# Restart and verify full recovery
pkill vledger && sleep 2
nohup ./target/release/vledger start --max-connections 200 --pgwire &
sleep 120 # wait for WAL replay
psql "host=127.0.0.1 port=5432 user=admin dbname=vledger sslmode=require"
SELECT COUNT(*) FROM ledger;
SELECT VERIFY_CHAIN();
5. Logical Tampering Test
Confirms that the BLAKE3 hash chain detects in-memory data manipulation. TAMPER_ENTRY mutates an entry's description without updating its hash - simulating what a malicious actor would need to do to falsify a record.
-- Establish baseline
SELECT VERIFY_CHAIN();
-- Tamper with a specific entry
SELECT TAMPER_ENTRY(999999, 'THIS RECORD HAS BEEN FALSIFIED');
-- Hash chain must now detect the mutation
SELECT VERIFY_CHAIN();
-- Expected: ERROR: INTEGRITY FAILURE: Hash chain broken at sequence 999999
-- Confirm the specific entry is marked corrupted
SELECT VERIFY_ENTRY(999999);
-- Expected: status = CORRUPTED
6. Crash / Restart Recovery Test
Confirms that committed transactions survive a hard kill mid-write and that uncommitted transactions are rolled back cleanly.
# Start benchmark in background
cargo run --release --package vledger-bench -- \
--username admin --password <password> \
--clients 10 --transactions 10000 --workload insert &
BENCH_PID=$!
# Hard-kill the server while writes are in flight
sleep 10
kill -9 $(pgrep -f "vledger start")
echo "Server killed mid-write"
wait $BENCH_PID
# Restart - WAL replay recovers all committed transactions
nohup ./target/release/vledger start --max-connections 200 --pgwire &
sleep 120
psql "host=127.0.0.1 port=5432 user=admin dbname=vledger sslmode=require"
SELECT COUNT(*) FROM ledger;
SELECT VERIFY_CHAIN();
-- Chain must be OK. Some in-flight transactions may be missing (expected).
-- All committed transactions must be present and valid.
7. Integrity Self-Test Suite
The built-in self-test runs five automated phases against a completely isolated temporary database. Your production data is never touched.
# Quick smoke test - 1K entries, instant
./target/release/vledger verify --self-test --entries 1000 2>/dev/null
# Dev run - 10K entries, ~5 seconds
./target/release/vledger verify --self-test --entries 10000 2>/dev/null
# Standard - 100K entries, ~30-60 seconds (default)
./target/release/vledger verify --self-test 2>/dev/null
# Enterprise stress test - 1M entries, ~10-15 minutes
./target/release/vledger verify --self-test --entries 1000000 2>/dev/null
# Keep the test database for manual inspection
./target/release/vledger verify --self-test --entries 10000 --keep-data 2>/dev/null
What the self-test verifies
| Phase | What it tests |
|---|---|
| A - Baseline | Inserts N deterministic entries with varied amounts, verifies the hash chain immediately |
| B - WAL Integrity | Corrupts a WAL byte, confirms server detects and rejects it, restores the byte |
| C - Crash Recovery | Reopens the database, confirms 100% of entries recovered with chain intact |
| D - Logical Integrity | Mutates an entry in memory without updating its hash, confirms VERIFY_CHAIN() detects it |
| E - Entry Verification | Spot-checks five entries spread across the ledger with VERIFY_ENTRY() |
Inspecting the self-test database manually
# Run with --keep-data to retain the database after the test
./target/release/vledger verify --self-test --entries 10000 --keep-data 2>/dev/null
# Note the directory printed at the end, then start a server against it
cat /path/to/vledger-self-test-<timestamp>/catalog/.admin_initial_credentials
./target/release/vledger start \
--data-dir /path/to/vledger-self-test-<timestamp> \
--max-connections 10 --pgwire &
sleep 5
psql "host=127.0.0.1 port=5432 user=admin dbname=vledger sslmode=require"
SELECT COUNT(*) FROM ledger;
SELECT VERIFY_CHAIN();
SELECT VERIFY_ENTRY(1);
SELECT VERIFY_ENTRY(5000);
SELECT VERIFY_ENTRY(10000);
\x
SELECT * FROM ledger ORDER BY sequence LIMIT 10;
SELECT * FROM ledger WHERE sequence = 5000;
SELECT * FROM ledger_lines WHERE sequence = 5000;
8. Chain Range Verification
Verify a specific range of entries rather than the full chain:
-- Verify entries 1 through 100,000
SELECT VERIFY_CHAIN(1, 100000);
-- Verify from 1,000,000 to end
SELECT VERIFY_CHAIN(1000000);
-- Verify the full chain
SELECT VERIFY_CHAIN();
9. Direct SQL Queries (without psql)
Query the production database directly from the terminal without starting psql:
./target/release/vledger sql --query "SELECT COUNT(*) FROM ledger"
./target/release/vledger sql --query "SELECT VERIFY_CHAIN()"
./target/release/vledger sql --query "SELECT * FROM ledger WHERE sequence = 500000"
./target/release/vledger sql --query "SELECT VERIFY_ENTRY(500000)"
./target/release/vledger sql --query "SELECT VERIFY_CHAIN(1000000, 1100000)"
./target/release/vledger sql --query "SELECT * FROM ledger LIMIT 10"
./target/release/vledger sql --query "SELECT * FROM ledger_lines WHERE sequence = 500000"
Each vledger sql invocation opens a fresh TLS connection, authenticates, runs the query, and closes. There is no persistent session between calls, so credentials are prompted every time by default.
To avoid retyping credentials on every query, set environment variables:
export VLEDGER_CLI_USERNAME=admin
export VLEDGER_CLI_PASSWORD=<your-password>
# All subsequent queries run without prompting
./target/release/vledger sql --query "SELECT COUNT(*) FROM ledger"
./target/release/vledger sql --query "SELECT VERIFY_CHAIN()"
./target/release/vledger sql --query "SELECT VERIFY_ENTRY(500000)"
Alternatively, use psql for an interactive session - authenticate once and run as many queries as you want:
psql "host=127.0.0.1 port=5432 user=admin dbname=vledger sslmode=require"
# Type \q to exit
10. Audit Package (Cryptographic Evidence Export)
Generate a portable audit evidence package and verify it independently — no database access required for verification.
# Generate commitment (fast — works at any scale)
./target/release/vledger audit-package \
--data-dir ./vledger-data \
--output audit-commitment.json
# Prove a specific entry to an auditor
./target/release/vledger audit-proof \
--data-dir ./vledger-data \
--commitment audit-commitment.json \
--sequence 500000 \
--output entry-500000-proof.json
# Verify — no database access required
./target/release/vledger verify-audit-package --file audit-commitment.json
./target/release/vledger verify-audit-package --file entry-500000-proof.json
Running the Test Suite
# Unit and integration tests
cargo test --workspace
# Self-test (exercises the full engine end-to-end)
cargo run --release -- self-test
cargo run --release -- self-test-phase3
# Security audit (checks for known vulnerabilities in dependencies)
cargo install cargo-audit
cargo audit
WAL Sync Modes
VectorLedger ships with three WAL sync modes selectable at startup.
| Mode | Durability | Typical Use |
|---|---|---|
group_commit | Up to one flush window of data loss on hard crash | Default recommended for most deployments |
per_record | Zero data loss every write fsynced immediately | Strict regulatory environments |
no_sync | None | Development and CI only |
# Default group commit (2 ms flush interval)
vledger start
# Per-record fsync (safest, lower TPS)
vledger start --wal-sync-mode per_record
# Tune the flush interval
vledger start --wal-sync-mode group_commit --group-commit-delay-ms 5
Connection Resource Controls
Per-listener resource controls to prevent resource exhaustion:
| Control | Native (5433) | PgWire (5432) |
|---|---|---|
max_connections semaphore | 128 | 64 |
| Per-IP token bucket (burst=10, refill=2/s) | Yes | Yes |
| Auth timeout | 30 s | 30 s |
| Idle timeout | 5 min | 5 min |
| Request frame size limit | 4 MiB | 16 MiB |
| Graceful shutdown drain | Yes | Yes |
Production Status
This matrix reflects the current tested and verified state of each capability. Status labels are conservative a capability is only marked Production after it has been exercised under real or realistic load with the full mTLS / key pipeline in place.
| Capability | Status | Notes |
|---|---|---|
| Core ledger (append, query, BLAKE3 chain) | Production | Exercised by full self-test suite and phase3 tests |
| WAL (group commit + per-record fsync) | Production | Both sync modes stable; no_sync is dev/CI only |
| Cryptographic integrity (AES-256-GCM, Merkle proofs) | Production | All proofs verified by vledger verify and self-test |
| Local PyHSM (Model 1 Unix socket) | Production | Default configuration; recommended for single-server deployments |
| Remote PyHSM (Model 2 TLS 1.3 + mTLS) | Beta | CLI flags fully implemented. process.ts TLS listener requires manual env-var configuration verify end-to-end before calling production-ready. See Model 2 warning. |
PostgreSQL wire protocol (--pgwire) |
Production | Compatible with psql, standard PG drivers, and ORMs |
| AWS KMS | Supported | GenerateDataKey + Decrypt with HMAC-protected local cache |
| HashiCorp Vault KV v2 | Supported | Token-based auth; TTL expiry warning at startup |
| WAL replication (hot standby) | Beta | Failover is manual; no automatic primary election. See Replication limitations. |
| Compliance evidence (SOC 2 / PCI-DSS) | Technical evidence | Machine-generated evidence supporting control assessments not a substitute for full audit. See scope note. |
Performance Benchmarks
Benchmarked on Apple Silicon (MacBook, macOS) running in group_commit WAL mode with a mixed read/write workload (10 concurrent clients, 1,000 transactions each, 70% INSERT / 30% SELECT):
| Metric | Value |
|---|---|
| Throughput | 430 TPS |
| Min latency | 311 µs |
| p50 latency | 23 ms |
| p95 latency | 36 ms |
| p99 latency | 42 ms |
| Errors | 0 / 10,000 |
Benchmark environment matrix
The table below will be populated as benchmarks are run on server-class hardware. Contributions and independent reproduction are welcome. All measurements use --wal-sync-mode group_commit (default) with a 70% INSERT / 30% SELECT mixed workload unless noted otherwise.
| Environment | Storage | Concurrency | TPS | p50 | p95 | p99 |
|---|---|---|---|---|---|---|
| Apple M-series (dev baseline) | NVMe | 10 | 430 | 23 ms | 36 ms | 42 ms |
| AWS Graviton3 (c7g) | EBS gp3 | 10 | — | — | — | — |
| AWS Graviton3 (c7g) | EBS gp3 | 100 | — | — | — | — |
| AWS x86 (c6i) | EBS gp3 | 10 | — | — | — | — |
| AWS x86 (c6i) | EBS gp3 | 100 | — | — | — | — |
| AWS x86 (c6i) | EBS gp3 | 1,000 | — | — | — | — |
| Bare metal | NVMe | 100 | — | — | — | — |
| Bare metal | NVMe | 1,000 | — | — | — | — |