tempo_primitives/transaction/
tt_authorization.rs1use alloc::vec::Vec;
2use alloy_eips::eip7702::{Authorization, RecoveredAuthority, RecoveredAuthorization};
3#[cfg(feature = "evm")]
4use alloy_primitives::U256;
5use alloy_primitives::{Address, B256, keccak256};
6use alloy_rlp::{BufMut, Decodable, Encodable, Header, Result as RlpResult, length_of_length};
7use core::ops::Deref;
8#[cfg(feature = "evm")]
9use revm::context::transaction::AuthorizationTr;
10
11#[cfg(not(feature = "std"))]
12use once_cell::race::OnceBox as OnceLock;
13#[cfg(feature = "std")]
14use std::sync::OnceLock;
15
16use crate::TempoSignature;
17
18pub const MAGIC: u8 = 0x05;
20
21#[derive(Clone, Debug, Eq, PartialEq, Hash)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
32#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
33pub struct TempoSignedAuthorization {
34 #[cfg_attr(feature = "serde", serde(flatten))]
36 inner: Authorization,
37 signature: TempoSignature,
39}
40
41impl TempoSignedAuthorization {
42 pub const fn new_unchecked(inner: Authorization, signature: TempoSignature) -> Self {
46 Self { inner, signature }
47 }
48
49 pub const fn signature(&self) -> &TempoSignature {
53 &self.signature
54 }
55
56 pub fn strip_signature(self) -> Authorization {
58 self.inner
59 }
60
61 pub const fn inner(&self) -> &Authorization {
63 &self.inner
64 }
65
66 #[inline]
71 pub fn signature_hash(&self) -> B256 {
72 let mut buf = Vec::new();
73 buf.push(MAGIC);
74 self.inner.encode(&mut buf);
75 keccak256(buf)
76 }
77
78 pub fn recover_authority(&self) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
84 let sig_hash = self.signature_hash();
85 self.signature.recover_signer(&sig_hash)
86 }
87
88 pub fn into_recovered(self) -> RecoveredAuthorization {
91 let authority_result = self.recover_authority();
92 let authority =
93 authority_result.map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid);
94
95 RecoveredAuthorization::new_unchecked(self.inner, authority)
96 }
97
98 fn decode_fields(buf: &mut &[u8]) -> RlpResult<Self> {
100 Ok(Self {
101 inner: Authorization {
102 chain_id: Decodable::decode(buf)?,
103 address: Decodable::decode(buf)?,
104 nonce: Decodable::decode(buf)?,
105 },
106 signature: Decodable::decode(buf)?,
107 })
108 }
109
110 fn fields_len(&self) -> usize {
112 self.inner.chain_id.length()
113 + self.inner.address.length()
114 + self.inner.nonce.length()
115 + self.signature.length()
116 }
117
118 pub fn size(&self) -> usize {
120 size_of::<Self>()
121 }
122}
123
124impl Decodable for TempoSignedAuthorization {
125 fn decode(buf: &mut &[u8]) -> RlpResult<Self> {
126 let header = Header::decode(buf)?;
127 if !header.list {
128 return Err(alloy_rlp::Error::UnexpectedString);
129 }
130 let started_len = buf.len();
131
132 let this = Self::decode_fields(buf)?;
133
134 let consumed = started_len - buf.len();
135 if consumed != header.payload_length {
136 return Err(alloy_rlp::Error::ListLengthMismatch {
137 expected: header.payload_length,
138 got: consumed,
139 });
140 }
141
142 Ok(this)
143 }
144}
145
146impl Encodable for TempoSignedAuthorization {
147 fn encode(&self, buf: &mut dyn BufMut) {
148 Header {
149 list: true,
150 payload_length: self.fields_len(),
151 }
152 .encode(buf);
153 self.inner.chain_id.encode(buf);
154 self.inner.address.encode(buf);
155 self.inner.nonce.encode(buf);
156 self.signature.encode(buf);
157 }
158
159 fn length(&self) -> usize {
160 let len = self.fields_len();
161 len + length_of_length(len)
162 }
163}
164
165impl Deref for TempoSignedAuthorization {
166 type Target = Authorization;
167
168 fn deref(&self) -> &Self::Target {
169 &self.inner
170 }
171}
172
173#[derive(Clone, Debug)]
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180pub struct RecoveredTempoAuthorization {
181 signed: TempoSignedAuthorization,
183 #[cfg_attr(feature = "serde", serde(skip))]
185 authority: OnceLock<RecoveredAuthority>,
186}
187
188impl RecoveredTempoAuthorization {
189 pub const fn new(signed: TempoSignedAuthorization) -> Self {
193 Self {
194 signed,
195 authority: OnceLock::new(),
196 }
197 }
198
199 pub fn new_unchecked(signed: TempoSignedAuthorization, authority: RecoveredAuthority) -> Self {
204 Self {
205 signed,
206 authority: {
207 let value = OnceLock::new();
208 #[allow(clippy::useless_conversion)]
209 let _ = value.set(authority.into());
210 value
211 },
212 }
213 }
214
215 pub fn recover(signed: TempoSignedAuthorization) -> Self {
219 let authority = signed
220 .recover_authority()
221 .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid);
222 Self::new_unchecked(signed, authority)
223 }
224
225 pub const fn signed(&self) -> &TempoSignedAuthorization {
227 &self.signed
228 }
229
230 pub const fn inner(&self) -> &Authorization {
232 self.signed.inner()
233 }
234
235 pub const fn signature(&self) -> &TempoSignature {
237 self.signed.signature()
238 }
239
240 pub fn authority(&self) -> Option<Address> {
244 match self.authority_status() {
245 RecoveredAuthority::Valid(addr) => Some(*addr),
246 RecoveredAuthority::Invalid => None,
247 }
248 }
249
250 pub fn authority_status(&self) -> &RecoveredAuthority {
254 #[allow(clippy::useless_conversion)]
255 self.authority.get_or_init(|| {
256 self.signed
257 .recover_authority()
258 .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid)
259 .into()
260 })
261 }
262
263 pub fn into_recovered_authorization(self) -> RecoveredAuthorization {
265 let authority = self.authority_status().clone();
266 RecoveredAuthorization::new_unchecked(self.signed.strip_signature(), authority)
267 }
268}
269
270impl PartialEq for RecoveredTempoAuthorization {
271 fn eq(&self, other: &Self) -> bool {
272 self.signed == other.signed
273 }
274}
275
276impl Eq for RecoveredTempoAuthorization {}
277
278impl core::hash::Hash for RecoveredTempoAuthorization {
279 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
280 self.signed.hash(state);
281 }
282}
283
284impl Deref for RecoveredTempoAuthorization {
285 type Target = Authorization;
286
287 fn deref(&self) -> &Self::Target {
288 self.signed.inner()
289 }
290}
291
292#[cfg(feature = "evm")]
293impl AuthorizationTr for RecoveredTempoAuthorization {
294 fn chain_id(&self) -> U256 {
295 self.chain_id
296 }
297 fn address(&self) -> Address {
298 self.address
299 }
300 fn nonce(&self) -> u64 {
301 self.nonce
302 }
303
304 fn authority(&self) -> Option<Address> {
305 self.authority()
306 }
307}
308
309#[cfg(test)]
310pub mod tests {
311 use super::*;
312 use crate::TempoSignature;
313 use alloy_primitives::{U256, address};
314 use alloy_signer::SignerSync;
315 use alloy_signer_local::PrivateKeySigner;
316
317 #[test]
318 fn test_aa_signed_auth_encode_decode_roundtrip() {
319 let auth = Authorization {
320 chain_id: U256::from(1),
321 address: address!("0000000000000000000000000000000000000006"),
322 nonce: 1,
323 };
324
325 let signature = TempoSignature::default(); let signed = TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
327
328 let mut buf = Vec::new();
329 signed.encode(&mut buf);
330
331 let decoded = TempoSignedAuthorization::decode(&mut buf.as_slice()).unwrap();
332 assert_eq!(buf.len(), signed.length());
333 assert_eq!(decoded, signed);
334
335 assert_eq!(signed.inner(), &auth);
337 assert_eq!(signed.signature(), &signature);
338 assert!(signed.size() > 0);
339
340 assert_eq!(signed.chain_id, auth.chain_id);
342 assert_eq!(signed.address, auth.address);
343 assert_eq!(signed.nonce, auth.nonce);
344
345 let stripped = signed.strip_signature();
347 assert_eq!(stripped, auth);
348 }
349
350 #[test]
351 fn test_signature_hash() {
352 let auth = Authorization {
353 chain_id: U256::from(1),
354 address: address!("0000000000000000000000000000000000000006"),
355 nonce: 1,
356 };
357
358 let signature = TempoSignature::default();
359 let signed = TempoSignedAuthorization::new_unchecked(auth.clone(), signature);
360
361 let expected_hash = {
363 let mut buf = Vec::new();
364 buf.push(MAGIC);
365 auth.encode(&mut buf);
366 keccak256(buf)
367 };
368
369 assert_eq!(signed.signature_hash(), expected_hash);
370 }
371
372 pub fn generate_secp256k1_keypair() -> (PrivateKeySigner, Address) {
373 let signer = PrivateKeySigner::random();
374 let address = signer.address();
375 (signer, address)
376 }
377
378 pub fn sign_hash(signer: &PrivateKeySigner, hash: &B256) -> TempoSignature {
379 let signature = signer.sign_hash_sync(hash).expect("signing failed");
380 TempoSignature::from(signature)
381 }
382
383 #[test]
384 fn test_recover_authority() {
385 let (signing_key, expected_address) = generate_secp256k1_keypair();
386
387 let auth = Authorization {
388 chain_id: U256::ONE,
389 address: Address::random(),
390 nonce: 1,
391 };
392
393 let placeholder_sig = TempoSignature::default();
395 let temp_signed = TempoSignedAuthorization::new_unchecked(auth.clone(), placeholder_sig);
396 let signature = sign_hash(&signing_key, &temp_signed.signature_hash());
397 let signed = TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
398
399 let recovered = signed.recover_authority();
401 assert!(recovered.is_ok());
402 assert_eq!(recovered.unwrap(), expected_address);
403
404 let signed_for_into =
406 TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
407 let std_recovered = signed_for_into.into_recovered();
408 assert_eq!(std_recovered.authority(), Some(expected_address));
409
410 let signed_for_lazy =
412 TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
413 let lazy_recovered = RecoveredTempoAuthorization::new(signed_for_lazy);
414 assert_eq!(lazy_recovered.authority(), Some(expected_address));
415 assert!(matches!(
416 lazy_recovered.authority_status(),
417 RecoveredAuthority::Valid(_)
418 ));
419
420 let signed_for_eager =
422 TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
423 let eager_recovered = RecoveredTempoAuthorization::recover(signed_for_eager);
424 assert_eq!(eager_recovered.authority(), Some(expected_address));
425
426 assert_eq!(eager_recovered.signed().inner(), &auth);
428 assert_eq!(eager_recovered.inner(), &auth);
429 assert_eq!(eager_recovered.signature(), &signature);
430
431 let signed_for_convert = TempoSignedAuthorization::new_unchecked(auth.clone(), signature);
433 let converted = RecoveredTempoAuthorization::new(signed_for_convert);
434 let std_auth = converted.into_recovered_authorization();
435 assert_eq!(std_auth.authority(), Some(expected_address));
436
437 let wrong_hash = B256::random();
439 let wrong_signature = sign_hash(&signing_key, &wrong_hash);
440 let bad_signed = TempoSignedAuthorization::new_unchecked(auth, wrong_signature);
441
442 let recovered = bad_signed.recover_authority();
444 assert!(recovered.is_ok());
445 assert_ne!(recovered.unwrap(), expected_address);
446
447 let bad_lazy = RecoveredTempoAuthorization::new(bad_signed);
449 assert!(bad_lazy.authority().is_some());
450 assert_ne!(bad_lazy.authority().unwrap(), expected_address);
451 }
452}