@noble/curves 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +49 -24
  2. package/abstract/bls.js +1 -1
  3. package/abstract/bls.js.map +1 -1
  4. package/abstract/curve.d.ts +1 -1
  5. package/abstract/curve.d.ts.map +1 -1
  6. package/abstract/curve.js +13 -4
  7. package/abstract/curve.js.map +1 -1
  8. package/abstract/edwards.d.ts.map +1 -1
  9. package/abstract/edwards.js +17 -3
  10. package/abstract/edwards.js.map +1 -1
  11. package/abstract/fft.d.ts +120 -0
  12. package/abstract/fft.d.ts.map +1 -0
  13. package/abstract/fft.js +439 -0
  14. package/abstract/fft.js.map +1 -0
  15. package/abstract/hash-to-curve.d.ts +10 -5
  16. package/abstract/hash-to-curve.d.ts.map +1 -1
  17. package/abstract/hash-to-curve.js +31 -23
  18. package/abstract/hash-to-curve.js.map +1 -1
  19. package/abstract/modular.d.ts +13 -12
  20. package/abstract/modular.d.ts.map +1 -1
  21. package/abstract/modular.js +158 -158
  22. package/abstract/modular.js.map +1 -1
  23. package/abstract/montgomery.d.ts +4 -9
  24. package/abstract/montgomery.d.ts.map +1 -1
  25. package/abstract/montgomery.js +70 -90
  26. package/abstract/montgomery.js.map +1 -1
  27. package/abstract/poseidon.d.ts +39 -2
  28. package/abstract/poseidon.d.ts.map +1 -1
  29. package/abstract/poseidon.js +183 -4
  30. package/abstract/poseidon.js.map +1 -1
  31. package/abstract/tower.d.ts.map +1 -1
  32. package/abstract/tower.js +4 -5
  33. package/abstract/tower.js.map +1 -1
  34. package/abstract/utils.d.ts +1 -0
  35. package/abstract/utils.d.ts.map +1 -1
  36. package/abstract/utils.js +2 -0
  37. package/abstract/utils.js.map +1 -1
  38. package/abstract/weierstrass.d.ts +31 -9
  39. package/abstract/weierstrass.d.ts.map +1 -1
  40. package/abstract/weierstrass.js +67 -48
  41. package/abstract/weierstrass.js.map +1 -1
  42. package/bls12-381.d.ts.map +1 -1
  43. package/bls12-381.js +9 -23
  44. package/bls12-381.js.map +1 -1
  45. package/bn254.d.ts +1 -0
  46. package/bn254.d.ts.map +1 -1
  47. package/bn254.js +10 -0
  48. package/bn254.js.map +1 -1
  49. package/ed25519.d.ts +19 -5
  50. package/ed25519.d.ts.map +1 -1
  51. package/ed25519.js +29 -18
  52. package/ed25519.js.map +1 -1
  53. package/ed448.d.ts +21 -5
  54. package/ed448.d.ts.map +1 -1
  55. package/ed448.js +46 -34
  56. package/ed448.js.map +1 -1
  57. package/esm/abstract/bls.js +1 -1
  58. package/esm/abstract/bls.js.map +1 -1
  59. package/esm/abstract/curve.d.ts +1 -1
  60. package/esm/abstract/curve.d.ts.map +1 -1
  61. package/esm/abstract/curve.js +13 -4
  62. package/esm/abstract/curve.js.map +1 -1
  63. package/esm/abstract/edwards.d.ts.map +1 -1
  64. package/esm/abstract/edwards.js +19 -5
  65. package/esm/abstract/edwards.js.map +1 -1
  66. package/esm/abstract/fft.d.ts +120 -0
  67. package/esm/abstract/fft.d.ts.map +1 -0
  68. package/esm/abstract/fft.js +426 -0
  69. package/esm/abstract/fft.js.map +1 -0
  70. package/esm/abstract/hash-to-curve.d.ts +10 -5
  71. package/esm/abstract/hash-to-curve.d.ts.map +1 -1
  72. package/esm/abstract/hash-to-curve.js +32 -24
  73. package/esm/abstract/hash-to-curve.js.map +1 -1
  74. package/esm/abstract/modular.d.ts +13 -12
  75. package/esm/abstract/modular.d.ts.map +1 -1
  76. package/esm/abstract/modular.js +158 -158
  77. package/esm/abstract/modular.js.map +1 -1
  78. package/esm/abstract/montgomery.d.ts +4 -9
  79. package/esm/abstract/montgomery.d.ts.map +1 -1
  80. package/esm/abstract/montgomery.js +71 -91
  81. package/esm/abstract/montgomery.js.map +1 -1
  82. package/esm/abstract/poseidon.d.ts +39 -2
  83. package/esm/abstract/poseidon.d.ts.map +1 -1
  84. package/esm/abstract/poseidon.js +180 -5
  85. package/esm/abstract/poseidon.js.map +1 -1
  86. package/esm/abstract/tower.d.ts.map +1 -1
  87. package/esm/abstract/tower.js +4 -5
  88. package/esm/abstract/tower.js.map +1 -1
  89. package/esm/abstract/utils.d.ts +1 -0
  90. package/esm/abstract/utils.d.ts.map +1 -1
  91. package/esm/abstract/utils.js +2 -0
  92. package/esm/abstract/utils.js.map +1 -1
  93. package/esm/abstract/weierstrass.d.ts +31 -9
  94. package/esm/abstract/weierstrass.d.ts.map +1 -1
  95. package/esm/abstract/weierstrass.js +69 -50
  96. package/esm/abstract/weierstrass.js.map +1 -1
  97. package/esm/bls12-381.d.ts.map +1 -1
  98. package/esm/bls12-381.js +9 -23
  99. package/esm/bls12-381.js.map +1 -1
  100. package/esm/bn254.d.ts +1 -0
  101. package/esm/bn254.d.ts.map +1 -1
  102. package/esm/bn254.js +10 -0
  103. package/esm/bn254.js.map +1 -1
  104. package/esm/ed25519.d.ts +19 -5
  105. package/esm/ed25519.d.ts.map +1 -1
  106. package/esm/ed25519.js +29 -18
  107. package/esm/ed25519.js.map +1 -1
  108. package/esm/ed448.d.ts +21 -5
  109. package/esm/ed448.d.ts.map +1 -1
  110. package/esm/ed448.js +47 -35
  111. package/esm/ed448.js.map +1 -1
  112. package/esm/jubjub.d.ts +11 -1
  113. package/esm/jubjub.d.ts.map +1 -1
  114. package/esm/jubjub.js +11 -1
  115. package/esm/jubjub.js.map +1 -1
  116. package/esm/misc.d.ts +8 -2
  117. package/esm/misc.d.ts.map +1 -1
  118. package/esm/misc.js +10 -4
  119. package/esm/misc.js.map +1 -1
  120. package/esm/nist.d.ts +30 -0
  121. package/esm/nist.d.ts.map +1 -0
  122. package/esm/nist.js +121 -0
  123. package/esm/nist.js.map +1 -0
  124. package/esm/p256.d.ts +7 -9
  125. package/esm/p256.d.ts.map +1 -1
  126. package/esm/p256.js +6 -44
  127. package/esm/p256.js.map +1 -1
  128. package/esm/p384.d.ts +9 -10
  129. package/esm/p384.d.ts.map +1 -1
  130. package/esm/p384.js +7 -46
  131. package/esm/p384.js.map +1 -1
  132. package/esm/p521.d.ts +7 -8
  133. package/esm/p521.d.ts.map +1 -1
  134. package/esm/p521.js +6 -46
  135. package/esm/p521.js.map +1 -1
  136. package/esm/pasta.d.ts +9 -1
  137. package/esm/pasta.d.ts.map +1 -1
  138. package/esm/pasta.js +9 -1
  139. package/esm/pasta.js.map +1 -1
  140. package/esm/secp256k1.d.ts +3 -3
  141. package/esm/secp256k1.d.ts.map +1 -1
  142. package/esm/secp256k1.js +8 -9
  143. package/esm/secp256k1.js.map +1 -1
  144. package/jubjub.d.ts +11 -1
  145. package/jubjub.d.ts.map +1 -1
  146. package/jubjub.js +12 -5
  147. package/jubjub.js.map +1 -1
  148. package/misc.d.ts +8 -2
  149. package/misc.d.ts.map +1 -1
  150. package/misc.js +11 -5
  151. package/misc.js.map +1 -1
  152. package/nist.d.ts +30 -0
  153. package/nist.d.ts.map +1 -0
  154. package/nist.js +124 -0
  155. package/nist.js.map +1 -0
  156. package/p256.d.ts +7 -9
  157. package/p256.d.ts.map +1 -1
  158. package/p256.js +5 -49
  159. package/p256.js.map +1 -1
  160. package/p384.d.ts +9 -10
  161. package/p384.d.ts.map +1 -1
  162. package/p384.js +6 -51
  163. package/p384.js.map +1 -1
  164. package/p521.d.ts +7 -8
  165. package/p521.d.ts.map +1 -1
  166. package/p521.js +5 -51
  167. package/p521.js.map +1 -1
  168. package/package.json +117 -8
  169. package/pasta.d.ts +9 -1
  170. package/pasta.d.ts.map +1 -1
  171. package/pasta.js +9 -3
  172. package/pasta.js.map +1 -1
  173. package/secp256k1.d.ts +3 -3
  174. package/secp256k1.d.ts.map +1 -1
  175. package/secp256k1.js +9 -10
  176. package/secp256k1.js.map +1 -1
  177. package/src/abstract/bls.ts +1 -1
  178. package/src/abstract/curve.ts +11 -6
  179. package/src/abstract/edwards.ts +26 -12
  180. package/src/abstract/fft.ts +508 -0
  181. package/src/abstract/hash-to-curve.ts +44 -36
  182. package/src/abstract/modular.ts +154 -153
  183. package/src/abstract/montgomery.ts +78 -109
  184. package/src/abstract/poseidon.ts +208 -13
  185. package/src/abstract/tower.ts +4 -5
  186. package/src/abstract/utils.ts +2 -0
  187. package/src/abstract/weierstrass.ts +109 -61
  188. package/src/bls12-381.ts +11 -27
  189. package/src/bn254.ts +10 -0
  190. package/src/ed25519.ts +32 -19
  191. package/src/ed448.ts +91 -75
  192. package/src/jubjub.ts +12 -5
  193. package/src/misc.ts +10 -4
  194. package/src/nist.ts +155 -0
  195. package/src/p256.ts +6 -50
  196. package/src/p384.ts +8 -56
  197. package/src/p521.ts +6 -65
  198. package/src/pasta.ts +9 -1
  199. package/src/secp256k1.ts +12 -11
@@ -5,7 +5,7 @@
5
5
  * @module
6
6
  */
7
7
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
8
- import { mod, pow } from './modular.ts';
8
+ import { mod } from './modular.ts';
9
9
  import {
10
10
  aInRange,
11
11
  bytesToNumberLE,
@@ -16,19 +16,15 @@ import {
16
16
 
17
17
  const _0n = BigInt(0);
18
18
  const _1n = BigInt(1);
19
+ const _2n = BigInt(2);
19
20
  type Hex = string | Uint8Array;
20
21
 
21
22
  export type CurveType = {
22
23
  P: bigint; // finite field prime
23
- nByteLength: number;
24
- adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
25
- domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
26
- a: bigint;
27
- montgomeryBits: number;
28
- powPminus2?: (x: bigint) => bigint;
29
- xyToU?: (x: bigint, y: bigint) => bigint;
30
- Gu: bigint;
31
- randomBytes?: (bytesLength?: number) => Uint8Array;
24
+ type: 'x25519' | 'x448';
25
+ adjustScalarBytes: (bytes: Uint8Array) => Uint8Array;
26
+ powPminus2: (x: bigint) => bigint;
27
+ randomBytes: (bytesLength?: number) => Uint8Array;
32
28
  };
33
29
 
34
30
  export type CurveFn = {
@@ -41,66 +37,87 @@ export type CurveFn = {
41
37
  };
42
38
 
43
39
  function validateOpts(curve: CurveType) {
44
- validateObject(
45
- curve,
46
- {
47
- a: 'bigint',
48
- },
49
- {
50
- montgomeryBits: 'isSafeInteger',
51
- nByteLength: 'isSafeInteger',
52
- adjustScalarBytes: 'function',
53
- domain: 'function',
54
- powPminus2: 'function',
55
- Gu: 'bigint',
56
- }
57
- );
58
- // Set defaults
40
+ validateObject(curve, {
41
+ adjustScalarBytes: 'function',
42
+ powPminus2: 'function',
43
+ });
59
44
  return Object.freeze({ ...curve } as const);
60
45
  }
61
46
 
62
- // Uses only one coordinate instead of two
63
47
  export function montgomery(curveDef: CurveType): CurveFn {
64
48
  const CURVE = validateOpts(curveDef);
65
- const { P } = CURVE;
49
+ const { P, type, adjustScalarBytes, powPminus2 } = CURVE;
50
+ const is25519 = type === 'x25519';
51
+ if (!is25519 && type !== 'x448') throw new Error('invalid type');
52
+
53
+ const montgomeryBits = is25519 ? 255 : 448;
54
+ const fieldLen = is25519 ? 32 : 56;
55
+ const Gu = is25519 ? BigInt(9) : BigInt(5);
56
+ // RFC 7748 #5:
57
+ // The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519 and
58
+ // (156326 - 2) / 4 = 39081 for curve448/X448
59
+ // const a = is25519 ? 156326n : 486662n;
60
+ const a24 = is25519 ? BigInt(121665) : BigInt(39081);
61
+ // RFC: x25519 "the resulting integer is of the form 2^254 plus
62
+ // eight times a value between 0 and 2^251 - 1 (inclusive)"
63
+ // x448: "2^447 plus four times a value between 0 and 2^445 - 1 (inclusive)"
64
+ const minScalar = is25519 ? _2n ** BigInt(254) : _2n ** BigInt(447);
65
+ const maxAdded = is25519
66
+ ? BigInt(8) * _2n ** BigInt(251) - _1n
67
+ : BigInt(4) * _2n ** BigInt(445) - _1n;
68
+ const maxScalar = minScalar + maxAdded + _1n; // (inclusive)
66
69
  const modP = (n: bigint) => mod(n, P);
67
- const montgomeryBits = CURVE.montgomeryBits;
68
- const montgomeryBytes = Math.ceil(montgomeryBits / 8);
69
- const fieldLen = CURVE.nByteLength;
70
- const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
71
- const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P));
70
+ const GuBytes = encodeU(Gu);
71
+ function encodeU(u: bigint): Uint8Array {
72
+ return numberToBytesLE(modP(u), fieldLen);
73
+ }
74
+ function decodeU(u: Hex): bigint {
75
+ const _u = ensureBytes('u coordinate', u, fieldLen);
76
+ // RFC: When receiving such an array, implementations of X25519
77
+ // (but not X448) MUST mask the most significant bit in the final byte.
78
+ if (is25519) _u[31] &= 127; // 0b0111_1111
79
+ // RFC: Implementations MUST accept non-canonical values and process them as
80
+ // if they had been reduced modulo the field prime. The non-canonical
81
+ // values are 2^255 - 19 through 2^255 - 1 for X25519 and 2^448 - 2^224
82
+ // - 1 through 2^448 - 1 for X448.
83
+ return modP(bytesToNumberLE(_u));
84
+ }
85
+ function decodeScalar(scalar: Hex): bigint {
86
+ return bytesToNumberLE(adjustScalarBytes(ensureBytes('scalar', scalar, fieldLen)));
87
+ }
88
+ function scalarMult(scalar: Hex, u: Hex): Uint8Array {
89
+ const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar));
90
+ // Some public keys are useless, of low-order. Curve author doesn't think
91
+ // it needs to be validated, but we do it nonetheless.
92
+ // https://cr.yp.to/ecdh.html#validate
93
+ if (pu === _0n) throw new Error('invalid private or public key received');
94
+ return encodeU(pu);
95
+ }
96
+ // Computes public key from private. By doing scalar multiplication of base point.
97
+ function scalarMultBase(scalar: Hex): Uint8Array {
98
+ return scalarMult(scalar, GuBytes);
99
+ }
72
100
 
73
- // cswap from RFC7748. But it is not from RFC7748!
74
- /*
75
- cswap(swap, x_2, x_3):
76
- dummy = mask(swap) AND (x_2 XOR x_3)
77
- x_2 = x_2 XOR dummy
78
- x_3 = x_3 XOR dummy
79
- Return (x_2, x_3)
80
- Where mask(swap) is the all-1 or all-0 word of the same length as x_2
81
- and x_3, computed, e.g., as mask(swap) = 0 - swap.
82
- */
83
- function cswap(swap: bigint, x_2: bigint, x_3: bigint): [bigint, bigint] {
101
+ // cswap from RFC7748 "example code"
102
+ function cswap(swap: bigint, x_2: bigint, x_3: bigint): { x_2: bigint; x_3: bigint } {
103
+ // dummy = mask(swap) AND (x_2 XOR x_3)
104
+ // Where mask(swap) is the all-1 or all-0 word of the same length as x_2
105
+ // and x_3, computed, e.g., as mask(swap) = 0 - swap.
84
106
  const dummy = modP(swap * (x_2 - x_3));
85
- x_2 = modP(x_2 - dummy);
86
- x_3 = modP(x_3 + dummy);
87
- return [x_2, x_3];
107
+ x_2 = modP(x_2 - dummy); // x_2 = x_2 XOR dummy
108
+ x_3 = modP(x_3 + dummy); // x_3 = x_3 XOR dummy
109
+ return { x_2, x_3 };
88
110
  }
89
111
 
90
- // x25519 from 4
91
- // The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
92
- const a24 = (CURVE.a - BigInt(2)) / BigInt(4);
93
112
  /**
94
- *
113
+ * Montgomery x-only multiplication ladder.
95
114
  * @param pointU u coordinate (x) on Montgomery Curve 25519
96
115
  * @param scalar by which the point would be multiplied
97
116
  * @returns new Point on Montgomery curve
98
117
  */
99
118
  function montgomeryLadder(u: bigint, scalar: bigint): bigint {
100
119
  aInRange('u', u, _0n, P);
101
- aInRange('scalar', scalar, _0n, P);
102
- // Section 5: Implementations MUST accept non-canonical values and process them as
103
- // if they had been reduced modulo the field prime.
120
+ aInRange('scalar', scalar, minScalar, maxScalar);
104
121
  const k = scalar;
105
122
  const x_1 = u;
106
123
  let x_2 = _1n;
@@ -108,16 +125,11 @@ export function montgomery(curveDef: CurveType): CurveFn {
108
125
  let x_3 = u;
109
126
  let z_3 = _1n;
110
127
  let swap = _0n;
111
- let sw: [bigint, bigint];
112
128
  for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
113
129
  const k_t = (k >> t) & _1n;
114
130
  swap ^= k_t;
115
- sw = cswap(swap, x_2, x_3);
116
- x_2 = sw[0];
117
- x_3 = sw[1];
118
- sw = cswap(swap, z_2, z_3);
119
- z_2 = sw[0];
120
- z_3 = sw[1];
131
+ ({ x_2, x_3 } = cswap(swap, x_2, x_3));
132
+ ({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
121
133
  swap = k_t;
122
134
 
123
135
  const A = x_2 + z_2;
@@ -136,53 +148,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
136
148
  x_2 = modP(AA * BB);
137
149
  z_2 = modP(E * (AA + modP(a24 * E)));
138
150
  }
139
- // (x_2, x_3) = cswap(swap, x_2, x_3)
140
- sw = cswap(swap, x_2, x_3);
141
- x_2 = sw[0];
142
- x_3 = sw[1];
143
- // (z_2, z_3) = cswap(swap, z_2, z_3)
144
- sw = cswap(swap, z_2, z_3);
145
- z_2 = sw[0];
146
- z_3 = sw[1];
147
- // z_2^(p - 2)
148
- const z2 = powPminus2(z_2);
149
- // Return x_2 * (z_2^(p - 2))
150
- return modP(x_2 * z2);
151
- }
152
-
153
- function encodeUCoordinate(u: bigint): Uint8Array {
154
- return numberToBytesLE(modP(u), montgomeryBytes);
155
- }
156
-
157
- function decodeUCoordinate(uEnc: Hex): bigint {
158
- // Section 5: When receiving such an array, implementations of X25519
159
- // MUST mask the most significant bit in the final byte.
160
- const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
161
- if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
162
- return bytesToNumberLE(u);
163
- }
164
- function decodeScalar(n: Hex): bigint {
165
- const bytes = ensureBytes('scalar', n);
166
- const len = bytes.length;
167
- if (len !== montgomeryBytes && len !== fieldLen) {
168
- let valid = '' + montgomeryBytes + ' or ' + fieldLen;
169
- throw new Error('invalid scalar, expected ' + valid + ' bytes, got ' + len);
170
- }
171
- return bytesToNumberLE(adjustScalarBytes(bytes));
172
- }
173
- function scalarMult(scalar: Hex, u: Hex): Uint8Array {
174
- const pointU = decodeUCoordinate(u);
175
- const _scalar = decodeScalar(scalar);
176
- const pu = montgomeryLadder(pointU, _scalar);
177
- // The result was not contributory
178
- // https://cr.yp.to/ecdh.html#validate
179
- if (pu === _0n) throw new Error('invalid private or public key received');
180
- return encodeUCoordinate(pu);
181
- }
182
- // Computes public key from private. By doing scalar multiplication of base point.
183
- const GuBytes = encodeUCoordinate(CURVE.Gu);
184
- function scalarMultBase(scalar: Hex): Uint8Array {
185
- return scalarMult(scalar, GuBytes);
151
+ ({ x_2, x_3 } = cswap(swap, x_2, x_3));
152
+ ({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
153
+ const z2 = powPminus2(z_2); // `Fp.pow(x, P - _2n)` is much slower equivalent
154
+ return modP(x_2 * z2); // Return x_2 * (z_2^(p - 2))
186
155
  }
187
156
 
188
157
  return {
@@ -190,7 +159,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
190
159
  scalarMultBase,
191
160
  getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
192
161
  getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
193
- utils: { randomPrivateKey: () => CURVE.randomBytes!(CURVE.nByteLength) },
194
- GuBytes: GuBytes,
162
+ utils: { randomPrivateKey: () => CURVE.randomBytes!(fieldLen) },
163
+ GuBytes: GuBytes.slice(),
195
164
  };
196
165
  }
@@ -7,19 +7,127 @@
7
7
  * @module
8
8
  */
9
9
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
10
- import { FpPow, type IField, validateField } from './modular.ts';
10
+ import { FpInvertBatch, FpPow, type IField, validateField } from './modular.ts';
11
+ import { bitGet } from './utils.ts';
11
12
 
12
- export type PoseidonOpts = {
13
+ // Grain LFSR (Linear-Feedback Shift Register): https://eprint.iacr.org/2009/109.pdf
14
+ function grainLFSR(state: number[]): () => boolean {
15
+ let pos = 0;
16
+ if (state.length !== 80) throw new Error('grainLFRS: wrong state length, should be 80 bits');
17
+ const getBit = (): boolean => {
18
+ const r = (offset: number) => state[(pos + offset) % 80];
19
+ const bit = r(62) ^ r(51) ^ r(38) ^ r(23) ^ r(13) ^ r(0);
20
+ state[pos] = bit;
21
+ pos = ++pos % 80;
22
+ return !!bit;
23
+ };
24
+ for (let i = 0; i < 160; i++) getBit();
25
+ return () => {
26
+ // https://en.wikipedia.org/wiki/Shrinking_generator
27
+ while (true) {
28
+ const b1 = getBit();
29
+ const b2 = getBit();
30
+ if (!b1) continue;
31
+ return b2;
32
+ }
33
+ };
34
+ }
35
+
36
+ export type PoseidonBasicOpts = {
13
37
  Fp: IField<bigint>;
14
- t: number;
38
+ t: number; // t = rate + capacity
15
39
  roundsFull: number;
16
40
  roundsPartial: number;
41
+ isSboxInverse?: boolean;
42
+ };
43
+
44
+ function validateBasicOpts(opts: PoseidonBasicOpts) {
45
+ const { Fp, roundsFull } = opts;
46
+ validateField(Fp);
47
+ for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
48
+ if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
49
+ throw new Error('invalid number ' + i);
50
+ }
51
+ if (opts.isSboxInverse !== undefined && typeof opts.isSboxInverse !== 'boolean')
52
+ throw new Error(`Poseidon: invalid param isSboxInverse=${opts.isSboxInverse}`);
53
+ if (roundsFull & 1) throw new Error('roundsFull is not even' + roundsFull);
54
+ }
55
+
56
+ function poseidonGrain(opts: PoseidonBasicOpts) {
57
+ validateBasicOpts(opts);
58
+ const { Fp } = opts;
59
+ const state = Array(80).fill(1);
60
+ let pos = 0;
61
+ const writeBits = (value: bigint, bitCount: number) => {
62
+ for (let i = bitCount - 1; i >= 0; i--) state[pos++] = Number(bitGet(value, i));
63
+ };
64
+ const _0n = BigInt(0);
65
+ const _1n = BigInt(1);
66
+ writeBits(_1n, 2); // prime field
67
+ writeBits(opts.isSboxInverse ? _1n : _0n, 4); // b2..b5
68
+ writeBits(BigInt(Fp.BITS), 12); // b6..b17
69
+ writeBits(BigInt(opts.t), 12); // b18..b29
70
+ writeBits(BigInt(opts.roundsFull), 10); // b30..b39
71
+ writeBits(BigInt(opts.roundsPartial), 10); // b40..b49
72
+
73
+ const getBit = grainLFSR(state);
74
+ return (count: number, reject: boolean): bigint[] => {
75
+ const res: bigint[] = [];
76
+ for (let i = 0; i < count; i++) {
77
+ while (true) {
78
+ let num = _0n;
79
+ for (let i = 0; i < Fp.BITS; i++) {
80
+ num <<= _1n;
81
+ if (getBit()) num |= _1n;
82
+ }
83
+ if (reject && num >= Fp.ORDER) continue; // rejection sampling
84
+ res.push(Fp.create(num));
85
+ break;
86
+ }
87
+ }
88
+ return res;
89
+ };
90
+ }
91
+
92
+ export type PoseidonGrainOpts = PoseidonBasicOpts & {
17
93
  sboxPower?: number;
18
- reversePartialPowIdx?: boolean; // Hack for stark
19
- mds: bigint[][];
20
- roundConstants: bigint[][];
21
94
  };
22
95
 
96
+ type PoseidonConstants = { mds: bigint[][]; roundConstants: bigint[][] };
97
+
98
+ // NOTE: this is not standard but used often for constant generation for poseidon
99
+ // (grain LFRS-like structure)
100
+ export function grainGenConstants(opts: PoseidonGrainOpts, skipMDS: number = 0): PoseidonConstants {
101
+ const { Fp, t, roundsFull, roundsPartial } = opts;
102
+ const rounds = roundsFull + roundsPartial;
103
+ const sample = poseidonGrain(opts);
104
+ const roundConstants: bigint[][] = [];
105
+ for (let r = 0; r < rounds; r++) roundConstants.push(sample(t, true));
106
+ if (skipMDS > 0) for (let i = 0; i < skipMDS; i++) sample(2 * t, false);
107
+ const xs = sample(t, false);
108
+ const ys = sample(t, false);
109
+ // Construct MDS Matrix M[i][j] = 1 / (xs[i] + ys[j])
110
+ const mds: bigint[][] = [];
111
+ for (let i = 0; i < t; i++) {
112
+ const row: bigint[] = [];
113
+ for (let j = 0; j < t; j++) {
114
+ const xy = Fp.add(xs[i], ys[j]);
115
+ if (Fp.is0(xy))
116
+ throw new Error(`Error generating MDS matrix: xs[${i}] + ys[${j}] resulted in zero.`);
117
+ row.push(xy);
118
+ }
119
+ mds.push(FpInvertBatch(Fp, row));
120
+ }
121
+
122
+ return { roundConstants, mds };
123
+ }
124
+
125
+ export type PoseidonOpts = PoseidonBasicOpts &
126
+ PoseidonConstants & {
127
+ sboxPower?: number;
128
+ reversePartialPowIdx?: boolean; // Hack for stark
129
+ };
130
+
23
131
  export function validateOpts(opts: PoseidonOpts): Readonly<{
24
132
  rounds: number;
25
133
  sboxFn: (n: bigint) => bigint;
@@ -32,15 +140,10 @@ export function validateOpts(opts: PoseidonOpts): Readonly<{
32
140
  sboxPower?: number;
33
141
  reversePartialPowIdx?: boolean; // Hack for stark
34
142
  }> {
143
+ validateBasicOpts(opts);
35
144
  const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
36
145
  const { roundsFull, roundsPartial, sboxPower, t } = opts;
37
146
 
38
- validateField(Fp);
39
- for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
40
- if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
41
- throw new Error('invalid number ' + i);
42
- }
43
-
44
147
  // MDS is TxT matrix
45
148
  if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: invalid MDS matrix');
46
149
  const _mds = mds.map((mdsRow) => {
@@ -68,7 +171,7 @@ export function validateOpts(opts: PoseidonOpts): Readonly<{
68
171
  });
69
172
  });
70
173
 
71
- if (!sboxPower || ![3, 5, 7].includes(sboxPower)) throw new Error('invalid sboxPower');
174
+ if (!sboxPower || ![3, 5, 7, 17].includes(sboxPower)) throw new Error('invalid sboxPower');
72
175
  const _sboxPower = BigInt(sboxPower);
73
176
  let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
74
177
  // Unwrapped sbox power for common cases (195->142μs)
@@ -134,3 +237,95 @@ export function poseidon(opts: PoseidonOpts): {
134
237
  poseidonHash.roundConstants = roundConstants;
135
238
  return poseidonHash;
136
239
  }
240
+
241
+ export class PoseidonSponge {
242
+ private Fp: IField<bigint>;
243
+ readonly rate: number;
244
+ readonly capacity: number;
245
+ readonly hash: ReturnType<typeof poseidon>;
246
+ private state: bigint[]; // [...capacity, ...rate]
247
+ private pos = 0;
248
+ private isAbsorbing = true;
249
+
250
+ constructor(
251
+ Fp: IField<bigint>,
252
+ rate: number,
253
+ capacity: number,
254
+ hash: ReturnType<typeof poseidon>
255
+ ) {
256
+ this.Fp = Fp;
257
+ this.hash = hash;
258
+ this.rate = rate;
259
+ this.capacity = capacity;
260
+ this.state = new Array(rate + capacity);
261
+ this.clean();
262
+ }
263
+ private process(): void {
264
+ this.state = this.hash(this.state);
265
+ }
266
+ absorb(input: bigint[]): void {
267
+ for (const i of input)
268
+ if (typeof i !== 'bigint' || !this.Fp.isValid(i)) throw new Error('invalid input: ' + i);
269
+ for (let i = 0; i < input.length; ) {
270
+ if (!this.isAbsorbing || this.pos === this.rate) {
271
+ this.process();
272
+ this.pos = 0;
273
+ this.isAbsorbing = true;
274
+ }
275
+ const chunk = Math.min(this.rate - this.pos, input.length - i);
276
+ for (let j = 0; j < chunk; j++) {
277
+ const idx = this.capacity + this.pos++;
278
+ this.state[idx] = this.Fp.add(this.state[idx], input[i++]);
279
+ }
280
+ }
281
+ }
282
+ squeeze(count: number): bigint[] {
283
+ const res: bigint[] = [];
284
+ while (res.length < count) {
285
+ if (this.isAbsorbing || this.pos === this.rate) {
286
+ this.process();
287
+ this.pos = 0;
288
+ this.isAbsorbing = false;
289
+ }
290
+ const chunk = Math.min(this.rate - this.pos, count - res.length);
291
+ for (let i = 0; i < chunk; i++) res.push(this.state[this.capacity + this.pos++]);
292
+ }
293
+ return res;
294
+ }
295
+ clean(): void {
296
+ this.state.fill(this.Fp.ZERO);
297
+ this.isAbsorbing = true;
298
+ this.pos = 0;
299
+ }
300
+ clone(): PoseidonSponge {
301
+ const c = new PoseidonSponge(this.Fp, this.rate, this.capacity, this.hash);
302
+ c.pos = this.pos;
303
+ c.state = [...this.state];
304
+ return c;
305
+ }
306
+ }
307
+
308
+ export type PoseidonSpongeOpts = Omit<PoseidonOpts, 't'> & {
309
+ rate: number;
310
+ capacity: number;
311
+ };
312
+
313
+ /**
314
+ * The method is not defined in spec, but nevertheless used often.
315
+ * Check carefully for compatibility: there are many edge cases, like absorbing an empty array.
316
+ * We cross-test against:
317
+ * - https://github.com/ProvableHQ/snarkVM/tree/staging/algorithms
318
+ * - https://github.com/arkworks-rs/crypto-primitives/tree/main
319
+ */
320
+ export function poseidonSponge(opts: PoseidonSpongeOpts): () => PoseidonSponge {
321
+ for (const i of ['rate', 'capacity'] as const) {
322
+ if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
323
+ throw new Error('invalid number ' + i);
324
+ }
325
+ const { rate, capacity } = opts;
326
+ const t = opts.rate + opts.capacity;
327
+ // Re-use hash instance between multiple instances
328
+ const hash = poseidon({ ...opts, t });
329
+ const { Fp } = opts;
330
+ return () => new PoseidonSponge(Fp, rate, capacity, hash);
331
+ }
@@ -88,7 +88,7 @@ export function psiFrobenius(
88
88
  PSI2_X: Fp2;
89
89
  PSI2_Y: Fp2;
90
90
  } {
91
- // Ψ endomorphism
91
+ // GLV endomorphism Ψ(P)
92
92
  const PSI_X = Fp2.pow(base, (Fp.ORDER - _1n) / _3n); // u^((p-1)/3)
93
93
  const PSI_Y = Fp2.pow(base, (Fp.ORDER - _1n) / _2n); // u^((p-1)/2)
94
94
  function psi(x: Fp2, y: Fp2): [Fp2, Fp2] {
@@ -167,7 +167,6 @@ export function tower12(opts: Tower12Opts): {
167
167
  // Fp
168
168
  const Fp = mod.Field(ORDER);
169
169
  const FpNONRESIDUE = Fp.create(opts.NONRESIDUE || BigInt(-1));
170
- const FpLegendre = mod.FpLegendre(ORDER);
171
170
  const Fpdiv2 = Fp.div(Fp.ONE, _2n); // 1/2
172
171
 
173
172
  // Fp2
@@ -265,14 +264,14 @@ export function tower12(opts: Tower12Opts): {
265
264
  const { c0, c1 } = num;
266
265
  if (Fp.is0(c1)) {
267
266
  // if c0 is quadratic residue
268
- if (Fp.eql(FpLegendre(Fp, c0), Fp.ONE)) return Fp2.create({ c0: Fp.sqrt(c0), c1: Fp.ZERO });
267
+ if (mod.FpLegendre(Fp, c0) === 1) return Fp2.create({ c0: Fp.sqrt(c0), c1: Fp.ZERO });
269
268
  else return Fp2.create({ c0: Fp.ZERO, c1: Fp.sqrt(Fp.div(c0, FpNONRESIDUE)) });
270
269
  }
271
270
  const a = Fp.sqrt(Fp.sub(Fp.sqr(c0), Fp.mul(Fp.sqr(c1), FpNONRESIDUE)));
272
271
  let d = Fp.mul(Fp.add(a, c0), Fpdiv2);
273
- const legendre = FpLegendre(Fp, d);
272
+ const legendre = mod.FpLegendre(Fp, d);
274
273
  // -1, Quadratic non residue
275
- if (!Fp.is0(legendre) && !Fp.eql(legendre, Fp.ONE)) d = Fp.sub(d, a);
274
+ if (legendre === -1) d = Fp.sub(d, a);
276
275
  const a0 = Fp.sqrt(d);
277
276
  const candidateSqrt = Fp2.create({ c0: a0, c1: Fp.div(Fp.mul(c1, Fpdiv2), a0) });
278
277
  if (!Fp2.eql(Fp2.sqr(candidateSqrt), num)) throw new Error('Cannot find square root');
@@ -32,6 +32,7 @@ export function abool(title: string, value: boolean): void {
32
32
  if (typeof value !== 'boolean') throw new Error(title + ' boolean expected, got ' + value);
33
33
  }
34
34
 
35
+ // Used in weierstrass, der
35
36
  export function numberToHexUnpadded(num: number | bigint): string {
36
37
  const hex = num.toString(16);
37
38
  return hex.length & 1 ? '0' + hex : hex;
@@ -217,6 +218,7 @@ export function aInRange(title: string, n: bigint, min: bigint, max: bigint): vo
217
218
  /**
218
219
  * Calculates amount of bits in a bigint.
219
220
  * Same as `n.toString(2).length`
221
+ * TODO: merge with nLength in modular
220
222
  */
221
223
  export function bitLen(n: bigint): number {
222
224
  let len;