hash-to-curve: decrease coupling, improve tree shaking support

This commit is contained in:
Paul Miller 2023-01-21 18:02:45 +00:00
parent b9482bb17d
commit 40530eae0c
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
14 changed files with 237 additions and 186 deletions

@ -15,12 +15,7 @@ import * as mod from './modular.js';
import * as ut from './utils.js';
// Types require separate import
import { Hex, PrivKey } from './utils.js';
import {
htfOpts,
stringToBytes,
hash_to_field as hashToField,
expand_message_xmd as expandMessageXMD,
} from './hash-to-curve.js';
import * as htf from './hash-to-curve.js';
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
type Fp = bigint; // Can be different field?
@ -32,9 +27,14 @@ export type SignatureCoder<Fp2> = {
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
r: bigint;
G1: Omit<CurvePointsType<Fp>, 'n'>;
G1: Omit<CurvePointsType<Fp>, 'n'> & {
mapToCurve: htf.MapToCurve<Fp>;
htfDefaults: htf.Opts;
};
G2: Omit<CurvePointsType<Fp2>, 'n'> & {
Signature: SignatureCoder<Fp2>;
mapToCurve: htf.MapToCurve<Fp2>;
htfDefaults: htf.Opts;
};
x: bigint;
Fp: mod.Field<Fp>;
@ -51,7 +51,7 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
conjugate(num: Fp12): Fp12;
finalExponentiate(num: Fp12): Fp12;
};
htfDefaults: htfOpts;
htfDefaults: htf.Opts;
hash: ut.CHash; // Because we need outputLen for DRBG
randomBytes: (bytesLength?: number) => Uint8Array;
};
@ -68,6 +68,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][];
// prettier-ignore
hashToCurve: {
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
},
pairing: (P: PointType<Fp>, Q: PointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: {
@ -93,11 +98,9 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
publicKeys: (Hex | PointType<Fp>)[]
) => boolean;
utils: {
stringToBytes: typeof stringToBytes;
hashToField: typeof hashToField;
expandMessageXMD: typeof expandMessageXMD;
getDSTLabel: () => string;
setDSTLabel(newLabel: string): void;
stringToBytes: typeof htf.stringToBytes;
hashToField: typeof htf.hash_to_field;
expandMessageXMD: typeof htf.expand_message_xmd;
};
};
@ -174,26 +177,18 @@ export function bls<Fp2, Fp6, Fp12>(
const utils = {
hexToBytes: ut.hexToBytes,
bytesToHex: ut.bytesToHex,
stringToBytes: stringToBytes,
stringToBytes: htf.stringToBytes,
// TODO: do we need to export it here?
hashToField: (
msg: Uint8Array,
count: number,
options: Partial<typeof CURVE.htfDefaults> = {}
) => hashToField(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, DST, lenInBytes, H),
htf.expand_message_xmd(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)),
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
getDSTLabel: () => CURVE.htfDefaults.DST,
setDSTLabel(newLabel: string) {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
throw new TypeError('Invalid DST');
}
CURVE.htfDefaults.DST = newLabel;
},
};
// Point on G1 curve: (x, y)
@ -201,6 +196,10 @@ export function bls<Fp2, Fp6, Fp12>(
n: Fr.ORDER,
...CURVE.G1,
});
const G1HashToCurve = htf.hashToCurve(G1.Point, CURVE.G1.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G1.htfDefaults,
});
// Sparse multiplication against precomputed coefficients
// TODO: replace with weakmap?
@ -227,6 +226,11 @@ export function bls<Fp2, Fp6, Fp12>(
n: Fr.ORDER,
...CURVE.G2,
});
const G2HashToCurve = htf.hashToCurve(G2.Point, CURVE.G2.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G2.htfDefaults,
});
const { Signature } = CURVE.G2;
// Calculates bilinear pairing
@ -250,8 +254,8 @@ export function bls<Fp2, Fp6, Fp12>(
function normP2(point: G2Hex): G2 {
return point instanceof G2.Point ? point : Signature.decode(point);
}
function normP2Hash(point: G2Hex): G2 {
return point instanceof G2.Point ? point : G2.Point.hashToCurve(point);
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
return point instanceof G2.Point ? point : (G2HashToCurve.hashToCurve(point, htfOpts) as G2);
}
// Multiplies generator by private key.
@ -262,10 +266,10 @@ export function bls<Fp2, Fp6, Fp12>(
// Executes `hashToCurve` on the message and then multiplies the result by private key.
// S = pk x H(m)
function sign(message: Hex, privateKey: PrivKey): Uint8Array;
function sign(message: G2, privateKey: PrivKey): G2;
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 {
const msgPoint = normP2Hash(message);
function sign(message: Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array;
function sign(message: G2, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): G2;
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
const msgPoint = normP2Hash(message, htfOpts);
msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
if (message instanceof G2.Point) return sigPoint;
@ -274,9 +278,14 @@ export function bls<Fp2, Fp6, Fp12>(
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
// e(P, H(m)) == e(G, S)
function verify(signature: G2Hex, message: G2Hex, publicKey: G1Hex): boolean {
function verify(
signature: G2Hex,
message: G2Hex,
publicKey: G1Hex,
htfOpts?: htf.htfBasicOpts
): boolean {
const P = normP1(publicKey);
const Hm = normP2Hash(message);
const Hm = normP2Hash(message, htfOpts);
const G = G1.Point.BASE;
const S = normP2(signature);
// Instead of doing 2 exponentiations, we use property of billinear maps
@ -323,12 +332,17 @@ export function bls<Fp2, Fp6, Fp12>(
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
function verifyBatch(signature: G2Hex, messages: G2Hex[], publicKeys: G1Hex[]): boolean {
function verifyBatch(
signature: G2Hex,
messages: G2Hex[],
publicKeys: G1Hex[],
htfOpts?: htf.htfBasicOpts
): boolean {
if (!messages.length) throw new Error('Expected non-empty messages array');
if (publicKeys.length !== messages.length)
throw new Error('Pubkey count should equal msg count');
const sig = normP2(signature);
const nMessages = messages.map(normP2Hash);
const nMessages = messages.map((i) => normP2Hash(i, htfOpts));
const nPublicKeys = publicKeys.map(normP1);
try {
const paired = [];
@ -365,6 +379,7 @@ export function bls<Fp2, Fp6, Fp12>(
Signature,
millerLoop,
calcPairingPrecomputes,
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve },
pairing,
getPublicKey,
sign,

@ -13,7 +13,6 @@ import * as mod from './modular.js';
import * as ut from './utils.js';
import { ensureBytes, Hex, PrivKey } from './utils.js';
import { Group, GroupConstructor, wNAF } from './group.js';
import { hash_to_field as hashToField, htfOpts, validateHTFOpts } from './hash-to-curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0);
@ -39,8 +38,6 @@ export type CurveType = ut.BasicCurve<bigint> & {
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
// RFC 8032 pre-hashing of messages to sign() / verify()
preHash?: ut.CHash;
// Hash to field options
htfDefaults?: htfOpts;
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint };
};
@ -59,7 +56,6 @@ function validateOpts(curve: CurveType) {
if (opts[fn] === undefined) continue; // Optional
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
}
if (opts.htfDefaults !== undefined) validateHTFOpts(opts.htfDefaults);
// Set defaults
return Object.freeze({ ...opts } as const);
}
@ -114,8 +110,6 @@ export interface PointConstructor extends GroupConstructor<PointType> {
new (x: bigint, y: bigint): PointType;
fromHex(hex: Hex): PointType;
fromPrivateKey(privateKey: PrivKey): PointType;
hashToCurve(msg: Hex, options?: Partial<htfOpts>): PointType;
encodeToCurve(msg: Hex, options?: Partial<htfOpts>): PointType;
}
export type PubKey = Hex | PointType;
@ -484,25 +478,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
clearCofactor() {
return ExtendedPoint.fromAffine(this).clearCofactor().toAffine();
}
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
const { mapToCurve, htfDefaults } = CURVE;
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
const u = hashToField(ensureBytes(msg), 2, { ...htfDefaults, ...options } as htfOpts);
const { x: x0, y: y0 } = mapToCurve(u[0]);
const { x: x1, y: y1 } = mapToCurve(u[1]);
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
return p;
}
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
const { mapToCurve, htfDefaults } = CURVE;
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
const u = hashToField(ensureBytes(msg), 1, { ...htfDefaults, ...options } as htfOpts);
const { x, y } = mapToCurve(u[0]);
return new Point(x, y).clearCofactor();
}
}
/**

@ -1,11 +1,13 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { CHash, concatBytes } from './utils.js';
import * as ut from './utils.js';
import * as mod from './modular.js';
import type { Group, GroupConstructor } from './group.js';
export type htfOpts = {
export type Opts = {
// DST: a domain separation tag
// defined in section 2.2.5
DST: string;
encodeDST: string;
// p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m
p: bigint;
@ -22,10 +24,10 @@ export type htfOpts = {
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: CHash;
hash: ut.CHash;
};
export function validateHTFOpts(opts: htfOpts) {
export function validateOpts(opts: Opts) {
if (typeof opts.DST !== 'string') throw new Error('Invalid htf/DST');
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
@ -81,25 +83,25 @@ export function expand_message_xmd(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
H: CHash
H: ut.CHash
): Uint8Array {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
if (DST.length > 255) DST = H(ut.concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen;
const r_in_bytes = H.blockLen;
const ell = Math.ceil(lenInBytes / b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
const DST_prime = ut.concatBytes(DST, i2osp(DST.length, 1));
const Z_pad = i2osp(0, r_in_bytes);
const l_i_b_str = i2osp(lenInBytes, 2);
const b = new Array<Uint8Array>(ell);
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
const b_0 = H(ut.concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
b[0] = H(ut.concatBytes(b_0, i2osp(1, 1), DST_prime));
for (let i = 1; i <= ell; i++) {
const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
b[i] = H(concatBytes(...args));
b[i] = H(ut.concatBytes(...args));
}
const pseudo_random_bytes = concatBytes(...b);
const pseudo_random_bytes = ut.concatBytes(...b);
return pseudo_random_bytes.slice(0, lenInBytes);
}
@ -108,7 +110,7 @@ export function expand_message_xof(
DST: Uint8Array,
lenInBytes: number,
k: number,
H: CHash
H: ut.CHash
): Uint8Array {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
@ -137,7 +139,7 @@ export function expand_message_xof(
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/
export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][] {
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
// if options is provided but incomplete, fill any missing fields with the
// value in hftDefaults (ie hash to G2).
const log2p = options.p.toString(2).length;
@ -175,3 +177,48 @@ export function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[],
return { x, y };
};
}
export interface Point<T> extends Group<Point<T>> {
readonly x: T;
readonly y: T;
clearCofactor(): Point<T>;
}
export interface PointConstructor<T> extends GroupConstructor<Point<T>> {
new (x: T, y: T): Point<T>;
}
export type MapToCurve<T> = (scalar: bigint[]) => { x: T; y: T };
// Separated from initialization opts, so users won't accidentally change per-curve parameters (changing DST is ok!)
export type htfBasicOpts = {
DST: string;
};
export function hashToCurve<T>(Point: PointConstructor<T>, mapToCurve: MapToCurve<T>, def: Opts) {
validateOpts(def);
if (typeof mapToCurve !== 'function')
throw new Error('hashToCurve: mapToCurve() has not been defined');
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
hashToCurve(msg: ut.Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const { x: x0, y: y0 } = mapToCurve(u[0]);
const { x: x1, y: y1 } = mapToCurve(u[1]);
return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
},
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
encodeToCurve(msg: ut.Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const { x, y } = mapToCurve(u[0]);
return new Point(x, y).clearCofactor();
},
};
}

@ -11,7 +11,6 @@
import * as mod from './modular.js';
import * as ut from './utils.js';
import { bytesToHex, Hex, PrivKey } from './utils.js';
import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js';
import { Group, GroupConstructor, wNAF } from './group.js';
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
@ -39,9 +38,6 @@ export type BasicCurve<T> = ut.BasicCurve<T> & {
c: ProjectiveConstructor<T>,
point: ProjectivePointType<T>
) => ProjectivePointType<T>;
// Hash to field options
htfDefaults?: htfOpts;
mapToCurve?: (scalar: bigint[]) => { x: T; y: T };
};
// ASN.1 DER encoding utilities
@ -121,6 +117,7 @@ export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
multiply(scalar: number | bigint, affinePoint?: PointType<T>): ProjectivePointType<T>;
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
toAffine(invZ?: T): PointType<T>;
clearCofactor(): ProjectivePointType<T>;
}
// Static methods for 3d XYZ points
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
@ -139,14 +136,13 @@ export interface PointType<T> extends Group<PointType<T>> {
toHex(isCompressed?: boolean): string;
assertValidity(): void;
multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined;
clearCofactor(): PointType<T>;
}
// Static methods for 2d XY points
export interface PointConstructor<T> extends GroupConstructor<PointType<T>> {
new (x: T, y: T): PointType<T>;
fromHex(hex: Hex): PointType<T>;
fromPrivateKey(privateKey: PrivKey): PointType<T>;
hashToCurve(msg: Hex, options?: Partial<htfOpts>): PointType<T>;
encodeToCurve(msg: Hex, options?: Partial<htfOpts>): PointType<T>;
}
export type CurvePointsType<T> = BasicCurve<T> & {
@ -162,7 +158,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
if (!Fp.isValid(curve[i]))
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
}
for (const i of ['isTorsionFree', 'clearCofactor', 'mapToCurve'] as const) {
for (const i of ['isTorsionFree', 'clearCofactor'] as const) {
if (curve[i] === undefined) continue; // Optional
if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`);
}
@ -181,8 +177,6 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
}
if (typeof opts.fromBytes !== 'function') throw new Error('Invalid fromBytes function');
if (typeof opts.toBytes !== 'function') throw new Error('Invalid fromBytes function');
// Requires including hashToCurve file
if (opts.htfDefaults !== undefined) validateHTFOpts(opts.htfDefaults);
// Set defaults
return Object.freeze({ ...opts } as const);
}
@ -671,28 +665,6 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const sum = aP.add(bQ);
return sum.equals(ProjectivePoint.ZERO) ? undefined : sum.toAffine();
}
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
const { mapToCurve } = CURVE;
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts);
const { x: x0, y: y0 } = mapToCurve(u[0]);
const { x: x1, y: y1 } = mapToCurve(u[1]);
return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
}
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
const { mapToCurve } = CURVE;
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts);
const { x, y } = mapToCurve(u[0]);
return new Point(x, y).clearCofactor();
}
}
return {

@ -920,6 +920,7 @@ const htfDefaults = {
// defined in section 2.2.5
// Use utils.getDSTLabel(), utils.setDSTLabel(value)
DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
encodeDST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
// p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m
p: Fp.ORDER,

@ -12,6 +12,7 @@ import {
numberToBytesLE,
Hex,
} from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js';
/**
* ed25519 Twisted Edwards curve with following addons:
@ -189,15 +190,6 @@ const ED25519_DEF = {
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
// Constant-time, u/√v
uvRatio,
htfDefaults: {
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha512,
},
mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
} as const;
export const ed25519 = twistedEdwards(ED25519_DEF);
@ -217,6 +209,21 @@ export const ed25519ph = twistedEdwards({
preHash: sha512,
});
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
ed25519.Point,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha512,
}
);
export { hashToCurve, encodeToCurve };
export const x25519 = montgomery({
P: ED25519_P,
a24: BigInt('121665'),

@ -4,6 +4,7 @@ import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/h
import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Fp as Field } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js';
import * as htf from './abstract/hash-to-curve.js';
/**
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
@ -189,21 +190,27 @@ const ED448_DEF = {
// square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x };
},
htfDefaults: {
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
p: Fp.ORDER,
m: 1,
k: 224,
expand: 'xof',
hash: shake256,
},
mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
} as const;
export const ed448 = twistedEdwards(ED448_DEF);
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
ed448.Point,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
p: Fp.ORDER,
m: 1,
k: 224,
expand: 'xof',
hash: shake256,
}
);
export { hashToCurve, encodeToCurve };
export const x448 = montgomery({
a24: BigInt(39081),
montgomeryBits: 448,

@ -3,6 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
// NIST secp256r1 aka P256
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
@ -31,16 +32,22 @@ export const P256 = createCurve(
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
h: BigInt(1),
lowS: false,
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
DST: 'P256_XMD:SHA-256_SSWU_RO_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
},
} as const,
sha256
);
export const secp256r1 = P256;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
secp256r1.Point,
(scalars: bigint[]) => mapSWU(scalars[0]),
{
DST: 'P256_XMD:SHA-256_SSWU_RO_',
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
}
);
export { hashToCurve, encodeToCurve };

@ -3,6 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha384 } from '@noble/hashes/sha512';
import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
// NIST secp384r1 aka P384
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
@ -35,16 +36,22 @@ export const P384 = createCurve({
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
h: BigInt(1),
lowS: false,
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
DST: 'P384_XMD:SHA-384_SSWU_RO_',
p: Fp.ORDER,
m: 1,
k: 192,
expand: 'xmd',
hash: sha384,
},
} as const,
sha384
);
export const secp384r1 = P384;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
secp384r1.Point,
(scalars: bigint[]) => mapSWU(scalars[0]),
{
DST: 'P384_XMD:SHA-384_SSWU_RO_',
encodeDST: 'P384_XMD:SHA-384_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 192,
expand: 'xmd',
hash: sha384,
}
);
export { hashToCurve, encodeToCurve };

@ -4,6 +4,7 @@ import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex, PrivKey } from './abstract/utils.js';
import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
// NIST secp521r1 aka P521
// Note that it's 521, which differs from 512 of its hash function.
@ -48,14 +49,20 @@ export const P521 = createCurve({
}
return key.padStart(66 * 2, '0');
},
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
} as const, sha512);
export const secp521r1 = P521;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
secp521r1.Point,
(scalars: bigint[]) => mapSWU(scalars[0]),
{
DST: 'P521_XMD:SHA-512_SSWU_RO_',
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 256,
expand: 'xmd',
expand: 'xmd',
hash: sha512,
},
} as const, sha512);
export const secp521r1 = P521;
}
);
export { hashToCurve, encodeToCurve };

@ -12,7 +12,7 @@ import {
PrivKey,
} from './abstract/utils.js';
import { randomBytes } from '@noble/hashes/utils';
import { isogenyMap } from './abstract/hash-to-curve.js';
import * as htf from './abstract/hash-to-curve.js';
/**
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
@ -62,7 +62,7 @@ function sqrtMod(y: bigint): bigint {
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
type Fp = bigint;
const isoMap = isogenyMap(
const isoMap = htf.isogenyMap(
Fp,
[
// xNum
@ -143,22 +143,28 @@ export const secp256k1 = createCurve(
return { k1neg, k1, k2neg, k2 };
},
},
mapToCurve: (scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
return isoMap(x, y);
},
htfDefaults: {
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
},
},
sha256
);
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
secp256k1.Point,
(scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
return isoMap(x, y);
},
{
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
}
);
export { hashToCurve, encodeToCurve };
// Schnorr
const _0n = BigInt(0);
const numTo32b = secp256k1.utils._bigintToBytes;

@ -1459,8 +1459,8 @@ describe('hash-to-curve', () => {
];
for (let i = 0; i < VECTORS_G1.length; i++) {
const t = VECTORS_G1[i];
should(`hashToCurve/G1 Killic (${i})`, async () => {
const p = await bls.G1.Point.hashToCurve(t.msg, {
should(`hashToCurve/G1 Killic (${i})`, () => {
const p = bls.hashToCurve.G1.hashToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
});
deepStrictEqual(p.toHex(), t.expected);
@ -1514,8 +1514,8 @@ describe('hash-to-curve', () => {
];
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})`, async () => {
const p = await bls.G1.Point.hashToCurve(t.msg, {
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(), t.expected);
@ -1560,8 +1560,8 @@ describe('hash-to-curve', () => {
];
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})`, async () => {
const p = await bls.G1.Point.encodeToCurve(t.msg, {
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(), t.expected);
@ -1597,8 +1597,8 @@ describe('hash-to-curve', () => {
];
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
const t = VECTORS_ENCODE_G1[i];
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, async () => {
const p = await bls.G1.Point.encodeToCurve(t.msg, {
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
});
deepStrictEqual(p.toHex(), t.expected);
@ -1643,8 +1643,8 @@ describe('hash-to-curve', () => {
];
for (let i = 0; i < VECTORS_G2.length; i++) {
const t = VECTORS_G2[i];
should(`hashToCurve/G2 Killic (${i})`, async () => {
const p = await bls.G2.Point.hashToCurve(t.msg, {
should(`hashToCurve/G2 Killic (${i})`, () => {
const p = bls.hashToCurve.G2.hashToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
});
deepStrictEqual(p.toHex(), t.expected);
@ -1708,8 +1708,8 @@ describe('hash-to-curve', () => {
];
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})`, async () => {
const p = await bls.G2.Point.hashToCurve(t.msg, {
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(), t.expected);
@ -1773,8 +1773,8 @@ describe('hash-to-curve', () => {
];
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})`, async () => {
const p = await bls.G2.Point.encodeToCurve(t.msg, {
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(), t.expected);
@ -1818,8 +1818,8 @@ describe('hash-to-curve', () => {
];
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
const t = VECTORS_ENCODE_G2[i];
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, async () => {
const p = await bls.G2.Point.encodeToCurve(t.msg, {
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
});
deepStrictEqual(p.toHex(), t.expected);

@ -5,12 +5,12 @@ import { bytesToHex } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512';
import { shake128, shake256 } from '@noble/hashes/sha3';
import { secp256r1 } from '../lib/esm/p256.js';
import { secp384r1 } from '../lib/esm/p384.js';
import { secp521r1 } from '../lib/esm/p521.js';
import { ed25519 } from '../lib/esm/ed25519.js';
import { ed448 } from '../lib/esm/ed448.js';
import { secp256k1 } from '../lib/esm/secp256k1.js';
import * as secp256r1 from '../lib/esm/p256.js';
import * as secp384r1 from '../lib/esm/p384.js';
import * as secp521r1 from '../lib/esm/p521.js';
import * as ed25519 from '../lib/esm/ed25519.js';
import * as ed448 from '../lib/esm/ed448.js';
import * as secp256k1 from '../lib/esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js';
import {
stringToBytes,
@ -111,7 +111,7 @@ function testCurve(curve, ro, nu) {
for (let i = 0; i < ro.vectors.length; i++) {
const t = ro.vectors[i];
should(`(${i})`, () => {
const p = curve.Point.hashToCurve(stringToBytes(t.msg), {
const p = curve.hashToCurve(stringToBytes(t.msg), {
DST: ro.dst,
});
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
@ -123,7 +123,7 @@ function testCurve(curve, ro, nu) {
for (let i = 0; i < nu.vectors.length; i++) {
const t = nu.vectors[i];
should(`(${i})`, () => {
const p = curve.Point.encodeToCurve(stringToBytes(t.msg), {
const p = curve.encodeToCurve(stringToBytes(t.msg), {
DST: nu.dst,
});
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
@ -137,8 +137,8 @@ testCurve(secp256r1, p256_ro, p256_nu);
testCurve(secp384r1, p384_ro, p384_nu);
testCurve(secp521r1, p521_ro, p521_nu);
// TODO: remove same tests from bls12
testCurve(bls12_381.G1, g1_ro, g1_nu);
testCurve(bls12_381.G2, g2_ro, g2_nu);
testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu);
testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
testCurve(ed25519, ed25519_ro, ed25519_nu);
testCurve(ed448, ed448_ro, ed448_nu);

@ -255,7 +255,7 @@ should('Starknet.js cross-tests', () => {
);
const msgHash = '0x6d1706bd3d1ba7c517be2a2a335996f63d4738e2f182144d078a1dd9997062e';
const sig = starknet.sign(msgHash, privateKey);
const { r, s } = (sig);
const { r, s } = sig;
deepStrictEqual(
r.toString(),