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

@omnituum/noise-kyber

v0.1.0

Published

Post-quantum secure Noise XX + ML-KEM device pairing for Loggie

Downloads

190

Readme

@loggie/noise-kyber

Post-quantum secure device pairing for Loggie

Implements Noise XX with hybrid X25519 + ML-KEM-768 (Kyber) for forward-secret, authenticated device synchronization.

Features

Post-quantum security - Hybrid X25519 + ML-KEM-768 (Kyber) ✅ Forward secrecy - Fresh ephemeral keys every session ✅ Mutual authentication - Long-term static device keys ✅ PSK binding - QR token prevents MitM ✅ Compact QR codes - ~190 characters (hash-commit pattern) ✅ Auth code verification - User-friendly 15-char codes (ABC-DEF-GHI-JKL) ✅ Production-grade - Timing-safe, memory-zeroing, comprehensive tests

Installation

pnpm add @loggie/noise-kyber

Quick Start

Device B (Shows QR Code)

import {
  createPairingSession,
  createQRFromSession,
  PairingResponder
} from '@loggie/noise-kyber';

// 1. Create pairing session
const session = await createPairingSession(
  '0x1234...', // LoggieCIDV3 identity anchor
  'My Laptop'  // Device label
);

// 2. Generate QR code
const qrCode = createQRFromSession(session);
displayQR(qrCode); // Show to user

// 3. Wait for WebRTC connection
const dataChannel = await waitForConnection();

// 4. Perform handshake
const responder = new PairingResponder(session);

dataChannel.onmessage = async (event) => {
  const message = new Uint8Array(event.data);

  // Receive message 1
  await responder.receiveMessage1(message);

  // Send message 2 + auth code
  const { message: msg2, authCode, kyberPublicFull } = await responder.sendMessage2();
  dataChannel.send(msg2);
  dataChannel.send(new TextEncoder().encode(kyberPublicFull)); // Send full Kyber key

  console.log('Show auth code to user:', authCode);

  // User verifies codes match...

  // Receive message 3
  const msg3 = await receiveNextMessage();
  const result = await responder.receiveMessage3(msg3);

  console.log('Pairing complete!', result.transportKeys);
  // Now use result.transportKeys for encrypted communication
};

Device A (Scans QR Code)

import {
  decodeQRPayload,
  PairingInitiator
} from '@loggie/noise-kyber';

// 1. Scan and decode QR
const qrCode = await scanQRCode(); // User scans QR
const payload = decodeQRPayload(qrCode);

// 2. Connect via WebRTC
const dataChannel = await connectToDevice();

// 3. Perform handshake
const initiator = await PairingInitiator.create(
  payload.token,
  { label: 'My Phone' }
);

// Send message 1
const msg1 = await initiator.sendMessage1();
dataChannel.send(msg1);

// Receive message 2 + Kyber key
const msg2 = await receiveNextMessage();
const kyberFull = await receiveNextMessage(); // Full Kyber key

const { authCode, remoteDeviceLabel, identityAnchor } =
  await initiator.receiveMessage2(msg2, kyberFull);

console.log('Show auth code to user:', authCode);
console.log('Pairing with:', remoteDeviceLabel);

// User verifies codes match and approves...

// Send message 3
const result = await initiator.sendMessage3Approved();

console.log('Pairing complete!', result.transportKeys);

API Reference

High-Level API

createPairingSession(identityAnchor, deviceLabel, staticKeys?)

Creates a new pairing session (Device B).

const session = await createPairingSession(
  '0x1234...', // LoggieCIDV3 identity
  'My Laptop', // Device name
  staticKeys   // Optional: long-term device keys
);

createQRFromSession(session)

Generates compact QR payload (~190 chars).

const qrCode = createQRFromSession(session);
// qrCode: "3A7f2g9H..." (Base58)

decodeQRPayload(qrCode)

Decodes and validates QR payload.

const payload = decodeQRPayload(qrCode);
// payload: { token, x25519Public, kyberPublicHash, ... }

class PairingInitiator

Handles pairing from scanning device (Device A).

const initiator = await PairingInitiator.create(token, deviceInfo, staticKeys?);

await initiator.sendMessage1();
const { authCode } = await initiator.receiveMessage2(msg, kyberFull);
const result = await initiator.sendMessage3Approved();

class PairingResponder

Handles pairing from QR-showing device (Device B).

const responder = new PairingResponder(session);

await responder.receiveMessage1(msg);
const { authCode } = await responder.sendMessage2();
const result = await responder.receiveMessage3(msg);

Advanced API

Key Generation

import { generateEphemeralKeys, generateStaticKeys } from '@loggie/noise-kyber';

const ephemeral = await generateEphemeralKeys();
const static = await generateStaticKeys();

Cryptographic Primitives

import {
  deriveAuthCode,
  deriveTransportKeys,
  timingSafeEqual,
  secureZero
} from '@loggie/noise-kyber';

const authCode = deriveAuthCode(transcriptHash);
const transportKeys = deriveTransportKeys(finalHash, 'initiator');

if (timingSafeEqual(code1, code2)) {
  console.log('Auth codes match!');
}

secureZero(sensitiveData); // Zero memory

Security

Threat Model

Protects against:

  • Man-in-the-middle attacks (PSK binding + auth codes)
  • Post-quantum attacks (ML-KEM-768)
  • Replay attacks (timestamp validation)
  • Timing attacks (constant-time comparisons)

⚠️ Does NOT protect against:

  • Compromised device endpoints
  • Physical device theft (use biometric approval)
  • Malicious QR codes (validate identity anchor)

Best Practices

  1. Always verify auth codes - Display on both devices
  2. Use static keys - Enables device authentication
  3. Validate timestamps - QR expires after 5 minutes
  4. Clean up - Call .cleanup() after handshake
  5. Use biometrics - Gate pairing approval with biometric check

Testing

# Run tests
pnpm test

# Watch mode
pnpm test:watch

# E2E tests
pnpm test:e2e

# Coverage
pnpm test --coverage

Architecture

Noise XX Pattern:
  Device A (Initiator)         Device B (Responder)
  ────────────────────────────────────────────────
  1. e                     →
                           ←   2. e, ee, s, es
  3. s, se                 →

Where:
  e   = ephemeral key
  s   = static key
  ee  = ephemeral-ephemeral DH + KEM
  es  = ephemeral-static DH
  se  = static-ephemeral DH

Hybrid Post-Quantum KDF

PSK (QR token)
  ↓
X25519 DH + ML-KEM-768 encapsulation
  ↓
BLAKE3 transcript hashing
  ↓
HKDF-SHA256 key derivation
  ↓
ChaCha20-Poly1305 encryption
  ↓
Transport keys

Troubleshooting

QR Code Too Large

Use hash-commit pattern (already default):

  • QR contains Kyber public hash (32 bytes)
  • Full key (1184 bytes) sent via WebRTC
  • Keeps QR at ~190 chars

Timing Attacks

Use provided utilities:

import { timingSafeEqual } from '@loggie/noise-kyber';

// ❌ NEVER:
if (code1 === code2) { ... }

// ✅ ALWAYS:
if (timingSafeEqual(code1, code2)) { ... }

Memory Safety

Always cleanup:

const responder = new PairingResponder(session);
try {
  // ... handshake ...
} finally {
  responder.cleanup(); // Zeros sensitive data
}

License

MIT

Contributing

See CONTRIBUTING.md

Support