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

@tnid/encryption

v0.1.0

Published

Format-preserving encryption for TNIDs - convert time-ordered IDs to random-looking IDs

Readme

@tnid/encryption

Encrypt V0 TNIDs to V1 to hide timestamp information.

Why Encrypt TNIDs?

V0 TNIDs contain a timestamp (like UUIDv7), which reveals when the ID was created. This can leak information you may not want to expose publicly, such as:

  • When a user account was created
  • The order in which records were created
  • Approximate creation rates

By encrypting V0 to V1, you get a valid high-entropy V1 TNID that hides this information while remaining decryptable on the backend.

Installation

# npm
npm install @tnid/encryption @tnid/core

# pnpm
pnpm add @tnid/encryption @tnid/core

# bun
bun add @tnid/encryption @tnid/core

# deno
deno add npm:@tnid/encryption npm:@tnid/core

Platform Support

Requires globalThis.crypto (Web Crypto API):

  • Node.js 20+
  • Deno 1.0+
  • Bun 1.0+
  • Modern browsers (ES2020+)

Quick Start

import { Tnid, TnidType } from "@tnid/core";
import { EncryptionKey, encryptV0ToV1, decryptV1ToV0 } from "@tnid/encryption";

const UserId = Tnid("user");
type UserId = TnidType<typeof UserId>;

// Create an encryption key (16 bytes / 128 bits)
const key = EncryptionKey.fromHex("0102030405060708090a0b0c0d0e0f10");

// Create a time-ordered V0 ID
const v0 = UserId.new_v0();

// Encrypt to V1 before sending to client
const v1 = await encryptV0ToV1(v0, key);

// Decrypt on the backend to recover the original
const decrypted = await decryptV1ToV0(v1, key);
// decrypted === v0

How It Works

The encryption converts the 100 payload bits while preserving the TNID structure. The result is a valid V1 TNID that is indistinguishable from a randomly generated one.

API Reference

EncryptionKey

A 128-bit (16 byte) encryption key.

// From 32-character hex string
const key = EncryptionKey.fromHex("0102030405060708090a0b0c0d0e0f10");

// From raw bytes
const key = EncryptionKey.fromBytes(
  new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
);

// Get key bytes (returns a copy)
const bytes: Uint8Array = key.asBytes();

encryptV0ToV1(tnid, key)

Encrypts a V0 TNID to V1, hiding timestamp information.

const v0: UserId = UserId.new_v0();
const v1: UserId = await encryptV0ToV1(v0, key); // Type preserved!
  • Input: V0 TNID (any typed TNID or DynamicTnid)
  • Output: V1 TNID (same type as input)
  • Idempotent: If input is already V1, returns it unchanged
  • Throws: EncryptionError if variant is unsupported (v2/v3)

decryptV1ToV0(tnid, key)

Decrypts a V1 TNID back to V0, recovering timestamp information.

const decrypted: UserId = await decryptV1ToV0(v1, key); // Type preserved!
  • Input: V1 TNID (any typed TNID or DynamicTnid)
  • Output: V0 TNID (same type as input)
  • Idempotent: If input is already V0, returns it unchanged
  • Throws: EncryptionError if variant is unsupported (v2/v3)

Error Classes

import { EncryptionKeyError, EncryptionError } from "@tnid/encryption";

// EncryptionKeyError - invalid key format
try {
  EncryptionKey.fromHex("invalid");
} catch (e) {
  if (e instanceof EncryptionKeyError) {
    console.log("Invalid key:", e.message);
  }
}

Implementation Details

Uses FF1 format-preserving encryption (NIST SP 800-38G) with AES-128, which allows encrypting the 100 payload bits while maintaining the exact same bit length. This implementation is bit-compatible with the Rust TNID library.

Note

The encryption functionality is not part of the TNID specification. Encrypted TNIDs are standard V1 TNIDs and remain fully compatible with any TNID implementation.

License

MIT