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.3.0

Published

Digital signatures, key agreement, and key encapsulation for Effect

Readme

@scenesystems/sign

@scenesystems/sign provides digital signatures, X25519 key agreement, and XWing hybrid key encapsulation for programs built with Effect. Use it when a message must be attributable to the holder of a key, when two parties need a shared secret, or when a protocol must stay secure against a future quantum adversary.

The three families are kept apart because their security roles differ. Signatures prove possession of a signing key under an identity policy you define. Agreement and encapsulation produce raw shared secret material that must pass through a key derivation function before it becomes a key. Each family has its own operations and its own tagged result schemas: Signature, SharedSecret, and KemCiphertext, with KeyPair shared across all algorithms. Primitive implementations come from the Noble Curves, Hashes, and Post-Quantum projects.

The package does not provide identity, authorization, certificates, trust roots, key storage, rotation, transcript construction, or protocol policy. A KEM is unauthenticated on its own, so a protocol must bind recipient keys and transcripts through an authenticated channel. @scenesystems/digest supplies HKDF and BLAKE3 key derivation for the shared secrets this package produces, and @scenesystems/seal encrypts under the derived keys.

Installation

npm install @scenesystems/sign effect

Effect ^3.22.1 is a required peer dependency. The package has one entrypoint, @scenesystems/sign.

Basic use

generateKeyPair accepts any algorithm from the three families and returns a KeyPair. sign produces a Signature carrier that records the algorithm and public key beside the signature bytes. A direct verifier such as ed25519Verify checks detached signature bytes against a public key you supply yourself.

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

export const program = Effect.gen(function* () {
  const keys = yield* generateKeyPair("ed25519")
  const message = utf8ToBytes("signed content")
  const signed = yield* sign("ed25519", message, keys.secretKey, keys.publicKey)
  const valid = yield* ed25519Verify(signed.signature, message, keys.publicKey)
  return { signed, valid }
})

Prefer a direct verifier whenever your protocol fixes the algorithm and authenticates the public key independently. The generic verify(signature, message) dispatches on the algorithm and public key carried inside the Signature, which is only appropriate when that self-describing model is deliberately part of the protocol.

Supported families

| Family | Algorithms | Operations | | ---------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | | Signatures | ed25519, secp256k1-ecdsa, secp256k1-schnorr, ml-dsa-44, ml-dsa-65, ml-dsa-87, four slh-dsa-sha2-* sets | sign, verify, algorithm-specific functions | | Agreement | x25519 | deriveSharedSecret | | KEM | xwing (X25519 with ML-KEM-768) | encapsulate, decapsulate |

The algorithm names are the literal unions SignatureAlgorithm (with the KeyOnlySigningAlgorithm subset that sign accepts), AgreementAlgorithm, and KemAlgorithm. Every family also exposes algorithm-specific functions, such as ed25519Sign and xwingEncapsulate, for callers that want the algorithm fixed in the type rather than passed as a value.

Key agreement returns a SharedSecret whose 32 raw bytes must be derived before use. The same rule applies to the sharedSecret field of a KemCiphertext.

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

export const agree = Effect.gen(function* () {
  const alice = yield* generateKeyPair("x25519")
  const bob = yield* generateKeyPair("x25519")
  const fromAlice = yield* deriveSharedSecret("x25519", alice.secretKey, bob.publicKey)
  const fromBob = yield* deriveSharedSecret("x25519", bob.secretKey, alice.publicKey)
  return equalBytes(fromAlice.sharedSecret, fromBob.sharedSecret)
})

Strict direct verification

ed25519Verify, p256Sha256P1363LowSVerify, and mlDsa65Verify take detached signature bytes, the protected message bytes, and an explicit public key. They never read an algorithm field and never accept or return the Signature carrier. Their result contract is strict.

| Input or result | Effect result | | --------------------------------------------------------------------- | -------------------------- | | Canonical, admitted signature verifies | true | | Canonical, admitted signature does not verify | false | | Malformed, noncanonical, wrong-length, or unsupported primitive input | InvalidVerificationInput | | Admitted input reaches an unavailable backend | VerificationUnavailable |

Both errors carry no material: no algorithm, key, signature, message, context, provider reason, or underlying exception. Inputs are admitted and copied on every execution of the returned Effect, so a buffer mutated between runs is rejected rather than silently reread. Primitive calls run synchronously and cannot be interrupted.

The profiles are fixed and reject alternate encodings:

  • Ed25519 follows RFC 8032 without ZIP-215 leniency. Public keys and the signature R point must be canonical, non-small-order encodings, and S must be below the subgroup order. Public keys are 32 bytes and signatures 64 bytes.
  • P-256 applies SHA-256 exactly once. Public keys must be 65-byte uncompressed SEC1 encodings and signatures 64-byte IEEE P1363 r || s with low S. DER, compressed keys, and high-S signatures are rejected.
  • ML-DSA-65 follows FIPS 204 with an explicit context. Public keys are 1,952 bytes and signatures 3,309 bytes with canonical hint encoding. An empty context and a nonempty context define distinct profiles.

Direct verification admits messages up to 8,192 bytes and rejects longer input before the primitive runs. This bound protects the verifier; it is not a wire-format limit, and your protocol must still define its own message-size policy.

Post-quantum signatures

Production ML-DSA-65 signing uses mlDsa65SignHedged, which requires exactly 32 bytes of fresh cryptographic entropy from the caller and an explicit FIPS 204 context of 0 through 255 bytes. Ambient randomness is never consulted, which keeps signing reproducible under test and auditable in production. mlDsa65SignDeterministic exists for conformance vectors. The dispatching sign function accepts a KeyOnlySigningAlgorithm, which excludes "ml-dsa-65" at the type level because its four-argument signature has nowhere to accept the entropy or the context; verify still checks every SignatureAlgorithm, including ML-DSA-65 with the empty context.

import { generateEntropy, generateKeyPair, mlDsa65SignHedged, mlDsa65Verify, utf8ToBytes } from "@scenesystems/sign"
import { Effect } from "effect"

export const signDocument = Effect.gen(function* () {
  const keys = yield* generateKeyPair("ml-dsa-65")
  const message = utf8ToBytes("quantum-resistant document")
  const context = utf8ToBytes("example.com/documents/v1")
  const entropy32 = yield* generateEntropy()
  const signature = yield* mlDsa65SignHedged(message, keys.secretKey, keys.publicKey, context, entropy32)
  return yield* mlDsa65Verify(signature.signature, message, keys.publicKey, context)
})

generateEntropy reads the runtime CSPRNG through Noble and fails with EntropyGenerationFailed when the runtime has no crypto.getRandomValues or the requested length is outside what one call may draw; it deliberately does not use Effect's seedable Random service. XWing pairs X25519 with ML-KEM-768 so that the shared secret stays secure if either component is broken: encapsulate("xwing", recipientPublicKey) returns a KemCiphertext holding the ciphertext to send and the sender's copy of the shared secret, and decapsulate("xwing", ciphertext, recipientSecretKey) recovers the same secret on the receiving side.

Public surface

The package exports plain functions and schemas from a single entrypoint.

| Area | Exports | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Generic operations | generateKeyPair, generateEntropy, sign, verify, deriveSharedSecret, encapsulate, decapsulate | | Direct verification | ed25519Verify, p256Sha256P1363LowSVerify, mlDsa65Verify | | Algorithm functions | ed25519Sign, mlDsa65SignHedged, mlDsa65SignDeterministic, xwingEncapsulate, xwingDecapsulate, and peers | | Schemas | KeyPair, Signature, SharedSecret, KemCiphertext, SignatureAlgorithm, KeyOnlySigningAlgorithm, AgreementAlgorithm, KemAlgorithm | | Errors | SigningFailed, VerificationFailed, InvalidSignature, KeyGenerationFailed, EntropyGenerationFailed, AgreementFailed, KemFailed, InvalidVerificationInput, VerificationUnavailable | | Bytes | utf8ToBytes, toHex, equalBytes |

The full list with signatures is in the API reference.

Errors and boundaries

The generic operations fail with SigningFailed, VerificationFailed, InvalidSignature, KeyGenerationFailed, AgreementFailed, or KemFailed. Most of these carry an algorithm and a diagnostic reason, so decide what a protocol may reveal before forwarding them across an untrusted boundary. The direct verifiers use the narrower material-free contract described above.

A successful verification proves that bytes were signed under a key. Whether that means anything depends on how the key was authenticated and how the message domain, context, encoding, and algorithm were bound. Shared secrets from X25519 and XWing are not keys until a KDF has processed them together with the protocol transcript. Secret keys and shared secrets need application-owned secure storage and destruction.

Standards

The implemented profiles follow RFC 8032, RFC 7748, SEC 2, BIP 340, FIPS 203, FIPS 204, FIPS 205, and the X-Wing specification. The test suite checks independent standards vectors and strict admission cases. Noble's audits cover the primitive implementations; suite selection, key provenance, error handling, and protocol composition are reviewed separately in this package and in your application.

Examples

The examples directory contains three runnable programs: Ed25519 signing and verification, X25519 key agreement between two parties, and post-quantum signing and encapsulation with ML-DSA-65 and XWing.

Status

This package is pre-1.0. Minor releases may change public APIs; pin a compatible version and review the changelog when upgrading.

Contributing and support

Read the repository contributing guide before opening a pull request. Report defects and request changes through GitHub issues. For security concerns, follow the security policy.

License

MIT. Copyright 2026 Scene Systems.