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