hash-to-curve.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.expand_message_xmd = expand_message_xmd;
  4. exports.expand_message_xof = expand_message_xof;
  5. exports.hash_to_field = hash_to_field;
  6. exports.isogenyMap = isogenyMap;
  7. exports.createHasher = createHasher;
  8. const modular_js_1 = require("./modular.js");
  9. const utils_js_1 = require("./utils.js");
  10. // Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
  11. const os2ip = utils_js_1.bytesToNumberBE;
  12. // Integer to Octet Stream (numberToBytesBE)
  13. function i2osp(value, length) {
  14. if (value < 0 || value >= 1 << (8 * length)) {
  15. throw new Error(`bad I2OSP call: value=${value} length=${length}`);
  16. }
  17. const res = Array.from({ length }).fill(0);
  18. for (let i = length - 1; i >= 0; i--) {
  19. res[i] = value & 0xff;
  20. value >>>= 8;
  21. }
  22. return new Uint8Array(res);
  23. }
  24. function strxor(a, b) {
  25. const arr = new Uint8Array(a.length);
  26. for (let i = 0; i < a.length; i++) {
  27. arr[i] = a[i] ^ b[i];
  28. }
  29. return arr;
  30. }
  31. function anum(item) {
  32. if (!Number.isSafeInteger(item))
  33. throw new Error('number expected');
  34. }
  35. // Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
  36. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1
  37. function expand_message_xmd(msg, DST, lenInBytes, H) {
  38. (0, utils_js_1.abytes)(msg);
  39. (0, utils_js_1.abytes)(DST);
  40. anum(lenInBytes);
  41. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
  42. if (DST.length > 255)
  43. DST = H((0, utils_js_1.concatBytes)((0, utils_js_1.utf8ToBytes)('H2C-OVERSIZE-DST-'), DST));
  44. const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
  45. const ell = Math.ceil(lenInBytes / b_in_bytes);
  46. if (ell > 255)
  47. throw new Error('Invalid xmd length');
  48. const DST_prime = (0, utils_js_1.concatBytes)(DST, i2osp(DST.length, 1));
  49. const Z_pad = i2osp(0, r_in_bytes);
  50. const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
  51. const b = new Array(ell);
  52. const b_0 = H((0, utils_js_1.concatBytes)(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
  53. b[0] = H((0, utils_js_1.concatBytes)(b_0, i2osp(1, 1), DST_prime));
  54. for (let i = 1; i <= ell; i++) {
  55. const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
  56. b[i] = H((0, utils_js_1.concatBytes)(...args));
  57. }
  58. const pseudo_random_bytes = (0, utils_js_1.concatBytes)(...b);
  59. return pseudo_random_bytes.slice(0, lenInBytes);
  60. }
  61. // Produces a uniformly random byte string using an extendable-output function (XOF) H.
  62. // 1. The collision resistance of H MUST be at least k bits.
  63. // 2. H MUST be an XOF that has been proved indifferentiable from
  64. // a random oracle under a reasonable cryptographic assumption.
  65. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2
  66. function expand_message_xof(msg, DST, lenInBytes, k, H) {
  67. (0, utils_js_1.abytes)(msg);
  68. (0, utils_js_1.abytes)(DST);
  69. anum(lenInBytes);
  70. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
  71. // DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
  72. if (DST.length > 255) {
  73. const dkLen = Math.ceil((2 * k) / 8);
  74. DST = H.create({ dkLen }).update((0, utils_js_1.utf8ToBytes)('H2C-OVERSIZE-DST-')).update(DST).digest();
  75. }
  76. if (lenInBytes > 65535 || DST.length > 255)
  77. throw new Error('expand_message_xof: invalid lenInBytes');
  78. return (H.create({ dkLen: lenInBytes })
  79. .update(msg)
  80. .update(i2osp(lenInBytes, 2))
  81. // 2. DST_prime = DST || I2OSP(len(DST), 1)
  82. .update(DST)
  83. .update(i2osp(DST.length, 1))
  84. .digest());
  85. }
  86. /**
  87. * Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
  88. * https://www.rfc-editor.org/rfc/rfc9380#section-5.2
  89. * @param msg a byte string containing the message to hash
  90. * @param count the number of elements of F to output
  91. * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
  92. * @returns [u_0, ..., u_(count - 1)], a list of field elements.
  93. */
  94. function hash_to_field(msg, count, options) {
  95. (0, utils_js_1.validateObject)(options, {
  96. DST: 'stringOrUint8Array',
  97. p: 'bigint',
  98. m: 'isSafeInteger',
  99. k: 'isSafeInteger',
  100. hash: 'hash',
  101. });
  102. const { p, k, m, hash, expand, DST: _DST } = options;
  103. (0, utils_js_1.abytes)(msg);
  104. anum(count);
  105. const DST = typeof _DST === 'string' ? (0, utils_js_1.utf8ToBytes)(_DST) : _DST;
  106. const log2p = p.toString(2).length;
  107. const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
  108. const len_in_bytes = count * m * L;
  109. let prb; // pseudo_random_bytes
  110. if (expand === 'xmd') {
  111. prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
  112. }
  113. else if (expand === 'xof') {
  114. prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
  115. }
  116. else if (expand === '_internal_pass') {
  117. // for internal tests only
  118. prb = msg;
  119. }
  120. else {
  121. throw new Error('expand must be "xmd" or "xof"');
  122. }
  123. const u = new Array(count);
  124. for (let i = 0; i < count; i++) {
  125. const e = new Array(m);
  126. for (let j = 0; j < m; j++) {
  127. const elm_offset = L * (j + i * m);
  128. const tv = prb.subarray(elm_offset, elm_offset + L);
  129. e[j] = (0, modular_js_1.mod)(os2ip(tv), p);
  130. }
  131. u[i] = e;
  132. }
  133. return u;
  134. }
  135. function isogenyMap(field, map) {
  136. // Make same order as in spec
  137. const COEFF = map.map((i) => Array.from(i).reverse());
  138. return (x, y) => {
  139. const [xNum, xDen, yNum, yDen] = COEFF.map((val) => val.reduce((acc, i) => field.add(field.mul(acc, x), i)));
  140. x = field.div(xNum, xDen); // xNum / xDen
  141. y = field.mul(y, field.div(yNum, yDen)); // y * (yNum / yDev)
  142. return { x, y };
  143. };
  144. }
  145. function createHasher(Point, mapToCurve, def) {
  146. if (typeof mapToCurve !== 'function')
  147. throw new Error('mapToCurve() must be defined');
  148. return {
  149. // Encodes byte string to elliptic curve.
  150. // hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
  151. hashToCurve(msg, options) {
  152. const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options });
  153. const u0 = Point.fromAffine(mapToCurve(u[0]));
  154. const u1 = Point.fromAffine(mapToCurve(u[1]));
  155. const P = u0.add(u1).clearCofactor();
  156. P.assertValidity();
  157. return P;
  158. },
  159. // Encodes byte string to elliptic curve.
  160. // encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
  161. encodeToCurve(msg, options) {
  162. const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options });
  163. const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
  164. P.assertValidity();
  165. return P;
  166. },
  167. // Same as encodeToCurve, but without hash
  168. mapToCurve(scalars) {
  169. if (!Array.isArray(scalars))
  170. throw new Error('mapToCurve: expected array of bigints');
  171. for (const i of scalars)
  172. if (typeof i !== 'bigint')
  173. throw new Error(`mapToCurve: expected array of bigints, got ${i} in array`);
  174. const P = Point.fromAffine(mapToCurve(scalars)).clearCofactor();
  175. P.assertValidity();
  176. return P;
  177. },
  178. };
  179. }
  180. //# sourceMappingURL=hash-to-curve.js.map