Skip to content

z-base/cvh

Repository files navigation

npm version CI codecov license

  1. Title

cvh

  1. Description

1–2 sentences:

what it is: Conflict-free Verifiable History

what it’s for: Making sensitive or critical claims by an entity immutably verifiable, it can prove that an entity said after this timestamp, do not trust anything else singed by me and it is conflict free so all replicas of the history become eventually consistent on merge etc

(optional) what it’s not for

  1. Compatibility

A small bullet list, always in this order:

Runtimes: Node >= X; Browsers: ; Workers/Edge:

Module format: ESM/CJS

Required globals / APIs: crypto.subtle, CompressionStream, indexedDB, etc.

TypeScript: bundled types / source TS / etc.

  1. Goals

3–6 bullets, phrased as outcomes:

“Developer-friendly API…”

“No deps…”

“Returns copies for safety…”

  1. Installation

Always the same triple:

npm install

or

pnpm add

or

yarn add

  1. Usage

Smallest runnable example (the “copy/paste test”)

Common patterns: 2–5 short subsections max (don’t turn this into an API book)

  1. Runtime behavior

This is where you put the “how it behaves in different environments” and “what errors look like”.

Use a consistent set of subheadings when relevant:

Node

Browsers / Edge runtimes

Validation & errors

Safety / copying semantics

Caching semantics (if applicable)

  1. Tests

This section should answer:

What test types exist? (unit / integration / e2e / type tests)

Where do they run? (Node versions; browser matrix)

Coverage: tool + percentage (and what it measures)

Status claim: “passes on …” (keep it factual)

Example format:

Suite: unit (Node), integration, E2E (Playwright)

Matrix: Chromium / Firefox / WebKit (+ mobile emulation if you do it)

Coverage: c8 — 100% statements/branches/functions/lines (Node)

Notes: any known skips

  1. Benchmarks

This should show actual numbers, plus reproduction context.

Minimum content:

How it was run: command

Environment: runtime version + platform

Results: table or block of key ops/s or timings

Disclaimer: results vary by machine

  1. License

One line. Always last.

EXAMPLES:

npm version CI codecov license

bytecodec

Typed JavaScript byte utilities for base64url, UTF-8 strings, JSON, and gzip that behave the same in browsers and Node. Built to make JavaScript/TypeScript projects with lots of byte-format data a breeze to build, without having to write your own utilities or boilerplate.

Compatibility

  • Runtimes: Node >= 18; Browsers: modern browsers with TextEncoder/TextDecoder + btoa/atob; Workers/Edge: runtimes with TextEncoder/TextDecoder + btoa/atob (gzip needs CompressionStream/DecompressionStream).
  • Module format: ESM-only (no CJS build).
  • Required globals / APIs: Node Buffer (base64/UTF-8 fallback); browser/edge TextEncoder, TextDecoder, btoa, atob; gzip in browser/edge needs CompressionStream/DecompressionStream.
  • TypeScript: bundled types.

Goals

  • Developer-friendly API for base64url, UTF-8, JSON, gzip, concat, and equality.
  • No dependencies or bundler shims.
  • ESM-only and side-effect free for tree-shaking.
  • Returns copies for safety when normalizing inputs.
  • Consistent behavior across Node, browsers, and edge runtimes.

Installation

npm install @z-base/bytecodec
# or
pnpm add @z-base/bytecodec
# or
yarn add @z-base/bytecodec

Usage

Bytes wrapper

import { Bytes } from '@z-base/bytecodec'
// The `Bytes` convenience class wraps the same functions as static methods.
const encoded = Bytes.toBase64UrlString(new Uint8Array([1, 2, 3]))

Base64URL

import { toBase64UrlString, fromBase64UrlString } from '@z-base/bytecodec'

const bytes = new Uint8Array([104, 101, 108, 108, 111])
const encoded = toBase64UrlString(bytes) // string of base64url chars
const decoded = fromBase64UrlString(encoded) // Uint8Array

UTF-8 strings

import { fromString, toString } from '@z-base/bytecodec'

const textBytes = fromString('caffe and rockets') // Uint8Array
const text = toString(textBytes) // "caffe and rockets"

JSON

import { fromJSON, toJSON } from '@z-base/bytecodec'

const jsonBytes = fromJSON({ ok: true, count: 3 }) // Uint8Array
const obj = toJSON(jsonBytes) // { ok: true, count: 3 }

Compression

import { toCompressed, fromCompressed } from '@z-base/bytecodec'

const compressed = await toCompressed(new Uint8Array([1, 2, 3])) // Uint8Array
const restored = await fromCompressed(compressed) // Uint8Array

Normalization

import { toUint8Array, toArrayBuffer, toBufferSource } from '@z-base/bytecodec'

const normalized = toUint8Array([1, 2, 3]) // Uint8Array
const copied = toArrayBuffer(normalized) // ArrayBuffer
const bufferSource = toBufferSource(normalized) // Uint8Array as BufferSource

Equality

import { equals } from '@z-base/bytecodec'

const isSame = equals(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])) // true | false

Concatenating

import { concat } from '@z-base/bytecodec'

const joined = concat([new Uint8Array([1, 2]), new Uint8Array([3, 4]), [5, 6]]) // Uint8Array

Runtime behavior

Node

Uses Buffer.from for base64 and TextEncoder/TextDecoder when available, with Buffer fallback; gzip uses node:zlib.

Browsers / Edge runtimes

Uses TextEncoder/TextDecoder and btoa/atob. Gzip uses CompressionStream/DecompressionStream when available.

Validation & errors

Validation failures throw BytecodecError with a code string (for example BASE64URL_INVALID_LENGTH, UTF8_DECODER_UNAVAILABLE, GZIP_COMPRESSION_UNAVAILABLE), while underlying runtime errors may bubble through.

Safety / copying semantics

Normalization helpers return copies (Uint8Array/ArrayBuffer) to avoid mutating caller-owned buffers.

Tests

Suite: unit + integration (Node), E2E (Playwright) Matrix: Chromium / Firefox / WebKit + mobile emulation (Pixel 5, iPhone 12) Coverage: c8 — 100% statements/branches/functions/lines (Node) Notes: no known skips

Benchmarks

How it was run: node benchmark/bench.js Environment: Node v22.14.0 (win32 x64) Results:

Benchmark Result
base64 encode 514,743 ops/s (97.1 ms)
base64 decode 648,276 ops/s (77.1 ms)
utf8 encode 1,036,895 ops/s (48.2 ms)
utf8 decode 2,893,954 ops/s (17.3 ms)
json encode 698,985 ops/s (28.6 ms)
json decode 791,690 ops/s (25.3 ms)
concat 3 buffers 617,497 ops/s (81.0 ms)
toUint8Array 10,149,502 ops/s (19.7 ms)
toArrayBuffer 620,992 ops/s (322.1 ms)
toBufferSource 8,297,585 ops/s (24.1 ms)
equals same 4,035,195 ops/s (49.6 ms)
equals diff 2,760,784 ops/s (72.4 ms)
gzip compress 10,275 ops/s (38.9 ms)
gzip decompress 18,615 ops/s (21.5 ms)

Results vary by machine.

License

Apache

npm version CI codecov license

cryptosuite

Developer-experience-first cryptography toolkit that lets you powerfully express cryptographic intentions through a semantic and declarative API surface.

Compatibility

  • Runtimes: Modern JavaScript hosts with WebCrypto.
  • Module format: ESM-only (no CJS build).
  • Required globals / APIs: crypto, crypto.subtle, crypto.getRandomValues.
  • TypeScript: bundled types.

Goals

  • Consistent JWK validation for AES-GCM, HMAC, Ed25519, and RSA-OAEP.
  • Byte-oriented APIs (Uint8Array and ArrayBuffer) to avoid ambiguous inputs.
  • No side effects on import; all work happens per call.
  • Clean separation between agents (stateful) and clusters (cached).
  • Minimal, but strict WebCrypto wrappers with explicit CryptosuiteError codes.

Installation

npm install @z-base/cryptosuite
# or
pnpm add @z-base/cryptosuite
# or
yarn add @z-base/cryptosuite

Usage

Cryptosuite wrapper

import { Cryptosuite } from '@z-base/cryptosuite'
// The `Cryptosuite` convenience class wraps classes and functions into an intuitive structure.
const cipherJwk = await Cryptosuite.cipher.generateKey()
const payload = new Uint8Array([1, 2, 3])
const artifact = await Cryptosuite.cipher.encrypt(cipherJwk, payload)
const roundtrip = await Cryptosuite.cipher.decrypt(cipherJwk, artifact)

OpaqueIdentifier

import {
  deriveOID,
  generateOID,
  validateOID,
  type OpaqueIdentifier,
} from '@z-base/cryptosuite'

const oid = await generateOID() // 43 random base64url chars
const derived = await deriveOID(idBytesFromSomewhere) // 43 deterministic base64url chars
const valid = validateOID(uncontrolledOID) // 43 base64url chars | false
if (!valid) return

Cipher

import { fromJSON, toJSON } from '@z-base/bytecodec'
import {
  deriveCipherKey,
  CipherCluster,
  CipherAgent,
  type CipherJWK,
} from '@z-base/cryptosuite'

const cipherJwk = await deriveCipherKey(deterministicBytes)

const state = { name: 'Bob', email: 'bob@email.com' }
const enc = await CipherCluster.encrypt(cipherJwk, fromJSON(state)) // {iv, ciphertext}
const dec = await CipherCluster.decrypt(cipherJwk, enc)

const restored = toJSON(dec)
console.log(restored.name) // "Bob"

Exchange

import { fromString, toString } from '@z-base/bytecodec'
import {
  generateCipherKey,
  generateExchangePair,
  ExchangeCluster,
  WrapAgent,
  type WrapJWK,
  UnwrapAgent,
  type UnwrapJWK,
  CipherAgent,
  type CipherJWK,
} from '@z-base/cryptosuite'

const { wrapJwk, unwrapJwk } = await generateExchangePair()
const encryptJwk = await generateCipherKey()
const encryptAgent = new CipherAgent(encryptJwk)
const body = await encryptAgent.encrypt(fromString('Hello world!')) // {iv, ciphertext}
const header = await ExchangeCluster.wrap(wrapJwk, encryptJwk) // ArrayBuffer
const message = { header, body }
const decryptJwk = (await ExchangeCluster.unwrap(
  unwrapJwk,
  message.header
)) as CipherJWK
const decryptAgent = new CipherAgent(decryptJwk)
const decryptedBody = await decryptAgent.decrypt(message.body)
const messageText = toString(decryptedBody) // "Hello world!"

HMAC

import { fromString } from '@z-base/bytecodec'
import {
  generateHMACKey,
  HMACCluster,
  HMACAgent,
  type HMACJWK,
} from '@z-base/cryptosuite'

const hmacJwk = await generateHMACKey()

const challenge = crypto.getRandomValues(new Uint8Array(32))
const sig = await HMACCluster.sign(hmacJwk, challenge) // ArrayBuffer
const ok = await HMACCluster.verify(hmacJwk, challenge, sig) // true | false

Verification

import {
  generateVerificationPair,
  VerificationCluster,
  SignAgent,
  type SignJWK,
  VerifyAgent,
  type VerifyJWK,
} from '@z-base/cryptosuite'

const { signJwk, verifyJwk } = await generateVerificationPair()
const payload = new Uint8Array([9, 8, 7])
const sig = await VerificationCluster.sign(signJwk, payload) // ArrayBuffer
const ok = await VerificationCluster.verify(verifyJwk, payload, sig) // true | false

Runtime behavior

Node

Uses Node's global WebCrypto (globalThis.crypto) when available. Node is not the primary target, but tests and benchmarks run on Node 18+.

Browsers / Edge runtimes

Uses crypto.subtle and crypto.getRandomValues. Ed25519 and RSA-OAEP support vary by engine; unsupported operations throw CryptosuiteError codes.

Validation & errors

Validation failures throw CryptosuiteError with a code string (for example AES_GCM_KEY_EXPECTED, RSA_OAEP_UNSUPPORTED, ED25519_ALG_INVALID). Cryptographic failures (e.g., decrypt with the wrong key) bubble the underlying WebCrypto error.

Security considerations

  • Keep {iv, ciphertext} together and never mix IVs across messages or keys.
  • Treat all JWKs and raw key bytes as secrets; never log them and rotate on exposure.
  • Always sign a canonical byte serialization so verifiers see identical bytes.
  • Ciphertext length leaks; add padding at your protocol layer if size is sensitive.
  • Handle decrypt/verify failures uniformly; don't leak which check failed.

Tests

Suite: unit + integration (Node), E2E (Playwright) Matrix: Chromium / Firefox / WebKit + mobile emulation (Pixel 5, iPhone 12) Coverage: c8 — 100% statements/branches/functions/lines (Node)

Benchmarks

How it was run: node benchmark/bench.js Environment: Node v22.14.0 (win32 x64) Results:

Benchmark Result
AES-GCM encrypt 30.41ms (6575.9 ops/sec)
HMAC sign+verify 29.95ms (6678.1 ops/sec)
Ed25519 sign+verify 76.45ms (2616.0 ops/sec)
RSA-OAEP wrap+unwrap 1224.07ms (163.4 ops/sec)

Results vary by machine.

License

Apache

npm version CI codecov license

zero-knowledge-credentials

Client-side WebAuthn credential discovery for strict zero-knowledge apps. Deterministically derive a routing identifier and cryptographic root keys from a user-verifying authenticator, without accounts, identifiers, or server-side state.

Compatibility

  • Runtimes: modern browsers with WebAuthn + PRF extension + user verification.
  • Module format: ESM-only (no CJS build).
  • Required globals / APIs: window, navigator.credentials, PublicKeyCredential, PRF extension, crypto.subtle, crypto.getRandomValues.
  • TypeScript: bundled types.

Goals

  • Enable strict local-first zero-knowledge for browsers.
  • Deterministic, runtime-only derivation of an opaque ID and root keys.
  • No storage, no networking, no server-side requirements.
  • Explicit failure modes with stable error codes.

Installation

npm install @z-base/zero-knowledge-credentials
# or
pnpm add @z-base/zero-knowledge-credentials
# or
yarn add @z-base/zero-knowledge-credentials

Usage

These give a general idea and MUST NOT be interpreted as a full solution.

Register a credential

import {
  ZKCredentials,
  type ZKCredential,
  type ZKCredentialErrorCode,
} from '@z-base/zero-knowledge-credentials'

await ZKCredentials.registerCredential(
  'User display name',
  'platform' // or 'cross-platform'
)

Discover a credential

import { Bytes } from '@z-base/bytecodec'
import { Cryptosuite } from '@z-base/cryptosuite'
import { ZKCredentials } from '@z-base/zero-knowledge-credentials'

const root = await ZKCredentials.discoverCredential()

const id = root.id // routing identifier / OpaqueIdentifier
const hmacJwk = root.hmacJwk // HMAC root key / HMACJWK
const cipherJwk = root.cipherJwk // AES-GCM root key / CipherJWK

const cache = await caches.open('opaque-blobs')

let artifact = await cache.match(id) // {iv, ciphertext}

if (!artifact) {
  const challengeRaw = await fetch(`/api/v1/artifact/${id}/challenge`)
  const challengeText = await challengeRaw.text()
  const challengeBytes = Bytes.fromBase64UrlString(challengeText)
  const signature = await Cryptosuite.hmac.sign(hmacJwk, challengeBytes)
  const raw = await fetch(`/api/v1/artifact/${id}`, {
    headers: {
      Authorization: Bytes.toBase64UrlString(signature),
    },
  })
  artifact = await raw.json() // {iv, ciphertext}
}

const accountCredentials = await Cryptosuite.cipher.decrypt(cipherJwk, artifact)

// const {id, hmacJwk, cipherJwk} = accountCredentials
// repeat...
// const {profileCredentials, workspaceCredentials}  = resourceCredentials

Generate a credential

import { Bytes } from '@z-base/bytecodec'
import { Cryptosuite } from '@z-base/cryptosuite'
import { ZKCredentials } from '@z-base/zero-knowledge-credentials'

const profile = {
  name: 'Bob',
  preferences: {
    theme: 'dark',
  },
}

const credentials = await ZKCredentials.generateCredential()

const id = credentials.id // resource routing identifier / OpaqueIdentifier
const hmacJwk = credentials.hmacJwk // HMAC resource key / HMACJWK
const cipherJwk = credentials.cipherJwk // AES-GCM resource key / CipherJWK

const profileBytes = Bytes.fromJSON(profile)
const artifact = await Cryptosuite.cipher.encrypt(cipherJwk, profileBytes)
fetch(
  `/api/v1/artifact/${id}`,
  JSON.stringify({
    verifier: hmacJwk,
    state: {
      iv: Bytes.toBase64UrlString(artifact.iv),
      ciphertext: Bytes.toBase64UrlString(artifact.ciphertext),
    },
  }),
  {
    method: 'POST',
  }
)

Runtime behavior

Browsers

Uses WebAuthn PRF outputs to derive:

  • id (SHA-256 -> base64url of rawId)
  • cipherJwk (AES-GCM)
  • hmacJwk (HMAC-SHA256)

Validation & errors

All failures are explicit and semantic. Errors are instances of ZKCredentialError with a stable code:

  • unsupported
  • aborted
  • user-denied
  • no-credential
  • prf-unavailable
  • key-derivation-failed

Tests

Suite: unit + integration (Node), E2E (Playwright) Matrix: Chromium / Firefox / WebKit + mobile emulation (Pixel 5, iPhone 12) Coverage: c8 — 100% statements/branches/functions/lines (dist via source maps)

Benchmarks

How it was run: npm run bench Environment: Node v22.14.0 (win32 x64) Results:

Benchmark Result
fromPRF 5,224 ops/s (0.191 ms/op, 200 ops)
generateCredential 5,825 ops/s (0.172 ms/op, 50 ops)

Results vary by machine.

License

Apache

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published