1#[cfg(not(feature = "std"))]
2use alloc::{
3 string::{String, ToString},
4 vec,
5 vec::Vec,
6};
7
8use ethereum_types::Address;
9use sha2::Digest as _;
10
11#[derive(Debug, thiserror::Error)]
13pub enum CryptoError {
14 #[error("invalid signature")]
15 InvalidSignature,
16 #[error("invalid recovery id")]
17 InvalidRecoveryId,
18 #[error("recovery failed")]
19 RecoveryFailed,
20 #[error("invalid point: {0}")]
21 InvalidPoint(&'static str),
22 #[error("invalid input: {0}")]
23 InvalidInput(&'static str),
24 #[error("verification failed")]
25 VerificationFailed,
26 #[error("unsupported: {0}")]
27 Unsupported(&'static str),
28 #[error("{0}")]
29 Other(String),
30}
31
32#[cfg(not(feature = "blst"))]
37const BLS_UNSUPPORTED: &str =
38 "bls12_381 requires the `blst` feature (host/L1) or a zkVM provider override";
39
40pub trait Crypto: Send + Sync + core::fmt::Debug {
71 #[cfg(feature = "secp256k1")]
77 fn secp256k1_ecrecover(
78 &self,
79 sig: &[u8; 64],
80 recid: u8,
81 msg: &[u8; 32],
82 ) -> Result<[u8; 32], CryptoError> {
83 let recovery_id = secp256k1::ecdsa::RecoveryId::try_from(recid as i32)
84 .map_err(|_| CryptoError::InvalidRecoveryId)?;
85
86 let recoverable_sig =
87 secp256k1::ecdsa::RecoverableSignature::from_compact(sig, recovery_id)
88 .map_err(|_| CryptoError::InvalidSignature)?;
89
90 let message = secp256k1::Message::from_digest(*msg);
91
92 let public_key = recoverable_sig
93 .recover(&message)
94 .map_err(|_| CryptoError::RecoveryFailed)?;
95
96 let hash = crate::keccak::keccak_hash(&public_key.serialize_uncompressed()[1..]);
97 Ok(hash)
98 }
99
100 #[cfg(not(feature = "secp256k1"))]
101 fn secp256k1_ecrecover(
102 &self,
103 sig: &[u8; 64],
104 recid: u8,
105 msg: &[u8; 32],
106 ) -> Result<[u8; 32], CryptoError> {
107 use k256::{
108 AffinePoint, ProjectivePoint, Scalar,
109 elliptic_curve::{
110 PrimeField,
111 group::prime::PrimeCurveAffine,
112 ops::{Invert, LinearCombination, Reduce},
113 point::DecompressPoint,
114 sec1::ToEncodedPoint,
115 },
116 };
117
118 let r_bytes = k256::FieldBytes::from_slice(&sig[..32]);
120 let s_bytes = k256::FieldBytes::from_slice(&sig[32..]);
121 let r: Option<Scalar> = Scalar::from_repr(*r_bytes).into();
122 let s: Option<Scalar> = Scalar::from_repr(*s_bytes).into();
123
124 let (Some(r), Some(s)) = (r, s) else {
125 return Err(CryptoError::InvalidSignature);
126 };
127
128 if r.is_zero().into() || s.is_zero().into() {
129 return Err(CryptoError::InvalidSignature);
130 }
131
132 let y_is_odd = (recid & 1) != 0;
137 let r_point: Option<AffinePoint> =
138 AffinePoint::decompress(r_bytes, u8::from(y_is_odd).into()).into();
139 let Some(r_point) = r_point else {
140 return Err(CryptoError::RecoveryFailed);
141 };
142
143 let r_proj = ProjectivePoint::from(r_point);
145 let z = <Scalar as Reduce<k256::U256>>::reduce_bytes(k256::FieldBytes::from_slice(msg));
146 let r_inv: Option<Scalar> = r.invert_vartime().into();
147 let Some(r_inv) = r_inv else {
148 return Err(CryptoError::RecoveryFailed);
149 };
150 let u1 = -(r_inv * z);
151 let u2 = r_inv * s;
152 let pk = ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &r_proj, &u2);
153
154 let pk_affine = pk.to_affine();
155 if bool::from(pk_affine.is_identity()) {
156 return Err(CryptoError::RecoveryFailed);
157 }
158 let uncompressed = pk_affine.to_encoded_point(false);
159 let hash = crate::keccak::keccak_hash(&uncompressed.as_bytes()[1..]);
160 Ok(hash)
161 }
162
163 #[cfg(feature = "secp256k1")]
174 fn recover_public_key(&self, sig: &[u8; 65], msg: &[u8; 32]) -> Result<[u8; 65], CryptoError> {
175 const SECP256K1_N_HALF: [u8; 32] =
177 hex_literal::hex!("7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0");
178 if sig[32..64] > SECP256K1_N_HALF[..] {
179 return Err(CryptoError::InvalidSignature);
180 }
181
182 let recovery_id = secp256k1::ecdsa::RecoveryId::try_from(sig[64] as i32)
183 .map_err(|_| CryptoError::InvalidRecoveryId)?;
184 let recoverable_sig = secp256k1::ecdsa::RecoverableSignature::from_compact(
185 sig[..64]
186 .try_into()
187 .map_err(|_| CryptoError::InvalidSignature)?,
188 recovery_id,
189 )
190 .map_err(|_| CryptoError::InvalidSignature)?;
191
192 let public_key = recoverable_sig
193 .recover(&secp256k1::Message::from_digest(*msg))
194 .map_err(|_| CryptoError::RecoveryFailed)?;
195
196 Ok(public_key.serialize_uncompressed())
197 }
198
199 fn recover_signer(&self, sig: &[u8; 65], msg: &[u8; 32]) -> Result<Address, CryptoError> {
202 const SECP256K1_N_HALF: [u8; 32] =
204 hex_literal::hex!("7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0");
205 if sig[32..64] > SECP256K1_N_HALF[..] {
206 return Err(CryptoError::InvalidSignature);
207 }
208
209 let hash = self.secp256k1_ecrecover(
210 sig[..64]
211 .try_into()
212 .map_err(|_| CryptoError::InvalidSignature)?,
213 sig[64],
214 msg,
215 )?;
216 Ok(Address::from_slice(&hash[12..]))
217 }
218
219 fn keccak256(&self, input: &[u8]) -> [u8; 32] {
223 crate::keccak::keccak_hash(input)
224 }
225
226 fn sha256(&self, input: &[u8]) -> [u8; 32] {
228 sha2::Sha256::digest(input).into()
229 }
230
231 fn ripemd160(&self, input: &[u8]) -> [u8; 32] {
233 let mut hasher = ripemd::Ripemd160::new();
234 hasher.update(input);
235 let result = hasher.finalize();
236
237 let mut output = [0u8; 32];
238 output[12..].copy_from_slice(&result);
239 output
240 }
241
242 fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], CryptoError> {
248 use ark_bn254::Fq;
249 use ark_ec::CurveGroup;
250 use ark_ff::{BigInteger, PrimeField as _, Zero};
251
252 let parse_point = |bytes: &[u8]| -> Result<ark_bn254::G1Affine, CryptoError> {
253 if bytes.len() < 64 {
254 return Err(CryptoError::InvalidInput("G1 point must be 64 bytes"));
255 }
256 let x = Fq::from_be_bytes_mod_order(&bytes[..32]);
257 let y = Fq::from_be_bytes_mod_order(&bytes[32..64]);
258
259 if x.is_zero() && y.is_zero() {
260 return Ok(ark_bn254::G1Affine::identity());
261 }
262
263 let point = ark_bn254::G1Affine::new_unchecked(x, y);
264 if !point.is_on_curve() {
265 return Err(CryptoError::InvalidPoint("G1 point not on curve"));
266 }
267 Ok(point)
268 };
269
270 let pt1 = parse_point(p1)?;
271 let pt2 = parse_point(p2)?;
272
273 #[allow(clippy::arithmetic_side_effects)]
274 let sum = (pt1 + pt2).into_affine();
275
276 let mut out = [0u8; 64];
277 out[..32].copy_from_slice(&sum.x.into_bigint().to_bytes_be());
278 out[32..].copy_from_slice(&sum.y.into_bigint().to_bytes_be());
279 Ok(out)
280 }
281
282 fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], CryptoError> {
286 use ark_bn254::{Fq, Fr as FrArk};
287 use ark_ec::CurveGroup;
288 use ark_ff::{BigInteger, PrimeField as _, Zero};
289 use core::ops::Mul as _;
290
291 if point.len() < 64 || scalar.len() < 32 {
292 return Err(CryptoError::InvalidInput("invalid input length"));
293 }
294
295 let x = Fq::from_be_bytes_mod_order(&point[..32]);
296 let y = Fq::from_be_bytes_mod_order(&point[32..64]);
297
298 if x.is_zero() && y.is_zero() {
299 return Ok([0u8; 64]);
300 }
301
302 let pt = ark_bn254::G1Affine::new_unchecked(x, y);
303 if !pt.is_on_curve() {
304 return Err(CryptoError::InvalidPoint("G1 point not on curve"));
305 }
306
307 let s = FrArk::from_be_bytes_mod_order(scalar);
308 if s.is_zero() {
309 return Ok([0u8; 64]);
310 }
311
312 let result = pt.mul(s).into_affine();
313
314 let mut out = [0u8; 64];
315 out[..32].copy_from_slice(&result.x.into_bigint().to_bytes_be());
316 out[32..].copy_from_slice(&result.y.into_bigint().to_bytes_be());
317 Ok(out)
318 }
319
320 fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result<bool, CryptoError> {
324 use ark_bn254::{Bn254, Fq, G1Affine, G2Affine};
325 use ark_ec::pairing::Pairing;
326 use ark_ff::{One, PrimeField as _, QuadExtField, Zero};
327
328 let mut g1_points = Vec::with_capacity(pairs.len());
329 let mut g2_points = Vec::with_capacity(pairs.len());
330
331 for (g1_bytes, g2_bytes) in pairs {
332 if g1_bytes.len() < 64 {
333 return Err(CryptoError::InvalidInput("G1 must be 64 bytes"));
334 }
335 let g1x = Fq::from_be_bytes_mod_order(&g1_bytes[..32]);
336 let g1y = Fq::from_be_bytes_mod_order(&g1_bytes[32..64]);
337
338 let g1 = if g1x.is_zero() && g1y.is_zero() {
339 G1Affine::identity()
340 } else {
341 let p = G1Affine::new_unchecked(g1x, g1y);
342 if !p.is_on_curve() || !p.is_in_correct_subgroup_assuming_on_curve() {
343 return Err(CryptoError::InvalidPoint("G1 not on BN254 curve"));
344 }
345 p
346 };
347 g1_points.push(g1);
348
349 if g2_bytes.len() < 128 {
350 return Err(CryptoError::InvalidInput("G2 must be 128 bytes"));
351 }
352
353 let g2_x_im = Fq::from_be_bytes_mod_order(&g2_bytes[..32]);
354 let g2_x_re = Fq::from_be_bytes_mod_order(&g2_bytes[32..64]);
355 let g2_y_im = Fq::from_be_bytes_mod_order(&g2_bytes[64..96]);
356 let g2_y_re = Fq::from_be_bytes_mod_order(&g2_bytes[96..128]);
357
358 let g2 =
359 if g2_x_im.is_zero() && g2_x_re.is_zero() && g2_y_im.is_zero() && g2_y_re.is_zero()
360 {
361 G2Affine::identity()
362 } else {
363 let p = G2Affine::new_unchecked(
364 QuadExtField::new(g2_x_re, g2_x_im),
365 QuadExtField::new(g2_y_re, g2_y_im),
366 );
367 if !p.is_on_curve() || !p.is_in_correct_subgroup_assuming_on_curve() {
368 return Err(CryptoError::InvalidPoint("G2 not on BN254 curve"));
369 }
370 p
371 };
372 g2_points.push(g2);
373 }
374
375 Ok(Bn254::multi_pairing(g1_points, g2_points).0 == QuadExtField::one())
376 }
377
378 #[cfg(feature = "std")]
383 fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, CryptoError> {
384 use malachite::base::num::arithmetic::traits::ModPow as _;
385 use malachite::base::num::basic::traits::Zero as _;
386 use malachite::{Natural, base::num::conversion::traits::*};
387
388 let base_nat = Natural::from_power_of_2_digits_desc(8u64, base.iter().cloned())
389 .ok_or(CryptoError::InvalidInput("base"))?;
390 let exp_nat = Natural::from_power_of_2_digits_desc(8u64, exp.iter().cloned())
391 .ok_or(CryptoError::InvalidInput("exponent"))?;
392 let mod_nat = Natural::from_power_of_2_digits_desc(8u64, modulus.iter().cloned())
393 .ok_or(CryptoError::InvalidInput("modulus"))?;
394
395 let result = if mod_nat == Natural::ZERO {
396 Natural::ZERO
397 } else if exp_nat == Natural::ZERO {
398 Natural::from(1_u8) % &mod_nat
399 } else {
400 let base_mod = base_nat % &mod_nat;
401 base_mod.mod_pow(&exp_nat, &mod_nat)
402 };
403
404 let res_bytes: Vec<u8> = result.to_power_of_2_digits_desc(8);
405 pad_modexp_output(res_bytes, modulus.len())
406 }
407
408 #[cfg(not(feature = "std"))]
409 fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, CryptoError> {
410 use num_bigint::BigUint;
411
412 let base_nat = BigUint::from_bytes_be(base);
413 let exp_nat = BigUint::from_bytes_be(exp);
414 let mod_nat = BigUint::from_bytes_be(modulus);
415
416 let result = if mod_nat == BigUint::ZERO {
417 BigUint::ZERO
418 } else if exp_nat == BigUint::ZERO {
419 BigUint::from(1_u8) % &mod_nat
420 } else {
421 base_nat.modpow(&exp_nat, &mod_nat)
422 };
423
424 let res_bytes = result.to_bytes_be();
425 pad_modexp_output(res_bytes, modulus.len())
426 }
427
428 fn mulmod256(&self, a: &[u8; 32], b: &[u8; 32], m: &[u8; 32]) -> [u8; 32] {
432 let a = ethereum_types::U256::from_big_endian(a);
433 let b = ethereum_types::U256::from_big_endian(b);
434 let m = ethereum_types::U256::from_big_endian(m);
435
436 let result = if m.is_zero() {
437 ethereum_types::U256::zero()
438 } else {
439 let product = a.full_mul(b);
440 let m512 = ethereum_types::U512::from(m);
441 if product < m512 {
442 product.try_into().unwrap_or(ethereum_types::U256::zero())
444 } else {
445 let (_, rem) = product.div_mod(m512);
446 rem.try_into().unwrap_or(ethereum_types::U256::zero())
447 }
448 };
449
450 result.to_big_endian()
451 }
452
453 fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: [u64; 16], t: [u64; 2], f: bool) {
457 #[allow(clippy::as_conversions)]
458 crate::blake2f::blake2b_f(rounds as usize, h, &m, &t, f);
459 }
460
461 fn secp256r1_verify(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool {
465 use p256::{
466 EncodedPoint,
467 ecdsa::{Signature as P256Signature, signature::hazmat::PrehashVerifier},
468 elliptic_curve::bigint::U256 as P256Uint,
469 };
470
471 let r = P256Uint::from_be_slice(&sig[..32]);
472 let s = P256Uint::from_be_slice(&sig[32..]);
473
474 const P256_N: P256Uint = P256Uint::from_be_hex(
475 "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
476 );
477
478 if r == P256Uint::ZERO || r >= P256_N || s == P256Uint::ZERO || s >= P256_N {
479 return false;
480 }
481
482 let x_bytes: &[u8; 32] = match pk[..32].try_into() {
483 Ok(b) => b,
484 Err(_) => return false,
485 };
486 let y_bytes: &[u8; 32] = match pk[32..].try_into() {
487 Ok(b) => b,
488 Err(_) => return false,
489 };
490
491 let Ok(verifier) = p256::ecdsa::VerifyingKey::from_encoded_point(
492 &EncodedPoint::from_affine_coordinates(x_bytes.into(), y_bytes.into(), false),
493 ) else {
494 return false;
495 };
496
497 let r_arr: [u8; 32] = sig[..32].try_into().unwrap_or([0u8; 32]);
498 let s_arr: [u8; 32] = sig[32..].try_into().unwrap_or([0u8; 32]);
499
500 let Ok(signature) = P256Signature::from_scalars(r_arr, s_arr) else {
501 return false;
502 };
503
504 verifier.verify_prehash(msg, &signature).is_ok()
505 }
506
507 #[cfg(feature = "c-kzg")]
511 fn verify_kzg_proof(
512 &self,
513 z: &[u8; 32],
514 y: &[u8; 32],
515 commitment: &[u8; 48],
516 proof: &[u8; 48],
517 ) -> Result<(), CryptoError> {
518 let c_kzg_settings = c_kzg::ethereum_kzg_settings(crate::kzg::KZG_PRECOMPUTE);
519 c_kzg_settings
520 .verify_kzg_proof(
521 &(*commitment).into(),
522 &(*z).into(),
523 &(*y).into(),
524 &(*proof).into(),
525 )
526 .map_err(|e| CryptoError::Other(e.to_string()))
527 .and_then(|valid| {
528 if valid {
529 Ok(())
530 } else {
531 Err(CryptoError::VerificationFailed)
532 }
533 })
534 }
535
536 #[cfg(not(feature = "c-kzg"))]
537 fn verify_kzg_proof(
538 &self,
539 z: &[u8; 32],
540 y: &[u8; 32],
541 commitment: &[u8; 48],
542 proof: &[u8; 48],
543 ) -> Result<(), CryptoError> {
544 crate::kzg::verify_kzg_proof(*commitment, *z, *y, *proof)
545 .map_err(|e| CryptoError::Other(e.to_string()))
546 .and_then(|valid| {
547 if valid {
548 Ok(())
549 } else {
550 Err(CryptoError::VerificationFailed)
551 }
552 })
553 }
554
555 #[cfg(feature = "c-kzg")]
557 fn verify_blob_kzg_proof(
558 &self,
559 blob: &[u8],
560 commitment: &[u8; 48],
561 proof: &[u8; 48],
562 ) -> Result<bool, CryptoError> {
563 use crate::kzg::BYTES_PER_BLOB;
564
565 let blob_arr: [u8; BYTES_PER_BLOB] = blob
566 .try_into()
567 .map_err(|_| CryptoError::InvalidInput("blob must be 131072 bytes"))?;
568
569 let c_kzg_settings = c_kzg::ethereum_kzg_settings(crate::kzg::KZG_PRECOMPUTE);
570 c_kzg_settings
571 .verify_blob_kzg_proof(&blob_arr.into(), &(*commitment).into(), &(*proof).into())
572 .map_err(|e| CryptoError::Other(e.to_string()))
573 }
574
575 #[cfg(not(feature = "c-kzg"))]
576 fn verify_blob_kzg_proof(
577 &self,
578 blob: &[u8],
579 commitment: &[u8; 48],
580 proof: &[u8; 48],
581 ) -> Result<bool, CryptoError> {
582 use crate::kzg::BYTES_PER_BLOB;
583
584 let blob_arr: [u8; BYTES_PER_BLOB] = blob
585 .try_into()
586 .map_err(|_| CryptoError::InvalidInput("blob must be 131072 bytes"))?;
587
588 crate::kzg::verify_blob_kzg_proof(blob_arr, *commitment, *proof)
589 .map_err(|e| CryptoError::Other(e.to_string()))
590 }
591
592 fn bls12_381_g1_add(
604 &self,
605 a: ([u8; 48], [u8; 48]),
606 b: ([u8; 48], [u8; 48]),
607 ) -> Result<[u8; 96], CryptoError> {
608 #[cfg(feature = "blst")]
609 {
610 crate::bls_blst::g1_add(a, b)
611 }
612 #[cfg(not(feature = "blst"))]
613 {
614 let _ = (a, b);
615 Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
616 }
617 }
618
619 #[allow(clippy::type_complexity)]
621 fn bls12_381_g1_msm(
622 &self,
623 pairs: &[(([u8; 48], [u8; 48]), [u8; 32])],
624 ) -> Result<[u8; 96], CryptoError> {
625 #[cfg(feature = "blst")]
626 {
627 crate::bls_blst::g1_msm(pairs)
628 }
629 #[cfg(not(feature = "blst"))]
630 {
631 let _ = pairs;
632 Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
633 }
634 }
635
636 fn bls12_381_g2_add(
638 &self,
639 a: ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
640 b: ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
641 ) -> Result<[u8; 192], CryptoError> {
642 #[cfg(feature = "blst")]
643 {
644 crate::bls_blst::g2_add(a, b)
645 }
646 #[cfg(not(feature = "blst"))]
647 {
648 let _ = (a, b);
649 Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
650 }
651 }
652
653 #[allow(clippy::type_complexity)]
655 fn bls12_381_g2_msm(
656 &self,
657 pairs: &[(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])],
658 ) -> Result<[u8; 192], CryptoError> {
659 #[cfg(feature = "blst")]
660 {
661 crate::bls_blst::g2_msm(pairs)
662 }
663 #[cfg(not(feature = "blst"))]
664 {
665 let _ = pairs;
666 Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
667 }
668 }
669
670 #[allow(clippy::type_complexity)]
672 fn bls12_381_pairing_check(
673 &self,
674 pairs: &[(
675 ([u8; 48], [u8; 48]),
676 ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
677 )],
678 ) -> Result<bool, CryptoError> {
679 #[cfg(feature = "blst")]
680 {
681 crate::bls_blst::pairing_check(pairs)
682 }
683 #[cfg(not(feature = "blst"))]
684 {
685 let _ = pairs;
686 Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
687 }
688 }
689
690 fn bls12_381_fp_to_g1(&self, fp: &[u8; 48]) -> Result<[u8; 96], CryptoError> {
692 #[cfg(feature = "blst")]
693 {
694 crate::bls_blst::fp_to_g1(fp)
695 }
696 #[cfg(not(feature = "blst"))]
697 {
698 let _ = fp;
699 Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
700 }
701 }
702
703 fn bls12_381_fp2_to_g2(&self, fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], CryptoError> {
705 #[cfg(feature = "blst")]
706 {
707 crate::bls_blst::fp2_to_g2(fp2)
708 }
709 #[cfg(not(feature = "blst"))]
710 {
711 let _ = fp2;
712 Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
713 }
714 }
715}
716
717fn pad_modexp_output(res_bytes: Vec<u8>, modulus_len: usize) -> Result<Vec<u8>, CryptoError> {
721 let mut out = vec![0u8; modulus_len];
722 if res_bytes.len() <= modulus_len {
723 let offset = modulus_len - res_bytes.len();
724 out[offset..].copy_from_slice(&res_bytes);
725 } else {
726 out.copy_from_slice(&res_bytes[res_bytes.len() - modulus_len..]);
727 }
728 Ok(out)
729}
730
731