Rename group to curve. More refactoring

This commit is contained in:
Paul Miller 2023-01-26 03:14:21 +00:00
parent be0b2a32a5
commit 0fb78b7097
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
13 changed files with 190 additions and 738 deletions

@ -73,10 +73,10 @@
"import": "./lib/esm/abstract/hash-to-curve.js", "import": "./lib/esm/abstract/hash-to-curve.js",
"default": "./lib/abstract/hash-to-curve.js" "default": "./lib/abstract/hash-to-curve.js"
}, },
"./abstract/group": { "./abstract/curve": {
"types": "./lib/abstract/group.d.ts", "types": "./lib/abstract/curve.d.ts",
"import": "./lib/esm/abstract/group.js", "import": "./lib/esm/abstract/curve.js",
"default": "./lib/abstract/group.js" "default": "./lib/abstract/curve.js"
}, },
"./abstract/utils": { "./abstract/utils": {
"types": "./lib/abstract/utils.d.ts", "types": "./lib/abstract/utils.d.ts",

@ -193,7 +193,7 @@ export function bls<Fp2, Fp6, Fp12>(
) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }), ) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) => expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
htf.expand_message_xmd(msg, DST, lenInBytes, H), htf.expand_message_xmd(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)), hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(mod.hashToPrivateScalar(hash, CURVE.r)),
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength), randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)), randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
}; };

@ -1,5 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Abelian group utilities // Abelian group utilities
import { Field, validateField, nLength } from './modular.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
@ -144,3 +145,36 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
}, },
}; };
} }
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
// Though generator can be different (Fp2 / Fp6 for BLS).
export type BasicCurve<T> = {
Fp: Field<T>; // Field over which we'll do calculations (Fp)
n: bigint; // Curve order, total count of valid points in the field
nBitLength?: number; // bit length of curve order
nByteLength?: number; // byte length of curve order
h: bigint; // cofactor. we can assign default=1, but users will just ignore it w/o validation
hEff?: bigint; // Number to multiply to clear cofactor
Gx: T; // base point X coordinate
Gy: T; // base point Y coordinate
wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
};
export function validateBasicCurveOpts<FP, T>(curve: BasicCurve<FP> & T) {
validateField(curve.Fp);
for (const i of ['n', 'h'] as const) {
const val = curve[i];
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
}
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element');
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element');
for (const i of ['nBitLength', 'nByteLength'] as const) {
const val = curve[i];
if (val === undefined) continue; // Optional
if (!Number.isSafeInteger(val)) throw new Error(`Invalid param ${i}=${val} (${typeof val})`);
}
// Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
}

@ -1,9 +1,16 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y² // Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
import * as mod from './modular.js'; import { mod } from './modular.js';
import * as ut from './utils.js'; import {
import { ensureBytes, Hex } from './utils.js'; bytesToHex,
import { Group, GroupConstructor, wNAF } from './group.js'; bytesToNumberLE,
concatBytes,
ensureBytes,
FHash,
Hex,
numberToBytesLE,
} from './utils.js';
import { Group, GroupConstructor, wNAF, BasicCurve, validateBasicCurveOpts } from './curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0); const _0n = BigInt(0);
@ -12,20 +19,20 @@ const _2n = BigInt(2);
const _8n = BigInt(8); const _8n = BigInt(8);
// Edwards curves must declare params a & d. // Edwards curves must declare params a & d.
export type CurveType = ut.BasicCurve<bigint> & { export type CurveType = BasicCurve<bigint> & {
a: bigint; // curve param a a: bigint; // curve param a
d: bigint; // curve param d d: bigint; // curve param d
hash: ut.FHash; // Hashing hash: FHash; // Hashing
randomBytes: (bytesLength?: number) => Uint8Array; // CSPRNG randomBytes: (bytesLength?: number) => Uint8Array; // CSPRNG
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v) uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
preHash?: ut.FHash; // RFC 8032 pre-hashing of messages to sign() / verify() preHash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
mapToCurve?: (scalar: bigint[]) => AffinePoint; // for hash-to-curve standard mapToCurve?: (scalar: bigint[]) => AffinePoint; // for hash-to-curve standard
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = ut.validateOpts(curve); const opts = validateBasicCurveOpts(curve);
if (typeof opts.hash !== 'function') throw new Error('Invalid hash function'); if (typeof opts.hash !== 'function') throw new Error('Invalid hash function');
for (const i of ['a', 'd'] as const) { for (const i of ['a', 'd'] as const) {
const val = opts[i]; const val = opts[i];
@ -329,7 +336,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const normed = hex.slice(); // copy again, we'll manipulate it const normed = hex.slice(); // copy again, we'll manipulate it
const lastByte = hex[len - 1]; // select last byte const lastByte = hex[len - 1]; // select last byte
normed[len - 1] = lastByte & ~0x80; // clear last bit normed[len - 1] = lastByte & ~0x80; // clear last bit
const y = ut.bytesToNumberLE(normed); const y = bytesToNumberLE(normed);
if (y === _0n) { if (y === _0n) {
// y=0 is allowed // y=0 is allowed
} else { } else {
@ -355,20 +362,23 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
toRawBytes(): Uint8Array { toRawBytes(): Uint8Array {
const { x, y } = this.toAffine(); const { x, y } = this.toAffine();
const bytes = ut.numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y) const bytes = numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
return bytes; // and use the last byte to encode sign of x return bytes; // and use the last byte to encode sign of x
} }
toHex(): string { toHex(): string {
return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string. return bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.
} }
} }
const { BASE: G, ZERO: I } = Point; const { BASE: G, ZERO: I } = Point;
const wnaf = wNAF(Point, nByteLength * 8); const wnaf = wNAF(Point, nByteLength * 8);
function modN(a: bigint) {
return mod(a, CURVE_ORDER);
}
// Little-endian SHA512 with modulo n // Little-endian SHA512 with modulo n
function modnLE(hash: Uint8Array): bigint { function modN_LE(hash: Uint8Array): bigint {
return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER); return modN(bytesToNumberLE(hash));
} }
function isHex(item: Hex, err: string) { function isHex(item: Hex, err: string) {
if (typeof item !== 'string' && !(item instanceof Uint8Array)) if (typeof item !== 'string' && !(item instanceof Uint8Array))
@ -384,7 +394,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len); const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len);
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6) const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
const scalar = modnLE(head); // The actual private scalar const scalar = modN_LE(head); // The actual private scalar
const point = G.multiply(scalar); // Point on Edwards curve aka public key const point = G.multiply(scalar); // Point on Edwards curve aka public key
const pointBytes = point.toRawBytes(); // Uint8Array representation const pointBytes = point.toRawBytes(); // Uint8Array representation
return { head, prefix, scalar, point, pointBytes }; return { head, prefix, scalar, point, pointBytes };
@ -397,8 +407,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// int('LE', SHA512(dom2(F, C) || msgs)) mod N // int('LE', SHA512(dom2(F, C) || msgs)) mod N
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) { function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
const msg = ut.concatBytes(...msgs); const msg = concatBytes(...msgs);
return modnLE(cHash(domain(msg, ensureBytes(context), !!preHash))); return modN_LE(cHash(domain(msg, ensureBytes(context), !!preHash)));
} }
/** Signs message with privateKey. RFC8032 5.1.6 */ /** Signs message with privateKey. RFC8032 5.1.6 */
@ -410,9 +420,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M) const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
const R = G.multiply(r).toRawBytes(); // R = rG const R = G.multiply(r).toRawBytes(); // R = rG
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M) const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
const s = mod.mod(r + k * scalar, CURVE_ORDER); // S = (r + k * s) mod L const s = modN(r + k * scalar); // S = (r + k * s) mod L
assertGE0(s); // 0 <= s < l assertGE0(s); // 0 <= s < l
const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES)); const res = concatBytes(R, numberToBytesLE(s, Fp.BYTES));
return ensureBytes(res, nByteLength * 2); // 64-byte signature return ensureBytes(res, nByteLength * 2); // 64-byte signature
} }
@ -425,7 +435,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
if (preHash) msg = preHash(msg); // for ed25519ph, etc if (preHash) msg = preHash(msg); // for ed25519ph, etc
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l const s = bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l
const SB = G.multiplyUnsafe(s); const SB = G.multiplyUnsafe(s);
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg); const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
const RkA = R.add(A.multiplyUnsafe(k)); const RkA = R.add(A.multiplyUnsafe(k));

@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as ut from './utils.js'; import * as ut from './utils.js';
import * as mod from './modular.js'; import * as mod from './modular.js';
import type { Group, GroupConstructor } from './group.js'; import type { Group, GroupConstructor } from './curve.js';
export type Opts = { export type Opts = {
// DST: a domain separation tag // DST: a domain separation tag
@ -38,13 +38,16 @@ export function validateOpts(opts: Opts) {
throw new Error('Invalid htf/hash function'); throw new Error('Invalid htf/hash function');
} }
// UTF8 to ui8a // Global symbols in both browsers and Node.js since v11
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway // See https://github.com/microsoft/TypeScript/issues/31535
export function stringToBytes(str: string) { declare const TextEncoder: any;
// return new TextEncoder().encode(str); declare const TextDecoder: any;
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i); export function stringToBytes(str: string): Uint8Array {
return bytes; if (typeof str !== 'string') {
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
} }
// Octet Stream to Integer (bytesToNumberBE) // Octet Stream to Integer (bytesToNumberBE)

@ -1,7 +1,13 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// TODO: remove circular imports
import * as utils from './utils.js';
// Utilities for modular arithmetics and finite fields // Utilities for modular arithmetics and finite fields
import {
bitMask,
numberToBytesBE,
numberToBytesLE,
bytesToNumberBE,
bytesToNumberLE,
ensureBytes,
} from './utils.js';
// prettier-ignore // prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3); const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
// prettier-ignore // prettier-ignore
@ -306,6 +312,14 @@ export function FpIsSquare<T>(f: Field<T>) {
}; };
} }
// CURVE.n lengths
export function nLength(n: bigint, nBitLength?: number) {
// Bit size, byte size of CURVE.n
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
const nByteLength = Math.ceil(_nBitLength / 8);
return { nBitLength: _nBitLength, nByteLength };
}
// NOTE: very fragile, always bench. Major performance points: // NOTE: very fragile, always bench. Major performance points:
// - NonNormalized ops // - NonNormalized ops
// - Object.freeze // - Object.freeze
@ -318,14 +332,14 @@ export function Fp(
redef: Partial<Field<bigint>> = {} redef: Partial<Field<bigint>> = {}
): Readonly<FpField> { ): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
const { nBitLength: BITS, nByteLength: BYTES } = utils.nLength(ORDER, bitLen); const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported'); if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
const sqrtP = FpSqrt(ORDER); const sqrtP = FpSqrt(ORDER);
const f: Readonly<FpField> = Object.freeze({ const f: Readonly<FpField> = Object.freeze({
ORDER, ORDER,
BITS, BITS,
BYTES, BYTES,
MASK: utils.bitMask(BITS), MASK: bitMask(BITS),
ZERO: _0n, ZERO: _0n,
ONE: _1n, ONE: _1n,
create: (num) => mod(num, ORDER), create: (num) => mod(num, ORDER),
@ -358,12 +372,11 @@ export function Fp(
// TODO: do we really need constant cmov? // TODO: do we really need constant cmov?
// We don't have const-time bigints anyway, so probably will be not very useful // We don't have const-time bigints anyway, so probably will be not very useful
cmov: (a, b, c) => (c ? b : a), cmov: (a, b, c) => (c ? b : a),
toBytes: (num) => toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),
isLE ? utils.numberToBytesLE(num, BYTES) : utils.numberToBytesBE(num, BYTES),
fromBytes: (bytes) => { fromBytes: (bytes) => {
if (bytes.length !== BYTES) if (bytes.length !== BYTES)
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`); throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
return isLE ? utils.bytesToNumberLE(bytes) : utils.bytesToNumberBE(bytes); return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
}, },
} as FpField); } as FpField);
return Object.freeze(f); return Object.freeze(f);
@ -380,3 +393,26 @@ export function FpSqrtEven<T>(Fp: Field<T>, elm: T) {
const root = Fp.sqrt(elm); const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? Fp.neg(root) : root; return Fp.isOdd(root) ? Fp.neg(root) : root;
} }
/**
* FIPS 186 B.4.1-compliant "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 neglible.
* 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/
* @param hash hash output from SHA3 or a similar function
* @returns valid private scalar
*/
export function hashToPrivateScalar(
hash: string | Uint8Array,
groupOrder: bigint,
isLE = false
): bigint {
hash = ensureBytes(hash);
const hashLen = hash.length;
const minLen = nLength(groupOrder).nByteLength + 8;
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod(num, groupOrder - _1n) + _1n;
}

@ -1,6 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js'; import * as mod from './modular.js';
import { ensureBytes, numberToBytesLE, bytesToNumberLE, isPositiveInt } from './utils.js'; import { ensureBytes, numberToBytesLE, bytesToNumberLE } from './utils.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
@ -33,7 +33,7 @@ function validateOpts(curve: CurveType) {
} }
for (const i of ['montgomeryBits', 'nByteLength'] as const) { for (const i of ['montgomeryBits', 'nByteLength'] as const) {
if (curve[i] === undefined) continue; // Optional if (curve[i] === undefined) continue; // Optional
if (!isPositiveInt(curve[i])) if (!Number.isSafeInteger(curve[i]))
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
} }
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) { for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) {
@ -46,7 +46,6 @@ function validateOpts(curve: CurveType) {
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
} }
// Set defaults // Set defaults
// ...nLength(curve.n, curve.nBitLength),
return Object.freeze({ ...curve } as const); return Object.freeze({ ...curve } as const);
} }

@ -1,3 +1,4 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Poseidon Hash (https://eprint.iacr.org/2019/458.pdf) // Poseidon Hash (https://eprint.iacr.org/2019/458.pdf)
// Website: https://www.poseidon-hash.info // Website: https://www.poseidon-hash.info

@ -1,11 +1,9 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
const _2n = BigInt(2); const _2n = BigInt(2);
const str = (a: any): a is string => typeof a === 'string'; const str = (a: any): a is string => typeof a === 'string';
export const big = (a: any): a is bigint => typeof a === 'bigint';
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array; const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
// We accept hex strings besides Uint8Array for simplicity // We accept hex strings besides Uint8Array for simplicity
@ -20,54 +18,6 @@ export type CHash = {
}; };
export type FHash = (message: Uint8Array | string) => Uint8Array; export type FHash = (message: Uint8Array | string) => Uint8Array;
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
// But generator can be different (Fp2/Fp6 for bls?)
export type BasicCurve<T> = {
// Field over which we'll do calculations (Fp)
Fp: mod.Field<T>;
// Curve order, total count of valid points in the field
n: bigint;
// Bit/byte length of curve order
nBitLength?: number;
nByteLength?: number;
// Cofactor
// NOTE: we can assign default value of 1, but then users will just ignore it, without validating with spec
// Has not use for now, but nice to have in API
h: bigint;
hEff?: bigint; // Number to multiply to clear cofactor
// Base point (x, y) aka generator point
Gx: T;
Gy: T;
// Wrap private key by curve order (% CURVE.n instead of throwing error)
wrapPrivateKey?: boolean;
// Point at infinity is perfectly valid point, but not valid public key.
// Disabled by default because of compatibility reasons with @noble/secp256k1
allowInfinityPoint?: boolean;
};
// Bans floats and integers above 2^53-1
export function isPositiveInt(num: any): num is number {
return typeof num === 'number' && Number.isSafeInteger(num) && num > 0;
}
export function validateOpts<FP, T>(curve: BasicCurve<FP> & T) {
mod.validateField(curve.Fp);
for (const i of ['n', 'h'] as const) {
const val = curve[i];
if (!big(val)) throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
}
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element');
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element');
for (const i of ['nBitLength', 'nByteLength'] as const) {
const val = curve[i];
if (val === undefined) continue; // Optional
if (!isPositiveInt(val)) throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
}
// Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
}
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
export function bytesToHex(bytes: Uint8Array): string { export function bytesToHex(bytes: Uint8Array): string {
if (!u8a(bytes)) throw new Error('Expected Uint8Array'); if (!u8a(bytes)) throw new Error('Expected Uint8Array');
@ -147,33 +97,6 @@ export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
return result; return result;
} }
// CURVE.n lengths
export function nLength(n: bigint, nBitLength?: number) {
// Bit size, byte size of CURVE.n
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
const nByteLength = Math.ceil(_nBitLength / 8);
return { nBitLength: _nBitLength, nByteLength };
}
/**
* FIPS 186 B.4.1-compliant "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 neglible.
* 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/
* @param hash hash output from SHA3 or a similar function
* @returns valid private scalar
*/
export function hashToPrivateScalar(hash: Hex, groupOrder: bigint, isLE = false): bigint {
hash = ensureBytes(hash);
const hashLen = hash.length;
const minLen = nLength(groupOrder).nByteLength + 8;
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod.mod(num, groupOrder - _1n) + _1n;
}
export function equalBytes(b1: Uint8Array, b2: Uint8Array) { export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
// We don't care about timing attacks here // We don't care about timing attacks here
if (b1.length !== b2.length) return false; if (b1.length !== b2.length) return false;

@ -1,16 +1,34 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Short Weierstrass curve. The formula is: y² = x³ + ax + b // Short Weierstrass curve. The formula is: y² = x³ + ax + b
import * as mod from './modular.js'; import * as mod from './modular.js';
import * as ut from './utils.js'; // import * as ut from './utils.js';
import { Hex, PrivKey } from './utils.js'; import {
import { Group, GroupConstructor, wNAF } from './group.js'; Hex,
PrivKey,
bytesToHex,
bytesToNumberBE,
ensureBytes,
hexToBytes,
concatBytes,
bitMask,
numberToBytesBE,
CHash,
numberToHexUnpadded,
} from './utils.js';
import {
Group,
GroupConstructor,
wNAF,
BasicCurve as CBasicCurve,
validateBasicCurveOpts,
} from './curve.js';
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array; type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
type EndomorphismOpts = { type EndomorphismOpts = {
beta: bigint; beta: bigint;
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint }; splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
}; };
export type BasicCurve<T> = ut.BasicCurve<T> & { export type BasicCurve<T> = CBasicCurve<T> & {
// Params: a, b // Params: a, b
a: T; a: T;
b: T; b: T;
@ -44,7 +62,7 @@ const DER = {
}, },
parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } { parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } {
if (data.length < 2 || data[0] !== 0x02) { if (data.length < 2 || data[0] !== 0x02) {
throw new DERError(`Invalid signature integer tag: ${ut.bytesToHex(data)}`); throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`);
} }
const len = data[1]; const len = data[1];
const res = data.subarray(2, len + 2); const res = data.subarray(2, len + 2);
@ -55,11 +73,11 @@ const DER = {
if (res[0] === 0x00 && res[1] <= 0x7f) { if (res[0] === 0x00 && res[1] <= 0x7f) {
throw new DERError('Invalid signature integer: trailing length'); throw new DERError('Invalid signature integer: trailing length');
} }
return { data: ut.bytesToNumberBE(res), left: data.subarray(len + 2) }; return { data: bytesToNumberBE(res), left: data.subarray(len + 2) };
}, },
parseSig(data: Uint8Array): { r: bigint; s: bigint } { parseSig(data: Uint8Array): { r: bigint; s: bigint } {
if (data.length < 2 || data[0] != 0x30) { if (data.length < 2 || data[0] != 0x30) {
throw new DERError(`Invalid signature tag: ${ut.bytesToHex(data)}`); throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`);
} }
if (data[1] !== data.length - 2) { if (data[1] !== data.length - 2) {
throw new DERError('Invalid signature: incorrect length'); throw new DERError('Invalid signature: incorrect length');
@ -67,9 +85,7 @@ const DER = {
const { data: r, left: sBytes } = DER.parseInt(data.subarray(2)); const { data: r, left: sBytes } = DER.parseInt(data.subarray(2));
const { data: s, left: rBytesLeft } = DER.parseInt(sBytes); const { data: s, left: rBytesLeft } = DER.parseInt(sBytes);
if (rBytesLeft.length) { if (rBytesLeft.length) {
throw new DERError( throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`);
`Invalid signature: left bytes after parsing: ${ut.bytesToHex(rBytesLeft)}`
);
} }
return { r, s }; return { r, s };
}, },
@ -138,7 +154,7 @@ export type CurvePointsType<T> = BasicCurve<T> & {
}; };
function validatePointOpts<T>(curve: CurvePointsType<T>) { function validatePointOpts<T>(curve: CurvePointsType<T>) {
const opts = ut.validateOpts(curve); const opts = validateBasicCurveOpts(curve);
const Fp = opts.Fp; const Fp = opts.Fp;
for (const i of ['a', 'b'] as const) { for (const i of ['a', 'b'] as const) {
if (!Fp.isValid(curve[i])) if (!Fp.isValid(curve[i]))
@ -216,10 +232,10 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
} else if (typeof key === 'string') { } else if (typeof key === 'string') {
if (key.length !== 2 * groupLen) throw new Error(`must be ${groupLen} bytes`); if (key.length !== 2 * groupLen) throw new Error(`must be ${groupLen} bytes`);
// Validates individual octets // Validates individual octets
num = ut.hexToNumber(key); num = bytesToNumberBE(ensureBytes(key));
} else if (key instanceof Uint8Array) { } else if (key instanceof Uint8Array) {
if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`); if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`);
num = ut.bytesToNumberBE(key); num = bytesToNumberBE(key);
} else { } else {
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key); throw new Error('private key must be bytes, hex or bigint, not ' + typeof key);
} }
@ -281,7 +297,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @param hex short/long ECDSA hex * @param hex short/long ECDSA hex
*/ */
static fromHex(hex: Hex): ProjectivePoint { static fromHex(hex: Hex): ProjectivePoint {
const P = ProjectivePoint.fromAffine(CURVE.fromBytes(ut.ensureBytes(hex))); const P = ProjectivePoint.fromAffine(CURVE.fromBytes(ensureBytes(hex)));
P.assertValidity(); P.assertValidity();
return P; return P;
} }
@ -565,7 +581,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
} }
toHex(isCompressed = true): string { toHex(isCompressed = true): string {
return ut.bytesToHex(this.toRawBytes(isCompressed)); return bytesToHex(this.toRawBytes(isCompressed));
} }
} }
const _bits = CURVE.nBitLength; const _bits = CURVE.nBitLength;
@ -608,7 +624,7 @@ export type CurveType = BasicCurve<bigint> & {
// Default options // Default options
lowS?: boolean; lowS?: boolean;
// Hashes // Hashes
hash: ut.CHash; // Because we need outputLen for DRBG hash: CHash; // Because we need outputLen for DRBG
hmac: HmacFnSync; hmac: HmacFnSync;
randomBytes: (bytesLength?: number) => Uint8Array; randomBytes: (bytesLength?: number) => Uint8Array;
// truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => Uint8Array; // truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => Uint8Array;
@ -617,8 +633,8 @@ export type CurveType = BasicCurve<bigint> & {
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = ut.validateOpts(curve); const opts = validateBasicCurveOpts(curve);
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen)) if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
throw new Error('Invalid hash function'); throw new Error('Invalid hash function');
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function'); if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function'); if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
@ -683,7 +699,7 @@ function hmacDrbg<T>(
out.push(sl); out.push(sl);
len += v.length; len += v.length;
} }
return ut.concatBytes(...out); return concatBytes(...out);
}; };
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => { const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
reset(); reset();
@ -716,7 +732,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
toBytes(c, point, isCompressed: boolean): Uint8Array { toBytes(c, point, isCompressed: boolean): Uint8Array {
const a = point.toAffine(); const a = point.toAffine();
const x = Fp.toBytes(a.x); const x = Fp.toBytes(a.x);
const cat = ut.concatBytes; const cat = concatBytes;
if (isCompressed) { if (isCompressed) {
// TODO: hasEvenY // TODO: hasEvenY
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
@ -730,7 +746,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const tail = bytes.subarray(1); const tail = bytes.subarray(1);
// this.assertValidity() is done inside of fromHex // this.assertValidity() is done inside of fromHex
if (len === compressedLen && (head === 0x02 || head === 0x03)) { if (len === compressedLen && (head === 0x02 || head === 0x03)) {
const x = ut.bytesToNumberBE(tail); const x = bytesToNumberBE(tail);
if (!isValidFieldElement(x)) throw new Error('Point is not on curve'); if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
const y2 = weierstrassEquation(x); // y² = x³ + ax + b const y2 = weierstrassEquation(x); // y² = x³ + ax + b
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4 let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
@ -751,7 +767,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}, },
}); });
const numToNByteStr = (num: bigint): string => const numToNByteStr = (num: bigint): string =>
ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength)); bytesToHex(numberToBytesBE(num, CURVE.nByteLength));
function isBiggerThanHalfOrder(number: bigint) { function isBiggerThanHalfOrder(number: bigint) {
const HALF = CURVE_ORDER >> _1n; const HALF = CURVE_ORDER >> _1n;
@ -762,7 +778,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s; return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
} }
// slice bytes num // slice bytes num
const slcNum = (b: Uint8Array, from: number, to: number) => ut.bytesToNumberBE(b.slice(from, to)); const slcNum = (b: Uint8Array, from: number, to: number) => bytesToNumberBE(b.slice(from, to));
/** /**
* ECDSA signature with its (r, s) properties. Supports DER & compact representations. * ECDSA signature with its (r, s) properties. Supports DER & compact representations.
@ -775,7 +791,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// pair (bytes of r, bytes of s) // pair (bytes of r, bytes of s)
static fromCompact(hex: Hex) { static fromCompact(hex: Hex) {
const gl = CURVE.nByteLength; const gl = CURVE.nByteLength;
hex = ut.ensureBytes(hex, gl * 2); hex = ensureBytes(hex, gl * 2);
return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl)); return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl));
} }
@ -785,7 +801,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const arr = hex instanceof Uint8Array; const arr = hex instanceof Uint8Array;
if (typeof hex !== 'string' && !arr) if (typeof hex !== 'string' && !arr)
throw new Error(`Signature.fromDER: Expected string or Uint8Array`); throw new Error(`Signature.fromDER: Expected string or Uint8Array`);
const { r, s } = DER.parseSig(arr ? hex : ut.hexToBytes(hex)); const { r, s } = DER.parseSig(ensureBytes(hex));
return new Signature(r, s); return new Signature(r, s);
} }
@ -802,7 +818,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
recoverPublicKey(msgHash: Hex): typeof Point.BASE { recoverPublicKey(msgHash: Hex): typeof Point.BASE {
const { n: N } = CURVE; // ECDSA public key recovery secg.org/sec1-v2.pdf 4.1.6 const { n: N } = CURVE; // ECDSA public key recovery secg.org/sec1-v2.pdf 4.1.6
const { r, s, recovery: rec } = this; const { r, s, recovery: rec } = this;
const h = bits2int_modN(ut.ensureBytes(msgHash)); // Truncate hash const h = bits2int_modN(ensureBytes(msgHash)); // Truncate hash
if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid'); if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');
const radj = rec === 2 || rec === 3 ? r + N : r; const radj = rec === 2 || rec === 3 ? r + N : r;
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid'); if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
@ -831,10 +847,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// DER-encoded // DER-encoded
toDERRawBytes() { toDERRawBytes() {
return ut.hexToBytes(this.toDERHex()); return hexToBytes(this.toDERHex());
} }
toDERHex() { toDERHex() {
const { numberToHexUnpadded: toHex } = ut; const toHex = numberToHexUnpadded;
const sHex = DER.slice(toHex(this.s)); const sHex = DER.slice(toHex(this.s));
const rHex = DER.slice(toHex(this.r)); const rHex = DER.slice(toHex(this.r));
const sHexL = sHex.length / 2; const sHexL = sHex.length / 2;
@ -847,9 +863,15 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// padded bytes of r, then padded bytes of s // padded bytes of r, then padded bytes of s
toCompactRawBytes() { toCompactRawBytes() {
return ut.hexToBytes(this.toCompactHex()); // const l = CURVE.nByteLength;
// const a = new Uint8Array(2 * l);
// a.set(numberToBytesBE(this.r, l), 0);
// a.set(numberToBytesBE(this.s, l), l);
// return a;
return hexToBytes(this.toCompactHex());
} }
toCompactHex() { toCompactHex() {
// return bytesToHex(this.toCompactRawBytes());
return numToNByteStr(this.r) + numToNByteStr(this.s); return numToNByteStr(this.r) + numToNByteStr(this.s);
} }
} }
@ -869,7 +891,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes. * Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
*/ */
hashToPrivateKey: (hash: Hex): Uint8Array => hashToPrivateKey: (hash: Hex): Uint8Array =>
ut.numberToBytesBE(ut.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength), numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength),
/** /**
* Produces cryptographically secure private key from random of size (nBitLength+64) * Produces cryptographically secure private key from random of size (nBitLength+64)
@ -941,7 +963,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m) // For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
// for some cases, since bytes.length * 8 is not actual bitLength. // for some cases, since bytes.length * 8 is not actual bitLength.
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here const num = bytesToNumberBE(bytes); // check for == u8 done here
return delta > 0 ? num >> BigInt(delta) : num; return delta > 0 ? num >> BigInt(delta) : num;
}; };
const bits2int_modN = const bits2int_modN =
@ -950,13 +972,13 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return mod.mod(bits2int(bytes), CURVE_ORDER); // can't use bytesToNumberBE here return mod.mod(bits2int(bytes), CURVE_ORDER); // can't use bytesToNumberBE here
}; };
// NOTE: pads output with zero as per spec // NOTE: pads output with zero as per spec
const ORDER_MASK = ut.bitMask(CURVE.nBitLength); const ORDER_MASK = bitMask(CURVE.nBitLength);
function int2octets(num: bigint): Uint8Array { function int2octets(num: bigint): Uint8Array {
if (typeof num !== 'bigint') throw new Error('Expected bigint'); if (typeof num !== 'bigint') throw new Error('Expected bigint');
if (!(_0n <= num && num < ORDER_MASK)) if (!(_0n <= num && num < ORDER_MASK))
throw new Error(`Expected number < 2^${CURVE.nBitLength}`); throw new Error(`Expected number < 2^${CURVE.nBitLength}`);
// works with order, can have different size than numToField! // works with order, can have different size than numToField!
return ut.numberToBytesBE(num, CURVE.nByteLength); return numberToBytesBE(num, CURVE.nByteLength);
} }
// Steps A, D of RFC6979 3.2 // Steps A, D of RFC6979 3.2
// Creates RFC6979 seed; converts msg/privKey to numbers. // Creates RFC6979 seed; converts msg/privKey to numbers.
@ -969,7 +991,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// Ban legacy options // Ban legacy options
throw new Error('sign() legacy options not supported'); throw new Error('sign() legacy options not supported');
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
if (prehash) msgHash = CURVE.hash(ut.ensureBytes(msgHash)); if (prehash) msgHash = CURVE.hash(ensureBytes(msgHash));
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because
// Step A is ignored, since we already provide hash instead of msg // Step A is ignored, since we already provide hash instead of msg
@ -977,8 +999,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// custom truncation for stark. For other curves it is essentially same as calling bits2int + mod // custom truncation for stark. For other curves it is essentially same as calling bits2int + mod
// However, we cannot later call bits2octets (which is truncateHash + int2octets), since nested bits2int is broken // However, we cannot later call bits2octets (which is truncateHash + int2octets), since nested bits2int is broken
// for curves where nBitLength % 8 !== 0, so we unwrap it here as int2octets call. // for curves where nBitLength % 8 !== 0, so we unwrap it here as int2octets call.
// const bits2octets = (bits)=>int2octets(ut.bytesToNumberBE(truncateHash(bits))) // const bits2octets = (bits)=>int2octets(bytesToNumberBE(truncateHash(bits)))
const h1int = bits2int_modN(ut.ensureBytes(msgHash)); const h1int = bits2int_modN(ensureBytes(msgHash));
const h1octets = int2octets(h1int); const h1octets = int2octets(h1int);
const d = normalizePrivateKey(privateKey); const d = normalizePrivateKey(privateKey);
@ -987,14 +1009,14 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (ent != null) { if (ent != null) {
// RFC6979 3.6: additional k' (optional) // RFC6979 3.6: additional k' (optional)
if (ent === true) ent = CURVE.randomBytes(Fp.BYTES); if (ent === true) ent = CURVE.randomBytes(Fp.BYTES);
const e = ut.ensureBytes(ent); const e = ensureBytes(ent);
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`); if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
seedArgs.push(e); seedArgs.push(e);
} }
// seed is constructed from private key and message // seed is constructed from private key and message
// Step D // Step D
// V, 0x00 are done in HmacDRBG constructor. // V, 0x00 are done in HmacDRBG constructor.
const seed = ut.concatBytes(...seedArgs); const seed = concatBytes(...seedArgs);
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
// Converts signature params into point w r/s, checks result for validity. // Converts signature params into point w r/s, checks result for validity.
function k2sig(kBytes: Uint8Array): Signature | undefined { function k2sig(kBytes: Uint8Array): Signature | undefined {
@ -1080,7 +1102,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
_sig = Signature.fromCompact(signature as Hex); _sig = Signature.fromCompact(signature as Hex);
} }
} }
msgHash = ut.ensureBytes(msgHash); msgHash = ensureBytes(msgHash);
P = Point.fromHex(publicKey); P = Point.fromHex(publicKey);
} catch (error) { } catch (error) {
return false; return false;

@ -1,6 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { weierstrass } from './abstract/weierstrass.js';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { weierstrass } from './abstract/weierstrass.js';
import { getHash } from './_shortw_utils.js'; import { getHash } from './_shortw_utils.js';
import { Fp } from './abstract/modular.js'; import { Fp } from './abstract/modular.js';
/** /**

@ -6,7 +6,7 @@ import * as fc from 'fast-check';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' }; import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' }; import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };
import { wNAF } from '../lib/esm/abstract/group.js'; import { wNAF } from '../lib/esm/abstract/curve.js';
const bls = bls12_381; const bls = bls12_381;
const { Fp2 } = bls; const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint; const G1Point = bls.G1.ProjectivePoint;
@ -970,352 +970,6 @@ describe('pairing', () => {
}); });
// hashToCurve // hashToCurve
describe('hash-to-curve', () => { describe('hash-to-curve', () => {
const DST = 'QUUX-V01-CS02-with-expander-SHA256-128';
const VECTORS = [
{
msg: '',
len: 0x20,
expected: '68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235',
},
{
msg: 'abc',
len: 0x20,
expected: 'd8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b97902f53a8a0d605615',
},
{
msg: 'abcdef0123456789',
len: 0x20,
expected: 'eff31487c770a893cfb36f912fbfcbff40d5661771ca4b2cb4eafe524333f5c1',
},
{
msg:
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
len: 0x20,
expected: 'b23a1d2b4d97b2ef7785562a7e8bac7eed54ed6e97e29aa51bfe3f12ddad1ff9',
},
{
msg:
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
len: 0x20,
expected: '4623227bcc01293b8c130bf771da8c298dede7383243dc0993d2d94823958c4c',
},
{
msg: '',
len: 0x80,
expected:
'af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac0' +
'6d5e3e29485dadbee0d121587713a3e0dd4d5e69e93eb7cd4f5df4' +
'cd103e188cf60cb02edc3edf18eda8576c412b18ffb658e3dd6ec8' +
'49469b979d444cf7b26911a08e63cf31f9dcc541708d3491184472' +
'c2c29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced',
},
{
msg: 'abc',
len: 0x80,
expected:
'abba86a6129e366fc877aab32fc4ffc70120d8996c88aee2' +
'fe4b32d6c7b6437a647e6c3163d40b76a73cf6a5674ef1d890f95b' +
'664ee0afa5359a5c4e07985635bbecbac65d747d3d2da7ec2b8221' +
'b17b0ca9dc8a1ac1c07ea6a1e60583e2cb00058e77b7b72a298425' +
'cd1b941ad4ec65e8afc50303a22c0f99b0509b4c895f40',
},
{
msg: 'abcdef0123456789',
len: 0x80,
expected:
'ef904a29bffc4cf9ee82832451c946ac3c8f8058ae97d8d6' +
'29831a74c6572bd9ebd0df635cd1f208e2038e760c4994984ce73f' +
'0d55ea9f22af83ba4734569d4bc95e18350f740c07eef653cbb9f8' +
'7910d833751825f0ebefa1abe5420bb52be14cf489b37fe1a72f7d' +
'e2d10be453b2c9d9eb20c7e3f6edc5a60629178d9478df',
},
{
msg:
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
len: 0x80,
expected:
'80be107d0884f0d881bb460322f0443d38bd222db8bd0b0a' +
'5312a6fedb49c1bbd88fd75d8b9a09486c60123dfa1d73c1cc3169' +
'761b17476d3c6b7cbbd727acd0e2c942f4dd96ae3da5de368d26b3' +
'2286e32de7e5a8cb2949f866a0b80c58116b29fa7fabb3ea7d520e' +
'e603e0c25bcaf0b9a5e92ec6a1fe4e0391d1cdbce8c68a',
},
{
msg:
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
len: 0x80,
expected:
'546aff5444b5b79aa6148bd81728704c32decb73a3ba76e9' +
'e75885cad9def1d06d6792f8a7d12794e90efed817d96920d72889' +
'6a4510864370c207f99bd4a608ea121700ef01ed879745ee3e4cee' +
'f777eda6d9e5e38b90c86ea6fb0b36504ba4a45d22e86f6db5dd43' +
'd98a294bebb9125d5b794e9d2a81181066eb954966a487',
},
];
for (let i = 0; i < VECTORS.length; i++) {
const t = VECTORS[i];
should(`hash_to_field/expand_message_xmd(SHA-256) (${i})`, () => {
const p = bls.utils.expandMessageXMD(
bls.utils.stringToBytes(t.msg),
bls.utils.stringToBytes(DST),
t.len
);
deepStrictEqual(bls.utils.bytesToHex(p), t.expected);
});
}
const LONG_DST =
'QUUX-V01-CS02-with-expander-SHA256-128-long-DST-111111' +
'111111111111111111111111111111111111111111111111111111' +
'111111111111111111111111111111111111111111111111111111' +
'111111111111111111111111111111111111111111111111111111' +
'1111111111111111111111111111111111111111';
const VECTORS_BIG = [
{
msg: '',
len: 0x20,
expected: 'e8dc0c8b686b7ef2074086fbdd2f30e3f8bfbd3bdf177f73f04b97ce618a3ed3',
},
{
msg: 'abc',
len: 0x20,
expected: '52dbf4f36cf560fca57dedec2ad924ee9c266341d8f3d6afe5171733b16bbb12',
},
{
msg: 'abcdef0123456789',
len: 0x20,
expected: '35387dcf22618f3728e6c686490f8b431f76550b0b2c61cbc1ce7001536f4521',
},
{
msg:
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
len: 0x20,
expected: '01b637612bb18e840028be900a833a74414140dde0c4754c198532c3a0ba42bc',
},
{
msg:
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
len: 0x20,
expected: '20cce7033cabc5460743180be6fa8aac5a103f56d481cf369a8accc0c374431b',
},
{
msg: '',
len: 0x80,
expected:
'14604d85432c68b757e485c8894db3117992fc57e0e136f7' +
'1ad987f789a0abc287c47876978e2388a02af86b1e8d1342e5ce4f' +
'7aaa07a87321e691f6fba7e0072eecc1218aebb89fb14a0662322d' +
'5edbd873f0eb35260145cd4e64f748c5dfe60567e126604bcab1a3' +
'ee2dc0778102ae8a5cfd1429ebc0fa6bf1a53c36f55dfc',
},
{
msg: 'abc',
len: 0x80,
expected:
'1a30a5e36fbdb87077552b9d18b9f0aee16e80181d5b951d' +
'0471d55b66684914aef87dbb3626eaabf5ded8cd0686567e503853' +
'e5c84c259ba0efc37f71c839da2129fe81afdaec7fbdc0ccd4c794' +
'727a17c0d20ff0ea55e1389d6982d1241cb8d165762dbc39fb0cee' +
'4474d2cbbd468a835ae5b2f20e4f959f56ab24cd6fe267',
},
{
msg: 'abcdef0123456789',
len: 0x80,
expected:
'd2ecef3635d2397f34a9f86438d772db19ffe9924e28a1ca' +
'f6f1c8f15603d4028f40891044e5c7e39ebb9b31339979ff33a424' +
'9206f67d4a1e7c765410bcd249ad78d407e303675918f20f26ce6d' +
'7027ed3774512ef5b00d816e51bfcc96c3539601fa48ef1c07e494' +
'bdc37054ba96ecb9dbd666417e3de289d4f424f502a982',
},
{
msg:
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
len: 0x80,
expected:
'ed6e8c036df90111410431431a232d41a32c86e296c05d42' +
'6e5f44e75b9a50d335b2412bc6c91e0a6dc131de09c43110d9180d' +
'0a70f0d6289cb4e43b05f7ee5e9b3f42a1fad0f31bac6a625b3b5c' +
'50e3a83316783b649e5ecc9d3b1d9471cb5024b7ccf40d41d1751a' +
'04ca0356548bc6e703fca02ab521b505e8e45600508d32',
},
{
msg:
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
len: 0x80,
expected:
'78b53f2413f3c688f07732c10e5ced29a17c6a16f717179f' +
'fbe38d92d6c9ec296502eb9889af83a1928cd162e845b0d3c5424e' +
'83280fed3d10cffb2f8431f14e7a23f4c68819d40617589e4c4116' +
'9d0b56e0e3535be1fd71fbb08bb70c5b5ffed953d6c14bf7618b35' +
'fc1f4c4b30538236b4b08c9fbf90462447a8ada60be495',
},
];
for (let i = 0; i < VECTORS_BIG.length; i++) {
const t = VECTORS_BIG[i];
should(`hash_to_field/expand_message_xmd(SHA-256) (long DST) (${i})`, () => {
const p = bls.utils.expandMessageXMD(
bls.utils.stringToBytes(t.msg),
bls.utils.stringToBytes(LONG_DST),
t.len
);
deepStrictEqual(bls.utils.bytesToHex(p), t.expected);
});
}
const DST_512 = 'QUUX-V01-CS02-with-expander-SHA512-256';
const VECTORS_SHA512 = [
{
msg: '',
len: 0x20,
expected: '6b9a7312411d92f921c6f68ca0b6380730a1a4d982c507211a90964c394179ba',
},
{
msg: 'abc',
len: 0x20,
expected: '0da749f12fbe5483eb066a5f595055679b976e93abe9be6f0f6318bce7aca8dc',
},
{
msg: 'abcdef0123456789',
len: 0x20,
expected: '087e45a86e2939ee8b91100af1583c4938e0f5fc6c9db4b107b83346bc967f58',
},
{
msg:
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
len: 0x20,
expected: '7336234ee9983902440f6bc35b348352013becd88938d2afec44311caf8356b3',
},
{
msg:
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
len: 0x20,
expected: '57b5f7e766d5be68a6bfe1768e3c2b7f1228b3e4b3134956dd73a59b954c66f4',
},
{
msg: '',
len: 0x80,
expected:
'41b037d1734a5f8df225dd8c7de38f851efdb45c372887be' +
'655212d07251b921b052b62eaed99b46f72f2ef4cc96bfaf254ebb' +
'bec091e1a3b9e4fb5e5b619d2e0c5414800a1d882b62bb5cd1778f' +
'098b8eb6cb399d5d9d18f5d5842cf5d13d7eb00a7cff859b605da6' +
'78b318bd0e65ebff70bec88c753b159a805d2c89c55961',
},
{
msg: 'abc',
len: 0x80,
expected:
'7f1dddd13c08b543f2e2037b14cefb255b44c83cc397c178' +
'6d975653e36a6b11bdd7732d8b38adb4a0edc26a0cef4bb4521713' +
'5456e58fbca1703cd6032cb1347ee720b87972d63fbf232587043e' +
'd2901bce7f22610c0419751c065922b488431851041310ad659e4b' +
'23520e1772ab29dcdeb2002222a363f0c2b1c972b3efe1',
},
{
msg: 'abcdef0123456789',
len: 0x80,
expected:
'3f721f208e6199fe903545abc26c837ce59ac6fa45733f1b' +
'aaf0222f8b7acb0424814fcb5eecf6c1d38f06e9d0a6ccfbf85ae6' +
'12ab8735dfdf9ce84c372a77c8f9e1c1e952c3a61b7567dd069301' +
'6af51d2745822663d0c2367e3f4f0bed827feecc2aaf98c949b5ed' +
'0d35c3f1023d64ad1407924288d366ea159f46287e61ac',
},
{
msg:
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
len: 0x80,
expected:
'b799b045a58c8d2b4334cf54b78260b45eec544f9f2fb5bd' +
'12fb603eaee70db7317bf807c406e26373922b7b8920fa29142703' +
'dd52bdf280084fb7ef69da78afdf80b3586395b433dc66cde048a2' +
'58e476a561e9deba7060af40adf30c64249ca7ddea79806ee5beb9' +
'a1422949471d267b21bc88e688e4014087a0b592b695ed',
},
{
msg:
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
len: 0x80,
expected:
'05b0bfef265dcee87654372777b7c44177e2ae4c13a27f10' +
'3340d9cd11c86cb2426ffcad5bd964080c2aee97f03be1ca18e30a' +
'1f14e27bc11ebbd650f305269cc9fb1db08bf90bfc79b42a952b46' +
'daf810359e7bc36452684784a64952c343c52e5124cd1f71d474d5' +
'197fefc571a92929c9084ffe1112cf5eea5192ebff330b',
},
];
for (let i = 0; i < VECTORS_SHA512.length; i++) {
const t = VECTORS_SHA512[i];
should(`hash_to_field/expand_message_xmd(SHA-256) (long DST) (${i})`, () => {
const p = bls.utils.expandMessageXMD(
bls.utils.stringToBytes(t.msg),
bls.utils.stringToBytes(DST_512),
t.len,
sha512
);
deepStrictEqual(bls.utils.bytesToHex(p), t.expected);
});
}
// Point G1 // Point G1
const VECTORS_G1 = [ const VECTORS_G1 = [
{ {
@ -1354,107 +1008,7 @@ describe('hash-to-curve', () => {
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
}); });
} }
const VECTORS_G1_RO = [
{
msg: bls.utils.stringToBytes(''),
expected:
'052926add2207b76ca4fa57a8734416c8dc95e24501772c814278700eed6d1e4e8cf62d9c09db0fac349612b759e79a1' +
'08ba738453bfed09cb546dbb0783dbb3a5f1f566ed67bb6be0e8c67e2e81a4cc68ee29813bb7994998f3eae0c9c6a265',
},
{
msg: bls.utils.stringToBytes('abc'),
expected:
'03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903' +
'0b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfaaa154fa6b88176c229f2885d',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
expected:
'11e0b079dea29a68f0383ee94fed1b940995272407e3bb916bbf268c263ddd57a6a27200a784cbc248e84f357ce82d98' +
'03a87ae2caf14e8ee52e51fa2ed8eefe80f02457004ba4d486d6aa1f517c0889501dc7413753f9599b099ebcbbd2d709',
},
{
msg: bls.utils.stringToBytes(
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'
),
expected:
'15f68eaa693b95ccb85215dc65fa81038d69629f70aeee0d0f677cf22285e7bf58d7cb86eefe8f2e9bc3f8cb84fac488' +
'1807a1d50c29f430b8cafc4f8638dfeeadf51211e1602a5f184443076715f91bb90a48ba1e370edce6ae1062f5e6dd38',
},
{
msg: bls.utils.stringToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
'082aabae8b7dedb0e78aeb619ad3bfd9277a2f77ba7fad20ef6aabdc6c31d19ba5a6d12283553294c1825c4b3ca2dcfe' +
'05b84ae5a942248eea39e1d91030458c40153f3b654ab7872d779ad1e942856a20c438e8d99bc8abfbf74729ce1f7ac8',
},
];
for (let i = 0; i < VECTORS_G1_RO.length; i++) {
const t = VECTORS_G1_RO[i];
should(`hashToCurve/G1 (BLS12381G1_XMD:SHA-256_SSWU_RO_) (${i})`, () => {
const p = bls.hashToCurve.G1.hashToCurve(t.msg, {
DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_',
});
deepStrictEqual(p.toHex(false), t.expected);
});
}
const VECTORS_G1_NU = [
{
msg: bls.utils.stringToBytes(''),
expected:
'184bb665c37ff561a89ec2122dd343f20e0f4cbcaec84e3c3052ea81d1834e192c426074b02ed3dca4e7676ce4ce48ba' +
'04407b8d35af4dacc809927071fc0405218f1401a6d15af775810e4e460064bcc9468beeba82fdc751be70476c888bf3',
},
{
msg: bls.utils.stringToBytes('abc'),
expected:
'009769f3ab59bfd551d53a5f846b9984c59b97d6842b20a2c565baa167945e3d026a3755b6345df8ec7e6acb6868ae6d' +
'1532c00cf61aa3d0ce3e5aa20c3b531a2abd2c770a790a2613818303c6b830ffc0ecf6c357af3317b9575c567f11cd2c',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
expected:
'1974dbb8e6b5d20b84df7e625e2fbfecb2cdb5f77d5eae5fb2955e5ce7313cae8364bc2fff520a6c25619739c6bdcb6a' +
'15f9897e11c6441eaa676de141c8d83c37aab8667173cbe1dfd6de74d11861b961dccebcd9d289ac633455dfcc7013a3',
},
{
msg: bls.utils.stringToBytes(
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'
),
expected:
'0a7a047c4a8397b3446450642c2ac64d7239b61872c9ae7a59707a8f4f950f101e766afe58223b3bff3a19a7f754027c' +
'1383aebba1e4327ccff7cf9912bda0dbc77de048b71ef8c8a81111d71dc33c5e3aa6edee9cf6f5fe525d50cc50b77cc9',
},
{
msg: bls.utils.stringToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
'0e7a16a975904f131682edbb03d9560d3e48214c9986bd50417a77108d13dc957500edf96462a3d01e62dc6cd468ef11' +
'0ae89e677711d05c30a48d6d75e76ca9fb70fe06c6dd6ff988683d89ccde29ac7d46c53bb97a59b1901abf1db66052db',
},
];
for (let i = 0; i < VECTORS_G1_NU.length; i++) {
const t = VECTORS_G1_NU[i];
should(`hashToCurve/G1 (BLS12381G1_XMD:SHA-256_SSWU_NU_) (${i})`, () => {
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, {
DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_NU_',
});
deepStrictEqual(p.toHex(false), t.expected);
});
}
const VECTORS_ENCODE_G1 = [ const VECTORS_ENCODE_G1 = [
{ {
msg: bls.utils.stringToBytes(''), msg: bls.utils.stringToBytes(''),
@ -1538,136 +1092,7 @@ describe('hash-to-curve', () => {
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
}); });
} }
const VECTORS_G2_RO = [
{
msg: bls.utils.stringToBytes(''),
expected:
'05cb8437535e20ecffaef7752baddf98034139c38452458baeefab379ba13dff5bf5dd71b72418717047f5b0f37da03d' +
'0141ebfbdca40eb85b87142e130ab689c673cf60f1a3e98d69335266f30d9b8d4ac44c1038e9dcdd5393faf5c41fb78a' +
'12424ac32561493f3fe3c260708a12b7c620e7be00099a974e259ddc7d1f6395c3c811cdd19f1e8dbf3e9ecfdcbab8d6' +
'0503921d7f6a12805e72940b963c0cf3471c7b2a524950ca195d11062ee75ec076daf2d4bc358c4b190c0c98064fdd92',
},
{
msg: bls.utils.stringToBytes('abc'),
expected:
'139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd8' +
'02c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6' +
'00aa65dae3c8d732d10ecd2c50f8a1baf3001578f71c694e03866e9f3d49ac1e1ce70dd94a733534f106d4cec0eddd16' +
'1787327b68159716a37440985269cf584bcb1e621d3a7202be6ea05c4cfe244aeb197642555a0645fb87bf7466b2ba48',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
expected:
'190d119345b94fbd15497bcba94ecf7db2cbfd1e1fe7da034d26cbba169fb3968288b3fafb265f9ebd380512a71c3f2c' +
'121982811d2491fde9ba7ed31ef9ca474f0e1501297f68c298e9f4c0028add35aea8bb83d53c08cfc007c1e005723cd0' +
'0bb5e7572275c567462d91807de765611490205a941a5a6af3b1691bfe596c31225d3aabdf15faff860cb4ef17c7c3be' +
'05571a0f8d3c08d094576981f4a3b8eda0a8e771fcdcc8ecceaf1356a6acf17574518acb506e435b639353c2e14827c8',
},
{
msg: bls.utils.stringToBytes(
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'
),
expected:
'0934aba516a52d8ae479939a91998299c76d39cc0c035cd18813bec433f587e2d7a4fef038260eef0cef4d02aae3eb91' +
'19a84dd7248a1066f737cc34502ee5555bd3c19f2ecdb3c7d9e24dc65d4e25e50d83f0f77105e955d78f4762d33c17da' +
'09bcccfa036b4847c9950780733633f13619994394c23ff0b32fa6b795844f4a0673e20282d07bc69641cee04f5e5662' +
'14f81cd421617428bc3b9fe25afbb751d934a00493524bc4e065635b0555084dd54679df1536101b2c979c0152d09192',
},
{
msg: bls.utils.stringToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
'11fca2ff525572795a801eed17eb12785887c7b63fb77a42be46ce4a34131d71f7a73e95fee3f812aea3de78b4d01569' +
'01a6ba2f9a11fa5598b2d8ace0fbe0a0eacb65deceb476fbbcb64fd24557c2f4b18ecfc5663e54ae16a84f5ab7f62534' +
'03a47f8e6d1763ba0cad63d6114c0accbef65707825a511b251a660a9b3994249ae4e63fac38b23da0c398689ee2ab52' +
'0b6798718c8aed24bc19cb27f866f1c9effcdbf92397ad6448b5c9db90d2b9da6cbabf48adc1adf59a1a28344e79d57e',
},
];
for (let i = 0; i < VECTORS_G2_RO.length; i++) {
const t = VECTORS_G2_RO[i];
should(`hashToCurve/G2 (BLS12381G2_XMD:SHA-256_SSWU_RO_) (${i})`, () => {
const p = bls.hashToCurve.G2.hashToCurve(t.msg, {
DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_',
});
deepStrictEqual(p.toHex(false), t.expected);
});
}
const VECTORS_G2_NU = [
{
msg: bls.utils.stringToBytes(''),
expected:
'126b855e9e69b1f691f816e48ac6977664d24d99f8724868a184186469ddfd4617367e94527d4b74fc86413483afb35b' +
'00e7f4568a82b4b7dc1f14c6aaa055edf51502319c723c4dc2688c7fe5944c213f510328082396515734b6612c4e7bb7' +
'1498aadcf7ae2b345243e281ae076df6de84455d766ab6fcdaad71fab60abb2e8b980a440043cd305db09d283c895e3d' +
'0caead0fd7b6176c01436833c79d305c78be307da5f6af6c133c47311def6ff1e0babf57a0fb5539fce7ee12407b0a42',
},
{
msg: bls.utils.stringToBytes('abc'),
expected:
'0296238ea82c6d4adb3c838ee3cb2346049c90b96d602d7bb1b469b905c9228be25c627bffee872def773d5b2a2eb57d' +
'108ed59fd9fae381abfd1d6bce2fd2fa220990f0f837fa30e0f27914ed6e1454db0d1ee957b219f61da6ff8be0d6441f' +
'153606c417e59fb331b7ae6bce4fbf7c5190c33ce9402b5ebe2b70e44fca614f3f1382a3625ed5493843d0b0a652fc3f' +
'033f90f6057aadacae7963b0a0b379dd46750c1c94a6357c99b65f63b79e321ff50fe3053330911c56b6ceea08fee656',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
expected:
'0da75be60fb6aa0e9e3143e40c42796edf15685cafe0279afd2a67c3dff1c82341f17effd402e4f1af240ea90f4b659b' +
'038af300ef34c7759a6caaa4e69363cafeed218a1f207e93b2c70d91a1263d375d6730bd6b6509dcac3ba5b567e85bf3' +
'0492f4fed741b073e5a82580f7c663f9b79e036b70ab3e51162359cec4e77c78086fe879b65ca7a47d34374c8315ac5e' +
'19b148cbdf163cf0894f29660d2e7bfb2b68e37d54cc83fd4e6e62c020eaa48709302ef8e746736c0e19342cc1ce3df4',
},
{
msg: bls.utils.stringToBytes(
'q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'
),
expected:
'12c8c05c1d5fc7bfa847f4d7d81e294e66b9a78bc9953990c358945e1f042eedafce608b67fdd3ab0cb2e6e263b9b1ad' +
'0c5ae723be00e6c3f0efe184fdc0702b64588fe77dda152ab13099a3bacd3876767fa7bbad6d6fd90b3642e902b208f9' +
'11c624c56dbe154d759d021eec60fab3d8b852395a89de497e48504366feedd4662d023af447d66926a28076813dd646' +
'04e77ddb3ede41b5ec4396b7421dd916efc68a358a0d7425bddd253547f2fb4830522358491827265dfc5bcc1928a569',
},
{
msg: bls.utils.stringToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
'1565c2f625032d232f13121d3cfb476f45275c303a037faa255f9da62000c2c864ea881e2bcddd111edc4a3c0da3e88d' +
'0ea4e7c33d43e17cc516a72f76437c4bf81d8f4eac69ac355d3bf9b71b8138d55dc10fd458be115afa798b55dac34be1' +
'0f8991d2a1ad662e7b6f58ab787947f1fa607fce12dde171bc17903b012091b657e15333e11701edcf5b63ba2a561247' +
'043b6f5fe4e52c839148dc66f2b3751e69a0f6ebb3d056d6465d50d4108543ecd956e10fa1640dfd9bc0030cc2558d28',
},
];
for (let i = 0; i < VECTORS_G2_NU.length; i++) {
const t = VECTORS_G2_NU[i];
should(`hashToCurve/G2 (BLS12381G2_XMD:SHA-256_SSWU_NU_) (${i})`, () => {
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, {
DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_NU_',
});
deepStrictEqual(p.toHex(false), t.expected);
});
}
const VECTORS_ENCODE_G2 = [ const VECTORS_ENCODE_G2 = [
{ {
msg: bls.utils.stringToBytes(''), msg: bls.utils.stringToBytes(''),

@ -140,7 +140,6 @@ function testCurve(curve, ro, nu) {
testCurve(secp256r1, p256_ro, p256_nu); testCurve(secp256r1, p256_ro, p256_nu);
testCurve(secp384r1, p384_ro, p384_nu); testCurve(secp384r1, p384_ro, p384_nu);
testCurve(secp521r1, p521_ro, p521_nu); testCurve(secp521r1, p521_ro, p521_nu);
// TODO: remove same tests from bls12
testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu); testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu);
testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu); testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu); testCurve(secp256k1, secp256k1_ro, secp256k1_nu);