From 1545230ee59b6e360f0e660cc2eb567d46ff3805 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Fri, 18 Aug 2023 21:08:46 +0000 Subject: [PATCH] modular, weierstrass, bls: use new mapHashToField --- README.md | 4 --- src/abstract/bls.ts | 8 +++--- src/abstract/modular.ts | 51 +++++++++++++++++++++++++++---------- src/abstract/weierstrass.ts | 7 ++--- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index fac5167..9bdadcf 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,6 @@ Audited & minimal JS implementation of elliptic curve cryptography. for encoding or hashing an arbitrary string to an elliptic curve point - 🧜‍♂️ Poseidon ZK-friendly hash -Check out [Upgrading](#upgrading) if you've previously used single-feature noble -packages. See [Resources](#resources) for articles and real-world software that uses curves. - ### This library belongs to _noble_ crypto > **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools. @@ -40,7 +37,6 @@ For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/m For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values). If you don't like NPM, a standalone [noble-curves.js](https://github.com/paulmillr/noble-curves/releases) is also available. -- [Usage](#usage) - [Implementations](#implementations) - [ECDSA signature scheme](#ecdsa-signature-scheme) - [ECDSA public key recovery & extra entropy](#ecdsa-public-key-recovery--extra-entropy) diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index 282cbe7..fd56d6e 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -12,7 +12,7 @@ * Some projects may prefer to swap this relation, it is not supported for now. */ import { AffinePoint } from './curve.js'; -import { IField, hashToPrivateScalar } from './modular.js'; +import { IField, getMinHashLength, mapHashToField } from './modular.js'; import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js'; import * as htf from './hash-to-curve.js'; import { @@ -189,10 +189,8 @@ export function bls( const utils = { randomPrivateKey: (): Uint8Array => { - const bytesTaken = groupLen + Math.ceil(groupLen / 2); // e.g. 48b for 32b field - const rand = CURVE.randomBytes(bytesTaken); - const num = hashToPrivateScalar(rand, Fr.ORDER); - return Fr.toBytes(num); + const length = getMinHashLength(Fr.ORDER); + return mapHashToField(CURVE.randomBytes(length), Fr.ORDER); }, calcPairingPrecomputes, }; diff --git a/src/abstract/modular.ts b/src/abstract/modular.ts index b8cdbc7..285a6b3 100644 --- a/src/abstract/modular.ts +++ b/src/abstract/modular.ts @@ -409,16 +409,9 @@ export function FpSqrtEven(Fp: IField, elm: T) { /** * "Constant-time" private key generation utility. - * Can take (n + 8) or more bytes of uniform input e.g. from CSPRNG or KDF - * and convert them into private scalar, with the modulo bias being negligible. - * Needs at least 40 bytes of input for 32-byte private key. - * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ - * FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final - * hash-to-curve, https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-hashing-to-a-finite-field - * @param hash hash output from SHA3 or a similar function - * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n) - * @param isLE interpret hash bytes as LE num - * @returns valid private scalar + * Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field). + * Which makes it slightly more biased, less secure. + * @deprecated use mapKeyToField instead */ export function hashToPrivateScalar( hash: string | Uint8Array, @@ -428,11 +421,43 @@ export function hashToPrivateScalar( hash = ensureBytes('privateHash', hash); const hashLen = hash.length; const minLen = nLength(groupOrder).nByteLength + 8; // 8b (64 bits) gives 2^-64 bias - // Small numbers aren't supported: need to understand their security / bias story first. - // Huge numbers aren't supported for security: it's easier to detect timings of their ops in JS. if (minLen < 24 || hashLen < minLen || hashLen > 1024) throw new Error(`expected ${minLen}-1024 bytes of input, got ${hashLen}`); const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); - // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0 return mod(num, groupOrder - _1n) + _1n; } + +export function getFieldBytesLength(fieldOrder: bigint): number { + return nLength(fieldOrder).nByteLength; +} + +export function getMinHashLength(fieldOrder: bigint): number { + const length = getFieldBytesLength(fieldOrder); + return length + Math.ceil(length / 2); +} + +/** + * "Constant-time" private key generation utility. + * Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF + * and convert them into private scalar, with the modulo bias being negligible. + * Needs at least 48 bytes of input for 32-byte private key. + * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ + * FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final + * hash-to-curve, https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-hashing-to-a-finite-field + * @param hash hash output from SHA3 or a similar function + * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n) + * @param isLE interpret hash bytes as LE num + * @returns valid private scalar + */ +export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array { + const len = key.length; + const fieldLen = getFieldBytesLength(fieldOrder); + const minLen = getMinHashLength(fieldOrder); + // No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings. + if (len < 16 || len < minLen || len > 1024) + throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`); + const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key); + // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0 + const reduced = mod(num, fieldOrder - _1n) + _1n; + return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen) +} diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index efa2fd0..492e35e 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -849,11 +849,8 @@ export function weierstrass(curveDef: CurveType): CurveFn { * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible. */ randomPrivateKey: (): Uint8Array => { - const groupLen = CURVE.nByteLength; - const bytesTaken = groupLen + Math.ceil(groupLen / 2); // e.g. 48b for 32b field - const rand = CURVE.randomBytes(bytesTaken); - const num = mod.hashToPrivateScalar(rand, CURVE_ORDER); - return ut.numberToBytesBE(num, groupLen); + const length = mod.getMinHashLength(CURVE.n); + return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n); }, /**