UNPKG

noble-curves-extended

Version:

This project extends @noble/curves to allow randomBytes to be specified externally

374 lines (269 loc) 14.3 kB
# noble-curves-extended This project extends [@noble/curves](https://github.com/paulmillr/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 `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 ## Installation ```bash npm install noble-curves-extended ``` ## Peer Dependencies 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. ## 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`) ```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); ``` ### High-Level Unified API (`@/unified`) ```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 }); ``` ### 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. ```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 }); ``` ## API ### RandomBytes Type ```typescript type RandomBytes = (byteLength?: number) => Uint8Array; ``` ### Low-Level Curves (`@/curves`) #### Curve Creation Functions - `createEd25519(randomBytes: RandomBytes)`: Creates Ed25519 curve instance - `createSecp256k1(randomBytes: RandomBytes)`: Creates secp256k1 curve instance - `createP256(randomBytes: RandomBytes)`: Creates NIST P256 curve instance - `createP384(randomBytes: RandomBytes)`: Creates NIST P384 curve instance - `createP521(randomBytes: RandomBytes)`: Creates NIST P521 curve instance - `createX25519(randomBytes: RandomBytes)`: Creates X25519 curve instance - `createBls12_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 curve - `keyByteLength: number`: The byte length of the key - `randomPrivateKey(): Uint8Array`: Generate a random private key - `getPublicKey(privateKey: Uint8Array, compressed?: boolean): Uint8Array`: Derive public key from private key - `toJwkPrivateKey(privateKey: Uint8Array): JwkPrivateKey`: Convert private key to JWK format - `toJwkPublicKey(publicKey: Uint8Array): JwkPublicKey`: Convert public key to JWK format - `toRawPrivateKey(jwkPrivateKey: JwkPrivateKey): Uint8Array`: Convert JWK private key to raw format - `toRawPublicKey(jwkPublicKey: JwkPublicKey): Uint8Array`: Convert JWK public key to raw format #### Signature Curves Additional Methods - `signatureAlgorithmName: SignatureAlgorithmName`: The signature algorithm name - `sign({ privateKey, message, recovered? }): Uint8Array`: Sign a message - `verify({ publicKey, message, signature }): boolean`: Verify a signature - `recoverPublicKey({ 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. 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](https://tools.ietf.org/html/rfc7638): - `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); ``` ### BLS12-381 Specific 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 ## 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