npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chainproof

v0.2.2

Published

Hash-chained, Ed25519-signed append-only logs for TypeScript. Tamper-evident audit trails as a reusable primitive.

Readme

chainproof

Hash-chained, Ed25519-signed append-only logs for TypeScript. Tamper-evident audit trails as a reusable primitive.

Why chainproof?

Any system that needs a provable, tamper-evident history of events — audit logs, receipt chains, compliance trails, change tracking — needs three things:

  1. Hash linking — each entry commits to the previous one, so deletions and reordering are detectable
  2. Signatures — each entry is cryptographically signed, so forgery requires the private key
  3. Verification — anyone with the public key can verify the entire chain without trusting the writer

chainproof gives you all three in a single library. No database required — just append entries and verify.

Install

npm install chainproof

Quick Start

import { ChainLog, generateKeyPair } from 'chainproof'

// Generate Ed25519 key pair
const keyPair = generateKeyPair().unwrap()

// Create a chain and append entries
const chain = new ChainLog(keyPair)

chain.append({ tool: 'Edit', file: 'server.ts', user: 'alice' })
chain.append({ tool: 'Bash', command: 'pnpm test', user: 'alice' })
chain.append({ tool: 'Deploy', target: 'production', user: 'bob' })

// Verify the entire chain
const result = chain.verifyIntegrity()
console.log(result)
// { valid: true, chainLength: 3, lastValidSeq: 2, message: 'Chain verified: 3 entries' }

// Tamper with an entry — verification catches it
const tampered = chain.entries.map((e, i) =>
  i === 0 ? { ...e, data: { tool: 'FORGED', file: 'evil.ts', user: 'alice' } } : e
)
const check = ChainLog.verify(tampered, keyPair.publicKey)
console.log(check.valid)   // false
console.log(check.message) // 'Seq 0: invalid signature'

How It Works

Each entry in the chain contains:

| Field | Description | |-------|-------------| | id | UUIDv7 (time-ordered, RFC 9562) | | seq | Monotonic sequence number | | timestamp | Unix timestamp | | data | Your payload (generic T) | | dataHash | SHA-256 of canonical JSON of data | | prevHash | SHA-256 of previous entry's canonical bytes | | signature | Ed25519 signature (base64) |

Genesis: The first entry links to SHA-256("chainproof:genesis").

Canonical form: Entries are serialized with sorted keys and the signature field excluded before hashing and signing. This ensures deterministic verification regardless of JSON key ordering.

Tamper detection:

  • Modify any field → signature verification fails
  • Delete an entry → next entry's prevHash no longer matches
  • Reorder entries → hash chain breaks
  • Forge an entry → requires the private key

Usage

Chain Operations

import { ChainLog, generateKeyPair } from 'chainproof'

const keyPair = generateKeyPair().unwrap()
const chain = new ChainLog<{ action: string; user: string }>(keyPair)

// Append returns Result<ChainEntry<T>, ChainError>
const entry = chain.append({ action: 'create', user: 'alice' })
if (entry.isOk()) {
  console.log(entry.value.id)        // '019d1263-...'
  console.log(entry.value.seq)       // 0
  console.log(entry.value.signature) // 'base64...'
}

// Access entries
console.log(chain.length)    // 1
console.log(chain.entries)   // readonly ChainEntry<T>[]
console.log(chain.lastHash)  // SHA-256 of last entry

Verification

// Verify using the chain's own key pair
const result = chain.verifyIntegrity()

// Or verify externally with just the public key
const result = ChainLog.verify(entries, publicKey)

// Result shape
// { valid: boolean, chainLength: number, lastValidSeq: number, message: string }

JSONL Serialization

// Serialize to JSONL (one JSON entry per line)
const jsonl = chain.toJsonl()

// Restore from JSONL
const restored = ChainLog.fromJsonl(jsonl, keyPair)
if (restored.isOk()) {
  console.log(restored.value.length) // same as original
}

File Storage

import { appendToFile, readFromFile, saveChainToFile, loadChainFromFile } from 'chainproof'

// Append entries one at a time (ideal for live logging)
const entry = chain.append({ action: 'deploy' }).unwrap()
await appendToFile('/var/log/audit.jsonl', entry)

// Read entries from file
const entries = await readFromFile('/var/log/audit.jsonl')

// Save/load entire chain
await saveChainToFile('/var/log/audit.jsonl', chain)
const loaded = await loadChainFromFile('/var/log/audit.jsonl', keyPair)

Key Management

import {
  generateKeyPair,
  saveKeyPair,
  loadKeyPair,
  exportPublicKey,
  importPublicKey
} from 'chainproof'

// Generate and save keys (private key gets 0o600 permissions)
const keyPair = generateKeyPair().unwrap()
await saveKeyPair(keyPair, '/etc/myapp/keys')
// Creates: /etc/myapp/keys/chain.key (private, 0o600)
//          /etc/myapp/keys/chain.pub (public, 0o644)

// Load keys from disk
const loaded = await loadKeyPair('/etc/myapp/keys')

// Export public key for external verifiers
const pem = exportPublicKey(keyPair.publicKey).unwrap()
// Share this PEM — anyone can verify the chain without the private key

// Import a public key for verification
const pubKey = importPublicKey(pem).unwrap()
const result = ChainLog.verify(entries, pubKey)

Low-Level API

import { sha256, sign, verify, canonicalJson, createEntry, entryHash, genesisHash, uuid7 } from 'chainproof'

// SHA-256 hashing
sha256('hello') // '2cf24dba...'

// Canonical JSON (sorted keys, deterministic)
canonicalJson({ z: 1, a: 2 }) // '{"a":2,"z":1}'

// Ed25519 sign/verify
const sig = sign(keyPair.privateKey, 'data').unwrap()
const valid = verify(keyPair.publicKey, 'data', sig).unwrap() // true

// UUIDv7 (time-ordered)
uuid7() // '019d1263-7a2b-7c58-...'

// Manual entry creation
const entry = createEntry({ action: 'test' }, 0, genesisHash(), keyPair)
const hash = entryHash(entry.unwrap())

Error Handling

All fallible operations return Result<T, ChainError> or ResultAsync<T, ChainError> from @valencets/resultkit.

interface ChainError {
  readonly code: ChainErrorCode
  readonly message: string
}

// Error codes: INVALID_KEY, SIGN_FAILED, VERIFY_FAILED, CHAIN_BROKEN, IO_FAILED, PARSE_FAILED

Cryptographic Primitives

| Primitive | Purpose | Implementation | |-----------|---------|----------------| | SHA-256 | Hash chain links, data hashing | node:crypto (built-in) | | Ed25519 | Entry signing and verification | node:crypto (built-in) | | UUIDv7 | Time-ordered entry IDs | Custom (RFC 9562) | | Base64 | Signature encoding | Built-in |

No native addons. No npm crypto dependencies. Just node:crypto.

Design Principles

  1. Append-only — entries are never modified or deleted
  2. Externally verifiable — anyone with the public key can verify (no shared secret)
  3. Deterministic — canonical JSON serialization ensures consistent hashing
  4. Minimal — one runtime dependency (@valencets/resultkit)
  5. GenericChainLog<T> works with any serializable payload type

Requirements

  • Node.js >= 22
  • TypeScript >= 5.9
  • ESM only ("type": "module")

License

MIT