211 lines
8.4 KiB
JavaScript
211 lines
8.4 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports._DST_scalar = void 0;
|
|
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 utils_ts_1 = require("../utils.js");
|
|
const modular_ts_1 = require("./modular.js");
|
|
// Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
|
|
const os2ip = utils_ts_1.bytesToNumberBE;
|
|
// Integer to Octet Stream (numberToBytesBE)
|
|
function i2osp(value, length) {
|
|
anum(value);
|
|
anum(length);
|
|
if (value < 0 || value >= 1 << (8 * length))
|
|
throw new Error('invalid I2OSP input: ' + value);
|
|
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');
|
|
}
|
|
function normDST(DST) {
|
|
if (!(0, utils_ts_1.isBytes)(DST) && typeof DST !== 'string')
|
|
throw new Error('DST must be Uint8Array or string');
|
|
return typeof DST === 'string' ? (0, utils_ts_1.utf8ToBytes)(DST) : DST;
|
|
}
|
|
/**
|
|
* Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
|
* [RFC 9380 5.3.1](https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1).
|
|
*/
|
|
function expand_message_xmd(msg, DST, lenInBytes, H) {
|
|
(0, utils_ts_1.abytes)(msg);
|
|
anum(lenInBytes);
|
|
DST = normDST(DST);
|
|
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
|
|
if (DST.length > 255)
|
|
DST = H((0, utils_ts_1.concatBytes)((0, utils_ts_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 (lenInBytes > 65535 || ell > 255)
|
|
throw new Error('expand_message_xmd: invalid lenInBytes');
|
|
const DST_prime = (0, utils_ts_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_ts_1.concatBytes)(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
|
|
b[0] = H((0, utils_ts_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_ts_1.concatBytes)(...args));
|
|
}
|
|
const pseudo_random_bytes = (0, utils_ts_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.
|
|
* [RFC 9380 5.3.2](https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2).
|
|
*/
|
|
function expand_message_xof(msg, DST, lenInBytes, k, H) {
|
|
(0, utils_ts_1.abytes)(msg);
|
|
anum(lenInBytes);
|
|
DST = normDST(DST);
|
|
// 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_ts_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.
|
|
* [RFC 9380 5.2](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_ts_1._validateObject)(options, {
|
|
p: 'bigint',
|
|
m: 'number',
|
|
k: 'number',
|
|
hash: 'function',
|
|
});
|
|
const { p, k, m, hash, expand, DST } = options;
|
|
if (!(0, utils_ts_1.isHash)(options.hash))
|
|
throw new Error('expected valid hash');
|
|
(0, utils_ts_1.abytes)(msg);
|
|
anum(count);
|
|
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_ts_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 [xn, xd, yn, yd] = coeff.map((val) => val.reduce((acc, i) => field.add(field.mul(acc, x), i)));
|
|
// 6.6.3
|
|
// Exceptional cases of iso_map are inputs that cause the denominator of
|
|
// either rational function to evaluate to zero; such cases MUST return
|
|
// the identity point on E.
|
|
const [xd_inv, yd_inv] = (0, modular_ts_1.FpInvertBatch)(field, [xd, yd], true);
|
|
x = field.mul(xn, xd_inv); // xNum / xDen
|
|
y = field.mul(y, field.mul(yn, yd_inv)); // y * (yNum / yDev)
|
|
return { x, y };
|
|
};
|
|
}
|
|
exports._DST_scalar = (0, utils_ts_1.utf8ToBytes)('HashToScalar-');
|
|
/** Creates hash-to-curve methods from EC Point and mapToCurve function. See {@link H2CHasher}. */
|
|
function createHasher(Point, mapToCurve, defaults) {
|
|
if (typeof mapToCurve !== 'function')
|
|
throw new Error('mapToCurve() must be defined');
|
|
function map(num) {
|
|
return Point.fromAffine(mapToCurve(num));
|
|
}
|
|
function clear(initial) {
|
|
const P = initial.clearCofactor();
|
|
if (P.equals(Point.ZERO))
|
|
return Point.ZERO; // zero will throw in assert
|
|
P.assertValidity();
|
|
return P;
|
|
}
|
|
return {
|
|
defaults,
|
|
hashToCurve(msg, options) {
|
|
const opts = Object.assign({}, defaults, options);
|
|
const u = hash_to_field(msg, 2, opts);
|
|
const u0 = map(u[0]);
|
|
const u1 = map(u[1]);
|
|
return clear(u0.add(u1));
|
|
},
|
|
encodeToCurve(msg, options) {
|
|
const optsDst = defaults.encodeDST ? { DST: defaults.encodeDST } : {};
|
|
const opts = Object.assign({}, defaults, optsDst, options);
|
|
const u = hash_to_field(msg, 1, opts);
|
|
const u0 = map(u[0]);
|
|
return clear(u0);
|
|
},
|
|
/** See {@link H2CHasher} */
|
|
mapToCurve(scalars) {
|
|
if (!Array.isArray(scalars))
|
|
throw new Error('expected array of bigints');
|
|
for (const i of scalars)
|
|
if (typeof i !== 'bigint')
|
|
throw new Error('expected array of bigints');
|
|
return clear(map(scalars));
|
|
},
|
|
// hash_to_scalar can produce 0: https://www.rfc-editor.org/errata/eid8393
|
|
// RFC 9380, draft-irtf-cfrg-bbs-signatures-08
|
|
hashToScalar(msg, options) {
|
|
// @ts-ignore
|
|
const N = Point.Fn.ORDER;
|
|
const opts = Object.assign({}, defaults, { p: N, m: 1, DST: exports._DST_scalar }, options);
|
|
return hash_to_field(msg, 1, opts)[0][0];
|
|
},
|
|
};
|
|
}
|
|
//# sourceMappingURL=hash-to-curve.js.map
|