@@ -6,6 +6,8 @@ pragma solidity ^0.8.23;
6
6
/// defined in EIP-2537, see <https://eips.ethereum.org/EIPS/eip-2537>.
7
7
/// @dev Precompile addresses come from the BLS addresses submodule in AlphaNet, see
8
8
/// <https://github.com/paradigmxyz/alphanet/blob/main/crates/precompile/src/addresses.rs>
9
+ /// @notice `hashToCurve` logic is based on <https://github.com/ethyla/bls12-381-hash-to-curve/blob/main/src/HashToCurve.sol>
10
+ /// with small modifications.
9
11
library BLS {
10
12
/// @dev A base field element (Fp) is encoded as 64 bytes by performing the
11
13
/// BigEndian encoding of the corresponding (unsigned) integer. Due to the size of p,
@@ -150,4 +152,137 @@ library BLS {
150
152
require (success, "MAP_FP2_TO_G2 failed " );
151
153
return abi.decode (output, (G2Point));
152
154
}
155
+
156
+ /// @notice Computes a point in G2 from a message
157
+ /// @dev Uses the eip-2537 precompiles
158
+ /// @param message Arbitrarylength byte string to be hashed
159
+ /// @return A point in G2
160
+ function hashToCurveG2 (bytes memory message ) internal view returns (G2Point memory ) {
161
+ // 1. u = hash_to_field(msg, 2)
162
+ Fp2[2 ] memory u = hashToFieldFp2 (message, bytes ("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_ " ));
163
+ // 2. Q0 = map_to_curve(u[0])
164
+ G2Point memory q0 = MapFp2ToG2 (u[0 ]);
165
+ // 3. Q1 = map_to_curve(u[1])
166
+ G2Point memory q1 = MapFp2ToG2 (u[1 ]);
167
+ // 4. R = Q0 + Q1
168
+ return G2Add (q0, q1);
169
+ }
170
+
171
+ /// @notice Computes a field point from a message
172
+ /// @dev Follows https://datatracker.ietf.org/doc/html/rfc9380#section-5.2
173
+ /// @param message Arbitrarylength byte string to be hashed
174
+ /// @param dst The domain separation tag
175
+ /// @return Two field points
176
+ function hashToFieldFp2 (bytes memory message , bytes memory dst ) private view returns (Fp2[2 ] memory ) {
177
+ // 1. len_in_bytes = count * m * L
178
+ // so always 2 * 2 * 64 = 256
179
+ uint16 lenInBytes = 256 ;
180
+ // 2. uniform_bytes = expand_message(msg, DST, len_in_bytes)
181
+ bytes32 [] memory pseudoRandomBytes = expandMsgXmd (message, dst, lenInBytes);
182
+ Fp2[2 ] memory u;
183
+ // No loop here saves 800 gas hardcoding offset an additional 300
184
+ // 3. for i in (0, ..., count - 1):
185
+ // 4. for j in (0, ..., m - 1):
186
+ // 5. elm_offset = L * (j + i * m)
187
+ // 6. tv = substr(uniform_bytes, elm_offset, HTF_L)
188
+ // uint8 HTF_L = 64;
189
+ // bytes memory tv = new bytes(64);
190
+ // 7. e_j = OS2IP(tv) mod p
191
+ // 8. u_i = (e_0, ..., e_(m - 1))
192
+ // tv = bytes.concat(pseudo_random_bytes[0], pseudo_random_bytes[1]);
193
+ u[0 ].c0 = _modfield (pseudoRandomBytes[0 ], pseudoRandomBytes[1 ]);
194
+ u[0 ].c1 = _modfield (pseudoRandomBytes[2 ], pseudoRandomBytes[3 ]);
195
+ u[1 ].c0 = _modfield (pseudoRandomBytes[4 ], pseudoRandomBytes[5 ]);
196
+ u[1 ].c1 = _modfield (pseudoRandomBytes[6 ], pseudoRandomBytes[7 ]);
197
+ // 9. return (u_0, ..., u_(count - 1))
198
+ return u;
199
+ }
200
+
201
+ /// @notice Computes a field point from a message
202
+ /// @dev Follows https://datatracker.ietf.org/doc/html/rfc9380#section-5.3
203
+ /// @dev bytes32[] because len_in_bytes is always a multiple of 32 in our case even 128
204
+ /// @param message Arbitrarylength byte string to be hashed
205
+ /// @param dst The domain separation tag of at most 255 bytes
206
+ /// @param lenInBytes The length of the requested output in bytes
207
+ /// @return A field point
208
+ function expandMsgXmd (bytes memory message , bytes memory dst , uint16 lenInBytes )
209
+ private
210
+ pure
211
+ returns (bytes32 [] memory )
212
+ {
213
+ // 1. ell = ceil(len_in_bytes / b_in_bytes)
214
+ // b_in_bytes seems to be 32 for sha256
215
+ // ceil the division
216
+ uint256 ell = (lenInBytes - 1 ) / 32 + 1 ;
217
+
218
+ // 2. ABORT if ell > 255 or len_in_bytes > 65535 or len(DST) > 255
219
+ require (ell <= 255 , "len_in_bytes too large for sha256 " );
220
+ // Not really needed because of parameter type
221
+ // require(lenInBytes <= 65535, "len_in_bytes too large");
222
+ // no length normalizing via hashing
223
+ require (dst.length <= 255 , "dst too long " );
224
+
225
+ bytes memory dstPrime = bytes .concat (dst, bytes1 (uint8 (dst.length )));
226
+
227
+ // 4. Z_pad = I2OSP(0, s_in_bytes)
228
+ // this should be sha256 blocksize so 64 bytes
229
+ bytes memory zPad = new bytes (64 );
230
+
231
+ // 5. l_i_b_str = I2OSP(len_in_bytes, 2)
232
+ // length in byte string?
233
+ bytes2 libStr = bytes2 (lenInBytes);
234
+
235
+ // 6. msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime
236
+ bytes memory msgPrime = bytes .concat (zPad, message, libStr, hex "00 " , dstPrime);
237
+
238
+ // 7. b_0 = H(msg_prime)
239
+ bytes32 b_0 = sha256 (msgPrime);
240
+
241
+ bytes32 [] memory b = new bytes32 [](ell);
242
+
243
+ // 8. b_1 = H(b_0 || I2OSP(1, 1) || DST_prime)
244
+ b[0 ] = sha256 (bytes .concat (b_0, hex "01 " , dstPrime));
245
+
246
+ // 9. for i in (2, ..., ell):
247
+ for (uint8 i = 2 ; i <= ell; i++ ) {
248
+ // 10. b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime)
249
+ bytes memory tmp = abi.encodePacked (b_0 ^ b[i - 2 ], i, dstPrime);
250
+ b[i - 1 ] = sha256 (tmp);
251
+ }
252
+ // 11. uniform_bytes = b_1 || ... || b_ell
253
+ // 12. return substr(uniform_bytes, 0, len_in_bytes)
254
+ // Here we don't need the uniform_bytes because b is already properly formed
255
+ return b;
256
+ }
257
+
258
+ // passing two bytes32 instead of bytes memory saves approx 700 gas per call
259
+ // Computes the mod against the bls12-381 field modulus
260
+ function _modfield (bytes32 _b1 , bytes32 _b2 ) private view returns (Fp memory r ) {
261
+ (bool success , bytes memory output ) = address (0x5 ).staticcall (
262
+ abi.encode (
263
+ // arg[0] = base.length
264
+ 0x40 ,
265
+ // arg[1] = exp.length
266
+ 0x20 ,
267
+ // arg[2] = mod.length
268
+ 0x40 ,
269
+ // arg[3] = base.bits @ + 0x60
270
+ // places the first 32 bytes of _b1 and the last 32 bytes of _b2
271
+ _b1,
272
+ _b2,
273
+ // arg[4] = exp
274
+ // exponent always 1
275
+ 1 ,
276
+ // arg[5] = mod
277
+ // this field_modulus as hex 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
278
+ // we add the 0 prefix so that the result will be exactly 64 bytes
279
+ // saves 300 gas per call instead of sending it along every time
280
+ // places the first 32 bytes and the last 32 bytes of the field modulus
281
+ 0x000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd7 , // arg[5] = mod
282
+ 0x64774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab //
283
+ )
284
+ );
285
+ require (success, "MODEXP failed " );
286
+ return abi.decode (output, (Fp));
287
+ }
153
288
}
0 commit comments