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

@scenesystems/sign

v0.1.0

Published

Digital signatures, key agreement, and key encapsulation for Effect

Readme

@scenesystems/sign

License: MIT Effect

Digital signatures, key agreement, and key encapsulation for Effect. Classical and post-quantum algorithms, built on the Noble audited cryptographic ecosystem.

Installation

npm install @scenesystems/sign
# or
pnpm add @scenesystems/sign
# or
bun add @scenesystems/sign

Requires effect ≥ 3.20.0 as a peer dependency.

Why this package?

Digital signatures prove who sent a message. Key agreement lets two parties derive a shared secret. Key encapsulation does the same thing but with quantum resistance. @scenesystems/sign provides all three families through a single Effect-native API with typed errors, self-describing output types, and exhaustive algorithm dispatch — from Ed25519 to post-quantum ML-DSA and the XWing hybrid KEM.

  • Ed25519 + secp256k1 — classical signatures for general use and blockchain compatibility
  • ML-DSA + SLH-DSA — NIST post-quantum signatures (FIPS 204/205) at multiple security levels
  • X25519 — elliptic-curve Diffie–Hellman key agreement
  • XWing — hybrid KEM combining X25519 + ML-KEM-768 for quantum-resistant key transport

Algorithms

Signatures

| Algorithm | Family | Security | Signature size | Use case | | ------------------- | --------------- | -------- | -------------- | ------------------------------ | | ed25519 | EdDSA | 128-bit | 64 B | General purpose, fast | | secp256k1-ecdsa | ECDSA | 128-bit | 64 B | Bitcoin/Ethereum compatibility | | secp256k1-schnorr | Schnorr | 128-bit | 64 B | Taproot/batch verification | | ml-dsa-44 | Lattice (PQ) | NIST 2 | 2,420 B | Post-quantum, smallest | | ml-dsa-65 | Lattice (PQ) | NIST 3 | 3,309 B | Post-quantum, recommended | | ml-dsa-87 | Lattice (PQ) | NIST 5 | 4,627 B | Post-quantum, highest security | | slh-dsa-sha2-128f | Hash-based (PQ) | NIST 1 | ~7,856 B | Post-quantum, fast signing | | slh-dsa-sha2-128s | Hash-based (PQ) | NIST 1 | ~7,856 B | Post-quantum, small signatures | | slh-dsa-sha2-192f | Hash-based (PQ) | NIST 3 | ~16,224 B | Post-quantum, higher security | | slh-dsa-sha2-256f | Hash-based (PQ) | NIST 5 | ~29,792 B | Post-quantum, maximum security |

Key agreement

| Algorithm | Family | Security | Shared secret | | --------- | ------ | -------- | ------------- | | x25519 | ECDH | 128-bit | 32 B |

Key encapsulation (KEM)

| Algorithm | Family | Security | Ciphertext | | --------- | --------------- | --------- | ---------- | | xwing | X25519 + ML-KEM | Hybrid PQ | ~1,121 B |

Choosing an algorithm

Use Ed25519 for general-purpose signing — it's fast, widely supported, and deterministic.

Use secp256k1 when interacting with Bitcoin, Ethereum, or other blockchain systems.

Use ML-DSA-65 if you need post-quantum signatures today. It's the NIST-recommended parameter set (FIPS 204) with a good balance of security and size.

Use SLH-DSA when you want conservative post-quantum security that doesn't rely on lattice hardness — it's based purely on hash functions. Signing is slow (1–5 seconds), so it's best suited for infrequent operations like certificate issuance.

Use XWing KEM for quantum-resistant key establishment. It combines X25519 and ML-KEM-768 — an attacker must break both to recover the shared secret.

Quick start

import { generateKeyPair, sign, utf8ToBytes, verify } from "@scenesystems/sign"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const keys = yield* generateKeyPair("ed25519")
  const message = utf8ToBytes("hello, signatures!")

  const sig = yield* sign("ed25519", message, keys.secretKey, keys.publicKey)
  const valid = yield* verify(sig, message)
  // true
})

API

Signing

| Function | Description | | ------------------------------------------------ | ------------------------------------------------------ | | sign(algorithm, message, secretKey, publicKey) | Sign a message → Effect<Signature> | | verify(signature, message) | Verify a self-describing signature → Effect<boolean> |

Key agreement

| Function | Description | | ----------------------------------------------------- | ------------------------------------ | | deriveSharedSecret(algorithm, secretKey, publicKey) | X25519 ECDH → Effect<SharedSecret> |

Key encapsulation

| Function | Description | | ----------------------------------------------- | ------------------------------------- | | encapsulate(algorithm, publicKey) | Encapsulate → Effect<KemCiphertext> | | decapsulate(algorithm, ciphertext, secretKey) | Decapsulate → Effect<Uint8Array> |

Key generation

| Function | Description | | ---------------------------- | --------------------------------------------------- | | generateKeyPair(algorithm) | Generate keys for any algorithm → Effect<KeyPair> |

Encoding utilities

| Function | Description | | ------------------ | -------------------------------------- | | utf8ToBytes(str) | Convert a UTF-8 string to Uint8Array | | toHex(bytes) | Encode bytes to lowercase hex string | | equalBytes(a, b) | Constant-time byte array comparison |

Schema types

| Type | Description | | -------------------- | -------------------------------------------------------- | | SignatureAlgorithm | 10 signature algorithm literals | | AgreementAlgorithm | "x25519" | | KemAlgorithm | "xwing" | | Signature | Schema.Class — algorithm, signature, publicKey | | SharedSecret | Schema.Class — algorithm, sharedSecret | | KemCiphertext | Schema.Class — algorithm, ciphertext, sharedSecret | | KeyPair | Schema.Class — algorithm, publicKey, secretKey |

Errors

| Error | Raised by | Description | | --------------------- | ----------------- | ------------------------------------ | | SigningFailed | sign | Signing operation failed | | VerificationFailed | verify | Signature is valid but doesn't match | | InvalidSignature | verify | Signature data is malformed | | KeyGenerationFailed | generateKeyPair | Key generation failed |

Examples

Sign and verify

import { generateKeyPair, sign, utf8ToBytes, verify } from "@scenesystems/sign"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const keys = yield* generateKeyPair("ed25519")
  const message = utf8ToBytes("transfer 100 tokens")

  const sig = yield* sign("ed25519", message, keys.secretKey, keys.publicKey)
  const valid = yield* verify(sig, message)

  // Tampered message fails verification
  const tampered = utf8ToBytes("transfer 999 tokens")
  const invalid = yield* verify(sig, tampered)
  // false
})

Key agreement with X25519

import { deriveSharedSecret, generateKeyPair } from "@scenesystems/sign"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const alice = yield* generateKeyPair("x25519")
  const bob = yield* generateKeyPair("x25519")

  // Both sides derive the same shared secret
  const secretA = yield* deriveSharedSecret("x25519", alice.secretKey, bob.publicKey)
  const secretB = yield* deriveSharedSecret("x25519", bob.secretKey, alice.publicKey)
  // secretA.sharedSecret deep-equals secretB.sharedSecret
})

Post-quantum KEM with XWing

import { decapsulate, encapsulate, generateKeyPair } from "@scenesystems/sign"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  // Recipient generates a hybrid key pair
  const recipient = yield* generateKeyPair("xwing")

  // Sender encapsulates a shared secret for the recipient
  const { ciphertext, sharedSecret: senderSecret } = yield* encapsulate("xwing", recipient.publicKey)

  // Recipient decapsulates to recover the same shared secret
  const recipientSecret = yield* decapsulate("xwing", ciphertext, recipient.secretKey)
  // senderSecret deep-equals recipientSecret
})

Post-quantum signatures

import { generateKeyPair, sign, utf8ToBytes, verify } from "@scenesystems/sign"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  // ML-DSA-65 — NIST FIPS 204, recommended parameter set
  const keys = yield* generateKeyPair("ml-dsa-65")
  const message = utf8ToBytes("quantum-resistant document")

  const sig = yield* sign("ml-dsa-65", message, keys.secretKey, keys.publicKey)
  const valid = yield* verify(sig, message)
  // true — verified with post-quantum security
})

Error handling

import { generateKeyPair, sign, utf8ToBytes, verify } from "@scenesystems/sign"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const keys = yield* generateKeyPair("ed25519").pipe(
    Effect.catchTag("KeyGenerationFailed", (e) => Effect.die(`keygen failed: ${e.reason}`))
  )

  const message = utf8ToBytes("hello")
  const sig = yield* sign("ed25519", message, keys.secretKey, keys.publicKey).pipe(
    Effect.catchTag("SigningFailed", (e) => Effect.die(`signing failed: ${e.reason}`))
  )

  const valid = yield* verify(sig, message).pipe(Effect.catchTag("VerificationFailed", (e) => Effect.succeed(false)))
})

See the examples/ directory for complete runnable programs.

Cryptographic foundations

All primitives wrap the Noble cryptographic ecosystem — independently audited by Cure53 and Trail of Bits, zero-dependency, high-performance pure JavaScript implementations.

| Dependency | Audits | Purpose | | --------------------- | -------- | ----------------------------------------------------- | | @noble/curves | 6 audits | Ed25519, secp256k1, X25519 | | @noble/hashes | 6 audits | SHA-256/512 for key encoding | | @noble/post-quantum | 1 audit | ML-DSA (FIPS 204), SLH-DSA (FIPS 205), ML-KEM (XWing) |

Standards

| Algorithm | Specification | | ------------------- | ------------------------------------------------------------------------- | | Ed25519 | RFC 8032 | | X25519 | RFC 7748 | | secp256k1 | SEC 2 §2.4.1 | | Schnorr (secp256k1) | BIP-340 | | ML-DSA | NIST FIPS 204 | | SLH-DSA | NIST FIPS 205 | | ML-KEM (via XWing) | NIST FIPS 203 | | XWing | Barbosa et al. (2024) |

License

MIT — Copyright © 2026 Scene Systems