hash-to-curve: more type checks. Rename method to createHasher

This commit is contained in:
Paul Miller 2023-02-14 16:39:56 +00:00
parent 98ea15dca4
commit 80966cbd03
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
13 changed files with 128 additions and 153 deletions

@ -3,7 +3,8 @@
"version": "0.6.4", "version": "0.6.4",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [ "files": [
"lib" "lib",
"src"
], ],
"scripts": { "scripts": {
"bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js", "bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js",

@ -13,7 +13,7 @@
*/ */
import { AffinePoint } from './curve.js'; import { AffinePoint } from './curve.js';
import { Field, hashToPrivateScalar } from './modular.js'; import { Field, hashToPrivateScalar } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, hexToBytes, bytesToHex } from './utils.js'; import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
import * as htf from './hash-to-curve.js'; import * as htf from './hash-to-curve.js';
import { import {
CurvePointsType, CurvePointsType,
@ -67,16 +67,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
Fp2: Field<Fp2>; Fp2: Field<Fp2>;
Fp6: Field<Fp6>; Fp6: Field<Fp6>;
Fp12: Field<Fp12>; Fp12: Field<Fp12>;
G1: CurvePointsRes<Fp>; G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2>; G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>; Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][]; calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
// prettier-ignore
hashToCurve: {
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
},
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12; pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array; getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: { sign: {
@ -102,16 +97,14 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
publicKeys: (Hex | ProjPointType<Fp>)[] publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean; ) => boolean;
utils: { utils: {
stringToBytes: typeof htf.stringToBytes; randomPrivateKey: () => Uint8Array;
hashToField: typeof htf.hash_to_field;
expandMessageXMD: typeof htf.expand_message_xmd;
}; };
}; };
export function bls<Fp2, Fp6, Fp12>( export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12> CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> { ): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options // Fields looks pretty specific for curve, so for now we need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE; const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const BLS_X_LEN = bitLen(CURVE.x); const BLS_X_LEN = bitLen(CURVE.x);
const groupLen = 32; // TODO: calculate; hardcoded for now const groupLen = 32; // TODO: calculate; hardcoded for now
@ -180,31 +173,20 @@ export function bls<Fp2, Fp6, Fp12>(
} }
const utils = { const utils = {
hexToBytes: hexToBytes, randomPrivateKey: (): Uint8Array => {
bytesToHex: bytesToHex, return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.r));
stringToBytes: htf.stringToBytes, },
// TODO: do we need to export it here?
hashToField: (
msg: Uint8Array,
count: number,
options: Partial<typeof CURVE.htfDefaults> = {}
) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
htf.expand_message_xmd(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(hashToPrivateScalar(hash, CURVE.r)),
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
}; };
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)
const G1 = weierstrassPoints({ const G1_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G1 });
n: Fr.ORDER, const G1 = Object.assign(
...CURVE.G1, G1_,
}); htf.createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, { ...CURVE.htfDefaults,
...CURVE.htfDefaults, ...CURVE.G1.htfDefaults,
...CURVE.G1.htfDefaults, })
}); );
// Sparse multiplication against precomputed coefficients // Sparse multiplication against precomputed coefficients
// TODO: replace with weakmap? // TODO: replace with weakmap?
@ -223,15 +205,14 @@ export function bls<Fp2, Fp6, Fp12>(
// } // }
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) // Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
const G2 = weierstrassPoints({ const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 });
n: Fr.ORDER, const G2 = Object.assign(
...CURVE.G2, G2_,
}); htf.createHasher(G2_.ProjectivePoint as htf.H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
const C = G2.ProjectivePoint as htf.H2CPointConstructor<Fp2>; // TODO: fix ...CURVE.htfDefaults,
const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, { ...CURVE.G2.htfDefaults,
...CURVE.htfDefaults, })
...CURVE.G2.htfDefaults, );
});
const { Signature } = CURVE.G2; const { Signature } = CURVE.G2;
@ -260,7 +241,7 @@ export function bls<Fp2, Fp6, Fp12>(
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 { function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
return point instanceof G2.ProjectivePoint return point instanceof G2.ProjectivePoint
? point ? point
: (G2HashToCurve.hashToCurve(point, htfOpts) as G2); : (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
} }
// Multiplies generator by private key. // Multiplies generator by private key.
@ -383,7 +364,6 @@ export function bls<Fp2, Fp6, Fp12>(
Signature, Signature,
millerLoop, millerLoop,
calcPairingPrecomputes, calcPairingPrecomputes,
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve },
pairing, pairing,
getPublicKey, getPublicKey,
sign, sign,

@ -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 type { Group, GroupConstructor, AffinePoint } from './curve.js'; import type { Group, GroupConstructor, AffinePoint } from './curve.js';
import { mod, Field } from './modular.js'; import { mod, Field } from './modular.js';
import { CHash, Hex, concatBytes, ensureBytes, validateObject } from './utils.js'; import { CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
export type Opts = { export type Opts = {
DST: string; // DST: a domain separation tag, defined in section 2.2.5 DST: string; // DST: a domain separation tag, defined in section 2.2.5
@ -17,18 +17,6 @@ export type Opts = {
hash: CHash; hash: CHash;
}; };
// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
declare const TextDecoder: any;
export function stringToBytes(str: string): Uint8Array {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
// Octet Stream to Integer (bytesToNumberBE) // Octet Stream to Integer (bytesToNumberBE)
function os2ip(bytes: Uint8Array): bigint { function os2ip(bytes: Uint8Array): bigint {
let result = 0n; let result = 0n;
@ -60,6 +48,13 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
return arr; return arr;
} }
function isBytes(item: unknown): void {
if (!(item instanceof Uint8Array)) throw new Error('Uint8Array expected');
}
function isNum(item: unknown): void {
if (!Number.isSafeInteger(item)) throw new Error('number expected');
}
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits // Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
export function expand_message_xmd( export function expand_message_xmd(
@ -68,8 +63,11 @@ export function expand_message_xmd(
lenInBytes: number, lenInBytes: number,
H: CHash H: CHash
): Uint8Array { ): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // 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(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen; const b_in_bytes = H.outputLen;
const r_in_bytes = H.blockLen; const r_in_bytes = H.blockLen;
const ell = Math.ceil(lenInBytes / b_in_bytes); const ell = Math.ceil(lenInBytes / b_in_bytes);
@ -95,11 +93,14 @@ export function expand_message_xof(
k: number, k: number,
H: CHash H: CHash
): Uint8Array { ): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // 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)); // DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
if (DST.length > 255) { if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8); const dkLen = Math.ceil((2 * k) / 8);
DST = H.create({ dkLen }).update(stringToBytes('H2C-OVERSIZE-DST-')).update(DST).digest(); DST = H.create({ dkLen }).update(utf8ToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
} }
if (lenInBytes > 65535 || DST.length > 255) if (lenInBytes > 65535 || DST.length > 255)
throw new Error('expand_message_xof: invalid lenInBytes'); throw new Error('expand_message_xof: invalid lenInBytes');
@ -123,25 +124,27 @@ export function expand_message_xof(
* @returns [u_0, ..., u_(count - 1)], a list of field elements. * @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/ */
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): 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 const { p, k, m, hash, expand, DST: _DST } = options;
// value in hftDefaults (ie hash to G2). isBytes(msg);
const log2p = options.p.toString(2).length; isNum(count);
const L = Math.ceil((log2p + options.k) / 8); // section 5.1 of ietf draft link above if (typeof _DST !== 'string') throw new Error('DST must be valid');
const len_in_bytes = count * options.m * L; const log2p = p.toString(2).length;
const DST = stringToBytes(options.DST); const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
let pseudo_random_bytes = msg; const len_in_bytes = count * m * L;
if (options.expand === 'xmd') { const DST = utf8ToBytes(_DST);
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash); const pseudo_random_bytes =
} else if (options.expand === 'xof') { expand === 'xmd'
pseudo_random_bytes = expand_message_xof(msg, DST, len_in_bytes, options.k, options.hash); ? expand_message_xmd(msg, DST, len_in_bytes, hash)
} : expand === 'xof'
? expand_message_xof(msg, DST, len_in_bytes, k, hash)
: msg;
const u = new Array(count); const u = new Array(count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const e = new Array(options.m); const e = new Array(m);
for (let j = 0; j < options.m; j++) { for (let j = 0; j < m; j++) {
const elm_offset = L * (j + i * options.m); const elm_offset = L * (j + i * m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L); const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
e[j] = mod(os2ip(tv), options.p); e[j] = mod(os2ip(tv), p);
} }
u[i] = e; u[i] = e;
} }
@ -178,7 +181,7 @@ export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
// (changing DST is ok!) // (changing DST is ok!)
export type htfBasicOpts = { DST: string }; export type htfBasicOpts = { DST: string };
export function hashToCurve<T>( export function createHasher<T>(
Point: H2CPointConstructor<T>, Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>, mapToCurve: MapToCurve<T>,
def: Opts def: Opts
@ -197,21 +200,17 @@ export function hashToCurve<T>(
return { return {
// Encodes byte string to elliptic curve // Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
hashToCurve(msg: Hex, options?: htfBasicOpts) { hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts); const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])) const u0 = Point.fromAffine(mapToCurve(u[0]));
.add(Point.fromAffine(mapToCurve(u[1]))) const u1 = Point.fromAffine(mapToCurve(u[1]));
.clearCofactor(); const P = u0.add(u1).clearCofactor();
P.assertValidity(); P.assertValidity();
return P; return P;
}, },
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
encodeToCurve(msg: Hex, options?: htfBasicOpts) { encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts); const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor(); const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
P.assertValidity(); P.assertValidity();

@ -149,13 +149,13 @@ export function montgomery(curveDef: CurveType): CurveFn {
// MUST mask the most significant bit in the final byte. // MUST mask the most significant bit in the final byte.
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP // This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519 // fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
const u = ensureBytes(uEnc, montgomeryBytes); const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
// u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index) // u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111 if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
return bytesToNumberLE(u); return bytesToNumberLE(u);
} }
function decodeScalar(n: Hex): bigint { function decodeScalar(n: Hex): bigint {
const bytes = ensureBytes(n); const bytes = ensureBytes('scalar', n);
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen) if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`); throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
return bytesToNumberLE(adjustScalarBytes(bytes)); return bytesToNumberLE(adjustScalarBytes(bytes));

@ -944,7 +944,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab // p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
// m = 2 (or 1 for G1 see section 8.8.1) // m = 2 (or 1 for G1 see section 8.8.1)
// k = 128 // k = 128
const htfDefaults = { const htfDefaults = Object.freeze({
// DST: a domain separation tag // DST: a domain separation tag
// defined in section 2.2.5 // defined in section 2.2.5
// Use utils.getDSTLabel(), utils.setDSTLabel(value) // Use utils.getDSTLabel(), utils.setDSTLabel(value)
@ -966,7 +966,7 @@ const htfDefaults = {
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others. // 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 // BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
hash: sha256, hash: sha256,
} as const; } as const);
// Encoding utils // Encoding utils
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)
@ -1220,7 +1220,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
Signature: { Signature: {
// TODO: Optimize, it's very slow because of sqrt. // TODO: Optimize, it's very slow because of sqrt.
decode(hex: Hex): ProjPointType<Fp2> { decode(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes(hex); hex = ensureBytes('signatureHex', hex);
const P = Fp.ORDER; const P = Fp.ORDER;
const half = hex.length / 2; const half = hex.length / 2;
if (half !== 48 && half !== 96) if (half !== 48 && half !== 96)
@ -1247,7 +1247,6 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.neg(y); if (isGreater || isZero) y = Fp2.neg(y);
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
// console.log('Signature.decode', point);
point.assertValidity(); point.assertValidity();
return point; return point;
}, },

@ -5,12 +5,12 @@ import { twistedEdwards, ExtPointType } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js'; import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
import { import {
ensureBytes,
equalBytes, equalBytes,
bytesToHex, bytesToHex,
bytesToNumberLE, bytesToNumberLE,
numberToBytesLE, numberToBytesLE,
Hex, Hex,
ensureBytes,
} from './abstract/utils.js'; } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
@ -223,7 +223,7 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
const inv = Fp.invertBatch([xd, yd]); // batch division const inv = Fp.invertBatch([xd, yd]); // batch division
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd) return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
} }
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
ed25519.ExtendedPoint, ed25519.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{ {
@ -316,7 +316,7 @@ export class RistrettoPoint {
* @param hex 64-bit output of a hash function * @param hex 64-bit output of a hash function
*/ */
static hashToCurve(hex: Hex): RistrettoPoint { static hashToCurve(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 64); hex = ensureBytes('ristrettoHash', hex, 64);
const r1 = bytes255ToNumberLE(hex.slice(0, 32)); const r1 = bytes255ToNumberLE(hex.slice(0, 32));
const R1 = calcElligatorRistrettoMap(r1); const R1 = calcElligatorRistrettoMap(r1);
const r2 = bytes255ToNumberLE(hex.slice(32, 64)); const r2 = bytes255ToNumberLE(hex.slice(32, 64));
@ -330,7 +330,7 @@ export class RistrettoPoint {
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding * @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
*/ */
static fromHex(hex: Hex): RistrettoPoint { static fromHex(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 32); hex = ensureBytes('ristrettoHex', hex, 32);
const { a, d } = ed25519.CURVE; const { a, d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER; const P = ed25519.CURVE.Fp.ORDER;
const mod = ed25519.CURVE.Fp.create; const mod = ed25519.CURVE.Fp.create;

@ -225,7 +225,7 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
} }
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
ed448.ExtendedPoint, ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{ {

@ -37,7 +37,7 @@ export const P256 = createCurve(
); );
export const secp256r1 = P256; export const secp256r1 = P256;
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
secp256r1.ProjectivePoint, secp256r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]), (scalars: bigint[]) => mapSWU(scalars[0]),
{ {

@ -41,7 +41,7 @@ export const P384 = createCurve({
); );
export const secp384r1 = P384; export const secp384r1 = P384;
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
secp384r1.ProjectivePoint, secp384r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]), (scalars: bigint[]) => mapSWU(scalars[0]),
{ {

@ -41,7 +41,7 @@ export const P521 = createCurve({
} as const, sha512); } as const, sha512);
export const secp521r1 = P521; export const secp521r1 = P521;
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
secp521r1.ProjectivePoint, secp521r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]), (scalars: bigint[]) => mapSWU(scalars[0]),
{ {

@ -242,7 +242,7 @@ const mapSWU = mapToCurveSimpleSWU(Fp, {
B: BigInt('1771'), B: BigInt('1771'),
Z: Fp.create(BigInt('-11')), Z: Fp.create(BigInt('-11')),
}); });
export const { hashToCurve, encodeToCurve } = htf.hashToCurve( export const { hashToCurve, encodeToCurve } = htf.createHasher(
secp256k1.ProjectivePoint, secp256k1.ProjectivePoint,
(scalars: bigint[]) => { (scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0])); const { x, y } = mapSWU(Fp.create(scalars[0]));

@ -1,18 +1,14 @@
import { bls12_381 } from '../lib/esm/bls12-381.js';
import { describe, should } from 'micro-should';
import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert'; import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
import { sha512 } from '@noble/hashes/sha512';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { describe, should } from 'micro-should';
import { wNAF } from '../lib/esm/abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../lib/esm/abstract/utils.js';
import { hash_to_field } from '../lib/esm/abstract/hash-to-curve.js';
import { bls12_381 as bls } from '../lib/esm/bls12-381.js';
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/curve.js';
const bls = bls12_381;
const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8') const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim() .trim()
.split('\n') .split('\n')
@ -28,7 +24,10 @@ const SCALAR_VECTORS = readFileSync('./test/bls12-381/bls12-381-scalar-test-vect
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
fc.configureGlobal({ numRuns: NUM_RUNS }); fc.configureGlobal({ numRuns: NUM_RUNS });
// @ts-ignore const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const CURVE_ORDER = bls.CURVE.r; const CURVE_ORDER = bls.CURVE.r;
const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 }); const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 });
@ -851,7 +850,7 @@ describe('bls12-381/basic', () => {
for (let vector of G2_VECTORS) { for (let vector of G2_VECTORS) {
const [priv, msg, expected] = vector; const [priv, msg, expected] = vector;
const sig = bls.sign(msg, priv); const sig = bls.sign(msg, priv);
deepStrictEqual(bls.utils.bytesToHex(sig), expected); deepStrictEqual(bytesToHex(sig), expected);
} }
}); });
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => { should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
@ -863,8 +862,8 @@ describe('bls12-381/basic', () => {
for (let vector of SCALAR_VECTORS) { for (let vector of SCALAR_VECTORS) {
const [okmAscii, expectedHex] = vector; const [okmAscii, expectedHex] = vector;
const expected = BigInt('0x' + expectedHex); const expected = BigInt('0x' + expectedHex);
const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0))); const okm = utf8ToBytes(okmAscii);
const scalars = bls.utils.hashToField(okm, 1, options); const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options));
deepStrictEqual(scalars[0][0], expected); deepStrictEqual(scalars[0][0], expected);
} }
}); });
@ -973,25 +972,25 @@ describe('hash-to-curve', () => {
// Point G1 // Point G1
const VECTORS_G1 = [ const VECTORS_G1 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' + '0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' +
'1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae', '1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' + '061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' +
'0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d', '0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' + '0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' +
'177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57', '177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@ -1002,7 +1001,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_G1.length; i++) { for (let i = 0; i < VECTORS_G1.length; i++) {
const t = VECTORS_G1[i]; const t = VECTORS_G1[i];
should(`hashToCurve/G1 Killic (${i})`, () => { should(`hashToCurve/G1 Killic (${i})`, () => {
const p = bls.hashToCurve.G1.hashToCurve(t.msg, { const p = bls.G1.hashToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN', DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@ -1011,25 +1010,25 @@ describe('hash-to-curve', () => {
const VECTORS_ENCODE_G1 = [ const VECTORS_ENCODE_G1 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' + '1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' +
'0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5', '0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' + '179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' +
'0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4', '0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' + '15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' +
'0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788', '0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@ -1040,7 +1039,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) { for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
const t = VECTORS_ENCODE_G1[i]; const t = VECTORS_ENCODE_G1[i];
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => { should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, { const p = bls.G1.encodeToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN', DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@ -1049,7 +1048,7 @@ describe('hash-to-curve', () => {
// Point G2 // Point G2
const VECTORS_G2 = [ const VECTORS_G2 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' + '0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' +
'0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' + '0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' +
@ -1057,7 +1056,7 @@ describe('hash-to-curve', () => {
'0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83', '0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' + '03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' +
'1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' + '1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' +
@ -1065,7 +1064,7 @@ describe('hash-to-curve', () => {
'0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e', '0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' + '195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' +
'17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' + '17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' +
@ -1073,7 +1072,7 @@ describe('hash-to-curve', () => {
'174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e', '174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@ -1086,7 +1085,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_G2.length; i++) { for (let i = 0; i < VECTORS_G2.length; i++) {
const t = VECTORS_G2[i]; const t = VECTORS_G2[i];
should(`hashToCurve/G2 Killic (${i})`, () => { should(`hashToCurve/G2 Killic (${i})`, () => {
const p = bls.hashToCurve.G2.hashToCurve(t.msg, { const p = bls.G2.hashToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN', DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@ -1095,7 +1094,7 @@ describe('hash-to-curve', () => {
const VECTORS_ENCODE_G2 = [ const VECTORS_ENCODE_G2 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' + '0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' +
'027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' + '027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' +
@ -1103,7 +1102,7 @@ describe('hash-to-curve', () => {
'053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7', '053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' + '18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' +
'09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' + '09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' +
@ -1111,7 +1110,7 @@ describe('hash-to-curve', () => {
'02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811', '02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' + '19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' +
'149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' + '149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' +
@ -1119,7 +1118,7 @@ describe('hash-to-curve', () => {
'04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551', '04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@ -1132,7 +1131,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) { for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
const t = VECTORS_ENCODE_G2[i]; const t = VECTORS_ENCODE_G2[i];
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => { should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, { const p = bls.G2.encodeToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN', DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@ -1265,7 +1264,7 @@ describe('bls12-381 deterministic', () => {
should('Killic based/Pairing', () => { should('Killic based/Pairing', () => {
const t = bls.pairing(G1Point.BASE, G2Point.BASE); const t = bls.pairing(G1Point.BASE, G2Point.BASE);
deepStrictEqual( deepStrictEqual(
bls.utils.bytesToHex(Fp12.toBytes(t)), bytesToHex(Fp12.toBytes(t)),
killicHex([ killicHex([
'0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631', '0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631',
'04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef', '04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef',
@ -1287,7 +1286,7 @@ describe('bls12-381 deterministic', () => {
let p2 = G2Point.BASE; let p2 = G2Point.BASE;
for (let v of pairingVectors) { for (let v of pairingVectors) {
deepStrictEqual( deepStrictEqual(
bls.utils.bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))), bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))),
// Reverse order // Reverse order
v.match(/.{96}/g).reverse().join('') v.match(/.{96}/g).reverse().join('')
); );

@ -12,11 +12,8 @@ import * as ed25519 from '../lib/esm/ed25519.js';
import * as ed448 from '../lib/esm/ed448.js'; import * as ed448 from '../lib/esm/ed448.js';
import * as secp256k1 from '../lib/esm/secp256k1.js'; import * as secp256k1 from '../lib/esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../lib/esm/bls12-381.js';
import { import { expand_message_xmd, expand_message_xof } from '../lib/esm/abstract/hash-to-curve.js';
stringToBytes, import { utf8ToBytes } from '../lib/esm/abstract/utils.js';
expand_message_xmd,
expand_message_xof,
} from '../lib/esm/abstract/hash-to-curve.js';
// XMD // XMD
import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' }; import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' };
import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' }; import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };
@ -56,9 +53,9 @@ function testExpandXMD(hash, vectors) {
const t = vectors.tests[i]; const t = vectors.tests[i];
should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => { should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
const p = expand_message_xmd( const p = expand_message_xmd(
stringToBytes(t.msg), utf8ToBytes(t.msg),
stringToBytes(vectors.DST), utf8ToBytes(vectors.DST),
t.len_in_bytes, Number.parseInt(t.len_in_bytes),
hash hash
); );
deepStrictEqual(bytesToHex(p), t.uniform_bytes); deepStrictEqual(bytesToHex(p), t.uniform_bytes);
@ -79,9 +76,9 @@ function testExpandXOF(hash, vectors) {
const t = vectors.tests[i]; const t = vectors.tests[i];
should(`${i}`, () => { should(`${i}`, () => {
const p = expand_message_xof( const p = expand_message_xof(
stringToBytes(t.msg), utf8ToBytes(t.msg),
stringToBytes(vectors.DST), utf8ToBytes(vectors.DST),
+t.len_in_bytes, Number.parseInt(t.len_in_bytes),
vectors.k, vectors.k,
hash hash
); );
@ -112,7 +109,7 @@ function testCurve(curve, ro, nu) {
const t = ro.vectors[i]; const t = ro.vectors[i];
should(`(${i})`, () => { should(`(${i})`, () => {
const p = curve const p = curve
.hashToCurve(stringToBytes(t.msg), { .hashToCurve(utf8ToBytes(t.msg), {
DST: ro.dst, DST: ro.dst,
}) })
.toAffine(); .toAffine();
@ -126,7 +123,7 @@ function testCurve(curve, ro, nu) {
const t = nu.vectors[i]; const t = nu.vectors[i];
should(`(${i})`, () => { should(`(${i})`, () => {
const p = curve const p = curve
.encodeToCurve(stringToBytes(t.msg), { .encodeToCurve(utf8ToBytes(t.msg), {
DST: nu.dst, DST: nu.dst,
}) })
.toAffine(); .toAffine();
@ -140,8 +137,8 @@ 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);
testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu); testCurve(bls12_381.G1, g1_ro, g1_nu);
testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu); testCurve(bls12_381.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu); testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
testCurve(ed25519, ed25519_ro, ed25519_nu); testCurve(ed25519, ed25519_ro, ed25519_nu);
testCurve(ed448, ed448_ro, ed448_nu); testCurve(ed448, ed448_ro, ed448_nu);