use core::cmp::Ordering;
use eth_valkyoth_hash::{Keccak256, hash_one};
use eth_valkyoth_primitives::{Address, B256};
use eth_valkyoth_protocol::SignatureYParity;
#[cfg(feature = "secp256k1-k256")]
use k256::ecdsa::{
RecoveryId as Secp256k1RecoveryId, Signature as Secp256k1Signature,
VerifyingKey as Secp256k1VerifyingKey,
};
use crate::VerifyError;
pub const COMPACT_SIGNATURE_BYTES: usize = 64;
pub const ETHEREUM_SIGNATURE_BYTES: usize = 65;
pub const SIGNING_DIGEST_BYTES: usize = 32;
pub const ETHEREUM_PUBLIC_KEY_BYTES: usize = 64;
pub const SECP256K1_ORDER_BE: [u8; 32] = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41,
];
pub const SECP256K1_HALF_ORDER_BE: [u8; 32] = [
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x5d, 0x57, 0x6e, 0x73, 0x57, 0xa4, 0x50, 0x1d, 0xdf, 0xe9, 0x2f, 0x46, 0x68, 0x1b, 0x20, 0xa0,
];
pub trait RecoverableSecp256k1 {
fn recover_uncompressed_public_key(
&mut self,
signing_digest: B256,
signature: EthereumSignature,
) -> Result<[u8; ETHEREUM_PUBLIC_KEY_BYTES], VerifyError>;
}
impl<T> RecoverableSecp256k1 for &mut T
where
T: RecoverableSecp256k1 + ?Sized,
{
fn recover_uncompressed_public_key(
&mut self,
signing_digest: B256,
signature: EthereumSignature,
) -> Result<[u8; ETHEREUM_PUBLIC_KEY_BYTES], VerifyError> {
(**self).recover_uncompressed_public_key(signing_digest, signature)
}
}
#[cfg(feature = "secp256k1-k256")]
#[derive(Clone, Copy, Debug, Default)]
pub struct K256Secp256k1Backend;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EthereumSignature {
r: [u8; 32],
s: [u8; 32],
y_parity: SignatureYParity,
}
impl EthereumSignature {
#[must_use]
pub const fn from_parts(r: [u8; 32], s: [u8; 32], y_parity: SignatureYParity) -> Self {
Self { r, s, y_parity }
}
pub fn try_from_parts_with_y_parity(
r: [u8; 32],
s: [u8; 32],
y_parity: u8,
) -> Result<Self, VerifyError> {
let y_parity = SignatureYParity::try_new(u64::from(y_parity))
.map_err(|_| VerifyError::InvalidSignature)?;
Ok(Self::from_parts(r, s, y_parity))
}
pub fn try_from_bytes(bytes: [u8; ETHEREUM_SIGNATURE_BYTES]) -> Result<Self, VerifyError> {
let (compact, y_parity) = bytes.split_at(COMPACT_SIGNATURE_BYTES);
let (r, s) = compact.split_at(SIGNING_DIGEST_BYTES);
let r = <[u8; 32]>::try_from(r).map_err(|_| VerifyError::InvalidSignature)?;
let s = <[u8; 32]>::try_from(s).map_err(|_| VerifyError::InvalidSignature)?;
let y_parity = y_parity
.first()
.copied()
.ok_or(VerifyError::InvalidSignature)?;
Self::try_from_parts_with_y_parity(r, s, y_parity)
}
#[must_use]
pub const fn r(self) -> [u8; 32] {
self.r
}
#[must_use]
pub const fn s(self) -> [u8; 32] {
self.s
}
#[must_use]
pub const fn y_parity(self) -> SignatureYParity {
self.y_parity
}
pub fn validate_scalar_policy(self) -> Result<(), VerifyError> {
if !scalar_is_nonzero(self.r) || cmp_be(self.r, SECP256K1_ORDER_BE) != Ordering::Less {
return Err(VerifyError::InvalidSignature);
}
if !scalar_is_nonzero(self.s)
|| cmp_be(self.s, SECP256K1_HALF_ORDER_BE) == Ordering::Greater
{
return Err(VerifyError::InvalidSignature);
}
Ok(())
}
#[cfg(feature = "secp256k1-k256")]
fn secp256k1_signature(self) -> Result<Secp256k1Signature, VerifyError> {
let signature = Secp256k1Signature::from_scalars(self.r, self.s)
.map_err(|_| VerifyError::InvalidSignature)?;
if signature.normalize_s().is_some() {
return Err(VerifyError::InvalidSignature);
}
Ok(signature)
}
#[cfg(feature = "secp256k1-k256")]
fn secp256k1_recovery_id(self) -> Result<Secp256k1RecoveryId, VerifyError> {
Secp256k1RecoveryId::try_from(self.y_parity.get())
.map_err(|_| VerifyError::InvalidSignature)
}
}
#[cfg(feature = "secp256k1-k256")]
impl RecoverableSecp256k1 for K256Secp256k1Backend {
fn recover_uncompressed_public_key(
&mut self,
signing_digest: B256,
signature: EthereumSignature,
) -> Result<[u8; ETHEREUM_PUBLIC_KEY_BYTES], VerifyError> {
let secp256k1_signature = signature.secp256k1_signature()?;
let recovery_id = signature.secp256k1_recovery_id()?;
let digest_bytes = <[u8; SIGNING_DIGEST_BYTES]>::from(signing_digest);
let verifying_key = Secp256k1VerifyingKey::recover_from_prehash(
&digest_bytes,
&secp256k1_signature,
recovery_id,
)
.map_err(|_| VerifyError::InvalidSignature)?;
public_key_payload_from_verifying_key(verifying_key)
}
}
pub fn recover_sender_from_digest_with_backend<B, H>(
signing_digest: B256,
signature: EthereumSignature,
mut backend: B,
hasher: H,
) -> Result<Address, VerifyError>
where
B: RecoverableSecp256k1,
H: Keccak256,
{
signature.validate_scalar_policy()?;
let public_key = backend.recover_uncompressed_public_key(signing_digest, signature)?;
address_from_uncompressed_public_key(public_key, hasher)
}
fn scalar_is_nonzero(value: [u8; 32]) -> bool {
value.iter().any(|byte| *byte != 0)
}
fn cmp_be(left: [u8; 32], right: [u8; 32]) -> Ordering {
let mut ordering = Ordering::Equal;
for (left_byte, right_byte) in left.iter().zip(right.iter()) {
if ordering == Ordering::Equal {
ordering = left_byte.cmp(right_byte);
}
}
ordering
}
#[cfg(feature = "secp256k1-k256")]
pub fn recover_sender_from_digest<H>(
signing_digest: B256,
signature: EthereumSignature,
hasher: H,
) -> Result<Address, VerifyError>
where
H: Keccak256,
{
recover_sender_from_digest_with_backend(signing_digest, signature, K256Secp256k1Backend, hasher)
}
#[cfg(feature = "secp256k1-k256")]
fn public_key_payload_from_verifying_key(
verifying_key: Secp256k1VerifyingKey,
) -> Result<[u8; ETHEREUM_PUBLIC_KEY_BYTES], VerifyError> {
let encoded = verifying_key.to_encoded_point(false);
let public_key = encoded
.as_bytes()
.get(1..)
.ok_or(VerifyError::InvalidSignature)?;
if public_key.len() != ETHEREUM_PUBLIC_KEY_BYTES {
return Err(VerifyError::InvalidSignature);
}
<[u8; ETHEREUM_PUBLIC_KEY_BYTES]>::try_from(public_key)
.map_err(|_| VerifyError::InvalidSignature)
}
pub fn address_from_uncompressed_public_key<H>(
public_key: [u8; ETHEREUM_PUBLIC_KEY_BYTES],
hasher: H,
) -> Result<Address, VerifyError>
where
H: Keccak256,
{
let digest = hash_one(hasher, &public_key);
let digest_bytes = <[u8; SIGNING_DIGEST_BYTES]>::from(digest);
let address = digest_bytes
.get(12..)
.and_then(|bytes| <[u8; 20]>::try_from(bytes).ok())
.ok_or(VerifyError::InvalidSignature)?;
Ok(Address::from_bytes(address))
}
#[cfg(test)]
#[path = "sender_tests.rs"]
mod tests;