tempo_precompiles/signature_verifier/
mod.rs1pub mod dispatch;
2
3use crate::{SIGNATURE_VERIFIER_ADDRESS, error::Result};
4use alloy::primitives::{Address, B256, Bytes};
5use tempo_contracts::precompiles::SignatureVerifierError;
6use tempo_precompiles_macros::contract;
7use tempo_primitives::transaction::{SignatureType, tt_signature::PrimitiveSignature};
8
9const SECP256K1_VERIFY_GAS: u64 = 3_000;
11
12const P256_VERIFY_GAS: u64 = 8_000;
14
15const WEBAUTHN_VERIFY_GAS: u64 = 8_000;
17
18#[contract(addr = SIGNATURE_VERIFIER_ADDRESS)]
19pub struct SignatureVerifier {}
20
21impl SignatureVerifier {
22 pub fn initialize(&mut self) -> Result<()> {
23 self.__initialize()
24 }
25
26 pub fn recover(&mut self, hash: B256, signature: Bytes) -> Result<Address> {
27 let sig = PrimitiveSignature::from_bytes(&signature)
29 .map_err(|_| SignatureVerifierError::invalid_format())?;
30
31 let verify_gas = match sig.signature_type() {
33 SignatureType::Secp256k1 => SECP256K1_VERIFY_GAS,
34 SignatureType::P256 => P256_VERIFY_GAS,
35 SignatureType::WebAuthn => WEBAUTHN_VERIFY_GAS,
36 };
37 self.storage.deduct_gas(verify_gas)?;
38
39 sig.recover_signer(&hash)
41 .map_err(|_| SignatureVerifierError::invalid_signature().into())
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use crate::storage::{StorageCtx, hashmap::HashMapStorageProvider};
49 use alloy_signer::SignerSync;
50 use alloy_signer_local::PrivateKeySigner;
51 use tempo_chainspec::hardfork::TempoHardfork;
52 use tempo_primitives::transaction::tt_signature::{
53 SIGNATURE_TYPE_P256, SIGNATURE_TYPE_WEBAUTHN,
54 };
55
56 fn sign_recover(hash: B256, signature: Vec<u8>) -> Result<Address> {
57 SignatureVerifier::new().recover(hash, Bytes::from(signature))
58 }
59
60 #[test]
61 fn test_verify_secp256k1_valid() -> eyre::Result<()> {
62 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
63 StorageCtx::enter(&mut storage, || {
64 let signer = PrivateKeySigner::random();
65 let hash = B256::from([0xAA; 32]);
66 let sig = signer.sign_hash_sync(&hash)?;
67 let sig_bytes = sig.as_bytes().to_vec();
68 assert_eq!(sig_bytes.len(), 65);
69
70 let result = sign_recover(hash, sig_bytes)?;
71 assert_eq!(result, signer.address());
72 Ok(())
73 })
74 }
75
76 #[test]
77 fn test_verify_p256_valid() -> eyre::Result<()> {
78 use p256::{ecdsa::SigningKey, elliptic_curve::rand_core::OsRng};
79 use tempo_primitives::transaction::tt_signature::{derive_p256_address, normalize_p256_s};
80
81 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
82 StorageCtx::enter(&mut storage, || {
83 let signing_key = SigningKey::random(&mut OsRng);
84 let verifying_key = signing_key.verifying_key();
85 let encoded = verifying_key.to_encoded_point(false);
86 let pub_key_x =
87 B256::from_slice(encoded.x().ok_or_else(|| eyre::eyre!("missing x coord"))?);
88 let pub_key_y =
89 B256::from_slice(encoded.y().ok_or_else(|| eyre::eyre!("missing y coord"))?);
90 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
91
92 let hash = B256::from([0xBB; 32]);
93 let (signature, _) = signing_key.sign_prehash_recoverable(hash.as_slice())?;
94 let r = B256::from_slice(&signature.r().to_bytes());
95 let s =
96 normalize_p256_s(&signature.s().to_bytes()).expect("p256 crate produces valid s");
97
98 let mut sig_bytes = Vec::new();
100 sig_bytes.push(SIGNATURE_TYPE_P256);
101 sig_bytes.extend_from_slice(r.as_slice());
102 sig_bytes.extend_from_slice(s.as_slice());
103 sig_bytes.extend_from_slice(pub_key_x.as_slice());
104 sig_bytes.extend_from_slice(pub_key_y.as_slice());
105 sig_bytes.push(0); assert_eq!(sig_bytes.len(), 130);
107
108 let result = sign_recover(hash, sig_bytes)?;
109 assert_eq!(result, expected_address);
110 Ok(())
111 })
112 }
113
114 #[test]
115 fn test_verify_empty_signature_reverts() -> eyre::Result<()> {
116 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
117 StorageCtx::enter(&mut storage, || {
118 let result = sign_recover(B256::ZERO, vec![]);
119 assert!(result.is_err());
120 Ok(())
121 })
122 }
123
124 #[test]
125 fn test_verify_secp256k1_wrong_length_reverts() -> eyre::Result<()> {
126 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
127 StorageCtx::enter(&mut storage, || {
128 let result = sign_recover(B256::ZERO, vec![0u8; 64]);
130 assert!(result.is_err());
131 let result = sign_recover(B256::ZERO, vec![0u8; 66]);
133 assert!(result.is_err());
134 Ok(())
135 })
136 }
137
138 #[test]
139 fn test_verify_p256_wrong_length_reverts() -> eyre::Result<()> {
140 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
141 StorageCtx::enter(&mut storage, || {
142 let mut sig = vec![SIGNATURE_TYPE_P256];
144 sig.extend_from_slice(&[0u8; 128]);
145 let result = sign_recover(B256::ZERO, sig);
146 assert!(result.is_err());
147 Ok(())
148 })
149 }
150
151 #[test]
152 fn test_verify_webauthn_too_short_reverts() -> eyre::Result<()> {
153 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
154 StorageCtx::enter(&mut storage, || {
155 let mut sig = vec![SIGNATURE_TYPE_WEBAUTHN];
157 sig.extend_from_slice(&[0u8; 127]);
158 let result = sign_recover(B256::ZERO, sig);
159 assert!(result.is_err());
160 Ok(())
161 })
162 }
163
164 #[test]
165 fn test_verify_webauthn_too_long_reverts() -> eyre::Result<()> {
166 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
167 StorageCtx::enter(&mut storage, || {
168 let mut sig = vec![SIGNATURE_TYPE_WEBAUTHN];
170 sig.extend_from_slice(&[0u8; 2049]);
171 let result = sign_recover(B256::ZERO, sig);
172 assert!(result.is_err());
173 Ok(())
174 })
175 }
176
177 #[test]
178 fn test_verify_unknown_type_reverts() -> eyre::Result<()> {
179 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
180 StorageCtx::enter(&mut storage, || {
181 let mut sig = vec![0x05];
182 sig.extend_from_slice(&[0u8; 129]);
183 let result = sign_recover(B256::ZERO, sig);
184 assert!(result.is_err());
185 Ok(())
186 })
187 }
188
189 #[test]
190 fn test_verify_invalid_secp256k1_signature_reverts() -> eyre::Result<()> {
191 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
192 StorageCtx::enter(&mut storage, || {
193 let result = sign_recover(B256::ZERO, vec![0u8; 65]);
194 assert!(result.is_err());
195 Ok(())
196 })
197 }
198}