hash-to-curve.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
  2. import type { AffinePoint, Group, GroupConstructor } from './curve.js';
  3. import { IField, mod } from './modular.js';
  4. import type { CHash } from './utils.js';
  5. import { abytes, bytesToNumberBE, concatBytes, utf8ToBytes, validateObject } from './utils.js';
  6. /**
  7. * * `DST` is a domain separation tag, defined in section 2.2.5
  8. * * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
  9. * * `m` is extension degree (1 for prime fields)
  10. * * `k` is the target security target in bits (e.g. 128), from section 5.1
  11. * * `expand` is `xmd` (SHA2, SHA3, BLAKE) or `xof` (SHAKE, BLAKE-XOF)
  12. * * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
  13. */
  14. type UnicodeOrBytes = string | Uint8Array;
  15. export type Opts = {
  16. DST: UnicodeOrBytes;
  17. p: bigint;
  18. m: number;
  19. k: number;
  20. expand: 'xmd' | 'xof';
  21. hash: CHash;
  22. };
  23. // Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
  24. const os2ip = bytesToNumberBE;
  25. // Integer to Octet Stream (numberToBytesBE)
  26. function i2osp(value: number, length: number): Uint8Array {
  27. if (value < 0 || value >= 1 << (8 * length)) {
  28. throw new Error(`bad I2OSP call: value=${value} length=${length}`);
  29. }
  30. const res = Array.from({ length }).fill(0) as number[];
  31. for (let i = length - 1; i >= 0; i--) {
  32. res[i] = value & 0xff;
  33. value >>>= 8;
  34. }
  35. return new Uint8Array(res);
  36. }
  37. function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
  38. const arr = new Uint8Array(a.length);
  39. for (let i = 0; i < a.length; i++) {
  40. arr[i] = a[i] ^ b[i];
  41. }
  42. return arr;
  43. }
  44. function anum(item: unknown): void {
  45. if (!Number.isSafeInteger(item)) throw new Error('number expected');
  46. }
  47. // Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
  48. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1
  49. export function expand_message_xmd(
  50. msg: Uint8Array,
  51. DST: Uint8Array,
  52. lenInBytes: number,
  53. H: CHash
  54. ): Uint8Array {
  55. abytes(msg);
  56. abytes(DST);
  57. anum(lenInBytes);
  58. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
  59. if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
  60. const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
  61. const ell = Math.ceil(lenInBytes / b_in_bytes);
  62. if (ell > 255) throw new Error('Invalid xmd length');
  63. const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
  64. const Z_pad = i2osp(0, r_in_bytes);
  65. const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
  66. const b = new Array<Uint8Array>(ell);
  67. const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
  68. b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
  69. for (let i = 1; i <= ell; i++) {
  70. const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
  71. b[i] = H(concatBytes(...args));
  72. }
  73. const pseudo_random_bytes = concatBytes(...b);
  74. return pseudo_random_bytes.slice(0, lenInBytes);
  75. }
  76. // Produces a uniformly random byte string using an extendable-output function (XOF) H.
  77. // 1. The collision resistance of H MUST be at least k bits.
  78. // 2. H MUST be an XOF that has been proved indifferentiable from
  79. // a random oracle under a reasonable cryptographic assumption.
  80. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2
  81. export function expand_message_xof(
  82. msg: Uint8Array,
  83. DST: Uint8Array,
  84. lenInBytes: number,
  85. k: number,
  86. H: CHash
  87. ): Uint8Array {
  88. abytes(msg);
  89. abytes(DST);
  90. anum(lenInBytes);
  91. // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
  92. // DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
  93. if (DST.length > 255) {
  94. const dkLen = Math.ceil((2 * k) / 8);
  95. DST = H.create({ dkLen }).update(utf8ToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
  96. }
  97. if (lenInBytes > 65535 || DST.length > 255)
  98. throw new Error('expand_message_xof: invalid lenInBytes');
  99. return (
  100. H.create({ dkLen: lenInBytes })
  101. .update(msg)
  102. .update(i2osp(lenInBytes, 2))
  103. // 2. DST_prime = DST || I2OSP(len(DST), 1)
  104. .update(DST)
  105. .update(i2osp(DST.length, 1))
  106. .digest()
  107. );
  108. }
  109. /**
  110. * Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
  111. * https://www.rfc-editor.org/rfc/rfc9380#section-5.2
  112. * @param msg a byte string containing the message to hash
  113. * @param count the number of elements of F to output
  114. * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
  115. * @returns [u_0, ..., u_(count - 1)], a list of field elements.
  116. */
  117. export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
  118. validateObject(options, {
  119. DST: 'stringOrUint8Array',
  120. p: 'bigint',
  121. m: 'isSafeInteger',
  122. k: 'isSafeInteger',
  123. hash: 'hash',
  124. });
  125. const { p, k, m, hash, expand, DST: _DST } = options;
  126. abytes(msg);
  127. anum(count);
  128. const DST = typeof _DST === 'string' ? utf8ToBytes(_DST) : _DST;
  129. const log2p = p.toString(2).length;
  130. const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
  131. const len_in_bytes = count * m * L;
  132. let prb; // pseudo_random_bytes
  133. if (expand === 'xmd') {
  134. prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
  135. } else if (expand === 'xof') {
  136. prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
  137. } else if (expand === '_internal_pass') {
  138. // for internal tests only
  139. prb = msg;
  140. } else {
  141. throw new Error('expand must be "xmd" or "xof"');
  142. }
  143. const u = new Array(count);
  144. for (let i = 0; i < count; i++) {
  145. const e = new Array(m);
  146. for (let j = 0; j < m; j++) {
  147. const elm_offset = L * (j + i * m);
  148. const tv = prb.subarray(elm_offset, elm_offset + L);
  149. e[j] = mod(os2ip(tv), p);
  150. }
  151. u[i] = e;
  152. }
  153. return u;
  154. }
  155. export function isogenyMap<T, F extends IField<T>>(field: F, map: [T[], T[], T[], T[]]) {
  156. // Make same order as in spec
  157. const COEFF = map.map((i) => Array.from(i).reverse());
  158. return (x: T, y: T) => {
  159. const [xNum, xDen, yNum, yDen] = COEFF.map((val) =>
  160. val.reduce((acc, i) => field.add(field.mul(acc, x), i))
  161. );
  162. x = field.div(xNum, xDen); // xNum / xDen
  163. y = field.mul(y, field.div(yNum, yDen)); // y * (yNum / yDev)
  164. return { x, y };
  165. };
  166. }
  167. export interface H2CPoint<T> extends Group<H2CPoint<T>> {
  168. add(rhs: H2CPoint<T>): H2CPoint<T>;
  169. toAffine(iz?: bigint): AffinePoint<T>;
  170. clearCofactor(): H2CPoint<T>;
  171. assertValidity(): void;
  172. }
  173. export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> {
  174. fromAffine(ap: AffinePoint<T>): H2CPoint<T>;
  175. }
  176. export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
  177. // Separated from initialization opts, so users won't accidentally change per-curve parameters
  178. // (changing DST is ok!)
  179. export type htfBasicOpts = { DST: UnicodeOrBytes };
  180. export function createHasher<T>(
  181. Point: H2CPointConstructor<T>,
  182. mapToCurve: MapToCurve<T>,
  183. def: Opts & { encodeDST?: UnicodeOrBytes }
  184. ) {
  185. if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
  186. return {
  187. // Encodes byte string to elliptic curve.
  188. // hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
  189. hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
  190. const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
  191. const u0 = Point.fromAffine(mapToCurve(u[0]));
  192. const u1 = Point.fromAffine(mapToCurve(u[1]));
  193. const P = u0.add(u1).clearCofactor();
  194. P.assertValidity();
  195. return P;
  196. },
  197. // Encodes byte string to elliptic curve.
  198. // encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
  199. encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
  200. const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
  201. const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
  202. P.assertValidity();
  203. return P;
  204. },
  205. // Same as encodeToCurve, but without hash
  206. mapToCurve(scalars: bigint[]) {
  207. if (!Array.isArray(scalars)) throw new Error('mapToCurve: expected array of bigints');
  208. for (const i of scalars)
  209. if (typeof i !== 'bigint')
  210. throw new Error(`mapToCurve: expected array of bigints, got ${i} in array`);
  211. const P = Point.fromAffine(mapToCurve(scalars)).clearCofactor();
  212. P.assertValidity();
  213. return P;
  214. },
  215. };
  216. }