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

@xkore/dices

v0.1.3

Published

Transport-agnostic quantum-resistant encryption layer for P2P applications.

Readme

@xkore/dices

Transport-agnostic quantum-resistant encryption layer for P2P applications.

Table of Contents

What is Dices?

Building P2P applications requires secure encrypted data transfer between peers. You need:

  • End-to-end encryption without central servers
  • Protection against future quantum computer attacks
  • Forward secrecy (compromised keys don't reveal past messages)
  • Backward secrecy (compromised keys don't reveal future messages)
  • Simple API that doesn't expose cryptographic complexity

DICES provides encrypted channels between peers using just their node IDs. Two key components:

1. Encrypt and decrypt your data

The entire encryption system is exposed through two functions:

// Encrypt for a recipient
const cipherData = await encrypt(nodeId, data, overlay);

// Decrypt from sender
const data = await decrypt(cipherData, overlay);

Behind the scenes, DICES uses a bounded triple ratchet - three independent mechanisms that continuously update encryption keys to provide forward secrecy, backward secrecy, and quantum resistance.

2. Automatic channel establishment via DHT

When you encrypt data for a recipient, DICES automatically:

  • Fetches their public keys from the distributed hash table
  • Establishes an encrypted session
  • Handles all key rotations and updates

No manual key exchange. No central servers. Just node IDs.

The encryption provides quantum resistance (via ML-KEM-1024), forward secrecy, and backward secrecy. See detailed cryptography explanation below.

Installation

npm install @xkore/dices

Quick Start

1. Opening the Overlay

import { Overlay } from "@xkore/dices";
import { Client as DiceClient } from "@xkore/dice";
import { Level } from "level";

// Create and open overlay instance
const overlay = new Overlay({
	database: new Level("./db"),
	diceClient: new DiceClient({
		/* ... */
	}),
	secretKey: mySecretKey, // optional, generates random if omitted
});

await overlay.open();
// Overlay is now connected to the DHT and has published its initiation keys

2. Encrypting and Decrypting Messages

import { encrypt, decrypt } from "@xkore/dices";

// Encrypt message for Bob (automatically fetches his initiation keys from DHT)
const encrypted = await encrypt(bobNodeId, messageData, overlay);

// encrypted is a Uint8Array - send it via YOUR transport layer
// (UDP, TCP, WebRTC, WebSocket, carrier pigeon, etc.)
// Example: await yourTransport.send(bobAddress, encrypted);

// When you receive an encrypted buffer from your transport:
// yourTransport.on("message", async (buffer, sender) => {
const data = await decrypt(buffer, overlay);
console.log("Received message:", data);
// });

3. Closing the Overlay

// Cleanup resources when done
await overlay.close();

Core API

Overlay Constructor

Creates a new DICES overlay instance for quantum-resistant encrypted P2P communication.

const overlay = new Overlay(options);

Options:

  • database (required): LevelDB instance for persistent ratchet state storage
  • diceClient (required): DICE client instance for DHT operations and peer discovery
  • secretKey (optional): 32-byte secp256k1 secret key (generates random if not provided)
  • bootstrapTargets (optional): Initial DHT nodes to connect to (defaults to built-in bootstrap nodes)
  • concurrency (optional): Number of concurrent DHT operations (default: 3)
  • healthcheckIntervalMs (optional): Interval for DHT healthchecks in ms (default: 60000)
  • pruneIntervalMs (optional): Interval for cleaning expired ratchet state in ms (default: 3600000)
  • ratchetKeyTtl (optional): Time-to-live for ratchet keys in ms (default: 3600000)
  • timeoutMs (optional): Default timeout for operations in ms (default: 30000)

Example:

import { Level } from "level";
import { Client as DiceClient } from "@xkore/dice";

const overlay = new Overlay({
	database: new Level("./my-app-db"),
	diceClient: new DiceClient({
		secretKey: myDiceSecretKey,
		// ... other DICE options
	}),
	secretKey: myOverlaySecretKey,
	healthcheckIntervalMs: 30000, // 30 seconds
	timeoutMs: 10000, // 10 second timeout
});

open()

Opens the overlay and starts network operations.

await overlay.open(isBootstrapping?: boolean);

Opens the database, connects the DICE client to the network, loads or generates ratchet keys, publishes initiation keys to the DHT, and starts healthcheck and prune intervals.

Parameters:

  • isBootstrapping (optional): Whether to connect to bootstrap nodes (default: true)

Returns: Promise<void>

Example:

// Open with bootstrapping (default)
await overlay.open();

// Open without bootstrapping (already connected to DHT)
await overlay.open(false);

// Overlay is now ready for encrypted communication

encrypt()

Encrypts data for a remote peer.

import { encrypt } from "@xkore/dices";

const encrypted = await encrypt(remoteNodeId, data, overlay);

Creates an authenticated encrypted buffer using the bounded triple ratchet protocol. Automatically fetches initiation keys from the DHT for first messages. Initializes new ratchet sessions automatically. Handles ML-KEM rotation when message or time bounds are reached.

Parameters:

  • remoteNodeId: 20-byte nodeId of the recipient (Uint8Array)
  • data: Plaintext data to encrypt (Uint8Array)
  • overlay: The DICES overlay instance

Returns: Promise<Uint8Array> - Encrypted buffer

Throws: DicesOverlayError if unable to fetch initiation keys or state save fails

Example:

import { encrypt } from "@xkore/dices";

// Both first and subsequent messages use the same simple API
const encrypted = await encrypt(bobNodeId, messageData, overlay);

// Send encrypted buffer via your transport
await transport.send(bobAddress, encrypted);

decrypt()

Decrypts data from a remote peer.

import { decrypt } from "@xkore/dices";

const data = await decrypt(buffer, overlay);

Performs signature verification, initializes ratchet state if needed (for first message), handles DH ratchet updates, decrypts the message, and persists updated ratchet state.

Parameters:

  • buffer: The encrypted buffer to decrypt (Uint8Array)
  • overlay: The DICES overlay instance

Returns: Promise<Uint8Array> - Decrypted plaintext data

Throws: DicesOverlayError if signature verification fails, ratchet state invalid, or decryption fails

Example:

import { decrypt } from "@xkore/dices";

// Listen for incoming messages
transport.on("message", async (buffer, sender) => {
	try {
		const data = await decrypt(buffer, overlay);
		console.log("Received message:", new TextDecoder().decode(data));
	} catch (error) {
		console.error("Failed to decrypt:", error);
	}
});

close()

Closes the overlay and cleans up resources.

await overlay.close();

Stops healthcheck and prune intervals, disconnects the DICE client, and closes the database connection. Should be called when the overlay is no longer needed to prevent resource leaks.

Returns: Promise<void>

Example:

// Open overlay
await overlay.open();

// ... use overlay for encrypted communication ...

// Cleanup when done
await overlay.close();
console.log("Overlay closed and resources cleaned up");

Cryptography

DICES is inspired by and builds upon Signal's SPQR (Signal Protocol Post-Quantum Ratchet) system, adapting it for P2P networks with important improvements.

How It Works

The system uses a triple ratchet - three independent mechanisms that continuously update encryption keys:

  1. Symmetric Ratchet: Derives new message keys from a chain key using a one-way function (HMAC). Even if someone captures a message key, they can't reverse-engineer previous keys. This provides forward secrecy within a session.

  2. DH Ratchet: Uses X25519 (classical elliptic curve) ephemeral key exchanges. Each side can generate new ephemeral keys, and when both sides have updated, a completely new shared secret is created. This provides backward secrecy - if your keys are compromised, new messages stay protected once you rotate.

  3. KEM Ratchet: Uses ML-KEM-1024 (quantum-resistant key encapsulation). Like the DH ratchet but protected against quantum computer attacks. ML-KEM is part of NIST's post-quantum cryptography standards (FIPS 203).

All three ratchets work together - the output of the KEM and DH ratchets feeds into the symmetric ratchet, which generates the actual encryption keys for messages.

Additional Cryptography Components:

  • XChaCha20-Poly1305: The actual message encryption - authenticated encryption with extended nonce space
  • HKDF-SHA256: Derives multiple keys from ratchet outputs with domain separation (ensures keys used for different purposes can't interfere)
  • secp256k1: Node identity and message signatures (same curve as Bitcoin)
  • X25519: Classical elliptic curve key exchange for defense-in-depth alongside ML-KEM

Bounded Rotation

Signal's SPQR system has a potential weakness: unbounded one-sided messaging. If Alice keeps sending messages to Bob without getting replies, the ML-KEM keys never rotate because rotation requires participation from both parties. This means a very powerful attacker could potentially store all messages and wait for quantum computers.

DICES solves this with bounded rotation - automatic ML-KEM key rotation enforced by limits:

  • Message bound: New ML-KEM keys generated after N messages (default: 100)
  • Time bound: New ML-KEM keys generated after T time passes (default: 1 hour)

When either limit is reached, the sending node generates fresh ML-KEM keys, publishes them to the DHT, and forces rotation. The DH ratchet continues normally. This maintains all the forward/backward secrecy properties while ensuring quantum resistance doesn't degrade over time.

You can configure these bounds when creating an overlay:

const overlay = new Overlay({
	// ... other options
	maxMessagesBeforeRotation: 50, // Rotate after 50 messages
	maxTimeBeforeRotation: 1800000, // Rotate after 30 minutes
});

Events

The overlay emits events for monitoring and message handling:

// Connection events
overlay.events.on("open", () => {
	console.log("Overlay opened");
});

overlay.events.on("close", () => {
	console.log("Overlay closed");
});

// Error events
overlay.events.on("error", (error) => {
	console.error("Overlay error:", error);
});

// Message events
overlay.events.on("message", (message, context) => {
	// Handle incoming message
});

// Node updates (when local node's diceAddress changes)
overlay.events.on("node", (previousNode, nextNode) => {
	console.log("Node updated:", nextNode);
});

// Key rotation
overlay.events.on("rotate", (initiationKeys) => {
	console.log("Rotated initiation keys:", initiationKeys.keyId);
});

Development

Setup

# Install dependencies
npm install

# Build the package
npm run build

Testing

# Run all tests
npm test

# Run unit tests only
npm run test:unit

# Run integration tests only
npm run test:integration

# Run specific integration test
npm run integration -- testName

Project Structure

packages/dices/
├── src/
│   ├── models/
│   │   ├── Overlay/         # Main class, wrap/unwrap functions
│   │   ├── Keys/            # secp256k1 identity
│   │   ├── RatchetKeysItem/ # Initiation keys (ML-KEM + X25519)
│   │   ├── RatchetStateItem/# Per-peer ratchet sessions
│   │   ├── Value/           # DHT record with signatures
│   │   ├── Envelope/        # Wire format
│   │   ├── CipherData/      # XChaCha20-Poly1305 encryption
│   │   └── Nodes/           # Kademlia routing table
│   └── utilities/
│       └── (KDF functions, key derivation)
└── package.json

License

MIT

Related