This project extends @noble/curves to allow randomBytes to be specified externally
npm install noble-curves-extendedThis 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.
- External randomBytes function 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
``bash`
npm install noble-curves-extended
This package requires the following peer dependencies:
`bash`
npm install @noble/curves @noble/hashes
These dependencies are required because this package is a thin wrapper around @noble/curves and uses @noble/hashes for cryptographic operations.
This library provides two layers of functionality:
Direct curve implementations with external randomBytes injection. These provide the same API as @noble/curves but allow you to control the randomness source.
A unified interface that abstracts curve differences and provides consistent APIs for different cryptographic operations (signatures, ECDH).
`typescript
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);
`
`typescript
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:
`typescript
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 });
`
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.
`typescript
// 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 });
`
`typescript`
type RandomBytes = (byteLength?: number) => Uint8Array;
#### 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.
#### 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
#### 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. Supports ES256 → P-256, ES384 → P-384, ES512 → P-521, and ES256K → 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 only algorithmName is provided, derives the curve name from it. If only curveName is provided, returns it as-is. Throws an error if neither is provided or if they don't match.
Example:
`typescript
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 error
`
#### Algorithm 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. Supports P-256 → ES256, P-384 → ES384, P-521 → ES512, secp256k1 → ES256K, Ed25519 → EdDSA, and X25519 → ES256K. Returns undefined when 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 only curveName is provided, derives the algorithm name from it. If only algorithmName is provided, returns it as-is. Throws an error if neither is provided or if they don't match.
Example:
`typescript
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 error
`
#### JWK 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 as alg, kid, key_ops) are ignored when computing the thumbprint.
Example:
`typescript
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);
`
The BLS12-381 implementation provides:
- Custom random bytes generation through the randomBytes parameter
- Field operations over the BLS12-381 scalar field (Fr)
- Utility functions for key generation and management
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.
MIT