UNPKG

@noble/curves

Version:

Audited & minimal JS implementation of elliptic curve cryptography

598 lines 19.3 kB
/** * Hex, bytes and number utilities. * @module */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ import { abytes as abytes_, anumber as anumber_, bytesToHex as bytesToHex_, concatBytes as concatBytes_, hexToBytes as hexToBytes_, isBytes as isBytes_, randomBytes as randomBytes_, } from '@noble/hashes/utils.js'; /** * Validates that a value is a byte array. * @param value - Value to validate. * @param length - Optional exact byte length. * @param title - Optional field name. * @returns Original byte array. * @example * Reject non-byte input before passing data into curve code. * * ```ts * abytes(new Uint8Array(1)); * ``` */ export const abytes = (value, length, title) => abytes_(value, length, title); /** * Validates that a value is a non-negative safe integer. * @param n - Value to validate. * @param title - Optional field name. * @example * Validate a numeric length before allocating buffers. * * ```ts * anumber(1); * ``` */ export const anumber = anumber_; /** * Encodes bytes as lowercase hex. * @param bytes - Bytes to encode. * @returns Lowercase hex string. * @example * Serialize bytes as hex for logging or fixtures. * * ```ts * bytesToHex(Uint8Array.of(1, 2, 3)); * ``` */ export const bytesToHex = bytesToHex_; /** * Concatenates byte arrays. * @param arrays - Byte arrays to join. * @returns Concatenated bytes. * @example * Join domain-separated chunks into one buffer. * * ```ts * concatBytes(Uint8Array.of(1), Uint8Array.of(2)); * ``` */ export const concatBytes = (...arrays) => concatBytes_(...arrays); /** * Decodes lowercase or uppercase hex into bytes. * @param hex - Hex string to decode. * @returns Decoded bytes. * @example * Parse fixture hex into bytes before hashing. * * ```ts * hexToBytes('0102'); * ``` */ export const hexToBytes = (hex) => hexToBytes_(hex); /** * Checks whether a value is a Uint8Array. * @param a - Value to inspect. * @returns `true` when `a` is a Uint8Array. * @example * Branch on byte input before decoding it. * * ```ts * isBytes(new Uint8Array(1)); * ``` */ export const isBytes = isBytes_; /** * Reads random bytes from the platform CSPRNG. * @param bytesLength - Number of random bytes to read. * @returns Fresh random bytes. * @example * Generate a random seed for a keypair. * * ```ts * randomBytes(2); * ``` */ export const randomBytes = (bytesLength) => randomBytes_(bytesLength); const _0n = /* @__PURE__ */ BigInt(0); const _1n = /* @__PURE__ */ BigInt(1); /** * Validates that a flag is boolean. * @param value - Value to validate. * @param title - Optional field name. * @returns Original value. * @throws On wrong argument types. {@link TypeError} * @example * Reject non-boolean option flags early. * * ```ts * abool(true); * ``` */ export function abool(value, title = '') { if (typeof value !== 'boolean') { const prefix = title && `"${title}" `; throw new TypeError(prefix + 'expected boolean, got type=' + typeof value); } return value; } /** * Validates that a value is a non-negative bigint or safe integer. * @param n - Value to validate. * @returns The same validated value. * @throws On wrong argument ranges or values. {@link RangeError} * @example * Validate one integer-like value before serializing it. * * ```ts * abignumber(1n); * ``` */ export function abignumber(n) { if (typeof n === 'bigint') { if (!isPosBig(n)) throw new RangeError('positive bigint expected, got ' + n); } else anumber(n); return n; } /** * Validates that a value is a safe integer. * @param value - Integer to validate. * @param title - Optional field name. * @throws On wrong argument types. {@link TypeError} * @throws On wrong argument ranges or values. {@link RangeError} * @example * Validate a window size before scalar arithmetic uses it. * * ```ts * asafenumber(1); * ``` */ export function asafenumber(value, title = '') { if (typeof value !== 'number') { const prefix = title && `"${title}" `; throw new TypeError(prefix + 'expected number, got type=' + typeof value); } if (!Number.isSafeInteger(value)) { const prefix = title && `"${title}" `; throw new RangeError(prefix + 'expected safe integer, got ' + value); } } /** * Encodes a bigint into even-length big-endian hex. * The historical "unpadded" name only means "no fixed-width field padding"; odd-length hex still * gets one leading zero nibble so the result always represents whole bytes. * @param num - Number to encode. * @returns Big-endian hex string. * @throws On wrong argument ranges or values. {@link RangeError} * @example * Encode a scalar into hex without a `0x` prefix. * * ```ts * numberToHexUnpadded(255n); * ``` */ export function numberToHexUnpadded(num) { const hex = abignumber(num).toString(16); return hex.length & 1 ? '0' + hex : hex; } /** * Parses a big-endian hex string into bigint. * Accepts odd-length hex through the native `BigInt('0x' + hex)` parser and currently surfaces the * same native `SyntaxError` for malformed hex instead of wrapping it in a library-specific error. * @param hex - Hex string without `0x`. * @returns Parsed bigint value. * @throws On wrong argument types. {@link TypeError} * @example * Parse a scalar from fixture hex. * * ```ts * hexToNumber('ff'); * ``` */ export function hexToNumber(hex) { if (typeof hex !== 'string') throw new TypeError('hex string expected, got ' + typeof hex); return hex === '' ? _0n : BigInt('0x' + hex); // Big Endian } // BE: Big Endian, LE: Little Endian /** * Parses big-endian bytes into bigint. * @param bytes - Bytes in big-endian order. * @returns Parsed bigint value. * @throws On wrong argument types. {@link TypeError} * @example * Read a scalar encoded in network byte order. * * ```ts * bytesToNumberBE(Uint8Array.of(1, 0)); * ``` */ export function bytesToNumberBE(bytes) { return hexToNumber(bytesToHex_(bytes)); } /** * Parses little-endian bytes into bigint. * @param bytes - Bytes in little-endian order. * @returns Parsed bigint value. * @throws On wrong argument types. {@link TypeError} * @example * Read a scalar encoded in little-endian form. * * ```ts * bytesToNumberLE(Uint8Array.of(1, 0)); * ``` */ export function bytesToNumberLE(bytes) { return hexToNumber(bytesToHex_(copyBytes(abytes_(bytes)).reverse())); } /** * Encodes a bigint into fixed-length big-endian bytes. * @param n - Number to encode. * @param len - Output length in bytes. Must be greater than zero. * @returns Big-endian byte array. * @throws On wrong argument ranges or values. {@link RangeError} * @example * Serialize a scalar into a 32-byte field element. * * ```ts * numberToBytesBE(255n, 2); * ``` */ export function numberToBytesBE(n, len) { anumber_(len); if (len === 0) throw new RangeError('zero length'); n = abignumber(n); const hex = n.toString(16); // Detect overflow before hex parsing so oversized values don't leak the shared odd-hex error. if (hex.length > len * 2) throw new RangeError('number too large'); return hexToBytes_(hex.padStart(len * 2, '0')); } /** * Encodes a bigint into fixed-length little-endian bytes. * @param n - Number to encode. * @param len - Output length in bytes. * @returns Little-endian byte array. * @throws On wrong argument ranges or values. {@link RangeError} * @example * Serialize a scalar for little-endian protocols. * * ```ts * numberToBytesLE(255n, 2); * ``` */ export function numberToBytesLE(n, len) { return numberToBytesBE(n, len).reverse(); } // Unpadded, rarely used /** * Encodes a bigint into variable-length big-endian bytes. * @param n - Number to encode. * @returns Variable-length big-endian bytes. * @throws On wrong argument ranges or values. {@link RangeError} * @example * Serialize a bigint without fixed-width padding. * * ```ts * numberToVarBytesBE(255n); * ``` */ export function numberToVarBytesBE(n) { return hexToBytes_(numberToHexUnpadded(abignumber(n))); } // Compares 2 u8a-s in kinda constant time /** * Compares two byte arrays in constant-ish time. * @param a - Left byte array. * @param b - Right byte array. * @returns `true` when bytes match. * @example * Compare two encoded points without early exit. * * ```ts * equalBytes(Uint8Array.of(1), Uint8Array.of(1)); * ``` */ export function equalBytes(a, b) { a = abytes(a); b = abytes(b); if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i]; return diff === 0; } /** * Copies Uint8Array. We can't use u8a.slice(), because u8a can be Buffer, * and Buffer#slice creates mutable copy. Never use Buffers! * @param bytes - Bytes to copy. * @returns Detached copy. * @example * Make an isolated copy before mutating serialized bytes. * * ```ts * copyBytes(Uint8Array.of(1, 2, 3)); * ``` */ export function copyBytes(bytes) { // `Uint8Array.from(...)` would also accept arrays / other typed arrays. Keep this helper strict // because callers use it at byte-validation boundaries before mutating the detached copy. return Uint8Array.from(abytes(bytes)); } /** * Decodes 7-bit ASCII string to Uint8Array, throws on non-ascii symbols * Should be safe to use for things expected to be ASCII. * Returns exact same result as `TextEncoder` for ASCII or throws. * @param ascii - ASCII input text. * @returns Encoded bytes. * @throws On wrong argument types. {@link TypeError} * @example * Encode an ASCII domain-separation tag. * * ```ts * asciiToBytes('ABC'); * ``` */ export function asciiToBytes(ascii) { if (typeof ascii !== 'string') throw new TypeError('ascii string expected, got ' + typeof ascii); return Uint8Array.from(ascii, (c, i) => { const charCode = c.charCodeAt(0); if (c.length !== 1 || charCode > 127) { throw new RangeError(`string contains non-ASCII character "${ascii[i]}" with code ${charCode} at position ${i}`); } return charCode; }); } // Historical name: this accepts non-negative bigints, including zero. const isPosBig = (n) => typeof n === 'bigint' && _0n <= n; /** * Checks whether a bigint lies inside a half-open range. * @param n - Candidate value. * @param min - Inclusive lower bound. * @param max - Exclusive upper bound. * @returns `true` when the value is inside the range. * @example * Check whether a candidate scalar fits the field order. * * ```ts * inRange(2n, 1n, 3n); * ``` */ export function inRange(n, min, max) { return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max; } /** * Asserts `min <= n < max`. NOTE: upper bound is exclusive. * @param title - Value label for error messages. * @param n - Candidate value. * @param min - Inclusive lower bound. * @param max - Exclusive upper bound. * Wrong-type inputs are not separated from out-of-range values here: they still flow through the * shared `RangeError` path because this is only a throwing wrapper around `inRange(...)`. * @throws On wrong argument ranges or values. {@link RangeError} * @example * Assert that a bigint stays within one half-open range. * * ```ts * aInRange('x', 2n, 1n, 256n); * ``` */ export function aInRange(title, n, min, max) { // Why min <= n < max and not a (min < n < max) OR b (min <= n <= max)? // consider P=256n, min=0n, max=P // - a for min=0 would require -1: `inRange('x', x, -1n, P)` // - b would commonly require subtraction: `inRange('x', x, 0n, P - 1n)` // - our way is the cleanest: `inRange('x', x, 0n, P) if (!inRange(n, min, max)) throw new RangeError('expected valid ' + title + ': ' + min + ' <= n < ' + max + ', got ' + n); } // Bit operations /** * Calculates amount of bits in a bigint. * Same as `n.toString(2).length` * TODO: merge with nLength in modular * @param n - Value to inspect. * @returns Bit length. * @throws If the value is negative. {@link Error} * @example * Measure the bit length of a scalar before serialization. * * ```ts * bitLen(8n); * ``` */ export function bitLen(n) { // Size callers in this repo only use non-negative orders / scalars, so negative inputs are a // contract bug and must not silently collapse to zero bits. if (n < _0n) throw new Error('expected non-negative bigint, got ' + n); let len; for (len = 0; n > _0n; n >>= _1n, len += 1) ; return len; } /** * Gets single bit at position. * NOTE: first bit position is 0 (same as arrays) * Same as `!!+Array.from(n.toString(2)).reverse()[pos]` * @param n - Source value. * @param pos - Bit position. Negative positions are passed through to raw * bigint shift semantics; because the mask is built as `1n << pos`, * they currently collapse to `0n` and make the helper a no-op. * @returns Bit as bigint. * @example * Gets single bit at position. * * ```ts * bitGet(5n, 0); * ``` */ export function bitGet(n, pos) { return (n >> BigInt(pos)) & _1n; } /** * Sets single bit at position. * @param n - Source value. * @param pos - Bit position. Negative positions are passed through to raw bigint shift semantics, * so they currently behave like left shifts. * @param value - Whether the bit should be set. * @returns Updated bigint. * @example * Sets single bit at position. * * ```ts * bitSet(0n, 1, true); * ``` */ export function bitSet(n, pos, value) { const mask = _1n << BigInt(pos); // Clearing needs AND-not here; OR with zero leaves an already-set bit untouched. return value ? n | mask : n & ~mask; } /** * Calculate mask for N bits. Not using ** operator with bigints because of old engines. * Same as BigInt(`0b${Array(i).fill('1').join('')}`) * @param n - Number of bits. Negative widths are currently passed through to raw bigint shift * semantics and therefore produce `-1n`. * @returns Bitmask value. * @example * Calculate mask for N bits. * * ```ts * bitMask(4); * ``` */ export const bitMask = (n) => (_1n << BigInt(n)) - _1n; /** * Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs. * @param hashLen - Hash output size in bytes. Callers are expected to pass a positive length; `0` * is not rejected here and would make the internal generate loop non-progressing. * @param qByteLen - Requested output size in bytes. Callers are expected to pass a positive length. * @param hmacFn - HMAC implementation. * @returns Function that will call DRBG until the predicate returns anything * other than `undefined`. * @throws On wrong argument types. {@link TypeError} * @example * Build a deterministic nonce generator for RFC6979-style signing. * * ```ts * import { createHmacDrbg } from '@noble/curves/utils.js'; * import { hmac } from '@noble/hashes/hmac.js'; * import { sha256 } from '@noble/hashes/sha2.js'; * const drbg = createHmacDrbg(32, 32, (key, msg) => hmac(sha256, key, msg)); * const seed = new Uint8Array(32); * drbg(seed, (bytes) => bytes); * ``` */ export function createHmacDrbg(hashLen, qByteLen, hmacFn) { anumber_(hashLen, 'hashLen'); anumber_(qByteLen, 'qByteLen'); if (typeof hmacFn !== 'function') throw new TypeError('hmacFn must be a function'); // creates Uint8Array const u8n = (len) => new Uint8Array(len); const NULL = Uint8Array.of(); const byte0 = Uint8Array.of(0x00); const byte1 = Uint8Array.of(0x01); const _maxDrbgIters = 1000; // Step B, Step C: set hashLen to 8*ceil(hlen/8). // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 signatures. let v = u8n(hashLen); // Steps B and C of RFC6979 3.2. let k = u8n(hashLen); let i = 0; // Iterations counter, will throw when over 1000 const reset = () => { v.fill(1); k.fill(0); i = 0; }; // hmac(k)(v, ...values) const h = (...msgs) => hmacFn(k, concatBytes(v, ...msgs)); const reseed = (seed = NULL) => { // HMAC-DRBG reseed() function. Steps D-G k = h(byte0, seed); // k = hmac(k || v || 0x00 || seed) v = h(); // v = hmac(k || v) if (seed.length === 0) return; k = h(byte1, seed); // k = hmac(k || v || 0x01 || seed) v = h(); // v = hmac(k || v) }; const gen = () => { // HMAC-DRBG generate() function if (i++ >= _maxDrbgIters) throw new Error('drbg: tried max amount of iterations'); let len = 0; const out = []; while (len < qByteLen) { v = h(); const sl = v.slice(); out.push(sl); len += v.length; } return concatBytes(...out); }; const genUntil = (seed, pred) => { reset(); reseed(seed); // Steps D-G let res = undefined; // Step H: grind until the predicate accepts a candidate. // Falsy values like 0 are valid outputs. while ((res = pred(gen())) === undefined) reseed(); reset(); return res; }; return genUntil; } /** * Validates declared required and optional field types on a plain object. * Extra keys are intentionally ignored because many callers validate only the subset they use from * richer option bags or runtime objects. * @param object - Object to validate. * @param fields - Required field types. * @param optFields - Optional field types. * @throws On wrong argument types. {@link TypeError} * @example * Check user options before building a curve helper. * * ```ts * validateObject({ flag: true }, { flag: 'boolean' }); * ``` */ export function validateObject(object, fields = {}, optFields = {}) { if (Object.prototype.toString.call(object) !== '[object Object]') throw new TypeError('expected valid options object'); function checkField(fieldName, expectedType, isOpt) { // Config/data fields must be explicit own properties, but runtime objects such as Field // instances intentionally satisfy required method slots via their shared prototype. if (!isOpt && expectedType !== 'function' && !Object.hasOwn(object, fieldName)) throw new TypeError(`param "${fieldName}" is invalid: expected own property`); const val = object[fieldName]; if (isOpt && val === undefined) return; const current = typeof val; if (current !== expectedType || val === null) throw new TypeError(`param "${fieldName}" is invalid: expected ${expectedType}, got ${current}`); } const iter = (f, isOpt) => Object.entries(f).forEach(([k, v]) => checkField(k, v, isOpt)); iter(fields, false); iter(optFields, true); } /** * Throws not implemented error. * @returns Never returns. * @throws If the unfinished code path is reached. {@link Error} * @example * Surface the placeholder error from an unfinished code path. * * ```ts * try { * notImplemented(); * } catch {} * ``` */ export const notImplemented = () => { throw new Error('not implemented'); }; //# sourceMappingURL=utils.js.map