Curve25519 Signatures and Key Agreement
curve25519-js provides a JavaScript implementation of Curve25519, facilitating both digital signatures and X25519 Diffie-Hellman key agreement. The current stable version is 0.0.4. While its release cadence appears infrequent, with a significant rewrite in 2019, it serves as a functional library for cryptographic operations. A key differentiator is its ability to use a single X25519 key for both signing and key agreement, a feature that distinguishes it from standard Ed25519 implementations which typically use separate key types or require explicit conversion. This is achieved by embedding and extracting a sign bit into the signature during the process. The library is derived from TweetNaCl.js and is suitable for environments where direct Curve25519 operations are needed.
Common errors
-
TypeError: Buffer is not defined
cause Attempting to use `Buffer.from` in a browser environment without a compatible polyfill or shim. `Buffer` is a Node.js global.fixFor browser environments, ensure you have a `Buffer` polyfill (e.g., install `buffer` from npm and configure your bundler, like Webpack 5+, to alias it) or use a browser-native alternative for hex string to Uint8Array conversion. -
Error: Expected Uint8Array of length 32 for privateKey (or publicKey)
cause Cryptographic functions require specific byte lengths for keys. Providing a `Uint8Array` of incorrect length (e.g., 64 bytes instead of 32 for a Curve25519 key) will cause this error.fixAlways ensure your private and public keys are 32-byte `Uint8Array` instances. Verify the source and conversion process for your key material. -
Error: Signature verification failed.
cause The provided signature does not match the message and public key. This can be due to message tampering, incorrect public key, incorrect signature, or issues with the random data used during signing (if applicable).fixDouble-check the integrity of the message, ensure the correct public key corresponds to the private key used for signing, and confirm the signature has not been altered. Verify the inputs (`publicKey`, `message`, `signature`) are all correct and in the expected `Uint8Array` format.
Warnings
- gotcha The result of `sharedKey` (X25519 output) is a raw shared secret that should *not* be used directly as a cryptographic key. It must be processed with a strong one-way function, such as HKDF or SHA-256, before use in symmetric encryption to ensure security and prevent direct attacks.
- gotcha The `sign` and `signMessage` functions accept an optional `random` argument, which *must* be 64 cryptographically secure random bytes if provided. Omitting this argument leads to deterministic signatures, which can be a security risk in some protocols if not explicitly desired and accounted for.
- gotcha The `message` parameter in `sign`, `verify`, `signMessage`, and `openMessage` is typed as `any`. While flexible, cryptographic functions typically expect messages as `Uint8Array` or `Buffer` to ensure consistent and secure hashing. Passing arbitrary types could lead to unexpected internal conversions or vulnerabilities.
- gotcha This library uses X25519 keys for both signing and key agreement, embedding a sign bit into the signature for verification. If your application primarily uses Ed25519 keys and you only need X25519 for key agreement, it might be simpler and potentially more interoperable to use an Ed25519 library and an external conversion utility (like `ed2curve-js`) rather than relying on `curve25519-js`'s unique signing approach.
Install
-
npm install curve25519-js -
yarn add curve25519-js -
pnpm add curve25519-js
Imports
- sharedKey
const { sharedKey } = require('curve25519-js');import { sharedKey } from 'curve25519-js'; - generateKeyPair
const generateKeyPair = require('curve25519-js').generateKeyPair;import { generateKeyPair } from 'curve25519-js'; - sign
import sign from 'curve25519-js/sign';
import { sign } from 'curve25519-js'; - Buffer
const Buffer = require('buffer');import { Buffer } from 'buffer';
Quickstart
import { sharedKey } from 'curve25519-js';
import { Buffer } from 'buffer'; // Explicit import for browser compatibility
const ALICE_PRIV = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
const BOB_PUB = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
// Ensure Buffer is available for hex conversion
// In a browser environment, you might need a polyfill or alternative conversion
const alicePriv = Uint8Array.from(Buffer.from(ALICE_PRIV, 'hex'));
const bobPub = Uint8Array.from(Buffer.from(BOB_PUB, 'hex'));
// Perform the Diffie-Hellman key exchange to get the shared secret
const secret = sharedKey(alicePriv, bobPub);
console.log('Secret:', Buffer.from(secret).toString('hex'));
// Example of key generation (requires a CSPRNG seed)
import { generateKeyPair } from 'curve25519-js';
import crypto from 'crypto'; // For Node.js CSPRNG
const seed = crypto.randomBytes(32); // Generate a 32-byte cryptographically secure random seed
const keyPair = generateKeyPair(seed);
console.log('Generated Private Key:', Buffer.from(keyPair.private).toString('hex'));
console.log('Generated Public Key:', Buffer.from(keyPair.public).toString('hex'));