From 80966cbd0395868e450ad1a7db3d2025c295ed71 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 14 Feb 2023 16:39:56 +0000 Subject: [PATCH] hash-to-curve: more type checks. Rename method to createHasher --- package.json | 3 +- src/abstract/bls.ts | 70 +++++++++++------------------- src/abstract/hash-to-curve.ts | 81 +++++++++++++++++------------------ src/abstract/montgomery.ts | 4 +- src/bls12-381.ts | 7 ++- src/ed25519.ts | 8 ++-- src/ed448.ts | 2 +- src/p256.ts | 2 +- src/p384.ts | 2 +- src/p521.ts | 2 +- src/secp256k1.ts | 2 +- test/bls12-381.test.js | 71 +++++++++++++++--------------- test/hash-to-curve.test.js | 27 ++++++------ 13 files changed, 128 insertions(+), 153 deletions(-) diff --git a/package.json b/package.json index d9fe4fe..7a7e768 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "version": "0.6.4", "description": "Minimal, auditable JS implementation of elliptic curve cryptography", "files": [ - "lib" + "lib", + "src" ], "scripts": { "bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js", diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index ab135a2..b5cf743 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -13,7 +13,7 @@ */ import { AffinePoint } from './curve.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 { CurvePointsType, @@ -67,16 +67,11 @@ export type CurveFn = { Fp2: Field; Fp6: Field; Fp12: Field; - G1: CurvePointsRes; - G2: CurvePointsRes; + G1: CurvePointsRes & ReturnType>; + G2: CurvePointsRes & ReturnType>; Signature: SignatureCoder; millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; calcPairingPrecomputes: (p: AffinePoint) => [Fp2, Fp2, Fp2][]; - // prettier-ignore - hashToCurve: { - G1: ReturnType<(typeof htf.hashToCurve)>, - G2: ReturnType<(typeof htf.hashToCurve)>, - }, pairing: (P: ProjPointType, Q: ProjPointType, withFinalExponent?: boolean) => Fp12; getPublicKey: (privateKey: PrivKey) => Uint8Array; sign: { @@ -102,16 +97,14 @@ export type CurveFn = { publicKeys: (Hex | ProjPointType)[] ) => boolean; utils: { - stringToBytes: typeof htf.stringToBytes; - hashToField: typeof htf.hash_to_field; - expandMessageXMD: typeof htf.expand_message_xmd; + randomPrivateKey: () => Uint8Array; }; }; export function bls( CURVE: CurveType ): CurveFn { - // 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 BLS_X_LEN = bitLen(CURVE.x); const groupLen = 32; // TODO: calculate; hardcoded for now @@ -180,31 +173,20 @@ export function bls( } const utils = { - hexToBytes: hexToBytes, - bytesToHex: bytesToHex, - stringToBytes: htf.stringToBytes, - // TODO: do we need to export it here? - hashToField: ( - msg: Uint8Array, - count: number, - options: Partial = {} - ) => 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)), + randomPrivateKey: (): Uint8Array => { + return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.r)); + }, }; // Point on G1 curve: (x, y) - const G1 = weierstrassPoints({ - n: Fr.ORDER, - ...CURVE.G1, - }); - const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, { - ...CURVE.htfDefaults, - ...CURVE.G1.htfDefaults, - }); + const G1_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G1 }); + const G1 = Object.assign( + G1_, + htf.createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, { + ...CURVE.htfDefaults, + ...CURVE.G1.htfDefaults, + }) + ); // Sparse multiplication against precomputed coefficients // TODO: replace with weakmap? @@ -223,15 +205,14 @@ export function bls( // } // Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) - const G2 = weierstrassPoints({ - n: Fr.ORDER, - ...CURVE.G2, - }); - const C = G2.ProjectivePoint as htf.H2CPointConstructor; // TODO: fix - const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, { - ...CURVE.htfDefaults, - ...CURVE.G2.htfDefaults, - }); + const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 }); + const G2 = Object.assign( + G2_, + htf.createHasher(G2_.ProjectivePoint as htf.H2CPointConstructor, CURVE.G2.mapToCurve, { + ...CURVE.htfDefaults, + ...CURVE.G2.htfDefaults, + }) + ); const { Signature } = CURVE.G2; @@ -260,7 +241,7 @@ export function bls( function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 { return point instanceof G2.ProjectivePoint ? point - : (G2HashToCurve.hashToCurve(point, htfOpts) as G2); + : (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2); } // Multiplies generator by private key. @@ -383,7 +364,6 @@ export function bls( Signature, millerLoop, calcPairingPrecomputes, - hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve }, pairing, getPublicKey, sign, diff --git a/src/abstract/hash-to-curve.ts b/src/abstract/hash-to-curve.ts index 2c95192..bbdf456 100644 --- a/src/abstract/hash-to-curve.ts +++ b/src/abstract/hash-to-curve.ts @@ -1,7 +1,7 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ import type { Group, GroupConstructor, AffinePoint } from './curve.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 = { DST: string; // DST: a domain separation tag, defined in section 2.2.5 @@ -17,18 +17,6 @@ export type Opts = { 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) function os2ip(bytes: Uint8Array): bigint { let result = 0n; @@ -60,6 +48,13 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array { 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 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1 export function expand_message_xmd( @@ -68,8 +63,11 @@ export function expand_message_xmd( lenInBytes: number, H: CHash ): Uint8Array { + isBytes(msg); + isBytes(DST); + isNum(lenInBytes); // 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 r_in_bytes = H.blockLen; const ell = Math.ceil(lenInBytes / b_in_bytes); @@ -95,11 +93,14 @@ export function expand_message_xof( k: number, H: CHash ): Uint8Array { + isBytes(msg); + isBytes(DST); + isNum(lenInBytes); // 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)); if (DST.length > 255) { 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) 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. */ 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; - const L = Math.ceil((log2p + options.k) / 8); // section 5.1 of ietf draft link above - const len_in_bytes = count * options.m * L; - const DST = stringToBytes(options.DST); - let pseudo_random_bytes = msg; - if (options.expand === 'xmd') { - pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash); - } else if (options.expand === 'xof') { - pseudo_random_bytes = expand_message_xof(msg, DST, len_in_bytes, options.k, options.hash); - } + const { p, k, m, hash, expand, DST: _DST } = options; + isBytes(msg); + isNum(count); + if (typeof _DST !== 'string') throw new Error('DST must be valid'); + const log2p = p.toString(2).length; + const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above + const len_in_bytes = count * m * L; + const DST = utf8ToBytes(_DST); + const pseudo_random_bytes = + expand === 'xmd' + ? 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); for (let i = 0; i < count; i++) { - const e = new Array(options.m); - for (let j = 0; j < options.m; j++) { - const elm_offset = L * (j + i * options.m); + const e = new Array(m); + for (let j = 0; j < m; j++) { + const elm_offset = L * (j + i * m); 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; } @@ -178,7 +181,7 @@ export type MapToCurve = (scalar: bigint[]) => AffinePoint; // (changing DST is ok!) export type htfBasicOpts = { DST: string }; -export function hashToCurve( +export function createHasher( Point: H2CPointConstructor, mapToCurve: MapToCurve, def: Opts @@ -197,21 +200,17 @@ export function hashToCurve( return { // Encodes byte string to elliptic curve // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 - hashToCurve(msg: Hex, options?: htfBasicOpts) { - if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined'); - msg = ensureBytes(msg); + hashToCurve(msg: Uint8Array, options?: htfBasicOpts) { const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts); - const P = Point.fromAffine(mapToCurve(u[0])) - .add(Point.fromAffine(mapToCurve(u[1]))) - .clearCofactor(); + const u0 = Point.fromAffine(mapToCurve(u[0])); + const u1 = Point.fromAffine(mapToCurve(u[1])); + const P = u0.add(u1).clearCofactor(); P.assertValidity(); return P; }, // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 - encodeToCurve(msg: Hex, options?: htfBasicOpts) { - if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined'); - msg = ensureBytes(msg); + encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) { const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts); const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor(); P.assertValidity(); diff --git a/src/abstract/montgomery.ts b/src/abstract/montgomery.ts index e8abc00..d959c5f 100644 --- a/src/abstract/montgomery.ts +++ b/src/abstract/montgomery.ts @@ -149,13 +149,13 @@ export function montgomery(curveDef: CurveType): CurveFn { // 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 // 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) if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111 return bytesToNumberLE(u); } function decodeScalar(n: Hex): bigint { - const bytes = ensureBytes(n); + const bytes = ensureBytes('scalar', n); if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen) throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`); return bytesToNumberLE(adjustScalarBytes(bytes)); diff --git a/src/bls12-381.ts b/src/bls12-381.ts index 2d2c4be..ed1d874 100644 --- a/src/bls12-381.ts +++ b/src/bls12-381.ts @@ -944,7 +944,7 @@ function G2psi2(c: ProjConstructor, P: ProjPointType) { // p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab // m = 2 (or 1 for G1 see section 8.8.1) // k = 128 -const htfDefaults = { +const htfDefaults = Object.freeze({ // DST: a domain separation tag // defined in section 2.2.5 // 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. // BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247 hash: sha256, -} as const; +} as const); // Encoding utils // Point on G1 curve: (x, y) @@ -1220,7 +1220,7 @@ export const bls12_381: CurveFn = bls({ Signature: { // TODO: Optimize, it's very slow because of sqrt. decode(hex: Hex): ProjPointType { - hex = ensureBytes(hex); + hex = ensureBytes('signatureHex', hex); const P = Fp.ORDER; const half = hex.length / 2; if (half !== 48 && half !== 96) @@ -1247,7 +1247,6 @@ export const bls12_381: CurveFn = bls({ const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; if (isGreater || isZero) y = Fp2.neg(y); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); - // console.log('Signature.decode', point); point.assertValidity(); return point; }, diff --git a/src/ed25519.ts b/src/ed25519.ts index 57b20f0..19ab4c8 100644 --- a/src/ed25519.ts +++ b/src/ed25519.ts @@ -5,12 +5,12 @@ import { twistedEdwards, ExtPointType } from './abstract/edwards.js'; import { montgomery } from './abstract/montgomery.js'; import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js'; import { - ensureBytes, equalBytes, bytesToHex, bytesToNumberLE, numberToBytesLE, Hex, + ensureBytes, } from './abstract/utils.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 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, (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 */ static hashToCurve(hex: Hex): RistrettoPoint { - hex = ensureBytes(hex, 64); + hex = ensureBytes('ristrettoHash', hex, 64); const r1 = bytes255ToNumberLE(hex.slice(0, 32)); const R1 = calcElligatorRistrettoMap(r1); 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 */ static fromHex(hex: Hex): RistrettoPoint { - hex = ensureBytes(hex, 32); + hex = ensureBytes('ristrettoHex', hex, 32); const { a, d } = ed25519.CURVE; const P = ed25519.CURVE.Fp.ORDER; const mod = ed25519.CURVE.Fp.create; diff --git a/src/ed448.ts b/src/ed448.ts index 66399cd..2954c41 100644 --- a/src/ed448.ts +++ b/src/ed448.ts @@ -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) } -const { hashToCurve, encodeToCurve } = htf.hashToCurve( +const { hashToCurve, encodeToCurve } = htf.createHasher( ed448.ExtendedPoint, (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]), { diff --git a/src/p256.ts b/src/p256.ts index 0ba6ece..1c97652 100644 --- a/src/p256.ts +++ b/src/p256.ts @@ -37,7 +37,7 @@ export const P256 = createCurve( ); export const secp256r1 = P256; -const { hashToCurve, encodeToCurve } = htf.hashToCurve( +const { hashToCurve, encodeToCurve } = htf.createHasher( secp256r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), { diff --git a/src/p384.ts b/src/p384.ts index a28f9e0..ee78904 100644 --- a/src/p384.ts +++ b/src/p384.ts @@ -41,7 +41,7 @@ export const P384 = createCurve({ ); export const secp384r1 = P384; -const { hashToCurve, encodeToCurve } = htf.hashToCurve( +const { hashToCurve, encodeToCurve } = htf.createHasher( secp384r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), { diff --git a/src/p521.ts b/src/p521.ts index 5dacbd9..7324245 100644 --- a/src/p521.ts +++ b/src/p521.ts @@ -41,7 +41,7 @@ export const P521 = createCurve({ } as const, sha512); export const secp521r1 = P521; -const { hashToCurve, encodeToCurve } = htf.hashToCurve( +const { hashToCurve, encodeToCurve } = htf.createHasher( secp521r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), { diff --git a/src/secp256k1.ts b/src/secp256k1.ts index a0863b7..7b44cd3 100644 --- a/src/secp256k1.ts +++ b/src/secp256k1.ts @@ -242,7 +242,7 @@ const mapSWU = mapToCurveSimpleSWU(Fp, { B: BigInt('1771'), Z: Fp.create(BigInt('-11')), }); -export const { hashToCurve, encodeToCurve } = htf.hashToCurve( +export const { hashToCurve, encodeToCurve } = htf.createHasher( secp256k1.ProjectivePoint, (scalars: bigint[]) => { const { x, y } = mapSWU(Fp.create(scalars[0])); diff --git a/test/bls12-381.test.js b/test/bls12-381.test.js index dca52ff..786a12a 100644 --- a/test/bls12-381.test.js +++ b/test/bls12-381.test.js @@ -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 { sha512 } from '@noble/hashes/sha512'; import * as fc from 'fast-check'; 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 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') .trim() .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 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 FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 }); @@ -851,7 +850,7 @@ describe('bls12-381/basic', () => { for (let vector of G2_VECTORS) { const [priv, msg, expected] = vector; const sig = bls.sign(msg, priv); - deepStrictEqual(bls.utils.bytesToHex(sig), expected); + deepStrictEqual(bytesToHex(sig), expected); } }); should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => { @@ -863,8 +862,8 @@ describe('bls12-381/basic', () => { for (let vector of SCALAR_VECTORS) { const [okmAscii, expectedHex] = vector; const expected = BigInt('0x' + expectedHex); - const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0))); - const scalars = bls.utils.hashToField(okm, 1, options); + const okm = utf8ToBytes(okmAscii); + const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options)); deepStrictEqual(scalars[0][0], expected); } }); @@ -973,25 +972,25 @@ describe('hash-to-curve', () => { // Point G1 const VECTORS_G1 = [ { - msg: bls.utils.stringToBytes(''), + msg: utf8ToBytes(''), expected: '0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' + '1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae', }, { - msg: bls.utils.stringToBytes('abc'), + msg: utf8ToBytes('abc'), expected: '061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' + '0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d', }, { - msg: bls.utils.stringToBytes('abcdef0123456789'), + msg: utf8ToBytes('abcdef0123456789'), expected: '0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' + '177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57', }, { - msg: bls.utils.stringToBytes( + msg: utf8ToBytes( 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ), expected: @@ -1002,7 +1001,7 @@ describe('hash-to-curve', () => { for (let i = 0; i < VECTORS_G1.length; i++) { const t = VECTORS_G1[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', }); deepStrictEqual(p.toHex(false), t.expected); @@ -1011,25 +1010,25 @@ describe('hash-to-curve', () => { const VECTORS_ENCODE_G1 = [ { - msg: bls.utils.stringToBytes(''), + msg: utf8ToBytes(''), expected: '1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' + '0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5', }, { - msg: bls.utils.stringToBytes('abc'), + msg: utf8ToBytes('abc'), expected: '179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' + '0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4', }, { - msg: bls.utils.stringToBytes('abcdef0123456789'), + msg: utf8ToBytes('abcdef0123456789'), expected: '15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' + '0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788', }, { - msg: bls.utils.stringToBytes( + msg: utf8ToBytes( 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ), expected: @@ -1040,7 +1039,7 @@ 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})`, () => { - const p = bls.hashToCurve.G1.encodeToCurve(t.msg, { + const p = bls.G1.encodeToCurve(t.msg, { DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN', }); deepStrictEqual(p.toHex(false), t.expected); @@ -1049,7 +1048,7 @@ describe('hash-to-curve', () => { // Point G2 const VECTORS_G2 = [ { - msg: bls.utils.stringToBytes(''), + msg: utf8ToBytes(''), expected: '0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' + '0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' + @@ -1057,7 +1056,7 @@ describe('hash-to-curve', () => { '0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83', }, { - msg: bls.utils.stringToBytes('abc'), + msg: utf8ToBytes('abc'), expected: '03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' + '1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' + @@ -1065,7 +1064,7 @@ describe('hash-to-curve', () => { '0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e', }, { - msg: bls.utils.stringToBytes('abcdef0123456789'), + msg: utf8ToBytes('abcdef0123456789'), expected: '195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' + '17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' + @@ -1073,7 +1072,7 @@ describe('hash-to-curve', () => { '174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e', }, { - msg: bls.utils.stringToBytes( + msg: utf8ToBytes( 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ), expected: @@ -1086,7 +1085,7 @@ describe('hash-to-curve', () => { for (let i = 0; i < VECTORS_G2.length; i++) { const t = VECTORS_G2[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', }); deepStrictEqual(p.toHex(false), t.expected); @@ -1095,7 +1094,7 @@ describe('hash-to-curve', () => { const VECTORS_ENCODE_G2 = [ { - msg: bls.utils.stringToBytes(''), + msg: utf8ToBytes(''), expected: '0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' + '027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' + @@ -1103,7 +1102,7 @@ describe('hash-to-curve', () => { '053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7', }, { - msg: bls.utils.stringToBytes('abc'), + msg: utf8ToBytes('abc'), expected: '18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' + '09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' + @@ -1111,7 +1110,7 @@ describe('hash-to-curve', () => { '02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811', }, { - msg: bls.utils.stringToBytes('abcdef0123456789'), + msg: utf8ToBytes('abcdef0123456789'), expected: '19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' + '149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' + @@ -1119,7 +1118,7 @@ describe('hash-to-curve', () => { '04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551', }, { - msg: bls.utils.stringToBytes( + msg: utf8ToBytes( 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ), expected: @@ -1132,7 +1131,7 @@ 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})`, () => { - const p = bls.hashToCurve.G2.encodeToCurve(t.msg, { + const p = bls.G2.encodeToCurve(t.msg, { DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN', }); deepStrictEqual(p.toHex(false), t.expected); @@ -1265,7 +1264,7 @@ describe('bls12-381 deterministic', () => { should('Killic based/Pairing', () => { const t = bls.pairing(G1Point.BASE, G2Point.BASE); deepStrictEqual( - bls.utils.bytesToHex(Fp12.toBytes(t)), + bytesToHex(Fp12.toBytes(t)), killicHex([ '0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631', '04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef', @@ -1287,7 +1286,7 @@ describe('bls12-381 deterministic', () => { let p2 = G2Point.BASE; for (let v of pairingVectors) { deepStrictEqual( - bls.utils.bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))), + bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))), // Reverse order v.match(/.{96}/g).reverse().join('') ); diff --git a/test/hash-to-curve.test.js b/test/hash-to-curve.test.js index bd82950..044b8f1 100644 --- a/test/hash-to-curve.test.js +++ b/test/hash-to-curve.test.js @@ -12,11 +12,8 @@ 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, - expand_message_xmd, - expand_message_xof, -} from '../lib/esm/abstract/hash-to-curve.js'; +import { expand_message_xmd, expand_message_xof } from '../lib/esm/abstract/hash-to-curve.js'; +import { utf8ToBytes } from '../lib/esm/abstract/utils.js'; // 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_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]; should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => { const p = expand_message_xmd( - stringToBytes(t.msg), - stringToBytes(vectors.DST), - t.len_in_bytes, + utf8ToBytes(t.msg), + utf8ToBytes(vectors.DST), + Number.parseInt(t.len_in_bytes), hash ); deepStrictEqual(bytesToHex(p), t.uniform_bytes); @@ -79,9 +76,9 @@ function testExpandXOF(hash, vectors) { const t = vectors.tests[i]; should(`${i}`, () => { const p = expand_message_xof( - stringToBytes(t.msg), - stringToBytes(vectors.DST), - +t.len_in_bytes, + utf8ToBytes(t.msg), + utf8ToBytes(vectors.DST), + Number.parseInt(t.len_in_bytes), vectors.k, hash ); @@ -112,7 +109,7 @@ function testCurve(curve, ro, nu) { const t = ro.vectors[i]; should(`(${i})`, () => { const p = curve - .hashToCurve(stringToBytes(t.msg), { + .hashToCurve(utf8ToBytes(t.msg), { DST: ro.dst, }) .toAffine(); @@ -126,7 +123,7 @@ function testCurve(curve, ro, nu) { const t = nu.vectors[i]; should(`(${i})`, () => { const p = curve - .encodeToCurve(stringToBytes(t.msg), { + .encodeToCurve(utf8ToBytes(t.msg), { DST: nu.dst, }) .toAffine(); @@ -140,8 +137,8 @@ function testCurve(curve, ro, nu) { testCurve(secp256r1, p256_ro, p256_nu); testCurve(secp384r1, p384_ro, p384_nu); testCurve(secp521r1, p521_ro, p521_nu); -testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu); -testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu); +testCurve(bls12_381.G1, g1_ro, g1_nu); +testCurve(bls12_381.G2, g2_ro, g2_nu); testCurve(secp256k1, secp256k1_ro, secp256k1_nu); testCurve(ed25519, ed25519_ro, ed25519_nu); testCurve(ed448, ed448_ro, ed448_nu);