"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.expand_message_xmd = expand_message_xmd; exports.expand_message_xof = expand_message_xof; exports.hash_to_field = hash_to_field; exports.isogenyMap = isogenyMap; exports.createHasher = createHasher; const modular_js_1 = require("./modular.js"); const utils_js_1 = require("./utils.js"); // Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE. const os2ip = utils_js_1.bytesToNumberBE; // Integer to Octet Stream (numberToBytesBE) function i2osp(value, length) { if (value < 0 || value >= 1 << (8 * length)) { throw new Error(`bad I2OSP call: value=${value} length=${length}`); } const res = Array.from({ length }).fill(0); for (let i = length - 1; i >= 0; i--) { res[i] = value & 0xff; value >>>= 8; } return new Uint8Array(res); } function strxor(a, b) { const arr = new Uint8Array(a.length); for (let i = 0; i < a.length; i++) { arr[i] = a[i] ^ b[i]; } return arr; } function anum(item) { 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://www.rfc-editor.org/rfc/rfc9380#section-5.3.1 function expand_message_xmd(msg, DST, lenInBytes, H) { (0, utils_js_1.abytes)(msg); (0, utils_js_1.abytes)(DST); anum(lenInBytes); // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3 if (DST.length > 255) DST = H((0, utils_js_1.concatBytes)((0, utils_js_1.utf8ToBytes)('H2C-OVERSIZE-DST-'), DST)); const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H; const ell = Math.ceil(lenInBytes / b_in_bytes); if (ell > 255) throw new Error('Invalid xmd length'); const DST_prime = (0, utils_js_1.concatBytes)(DST, i2osp(DST.length, 1)); const Z_pad = i2osp(0, r_in_bytes); const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str const b = new Array(ell); const b_0 = H((0, utils_js_1.concatBytes)(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime)); b[0] = H((0, utils_js_1.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((0, utils_js_1.concatBytes)(...args)); } const pseudo_random_bytes = (0, utils_js_1.concatBytes)(...b); return pseudo_random_bytes.slice(0, lenInBytes); } // Produces a uniformly random byte string using an extendable-output function (XOF) H. // 1. The collision resistance of H MUST be at least k bits. // 2. H MUST be an XOF that has been proved indifferentiable from // a random oracle under a reasonable cryptographic assumption. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2 function expand_message_xof(msg, DST, lenInBytes, k, H) { (0, utils_js_1.abytes)(msg); (0, utils_js_1.abytes)(DST); anum(lenInBytes); // https://www.rfc-editor.org/rfc/rfc9380#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((0, utils_js_1.utf8ToBytes)('H2C-OVERSIZE-DST-')).update(DST).digest(); } if (lenInBytes > 65535 || DST.length > 255) throw new Error('expand_message_xof: invalid lenInBytes'); return (H.create({ dkLen: lenInBytes }) .update(msg) .update(i2osp(lenInBytes, 2)) // 2. DST_prime = DST || I2OSP(len(DST), 1) .update(DST) .update(i2osp(DST.length, 1)) .digest()); } /** * Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F * https://www.rfc-editor.org/rfc/rfc9380#section-5.2 * @param msg a byte string containing the message to hash * @param count the number of elements of F to output * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above * @returns [u_0, ..., u_(count - 1)], a list of field elements. */ function hash_to_field(msg, count, options) { (0, utils_js_1.validateObject)(options, { DST: 'stringOrUint8Array', p: 'bigint', m: 'isSafeInteger', k: 'isSafeInteger', hash: 'hash', }); const { p, k, m, hash, expand, DST: _DST } = options; (0, utils_js_1.abytes)(msg); anum(count); const DST = typeof _DST === 'string' ? (0, utils_js_1.utf8ToBytes)(_DST) : _DST; 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; let prb; // pseudo_random_bytes if (expand === 'xmd') { prb = expand_message_xmd(msg, DST, len_in_bytes, hash); } else if (expand === 'xof') { prb = expand_message_xof(msg, DST, len_in_bytes, k, hash); } else if (expand === '_internal_pass') { // for internal tests only prb = msg; } else { throw new Error('expand must be "xmd" or "xof"'); } const u = new Array(count); for (let i = 0; i < count; i++) { const e = new Array(m); for (let j = 0; j < m; j++) { const elm_offset = L * (j + i * m); const tv = prb.subarray(elm_offset, elm_offset + L); e[j] = (0, modular_js_1.mod)(os2ip(tv), p); } u[i] = e; } return u; } function isogenyMap(field, map) { // Make same order as in spec const COEFF = map.map((i) => Array.from(i).reverse()); return (x, y) => { const [xNum, xDen, yNum, yDen] = COEFF.map((val) => val.reduce((acc, i) => field.add(field.mul(acc, x), i))); x = field.div(xNum, xDen); // xNum / xDen y = field.mul(y, field.div(yNum, yDen)); // y * (yNum / yDev) return { x, y }; }; } function createHasher(Point, mapToCurve, def) { if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined'); return { // Encodes byte string to elliptic curve. // hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3 hashToCurve(msg, options) { const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options }); const u0 = Point.fromAffine(mapToCurve(u[0])); const u1 = Point.fromAffine(mapToCurve(u[1])); const P = u0.add(u1).clearCofactor(); P.assertValidity(); return P; }, // Encodes byte string to elliptic curve. // encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3 encodeToCurve(msg, options) { const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options }); const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor(); P.assertValidity(); return P; }, // Same as encodeToCurve, but without hash mapToCurve(scalars) { if (!Array.isArray(scalars)) throw new Error('mapToCurve: expected array of bigints'); for (const i of scalars) if (typeof i !== 'bigint') throw new Error(`mapToCurve: expected array of bigints, got ${i} in array`); const P = Point.fromAffine(mapToCurve(scalars)).clearCofactor(); P.assertValidity(); return P; }, }; } //# sourceMappingURL=hash-to-curve.js.map