noble-curves-extended
v0.2.11
Published
This project extends @noble/curves to allow randomBytes to be specified externally
Maintainers
Readme
noble-curves-extended
This project extends @noble/curves to allow randomBytes to be specified externally. This is particularly useful for environments where you need to control the source of randomness, such as in testing or when using specific cryptographic hardware.
Features
- External
randomBytesfunction injection for all curves - Support for multiple elliptic curves:
- Ed25519 (EdDSA signatures)
- NIST curves (P256, P384, P521)
- secp256k1 (Bitcoin and Ethereum curve)
- X25519 (ECDH key exchange)
- BLS12-381 (Boneh-Lynn-Shacham signatures)
- Two-layer architecture: low-level curve operations and high-level unified API
Installation
npm install noble-curves-extendedPeer Dependencies
This package requires the following peer dependencies:
npm install @noble/curves @noble/hashesThese dependencies are required because this package is a thin wrapper around @noble/curves and uses @noble/hashes for cryptographic operations.
Architecture
This library provides two layers of functionality:
1. Low-Level Curves (@/curves)
Direct curve implementations with external randomBytes injection. These provide the same API as @noble/curves but allow you to control the randomness source.
2. High-Level Unified API (@/unified)
A unified interface that abstracts curve differences and provides consistent APIs for different cryptographic operations (signatures, ECDH).
Usage
Low-Level Curves (@/curves)
import {
createEd25519,
createSecp256k1,
createP256,
createP384,
createP521,
createX25519,
createBls12_381,
} from 'noble-curves-extended';
// Create curve instances with your own randomBytes function
const ed25519 = createEd25519(randomBytes);
const secp256k1 = createSecp256k1(randomBytes);
const p256 = createP256(randomBytes);
const p384 = createP384(randomBytes);
const p521 = createP521(randomBytes);
const x25519 = createX25519(randomBytes);
const bls12_381 = createBls12_381(randomBytes);
// Use the curves as you would with @noble/curves
const privateKey = ed25519.utils.randomPrivateKey();
const publicKey = ed25519.getPublicKey(privateKey);High-Level Unified API (@/unified)
import { Ed25519 } from 'noble-curves-extended';
import { P256, P384, P521, Secp256k1 } from 'noble-curves-extended';
import { X25519 } from 'noble-curves-extended';
// Create dedicated unified curve classes
const ed25519 = new Ed25519(randomBytes);
const p256 = new P256(randomBytes);
const p384 = new P384(randomBytes);
const p521 = new P521(randomBytes);
const secp256k1 = new Secp256k1(randomBytes);
const x25519 = new X25519(randomBytes);
// Use unified API for signatures
const privateKey = ed25519.randomPrivateKey();
const publicKey = ed25519.getPublicKey(privateKey);
const message = new TextEncoder().encode('Hello, World!');
const signature = ed25519.sign({ privateKey, message });
const isValid = ed25519.verify({ publicKey, message, signature });
// Use unified API for ECDH
const alicePrivateKey = x25519.randomPrivateKey();
const alicePublicKey = x25519.getPublicKey(alicePrivateKey);
const bobPrivateKey = x25519.randomPrivateKey();
const bobPublicKey = x25519.getPublicKey(bobPrivateKey);
const aliceSharedSecret = x25519.getSharedSecret({
privateKey: alicePrivateKey,
publicKey: bobPublicKey,
});
const bobSharedSecret = x25519.getSharedSecret({
privateKey: bobPrivateKey,
publicKey: alicePublicKey,
});
// aliceSharedSecret === bobSharedSecret
// JWK operations
const jwkPrivateKey = ed25519.toJwkPrivateKey(privateKey);
const jwkPublicKey = ed25519.toJwkPublicKey(publicKey);
const recoveredPrivateKey = ed25519.toRawPrivateKey(jwkPrivateKey);
const recoveredPublicKey = ed25519.toRawPublicKey(jwkPublicKey);Alternatively, you can use factory functions when you want to obtain a curve by its name at runtime:
import { createSignatureCurve, createEcdhCurve } from 'noble-curves-extended';
// Create by curve name (runtime)
const ed25519 = createSignatureCurve('Ed25519', randomBytes);
const p256 = createSignatureCurve('P-256', randomBytes);
const secp256k1 = createSignatureCurve('secp256k1', randomBytes);
const x25519 = createEcdhCurve('X25519', randomBytes);
// Use the same unified API
const privateKey = ed25519.randomPrivateKey();
const publicKey = ed25519.getPublicKey(privateKey);
const message = new TextEncoder().encode('Hello, World!');
const signature = ed25519.sign({ privateKey, message });
const isValid = ed25519.verify({ publicKey, message, signature });RNG-Disallowed Signature Curves
When you need to forbid RNG usage (e.g., to enforce deterministic behavior or harden code paths), use the RNG-disallowed factory. It returns a signature curve with RNG operations disabled while keeping signing/verification available.
// Helper factory:
const curve = createSignatureCurveRngDisallowed('P-256');
// Example flow using the RNG-allowed factory to obtain a private key,
// then using the RNG-disallowed curve to sign/verify deterministically.
import {
createSignatureCurve,
createSignatureCurveRngDisallowed,
} from 'noble-curves-extended';
import { randomBytes } from '@noble/hashes/utils';
// Generate key material with RNG-allowed curve
const allowed = createSignatureCurve('P-256', randomBytes);
// Obtain an RNG-disallowed curve (no randomBytes/randomPrivateKey)
const noRng = createSignatureCurveRngDisallowed('P-256');
const privateKey = allowed.randomPrivateKey();
const publicKey = noRng.getPublicKey(privateKey, false);
// Deterministic sign (RFC 6979 for ECDSA; Ed25519 is deterministic by design)
const message = new TextEncoder().encode('Hello, RNG-free world!');
const signature = noRng.sign({ privateKey, message });
const ok = noRng.verify({ publicKey, message, signature });API
RandomBytes Type
type RandomBytes = (byteLength?: number) => Uint8Array;Low-Level Curves (@/curves)
Curve Creation Functions
createEd25519(randomBytes: RandomBytes): Creates Ed25519 curve instancecreateSecp256k1(randomBytes: RandomBytes): Creates secp256k1 curve instancecreateP256(randomBytes: RandomBytes): Creates NIST P256 curve instancecreateP384(randomBytes: RandomBytes): Creates NIST P384 curve instancecreateP521(randomBytes: RandomBytes): Creates NIST P521 curve instancecreateX25519(randomBytes: RandomBytes): Creates X25519 curve instancecreateBls12_381(randomBytes: RandomBytes): Creates BLS12-381 curve instance
Each curve instance provides the same API as its counterpart in @noble/curves.
High-Level Unified API (@/unified)
Dedicated Classes
new Ed25519(randomBytes: RandomBytes)new P256(randomBytes: RandomBytes)new P384(randomBytes: RandomBytes)new P521(randomBytes: RandomBytes)new Secp256k1(randomBytes: RandomBytes)new X25519(randomBytes: RandomBytes)
Factory Functions
createSignatureCurve(curveName: SignatureCurveName, randomBytes: RandomBytes)createEcdhCurve(curveName: EcdhCurveName, randomBytes: RandomBytes)createSignatureCurveRngDisallowed(curveName: SignatureCurveName)Returns a signature curve with RNG operations disabled (randomBytes and randomPrivateKey are omitted). Signing remains available and deterministic (ECDSA uses RFC 6979; Ed25519 is deterministic by design).
Supported Unified Curves
Signature: Ed25519, P-256, P-384, P-521, secp256k1
ECDH: P-256, P-384, P-521, secp256k1, X25519
Unified Interface
All unified curve instances provide:
curveName: CurveName: The name of the curvekeyByteLength: number: The byte length of the keyrandomPrivateKey(): Uint8Array: Generate a random private keygetPublicKey(privateKey: Uint8Array, compressed?: boolean): Uint8Array: Derive public key from private keytoJwkPrivateKey(privateKey: Uint8Array): JwkPrivateKey: Convert private key to JWK formattoJwkPublicKey(publicKey: Uint8Array): JwkPublicKey: Convert public key to JWK formattoRawPrivateKey(jwkPrivateKey: JwkPrivateKey): Uint8Array: Convert JWK private key to raw formattoRawPublicKey(jwkPublicKey: JwkPublicKey): Uint8Array: Convert JWK public key to raw format
Signature Curves Additional Methods
signatureAlgorithmName: SignatureAlgorithmName: The signature algorithm namesign({ privateKey, message, recovered? }): Uint8Array: Sign a messageverify({ publicKey, message, signature }): boolean: Verify a signaturerecoverPublicKey({ signature, message, compressed? }): Uint8Array: Recover public key from signature (Weierstrass curves only)
ECDH Curves Additional Methods
getSharedSecret({ privateKey, publicKey }): Uint8Array: Compute shared secret
Utilities
Curve Name Resolution
The library provides utility functions for resolving curve names from algorithm names or validating curve-algorithm pairs:
algorithmToCurveName(algorithmName: string): string: Converts an algorithm name to its corresponding curve name. SupportsES256→P-256,ES384→P-384,ES512→P-521, andES256K→secp256k1. Throws an error for unsupported algorithms.resolveCurveName({ curveName?, algorithmName? }): string: Resolves a curve name from either a curve name or an algorithm name. If both are provided, validates that they are consistent. If onlyalgorithmNameis provided, derives the curve name from it. If onlycurveNameis provided, returns it as-is. Throws an error if neither is provided or if they don't match.
Example:
import { algorithmToCurveName, resolveCurveName } from 'noble-curves-extended';
// Convert algorithm to curve name
const curveName = algorithmToCurveName('ES256'); // Returns 'P-256'
// Resolve curve name from algorithm
const resolved = resolveCurveName({ algorithmName: 'ES384' }); // Returns 'P-384'
// Resolve curve name from curve name
const resolved2 = resolveCurveName({ curveName: 'P-256' }); // Returns 'P-256'
// Validate consistency
const validated = resolveCurveName({
curveName: 'P-256',
algorithmName: 'ES256',
}); // Returns 'P-256' (validated)
// Throws error if mismatch
resolveCurveName({ curveName: 'P-256', algorithmName: 'ES384' }); // Throws errorAlgorithm Name Resolution
The library provides utility functions for resolving algorithm names from curve names or validating algorithm-curve pairs:
curveToAlgorithmName(curveName: string): string | undefined: Converts a curve name to its corresponding algorithm name. SupportsP-256→ES256,P-384→ES384,P-521→ES512,secp256k1→ES256K,Ed25519→EdDSA, andX25519→ES256K. Returnsundefinedwhen the curve name cannot uniquely determine an algorithm name.resolveAlgorithmName({ algorithmName?, curveName? }): string: Resolves an algorithm name from either an algorithm name or a curve name. If both are provided, validates that they are consistent. If onlycurveNameis provided, derives the algorithm name from it. If onlyalgorithmNameis provided, returns it as-is. Throws an error if neither is provided or if they don't match.
Example:
import {
curveToAlgorithmName,
resolveAlgorithmName,
} from 'noble-curves-extended';
// Convert curve to algorithm name
const algorithmName = curveToAlgorithmName('P-256'); // Returns 'ES256'
// Resolve algorithm name from curve
const resolved = resolveAlgorithmName({ curveName: 'P-384' }); // Returns 'ES384'
// Resolve algorithm name from algorithm name
const resolved2 = resolveAlgorithmName({ algorithmName: 'ES256' }); // Returns 'ES256'
// Validate consistency
const validated = resolveAlgorithmName({
algorithmName: 'ES256',
curveName: 'P-256',
}); // Returns 'ES256' (validated)
// Throws error if mismatch
resolveAlgorithmName({ algorithmName: 'ES256', curveName: 'P-384' }); // Throws errorJWK Thumbprint
The library provides a utility function for computing JWK thumbprints according to RFC 7638:
computeJwkThumbprint(jwk: JwkPublicKey): Uint8Array: Computes the JWK thumbprint as a SHA-256 hash. The thumbprint is computed by first generating the canonical JSON representation of the key (containing only the required fields in a specific order), then computing the SHA-256 hash of the UTF-8 encoded JSON string. Supports EC (Elliptic Curve) and OKP (Octet Key Pair) key types. Additional fields (such asalg,kid,key_ops) are ignored when computing the thumbprint.
Example:
import { computeJwkThumbprint } from 'noble-curves-extended';
import { encodeBase64Url } from 'u8a-utils';
// EC key thumbprint
const ecJwk = {
kty: 'EC',
crv: 'P-256',
x: 'MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4',
y: '4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM',
};
const thumbprint = computeJwkThumbprint(ecJwk);
const thumbprintBase64Url = encodeBase64Url(thumbprint);
// Returns: 'cn-I_WNMClehiVp51i_0VpOENW1upEerA8sEam5hn-s'
// OKP key thumbprint
const okpJwk = {
kty: 'OKP',
crv: 'Ed25519',
x: '11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo',
};
const okpThumbprint = computeJwkThumbprint(okpJwk);
const okpThumbprintBase64Url = encodeBase64Url(okpThumbprint);
// Returns: 'kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k'
// Additional fields are ignored
const jwkWithExtraFields = {
kty: 'EC',
crv: 'P-256',
alg: 'ES256',
x: 'MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4',
y: '4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM',
kid: 'test-key-id',
key_ops: ['verify'],
};
// Same thumbprint as ecJwk above (additional fields are ignored)
const thumbprintWithExtra = computeJwkThumbprint(jwkWithExtraFields);BLS12-381 Specific
The BLS12-381 implementation provides:
- Custom random bytes generation through the
randomBytesparameter - Field operations over the BLS12-381 scalar field (Fr)
- Utility functions for key generation and management
Security
This library is a thin wrapper around @noble/curves and inherits its security properties. The only modification is the ability to inject a custom randomBytes function.
License
MIT
