#[cfg(not(feature = "std"))]
use alloc::{
string::{String, ToString},
vec,
vec::Vec,
};
use ethereum_types::Address;
use sha2::Digest as _;
#[derive(Debug, thiserror::Error)]
pub enum CryptoError {
#[error("invalid signature")]
InvalidSignature,
#[error("invalid recovery id")]
InvalidRecoveryId,
#[error("recovery failed")]
RecoveryFailed,
#[error("invalid point: {0}")]
InvalidPoint(&'static str),
#[error("invalid input: {0}")]
InvalidInput(&'static str),
#[error("verification failed")]
VerificationFailed,
#[error("unsupported: {0}")]
Unsupported(&'static str),
#[error("{0}")]
Other(String),
}
#[cfg(not(feature = "blst"))]
const BLS_UNSUPPORTED: &str =
"bls12_381 requires the `blst` feature (host/L1) or a zkVM provider override";
pub trait Crypto: Send + Sync + core::fmt::Debug {
#[cfg(feature = "secp256k1")]
fn secp256k1_ecrecover(
&self,
sig: &[u8; 64],
recid: u8,
msg: &[u8; 32],
) -> Result<[u8; 32], CryptoError> {
let recovery_id = secp256k1::ecdsa::RecoveryId::try_from(recid as i32)
.map_err(|_| CryptoError::InvalidRecoveryId)?;
let recoverable_sig =
secp256k1::ecdsa::RecoverableSignature::from_compact(sig, recovery_id)
.map_err(|_| CryptoError::InvalidSignature)?;
let message = secp256k1::Message::from_digest(*msg);
let public_key = recoverable_sig
.recover(&message)
.map_err(|_| CryptoError::RecoveryFailed)?;
let hash = crate::keccak::keccak_hash(&public_key.serialize_uncompressed()[1..]);
Ok(hash)
}
#[cfg(not(feature = "secp256k1"))]
fn secp256k1_ecrecover(
&self,
sig: &[u8; 64],
recid: u8,
msg: &[u8; 32],
) -> Result<[u8; 32], CryptoError> {
use k256::{
AffinePoint, ProjectivePoint, Scalar,
elliptic_curve::{
PrimeField,
group::prime::PrimeCurveAffine,
ops::{Invert, LinearCombination, Reduce},
point::DecompressPoint,
sec1::ToEncodedPoint,
},
};
let r_bytes = k256::FieldBytes::from_slice(&sig[..32]);
let s_bytes = k256::FieldBytes::from_slice(&sig[32..]);
let r: Option<Scalar> = Scalar::from_repr(*r_bytes).into();
let s: Option<Scalar> = Scalar::from_repr(*s_bytes).into();
let (Some(r), Some(s)) = (r, s) else {
return Err(CryptoError::InvalidSignature);
};
if r.is_zero().into() || s.is_zero().into() {
return Err(CryptoError::InvalidSignature);
}
let y_is_odd = (recid & 1) != 0;
let r_point: Option<AffinePoint> =
AffinePoint::decompress(r_bytes, u8::from(y_is_odd).into()).into();
let Some(r_point) = r_point else {
return Err(CryptoError::RecoveryFailed);
};
let r_proj = ProjectivePoint::from(r_point);
let z = <Scalar as Reduce<k256::U256>>::reduce_bytes(k256::FieldBytes::from_slice(msg));
let r_inv: Option<Scalar> = r.invert_vartime().into();
let Some(r_inv) = r_inv else {
return Err(CryptoError::RecoveryFailed);
};
let u1 = -(r_inv * z);
let u2 = r_inv * s;
let pk = ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &r_proj, &u2);
let pk_affine = pk.to_affine();
if bool::from(pk_affine.is_identity()) {
return Err(CryptoError::RecoveryFailed);
}
let uncompressed = pk_affine.to_encoded_point(false);
let hash = crate::keccak::keccak_hash(&uncompressed.as_bytes()[1..]);
Ok(hash)
}
fn recover_signer(&self, sig: &[u8; 65], msg: &[u8; 32]) -> Result<Address, CryptoError> {
const SECP256K1_N_HALF: [u8; 32] =
hex_literal::hex!("7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0");
if sig[32..64] > SECP256K1_N_HALF[..] {
return Err(CryptoError::InvalidSignature);
}
let hash = self.secp256k1_ecrecover(
sig[..64]
.try_into()
.map_err(|_| CryptoError::InvalidSignature)?,
sig[64],
msg,
)?;
Ok(Address::from_slice(&hash[12..]))
}
fn keccak256(&self, input: &[u8]) -> [u8; 32] {
crate::keccak::keccak_hash(input)
}
fn sha256(&self, input: &[u8]) -> [u8; 32] {
sha2::Sha256::digest(input).into()
}
fn ripemd160(&self, input: &[u8]) -> [u8; 32] {
let mut hasher = ripemd::Ripemd160::new();
hasher.update(input);
let result = hasher.finalize();
let mut output = [0u8; 32];
output[12..].copy_from_slice(&result);
output
}
fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], CryptoError> {
use ark_bn254::Fq;
use ark_ec::CurveGroup;
use ark_ff::{BigInteger, PrimeField as _, Zero};
let parse_point = |bytes: &[u8]| -> Result<ark_bn254::G1Affine, CryptoError> {
if bytes.len() < 64 {
return Err(CryptoError::InvalidInput("G1 point must be 64 bytes"));
}
let x = Fq::from_be_bytes_mod_order(&bytes[..32]);
let y = Fq::from_be_bytes_mod_order(&bytes[32..64]);
if x.is_zero() && y.is_zero() {
return Ok(ark_bn254::G1Affine::identity());
}
let point = ark_bn254::G1Affine::new_unchecked(x, y);
if !point.is_on_curve() {
return Err(CryptoError::InvalidPoint("G1 point not on curve"));
}
Ok(point)
};
let pt1 = parse_point(p1)?;
let pt2 = parse_point(p2)?;
#[allow(clippy::arithmetic_side_effects)]
let sum = (pt1 + pt2).into_affine();
let mut out = [0u8; 64];
out[..32].copy_from_slice(&sum.x.into_bigint().to_bytes_be());
out[32..].copy_from_slice(&sum.y.into_bigint().to_bytes_be());
Ok(out)
}
fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], CryptoError> {
use ark_bn254::{Fq, Fr as FrArk};
use ark_ec::CurveGroup;
use ark_ff::{BigInteger, PrimeField as _, Zero};
use core::ops::Mul as _;
if point.len() < 64 || scalar.len() < 32 {
return Err(CryptoError::InvalidInput("invalid input length"));
}
let x = Fq::from_be_bytes_mod_order(&point[..32]);
let y = Fq::from_be_bytes_mod_order(&point[32..64]);
if x.is_zero() && y.is_zero() {
return Ok([0u8; 64]);
}
let pt = ark_bn254::G1Affine::new_unchecked(x, y);
if !pt.is_on_curve() {
return Err(CryptoError::InvalidPoint("G1 point not on curve"));
}
let s = FrArk::from_be_bytes_mod_order(scalar);
if s.is_zero() {
return Ok([0u8; 64]);
}
let result = pt.mul(s).into_affine();
let mut out = [0u8; 64];
out[..32].copy_from_slice(&result.x.into_bigint().to_bytes_be());
out[32..].copy_from_slice(&result.y.into_bigint().to_bytes_be());
Ok(out)
}
fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result<bool, CryptoError> {
use ark_bn254::{Bn254, Fq, G1Affine, G2Affine};
use ark_ec::pairing::Pairing;
use ark_ff::{One, PrimeField as _, QuadExtField, Zero};
let mut g1_points = Vec::with_capacity(pairs.len());
let mut g2_points = Vec::with_capacity(pairs.len());
for (g1_bytes, g2_bytes) in pairs {
if g1_bytes.len() < 64 {
return Err(CryptoError::InvalidInput("G1 must be 64 bytes"));
}
let g1x = Fq::from_be_bytes_mod_order(&g1_bytes[..32]);
let g1y = Fq::from_be_bytes_mod_order(&g1_bytes[32..64]);
let g1 = if g1x.is_zero() && g1y.is_zero() {
G1Affine::identity()
} else {
let p = G1Affine::new_unchecked(g1x, g1y);
if !p.is_on_curve() || !p.is_in_correct_subgroup_assuming_on_curve() {
return Err(CryptoError::InvalidPoint("G1 not on BN254 curve"));
}
p
};
g1_points.push(g1);
if g2_bytes.len() < 128 {
return Err(CryptoError::InvalidInput("G2 must be 128 bytes"));
}
let g2_x_im = Fq::from_be_bytes_mod_order(&g2_bytes[..32]);
let g2_x_re = Fq::from_be_bytes_mod_order(&g2_bytes[32..64]);
let g2_y_im = Fq::from_be_bytes_mod_order(&g2_bytes[64..96]);
let g2_y_re = Fq::from_be_bytes_mod_order(&g2_bytes[96..128]);
let g2 =
if g2_x_im.is_zero() && g2_x_re.is_zero() && g2_y_im.is_zero() && g2_y_re.is_zero()
{
G2Affine::identity()
} else {
let p = G2Affine::new_unchecked(
QuadExtField::new(g2_x_re, g2_x_im),
QuadExtField::new(g2_y_re, g2_y_im),
);
if !p.is_on_curve() || !p.is_in_correct_subgroup_assuming_on_curve() {
return Err(CryptoError::InvalidPoint("G2 not on BN254 curve"));
}
p
};
g2_points.push(g2);
}
Ok(Bn254::multi_pairing(g1_points, g2_points).0 == QuadExtField::one())
}
#[cfg(feature = "std")]
fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, CryptoError> {
use malachite::base::num::arithmetic::traits::ModPow as _;
use malachite::base::num::basic::traits::Zero as _;
use malachite::{Natural, base::num::conversion::traits::*};
let base_nat = Natural::from_power_of_2_digits_desc(8u64, base.iter().cloned())
.ok_or(CryptoError::InvalidInput("base"))?;
let exp_nat = Natural::from_power_of_2_digits_desc(8u64, exp.iter().cloned())
.ok_or(CryptoError::InvalidInput("exponent"))?;
let mod_nat = Natural::from_power_of_2_digits_desc(8u64, modulus.iter().cloned())
.ok_or(CryptoError::InvalidInput("modulus"))?;
let result = if mod_nat == Natural::ZERO {
Natural::ZERO
} else if exp_nat == Natural::ZERO {
Natural::from(1_u8) % &mod_nat
} else {
let base_mod = base_nat % &mod_nat;
base_mod.mod_pow(&exp_nat, &mod_nat)
};
let res_bytes: Vec<u8> = result.to_power_of_2_digits_desc(8);
pad_modexp_output(res_bytes, modulus.len())
}
#[cfg(not(feature = "std"))]
fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, CryptoError> {
use num_bigint::BigUint;
let base_nat = BigUint::from_bytes_be(base);
let exp_nat = BigUint::from_bytes_be(exp);
let mod_nat = BigUint::from_bytes_be(modulus);
let result = if mod_nat == BigUint::ZERO {
BigUint::ZERO
} else if exp_nat == BigUint::ZERO {
BigUint::from(1_u8) % &mod_nat
} else {
base_nat.modpow(&exp_nat, &mod_nat)
};
let res_bytes = result.to_bytes_be();
pad_modexp_output(res_bytes, modulus.len())
}
fn mulmod256(&self, a: &[u8; 32], b: &[u8; 32], m: &[u8; 32]) -> [u8; 32] {
let a = ethereum_types::U256::from_big_endian(a);
let b = ethereum_types::U256::from_big_endian(b);
let m = ethereum_types::U256::from_big_endian(m);
let result = if m.is_zero() {
ethereum_types::U256::zero()
} else {
let product = a.full_mul(b);
let m512 = ethereum_types::U512::from(m);
if product < m512 {
product.try_into().unwrap_or(ethereum_types::U256::zero())
} else {
let (_, rem) = product.div_mod(m512);
rem.try_into().unwrap_or(ethereum_types::U256::zero())
}
};
result.to_big_endian()
}
fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: [u64; 16], t: [u64; 2], f: bool) {
#[allow(clippy::as_conversions)]
crate::blake2f::blake2b_f(rounds as usize, h, &m, &t, f);
}
fn secp256r1_verify(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool {
use p256::{
EncodedPoint,
ecdsa::{Signature as P256Signature, signature::hazmat::PrehashVerifier},
elliptic_curve::bigint::U256 as P256Uint,
};
let r = P256Uint::from_be_slice(&sig[..32]);
let s = P256Uint::from_be_slice(&sig[32..]);
const P256_N: P256Uint = P256Uint::from_be_hex(
"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
);
if r == P256Uint::ZERO || r >= P256_N || s == P256Uint::ZERO || s >= P256_N {
return false;
}
let x_bytes: &[u8; 32] = match pk[..32].try_into() {
Ok(b) => b,
Err(_) => return false,
};
let y_bytes: &[u8; 32] = match pk[32..].try_into() {
Ok(b) => b,
Err(_) => return false,
};
let Ok(verifier) = p256::ecdsa::VerifyingKey::from_encoded_point(
&EncodedPoint::from_affine_coordinates(x_bytes.into(), y_bytes.into(), false),
) else {
return false;
};
let r_arr: [u8; 32] = sig[..32].try_into().unwrap_or([0u8; 32]);
let s_arr: [u8; 32] = sig[32..].try_into().unwrap_or([0u8; 32]);
let Ok(signature) = P256Signature::from_scalars(r_arr, s_arr) else {
return false;
};
verifier.verify_prehash(msg, &signature).is_ok()
}
#[cfg(feature = "c-kzg")]
fn verify_kzg_proof(
&self,
z: &[u8; 32],
y: &[u8; 32],
commitment: &[u8; 48],
proof: &[u8; 48],
) -> Result<(), CryptoError> {
let c_kzg_settings = c_kzg::ethereum_kzg_settings(crate::kzg::KZG_PRECOMPUTE);
c_kzg_settings
.verify_kzg_proof(
&(*commitment).into(),
&(*z).into(),
&(*y).into(),
&(*proof).into(),
)
.map_err(|e| CryptoError::Other(e.to_string()))
.and_then(|valid| {
if valid {
Ok(())
} else {
Err(CryptoError::VerificationFailed)
}
})
}
#[cfg(not(feature = "c-kzg"))]
fn verify_kzg_proof(
&self,
z: &[u8; 32],
y: &[u8; 32],
commitment: &[u8; 48],
proof: &[u8; 48],
) -> Result<(), CryptoError> {
crate::kzg::verify_kzg_proof(*commitment, *z, *y, *proof)
.map_err(|e| CryptoError::Other(e.to_string()))
.and_then(|valid| {
if valid {
Ok(())
} else {
Err(CryptoError::VerificationFailed)
}
})
}
#[cfg(feature = "c-kzg")]
fn verify_blob_kzg_proof(
&self,
blob: &[u8],
commitment: &[u8; 48],
proof: &[u8; 48],
) -> Result<bool, CryptoError> {
use crate::kzg::BYTES_PER_BLOB;
let blob_arr: [u8; BYTES_PER_BLOB] = blob
.try_into()
.map_err(|_| CryptoError::InvalidInput("blob must be 131072 bytes"))?;
let c_kzg_settings = c_kzg::ethereum_kzg_settings(crate::kzg::KZG_PRECOMPUTE);
c_kzg_settings
.verify_blob_kzg_proof(&blob_arr.into(), &(*commitment).into(), &(*proof).into())
.map_err(|e| CryptoError::Other(e.to_string()))
}
#[cfg(not(feature = "c-kzg"))]
fn verify_blob_kzg_proof(
&self,
blob: &[u8],
commitment: &[u8; 48],
proof: &[u8; 48],
) -> Result<bool, CryptoError> {
use crate::kzg::BYTES_PER_BLOB;
let blob_arr: [u8; BYTES_PER_BLOB] = blob
.try_into()
.map_err(|_| CryptoError::InvalidInput("blob must be 131072 bytes"))?;
crate::kzg::verify_blob_kzg_proof(blob_arr, *commitment, *proof)
.map_err(|e| CryptoError::Other(e.to_string()))
}
fn bls12_381_g1_add(
&self,
a: ([u8; 48], [u8; 48]),
b: ([u8; 48], [u8; 48]),
) -> Result<[u8; 96], CryptoError> {
#[cfg(feature = "blst")]
{
crate::bls_blst::g1_add(a, b)
}
#[cfg(not(feature = "blst"))]
{
let _ = (a, b);
Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
}
}
#[allow(clippy::type_complexity)]
fn bls12_381_g1_msm(
&self,
pairs: &[(([u8; 48], [u8; 48]), [u8; 32])],
) -> Result<[u8; 96], CryptoError> {
#[cfg(feature = "blst")]
{
crate::bls_blst::g1_msm(pairs)
}
#[cfg(not(feature = "blst"))]
{
let _ = pairs;
Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
}
}
fn bls12_381_g2_add(
&self,
a: ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
b: ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
) -> Result<[u8; 192], CryptoError> {
#[cfg(feature = "blst")]
{
crate::bls_blst::g2_add(a, b)
}
#[cfg(not(feature = "blst"))]
{
let _ = (a, b);
Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
}
}
#[allow(clippy::type_complexity)]
fn bls12_381_g2_msm(
&self,
pairs: &[(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])],
) -> Result<[u8; 192], CryptoError> {
#[cfg(feature = "blst")]
{
crate::bls_blst::g2_msm(pairs)
}
#[cfg(not(feature = "blst"))]
{
let _ = pairs;
Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
}
}
#[allow(clippy::type_complexity)]
fn bls12_381_pairing_check(
&self,
pairs: &[(
([u8; 48], [u8; 48]),
([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
)],
) -> Result<bool, CryptoError> {
#[cfg(feature = "blst")]
{
crate::bls_blst::pairing_check(pairs)
}
#[cfg(not(feature = "blst"))]
{
let _ = pairs;
Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
}
}
fn bls12_381_fp_to_g1(&self, fp: &[u8; 48]) -> Result<[u8; 96], CryptoError> {
#[cfg(feature = "blst")]
{
crate::bls_blst::fp_to_g1(fp)
}
#[cfg(not(feature = "blst"))]
{
let _ = fp;
Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
}
}
fn bls12_381_fp2_to_g2(&self, fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], CryptoError> {
#[cfg(feature = "blst")]
{
crate::bls_blst::fp2_to_g2(fp2)
}
#[cfg(not(feature = "blst"))]
{
let _ = fp2;
Err(CryptoError::Unsupported(BLS_UNSUPPORTED))
}
}
}
fn pad_modexp_output(res_bytes: Vec<u8>, modulus_len: usize) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; modulus_len];
if res_bytes.len() <= modulus_len {
let offset = modulus_len - res_bytes.len();
out[offset..].copy_from_slice(&res_bytes);
} else {
out.copy_from_slice(&res_bytes[res_bytes.len() - modulus_len..]);
}
Ok(out)
}