modular, weierstrass, bls: use new mapHashToField

This commit is contained in:
Paul Miller 2023-08-18 21:08:46 +00:00
parent 2ce3b825f8
commit 1545230ee5
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
4 changed files with 43 additions and 27 deletions

@ -13,9 +13,6 @@ Audited & minimal JS implementation of elliptic curve cryptography.
for encoding or hashing an arbitrary string to an elliptic curve point for encoding or hashing an arbitrary string to an elliptic curve point
- 🧜‍♂️ Poseidon ZK-friendly hash - 🧜‍♂️ 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 ### This library belongs to _noble_ crypto
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools. > **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). 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. 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) - [Implementations](#implementations)
- [ECDSA signature scheme](#ecdsa-signature-scheme) - [ECDSA signature scheme](#ecdsa-signature-scheme)
- [ECDSA public key recovery & extra entropy](#ecdsa-public-key-recovery--extra-entropy) - [ECDSA public key recovery & extra entropy](#ecdsa-public-key-recovery--extra-entropy)

@ -12,7 +12,7 @@
* Some projects may prefer to swap this relation, it is not supported for now. * Some projects may prefer to swap this relation, it is not supported for now.
*/ */
import { AffinePoint } from './curve.js'; 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 { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
import * as htf from './hash-to-curve.js'; import * as htf from './hash-to-curve.js';
import { import {
@ -189,10 +189,8 @@ export function bls<Fp2, Fp6, Fp12>(
const utils = { const utils = {
randomPrivateKey: (): Uint8Array => { randomPrivateKey: (): Uint8Array => {
const bytesTaken = groupLen + Math.ceil(groupLen / 2); // e.g. 48b for 32b field const length = getMinHashLength(Fr.ORDER);
const rand = CURVE.randomBytes(bytesTaken); return mapHashToField(CURVE.randomBytes(length), Fr.ORDER);
const num = hashToPrivateScalar(rand, Fr.ORDER);
return Fr.toBytes(num);
}, },
calcPairingPrecomputes, calcPairingPrecomputes,
}; };

@ -409,16 +409,9 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
/** /**
* "Constant-time" private key generation utility. * "Constant-time" private key generation utility.
* Can take (n + 8) or more bytes of uniform input e.g. from CSPRNG or KDF * Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
* and convert them into private scalar, with the modulo bias being negligible. * Which makes it slightly more biased, less secure.
* Needs at least 40 bytes of input for 32-byte private key. * @deprecated use mapKeyToField instead
* 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 hashToPrivateScalar( export function hashToPrivateScalar(
hash: string | Uint8Array, hash: string | Uint8Array,
@ -428,11 +421,43 @@ export function hashToPrivateScalar(
hash = ensureBytes('privateHash', hash); hash = ensureBytes('privateHash', hash);
const hashLen = hash.length; const hashLen = hash.length;
const minLen = nLength(groupOrder).nByteLength + 8; // 8b (64 bits) gives 2^-64 bias 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) if (minLen < 24 || hashLen < minLen || hashLen > 1024)
throw new Error(`expected ${minLen}-1024 bytes of input, got ${hashLen}`); throw new Error(`expected ${minLen}-1024 bytes of input, got ${hashLen}`);
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); 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; 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)
}

@ -849,11 +849,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible. * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
*/ */
randomPrivateKey: (): Uint8Array => { randomPrivateKey: (): Uint8Array => {
const groupLen = CURVE.nByteLength; const length = mod.getMinHashLength(CURVE.n);
const bytesTaken = groupLen + Math.ceil(groupLen / 2); // e.g. 48b for 32b field return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
const rand = CURVE.randomBytes(bytesTaken);
const num = mod.hashToPrivateScalar(rand, CURVE_ORDER);
return ut.numberToBytesBE(num, groupLen);
}, },
/** /**