tempo_precompiles/signature_verifier/
dispatch.rs1use super::SignatureVerifier;
2use crate::{Precompile, charge_input_cost, dispatch_call, view};
3use alloy::{primitives::Address, sol_types::SolInterface};
4use revm::precompile::PrecompileResult;
5use tempo_contracts::precompiles::{
6 ISignatureVerifier::ISignatureVerifierCalls as ISVCalls, SignatureVerifierError,
7};
8use tempo_primitives::MAX_WEBAUTHN_SIGNATURE_LENGTH;
9
10const MAX_CALLDATA_LEN: usize =
14 4 + 32 * 4 + (MAX_WEBAUTHN_SIGNATURE_LENGTH + 1).next_multiple_of(32);
15
16impl Precompile for SignatureVerifier {
17 fn call(&mut self, calldata: &[u8], _msg_sender: Address) -> PrecompileResult {
18 if let Some(err) = charge_input_cost(&mut self.storage, calldata) {
19 return err;
20 }
21
22 if calldata.len() > MAX_CALLDATA_LEN {
23 return Ok(self
24 .storage
25 .abi_revert(SignatureVerifierError::invalid_format()));
26 }
27
28 dispatch_call(calldata, &[], ISVCalls::abi_decode, |call| match call {
29 ISVCalls::recover(call) => view(call, |c| self.recover(c.hash, c.signature)),
30 ISVCalls::verify(call) => view(call, |c| {
31 self.recover(c.hash, c.signature).map(|sig| sig == c.signer)
32 }),
33 })
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40 use crate::{
41 Precompile, expect_precompile_revert,
42 storage::{StorageCtx, hashmap::HashMapStorageProvider},
43 test_util::{assert_full_coverage, check_selector_coverage},
44 };
45 use alloy::{primitives::B256, sol_types::SolCall};
46 use alloy_signer::SignerSync;
47 use alloy_signer_local::PrivateKeySigner;
48 use tempo_chainspec::hardfork::TempoHardfork;
49 use tempo_contracts::precompiles::ISignatureVerifier;
50
51 #[test]
52 fn test_signature_verifier_selector_coverage() -> eyre::Result<()> {
53 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
54 StorageCtx::enter(&mut storage, || {
55 let mut verifier = SignatureVerifier::new();
56
57 let unsupported = check_selector_coverage(
58 &mut verifier,
59 ISVCalls::SELECTORS,
60 "ISignatureVerifier",
61 ISVCalls::name_by_selector,
62 );
63
64 assert_full_coverage([unsupported]);
65 Ok(())
66 })
67 }
68
69 #[test]
70 fn test_verify_returns_true_for_correct_signer() -> eyre::Result<()> {
71 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
72 StorageCtx::enter(&mut storage, || {
73 let signer = PrivateKeySigner::random();
74 let hash = B256::from([0xAA; 32]);
75 let sig = signer.sign_hash_sync(&hash)?;
76
77 let calldata = ISignatureVerifier::verifyCall {
78 signer: signer.address(),
79 hash,
80 signature: sig.as_bytes().to_vec().into(),
81 }
82 .abi_encode();
83
84 let output = SignatureVerifier::new().call(&calldata, Address::ZERO)?;
85 let ret = ISignatureVerifier::verifyCall::abi_decode_returns(&output.bytes)?;
86 assert!(ret, "verify should return true for the correct signer");
87 Ok(())
88 })
89 }
90
91 #[test]
92 fn test_verify_returns_false_for_wrong_signer() -> eyre::Result<()> {
93 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
94 StorageCtx::enter(&mut storage, || {
95 let signer = PrivateKeySigner::random();
96 let hash = B256::from([0xBB; 32]);
97 let sig = signer.sign_hash_sync(&hash)?;
98
99 let calldata = ISignatureVerifier::verifyCall {
100 signer: Address::random(),
101 hash,
102 signature: sig.as_bytes().to_vec().into(),
103 }
104 .abi_encode();
105
106 let output = SignatureVerifier::new().call(&calldata, Address::ZERO)?;
107 let ret = ISignatureVerifier::verifyCall::abi_decode_returns(&output.bytes)?;
108 assert!(!ret, "verify should return false for a wrong signer");
109 Ok(())
110 })
111 }
112
113 #[test]
114 fn test_oversized_calldata_reverts_with_invalid_format() -> eyre::Result<()> {
115 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
116 StorageCtx::enter(&mut storage, || {
117 let calldata = vec![0u8; MAX_CALLDATA_LEN + 1];
118 let result = SignatureVerifier::new().call(&calldata, Address::ZERO);
119
120 expect_precompile_revert(&result, SignatureVerifierError::invalid_format());
121 Ok(())
122 })
123 }
124
125 #[test]
126 fn test_max_webauthn_verify_passes_size_guard() -> eyre::Result<()> {
127 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
128 StorageCtx::enter(&mut storage, || {
129 let mut sig = vec![0x02u8];
130 sig.extend_from_slice(&[0u8; MAX_WEBAUTHN_SIGNATURE_LENGTH]);
131
132 let calldata = ISignatureVerifier::verifyCall {
133 signer: Address::ZERO,
134 hash: B256::ZERO,
135 signature: sig.into(),
136 }
137 .abi_encode();
138
139 let result = SignatureVerifier::new().call(&calldata, Address::ZERO)?;
140 assert!(
142 SignatureVerifierError::abi_decode(&result.bytes)
143 .map(|e| e != SignatureVerifierError::invalid_format())
144 .unwrap_or(true),
145 "max-size WebAuthn calldata was wrongly rejected by size guard"
146 );
147 Ok(())
148 })
149 }
150
151 #[test]
152 fn test_max_calldata_is_not_rejected() -> eyre::Result<()> {
153 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
154 StorageCtx::enter(&mut storage, || {
155 let calldata = vec![0u8; MAX_CALLDATA_LEN];
159 let result = SignatureVerifier::new().call(&calldata, Address::ZERO)?;
160
161 assert!(result.is_revert());
162 assert!(
163 SignatureVerifierError::abi_decode(&result.bytes).is_err(),
164 "should not be an InvalidFormat revert"
165 );
166 Ok(())
167 })
168 }
169}