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