use core::fmt;
use eth_valkyoth_hash::Keccak256;
use eth_valkyoth_primitives::{Address, ChainId};
use eth_valkyoth_protocol::{
UnvalidatedAccessListTransaction, UnvalidatedBlobTransaction, UnvalidatedDynamicFeeTransaction,
UnvalidatedLegacyTransaction, UnvalidatedTransaction,
};
#[cfg(feature = "secp256k1-k256")]
use crate::K256Secp256k1Backend;
pub(crate) use crate::transaction_signature_set_code::validate_set_code_transaction_signature_with_backend;
use crate::{
EthereumSignature, RecoverableSecp256k1, TransactionSigningHash, TransactionSigningHashError,
VerifyError, access_list_transaction_signing_hash, blob_transaction_signing_hash,
dynamic_fee_transaction_signing_hash, legacy_eip155_transaction_signing_hash,
require_access_list_replay_domain, require_blob_replay_domain,
require_dynamic_fee_replay_domain, require_legacy_replay_domain,
transaction_signature_helpers::{legacy_signature, recover_and_check},
};
#[cfg(test)]
#[path = "transaction_signature_external_tests.rs"]
mod external_tests;
#[cfg(test)]
#[path = "transaction_signature_tests.rs"]
mod tests;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ValidatedTransactionSignature {
sender: Address,
signing_hash: TransactionSigningHash,
}
impl ValidatedTransactionSignature {
#[must_use]
pub(crate) const fn new(sender: Address, signing_hash: TransactionSigningHash) -> Self {
Self {
sender,
signing_hash,
}
}
#[must_use]
pub const fn sender(self) -> Address {
self.sender
}
#[must_use]
pub const fn signing_hash(self) -> TransactionSigningHash {
self.signing_hash
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TransactionSignatureValidationError {
ReplayDomain(VerifyError),
SigningHash(TransactionSigningHashError),
InvalidSignature,
UnsupportedTransactionType,
WrongSender,
}
impl TransactionSignatureValidationError {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::ReplayDomain(error) => error.code(),
Self::SigningHash(error) => error.code(),
Self::InvalidSignature => "ETH_TX_SIGNATURE_INVALID",
Self::UnsupportedTransactionType => "ETH_TX_SIGNATURE_UNSUPPORTED_TYPE",
Self::WrongSender => "ETH_TX_SIGNATURE_WRONG_SENDER",
}
}
#[must_use]
pub const fn message(self) -> &'static str {
match self {
Self::ReplayDomain(error) => error.message(),
Self::SigningHash(_) => "transaction signing hash construction failed",
Self::InvalidSignature => "transaction signature is not accepted",
Self::UnsupportedTransactionType => {
"transaction type has no signature validation helper yet"
}
Self::WrongSender => "transaction signature recovered a different sender",
}
}
#[must_use]
pub const fn category(self) -> TransactionSignatureValidationErrorCategory {
match self {
Self::ReplayDomain(_) => TransactionSignatureValidationErrorCategory::ReplayDomain,
Self::SigningHash(_) => TransactionSignatureValidationErrorCategory::SigningHash,
Self::InvalidSignature | Self::WrongSender | Self::UnsupportedTransactionType => {
TransactionSignatureValidationErrorCategory::Signature
}
}
}
}
impl fmt::Display for TransactionSignatureValidationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.message())
}
}
#[cfg(feature = "std")]
impl std::error::Error for TransactionSignatureValidationError {}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TransactionSignatureValidationErrorCategory {
ReplayDomain,
SigningHash,
Signature,
}
#[cfg(feature = "secp256k1-k256")]
pub fn validate_transaction_signature<H1, H2>(
expected_chain: ChainId,
transaction: UnvalidatedTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
H1: Keccak256,
H2: Keccak256,
{
validate_transaction_signature_with_backend(
expected_chain,
transaction,
expected_sender,
scratch,
signing_hasher,
K256Secp256k1Backend,
address_hasher,
)
}
pub fn validate_transaction_signature_with_backend<B, H1, H2>(
expected_chain: ChainId,
transaction: UnvalidatedTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
mut secp256k1_backend: B,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
B: RecoverableSecp256k1,
H1: Keccak256,
H2: Keccak256,
{
match transaction {
UnvalidatedTransaction::Legacy(tx) => validate_legacy_transaction_signature_with_backend(
expected_chain,
&tx,
expected_sender,
scratch,
signing_hasher,
&mut secp256k1_backend,
address_hasher,
),
UnvalidatedTransaction::AccessList(tx) => {
validate_access_list_transaction_signature_with_backend(
expected_chain,
&tx,
expected_sender,
scratch,
signing_hasher,
&mut secp256k1_backend,
address_hasher,
)
}
UnvalidatedTransaction::DynamicFee(tx) => {
validate_dynamic_fee_transaction_signature_with_backend(
expected_chain,
&tx,
expected_sender,
scratch,
signing_hasher,
&mut secp256k1_backend,
address_hasher,
)
}
UnvalidatedTransaction::Blob(tx) => validate_blob_transaction_signature_with_backend(
expected_chain,
&tx,
expected_sender,
scratch,
signing_hasher,
&mut secp256k1_backend,
address_hasher,
),
UnvalidatedTransaction::SetCode(tx) => {
validate_set_code_transaction_signature_with_backend(
expected_chain,
&tx,
expected_sender,
scratch,
signing_hasher,
&mut secp256k1_backend,
address_hasher,
)
}
}
}
#[cfg(feature = "secp256k1-k256")]
pub fn validate_legacy_transaction_signature<H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedLegacyTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
H1: Keccak256,
H2: Keccak256,
{
validate_legacy_transaction_signature_with_backend(
expected_chain,
transaction,
expected_sender,
scratch,
signing_hasher,
K256Secp256k1Backend,
address_hasher,
)
}
pub fn validate_legacy_transaction_signature_with_backend<B, H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedLegacyTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
secp256k1_backend: B,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
B: RecoverableSecp256k1,
H1: Keccak256,
H2: Keccak256,
{
require_legacy_replay_domain(expected_chain, transaction)
.map_err(TransactionSignatureValidationError::ReplayDomain)?;
let signing_hash = legacy_eip155_transaction_signing_hash(transaction, scratch, signing_hasher)
.map_err(TransactionSignatureValidationError::SigningHash)?;
let signature = legacy_signature(transaction)?;
recover_and_check(
signing_hash,
signature,
expected_sender,
secp256k1_backend,
address_hasher,
)
}
#[cfg(feature = "secp256k1-k256")]
pub fn validate_access_list_transaction_signature<H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedAccessListTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
H1: Keccak256,
H2: Keccak256,
{
validate_access_list_transaction_signature_with_backend(
expected_chain,
transaction,
expected_sender,
scratch,
signing_hasher,
K256Secp256k1Backend,
address_hasher,
)
}
pub fn validate_access_list_transaction_signature_with_backend<B, H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedAccessListTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
secp256k1_backend: B,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
B: RecoverableSecp256k1,
H1: Keccak256,
H2: Keccak256,
{
require_access_list_replay_domain(expected_chain, transaction)
.map_err(TransactionSignatureValidationError::ReplayDomain)?;
let signing_hash = access_list_transaction_signing_hash(transaction, scratch, signing_hasher)
.map_err(TransactionSignatureValidationError::SigningHash)?;
let signature =
EthereumSignature::from_parts(transaction.r, transaction.s, transaction.y_parity);
recover_and_check(
signing_hash,
signature,
expected_sender,
secp256k1_backend,
address_hasher,
)
}
#[cfg(feature = "secp256k1-k256")]
pub fn validate_dynamic_fee_transaction_signature<H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedDynamicFeeTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
H1: Keccak256,
H2: Keccak256,
{
validate_dynamic_fee_transaction_signature_with_backend(
expected_chain,
transaction,
expected_sender,
scratch,
signing_hasher,
K256Secp256k1Backend,
address_hasher,
)
}
pub fn validate_dynamic_fee_transaction_signature_with_backend<B, H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedDynamicFeeTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
secp256k1_backend: B,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
B: RecoverableSecp256k1,
H1: Keccak256,
H2: Keccak256,
{
require_dynamic_fee_replay_domain(expected_chain, transaction)
.map_err(TransactionSignatureValidationError::ReplayDomain)?;
let signing_hash = dynamic_fee_transaction_signing_hash(transaction, scratch, signing_hasher)
.map_err(TransactionSignatureValidationError::SigningHash)?;
let signature =
EthereumSignature::from_parts(transaction.r, transaction.s, transaction.y_parity);
recover_and_check(
signing_hash,
signature,
expected_sender,
secp256k1_backend,
address_hasher,
)
}
#[cfg(feature = "secp256k1-k256")]
pub fn validate_blob_transaction_signature<H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedBlobTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
H1: Keccak256,
H2: Keccak256,
{
validate_blob_transaction_signature_with_backend(
expected_chain,
transaction,
expected_sender,
scratch,
signing_hasher,
K256Secp256k1Backend,
address_hasher,
)
}
pub fn validate_blob_transaction_signature_with_backend<B, H1, H2>(
expected_chain: ChainId,
transaction: &UnvalidatedBlobTransaction<'_>,
expected_sender: Option<Address>,
scratch: &mut [u8],
signing_hasher: H1,
secp256k1_backend: B,
address_hasher: H2,
) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
where
B: RecoverableSecp256k1,
H1: Keccak256,
H2: Keccak256,
{
require_blob_replay_domain(expected_chain, transaction)
.map_err(TransactionSignatureValidationError::ReplayDomain)?;
let signing_hash = blob_transaction_signing_hash(transaction, scratch, signing_hasher)
.map_err(TransactionSignatureValidationError::SigningHash)?;
let signature =
EthereumSignature::from_parts(transaction.r, transaction.s, transaction.y_parity);
recover_and_check(
signing_hash,
signature,
expected_sender,
secp256k1_backend,
address_hasher,
)
}