1use core::fmt;
2
3use eth_valkyoth_hash::Keccak256;
4use eth_valkyoth_primitives::{Address, ChainId};
5use eth_valkyoth_protocol::{
6 UnvalidatedAccessListTransaction, UnvalidatedBlobTransaction, UnvalidatedDynamicFeeTransaction,
7 UnvalidatedLegacyTransaction, UnvalidatedTransaction,
8};
9
10#[cfg(feature = "secp256k1-k256")]
11use crate::K256Secp256k1Backend;
12pub(crate) use crate::transaction_signature_set_code::validate_set_code_transaction_signature_with_backend;
13use crate::{
14 EthereumSignature, RecoverableSecp256k1, TransactionSigningHash, TransactionSigningHashError,
15 VerifyError, access_list_transaction_signing_hash, blob_transaction_signing_hash,
16 dynamic_fee_transaction_signing_hash, legacy_eip155_transaction_signing_hash,
17 require_access_list_replay_domain, require_blob_replay_domain,
18 require_dynamic_fee_replay_domain, require_legacy_replay_domain,
19 transaction_signature_helpers::{legacy_signature, recover_and_check},
20};
21
22#[cfg(test)]
23#[path = "transaction_signature_external_tests.rs"]
24mod external_tests;
25#[cfg(test)]
26#[path = "transaction_signature_tests.rs"]
27mod tests;
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub struct ValidatedTransactionSignature {
33 sender: Address,
34 signing_hash: TransactionSigningHash,
35}
36
37impl ValidatedTransactionSignature {
38 #[must_use]
40 pub(crate) const fn new(sender: Address, signing_hash: TransactionSigningHash) -> Self {
41 Self {
42 sender,
43 signing_hash,
44 }
45 }
46
47 #[must_use]
49 pub const fn sender(self) -> Address {
50 self.sender
51 }
52
53 #[must_use]
55 pub const fn signing_hash(self) -> TransactionSigningHash {
56 self.signing_hash
57 }
58}
59
60#[non_exhaustive]
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum TransactionSignatureValidationError {
64 ReplayDomain(VerifyError),
66 SigningHash(TransactionSigningHashError),
68 InvalidSignature,
70 UnsupportedTransactionType,
72 WrongSender,
74}
75
76impl TransactionSignatureValidationError {
77 #[must_use]
79 pub const fn code(self) -> &'static str {
80 match self {
81 Self::ReplayDomain(error) => error.code(),
82 Self::SigningHash(error) => error.code(),
83 Self::InvalidSignature => "ETH_TX_SIGNATURE_INVALID",
84 Self::UnsupportedTransactionType => "ETH_TX_SIGNATURE_UNSUPPORTED_TYPE",
85 Self::WrongSender => "ETH_TX_SIGNATURE_WRONG_SENDER",
86 }
87 }
88
89 #[must_use]
91 pub const fn message(self) -> &'static str {
92 match self {
93 Self::ReplayDomain(error) => error.message(),
94 Self::SigningHash(_) => "transaction signing hash construction failed",
95 Self::InvalidSignature => "transaction signature is not accepted",
96 Self::UnsupportedTransactionType => {
97 "transaction type has no signature validation helper yet"
98 }
99 Self::WrongSender => "transaction signature recovered a different sender",
100 }
101 }
102
103 #[must_use]
105 pub const fn category(self) -> TransactionSignatureValidationErrorCategory {
106 match self {
107 Self::ReplayDomain(_) => TransactionSignatureValidationErrorCategory::ReplayDomain,
108 Self::SigningHash(_) => TransactionSignatureValidationErrorCategory::SigningHash,
109 Self::InvalidSignature | Self::WrongSender | Self::UnsupportedTransactionType => {
110 TransactionSignatureValidationErrorCategory::Signature
111 }
112 }
113 }
114}
115
116impl fmt::Display for TransactionSignatureValidationError {
117 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
118 formatter.write_str(self.message())
119 }
120}
121
122#[cfg(feature = "std")]
123impl std::error::Error for TransactionSignatureValidationError {}
124
125#[non_exhaustive]
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub enum TransactionSignatureValidationErrorCategory {
129 ReplayDomain,
131 SigningHash,
133 Signature,
135}
136
137#[cfg(feature = "secp256k1-k256")]
155pub fn validate_transaction_signature<H1, H2>(
156 expected_chain: ChainId,
157 transaction: UnvalidatedTransaction<'_>,
158 expected_sender: Option<Address>,
159 scratch: &mut [u8],
160 signing_hasher: H1,
161 address_hasher: H2,
162) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
163where
164 H1: Keccak256,
165 H2: Keccak256,
166{
167 validate_transaction_signature_with_backend(
168 expected_chain,
169 transaction,
170 expected_sender,
171 scratch,
172 signing_hasher,
173 K256Secp256k1Backend,
174 address_hasher,
175 )
176}
177
178pub fn validate_transaction_signature_with_backend<B, H1, H2>(
184 expected_chain: ChainId,
185 transaction: UnvalidatedTransaction<'_>,
186 expected_sender: Option<Address>,
187 scratch: &mut [u8],
188 signing_hasher: H1,
189 mut secp256k1_backend: B,
190 address_hasher: H2,
191) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
192where
193 B: RecoverableSecp256k1,
194 H1: Keccak256,
195 H2: Keccak256,
196{
197 match transaction {
198 UnvalidatedTransaction::Legacy(tx) => validate_legacy_transaction_signature_with_backend(
199 expected_chain,
200 &tx,
201 expected_sender,
202 scratch,
203 signing_hasher,
204 &mut secp256k1_backend,
205 address_hasher,
206 ),
207 UnvalidatedTransaction::AccessList(tx) => {
208 validate_access_list_transaction_signature_with_backend(
209 expected_chain,
210 &tx,
211 expected_sender,
212 scratch,
213 signing_hasher,
214 &mut secp256k1_backend,
215 address_hasher,
216 )
217 }
218 UnvalidatedTransaction::DynamicFee(tx) => {
219 validate_dynamic_fee_transaction_signature_with_backend(
220 expected_chain,
221 &tx,
222 expected_sender,
223 scratch,
224 signing_hasher,
225 &mut secp256k1_backend,
226 address_hasher,
227 )
228 }
229 UnvalidatedTransaction::Blob(tx) => validate_blob_transaction_signature_with_backend(
230 expected_chain,
231 &tx,
232 expected_sender,
233 scratch,
234 signing_hasher,
235 &mut secp256k1_backend,
236 address_hasher,
237 ),
238 UnvalidatedTransaction::SetCode(tx) => {
239 validate_set_code_transaction_signature_with_backend(
240 expected_chain,
241 &tx,
242 expected_sender,
243 scratch,
244 signing_hasher,
245 &mut secp256k1_backend,
246 address_hasher,
247 )
248 }
249 }
250}
251
252#[cfg(feature = "secp256k1-k256")]
254pub fn validate_legacy_transaction_signature<H1, H2>(
255 expected_chain: ChainId,
256 transaction: &UnvalidatedLegacyTransaction<'_>,
257 expected_sender: Option<Address>,
258 scratch: &mut [u8],
259 signing_hasher: H1,
260 address_hasher: H2,
261) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
262where
263 H1: Keccak256,
264 H2: Keccak256,
265{
266 validate_legacy_transaction_signature_with_backend(
267 expected_chain,
268 transaction,
269 expected_sender,
270 scratch,
271 signing_hasher,
272 K256Secp256k1Backend,
273 address_hasher,
274 )
275}
276
277pub fn validate_legacy_transaction_signature_with_backend<B, H1, H2>(
279 expected_chain: ChainId,
280 transaction: &UnvalidatedLegacyTransaction<'_>,
281 expected_sender: Option<Address>,
282 scratch: &mut [u8],
283 signing_hasher: H1,
284 secp256k1_backend: B,
285 address_hasher: H2,
286) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
287where
288 B: RecoverableSecp256k1,
289 H1: Keccak256,
290 H2: Keccak256,
291{
292 require_legacy_replay_domain(expected_chain, transaction)
293 .map_err(TransactionSignatureValidationError::ReplayDomain)?;
294 let signing_hash = legacy_eip155_transaction_signing_hash(transaction, scratch, signing_hasher)
295 .map_err(TransactionSignatureValidationError::SigningHash)?;
296 let signature = legacy_signature(transaction)?;
297 recover_and_check(
298 signing_hash,
299 signature,
300 expected_sender,
301 secp256k1_backend,
302 address_hasher,
303 )
304}
305
306#[cfg(feature = "secp256k1-k256")]
308pub fn validate_access_list_transaction_signature<H1, H2>(
309 expected_chain: ChainId,
310 transaction: &UnvalidatedAccessListTransaction<'_>,
311 expected_sender: Option<Address>,
312 scratch: &mut [u8],
313 signing_hasher: H1,
314 address_hasher: H2,
315) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
316where
317 H1: Keccak256,
318 H2: Keccak256,
319{
320 validate_access_list_transaction_signature_with_backend(
321 expected_chain,
322 transaction,
323 expected_sender,
324 scratch,
325 signing_hasher,
326 K256Secp256k1Backend,
327 address_hasher,
328 )
329}
330
331pub fn validate_access_list_transaction_signature_with_backend<B, H1, H2>(
333 expected_chain: ChainId,
334 transaction: &UnvalidatedAccessListTransaction<'_>,
335 expected_sender: Option<Address>,
336 scratch: &mut [u8],
337 signing_hasher: H1,
338 secp256k1_backend: B,
339 address_hasher: H2,
340) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
341where
342 B: RecoverableSecp256k1,
343 H1: Keccak256,
344 H2: Keccak256,
345{
346 require_access_list_replay_domain(expected_chain, transaction)
347 .map_err(TransactionSignatureValidationError::ReplayDomain)?;
348 let signing_hash = access_list_transaction_signing_hash(transaction, scratch, signing_hasher)
349 .map_err(TransactionSignatureValidationError::SigningHash)?;
350 let signature =
351 EthereumSignature::from_parts(transaction.r, transaction.s, transaction.y_parity);
352 recover_and_check(
353 signing_hash,
354 signature,
355 expected_sender,
356 secp256k1_backend,
357 address_hasher,
358 )
359}
360
361#[cfg(feature = "secp256k1-k256")]
363pub fn validate_dynamic_fee_transaction_signature<H1, H2>(
364 expected_chain: ChainId,
365 transaction: &UnvalidatedDynamicFeeTransaction<'_>,
366 expected_sender: Option<Address>,
367 scratch: &mut [u8],
368 signing_hasher: H1,
369 address_hasher: H2,
370) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
371where
372 H1: Keccak256,
373 H2: Keccak256,
374{
375 validate_dynamic_fee_transaction_signature_with_backend(
376 expected_chain,
377 transaction,
378 expected_sender,
379 scratch,
380 signing_hasher,
381 K256Secp256k1Backend,
382 address_hasher,
383 )
384}
385
386pub fn validate_dynamic_fee_transaction_signature_with_backend<B, H1, H2>(
388 expected_chain: ChainId,
389 transaction: &UnvalidatedDynamicFeeTransaction<'_>,
390 expected_sender: Option<Address>,
391 scratch: &mut [u8],
392 signing_hasher: H1,
393 secp256k1_backend: B,
394 address_hasher: H2,
395) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
396where
397 B: RecoverableSecp256k1,
398 H1: Keccak256,
399 H2: Keccak256,
400{
401 require_dynamic_fee_replay_domain(expected_chain, transaction)
402 .map_err(TransactionSignatureValidationError::ReplayDomain)?;
403 let signing_hash = dynamic_fee_transaction_signing_hash(transaction, scratch, signing_hasher)
404 .map_err(TransactionSignatureValidationError::SigningHash)?;
405 let signature =
406 EthereumSignature::from_parts(transaction.r, transaction.s, transaction.y_parity);
407 recover_and_check(
408 signing_hash,
409 signature,
410 expected_sender,
411 secp256k1_backend,
412 address_hasher,
413 )
414}
415
416#[cfg(feature = "secp256k1-k256")]
418pub fn validate_blob_transaction_signature<H1, H2>(
419 expected_chain: ChainId,
420 transaction: &UnvalidatedBlobTransaction<'_>,
421 expected_sender: Option<Address>,
422 scratch: &mut [u8],
423 signing_hasher: H1,
424 address_hasher: H2,
425) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
426where
427 H1: Keccak256,
428 H2: Keccak256,
429{
430 validate_blob_transaction_signature_with_backend(
431 expected_chain,
432 transaction,
433 expected_sender,
434 scratch,
435 signing_hasher,
436 K256Secp256k1Backend,
437 address_hasher,
438 )
439}
440
441pub fn validate_blob_transaction_signature_with_backend<B, H1, H2>(
443 expected_chain: ChainId,
444 transaction: &UnvalidatedBlobTransaction<'_>,
445 expected_sender: Option<Address>,
446 scratch: &mut [u8],
447 signing_hasher: H1,
448 secp256k1_backend: B,
449 address_hasher: H2,
450) -> Result<ValidatedTransactionSignature, TransactionSignatureValidationError>
451where
452 B: RecoverableSecp256k1,
453 H1: Keccak256,
454 H2: Keccak256,
455{
456 require_blob_replay_domain(expected_chain, transaction)
457 .map_err(TransactionSignatureValidationError::ReplayDomain)?;
458 let signing_hash = blob_transaction_signing_hash(transaction, scratch, signing_hasher)
459 .map_err(TransactionSignatureValidationError::SigningHash)?;
460 let signature =
461 EthereumSignature::from_parts(transaction.r, transaction.s, transaction.y_parity);
462 recover_and_check(
463 signing_hash,
464 signature,
465 expected_sender,
466 secp256k1_backend,
467 address_hasher,
468 )
469}