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#[cfg(feature = "reth-codec")]
172impl reth_codecs::Compact for TempoSignedAuthorization {
173 fn to_compact<B>(&self, buf: &mut B) -> usize
174 where
175 B: alloy_rlp::BufMut + AsMut<[u8]>,
176 {
177 let start_len = buf.remaining_mut();
179 self.encode(buf);
180 start_len - buf.remaining_mut()
181 }
182
183 fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
184 let mut buf_slice = &buf[..len];
185 let auth = Self::decode(&mut buf_slice).expect("valid RLP encoding");
186 (auth, &buf[len..])
187 }
188}
189
190#[derive(Clone, Debug)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197pub struct RecoveredTempoAuthorization {
198 signed: TempoSignedAuthorization,
200 #[cfg_attr(feature = "serde", serde(skip))]
202 authority: OnceLock<RecoveredAuthority>,
203}
204
205impl RecoveredTempoAuthorization {
206 pub const fn new(signed: TempoSignedAuthorization) -> Self {
210 Self {
211 signed,
212 authority: OnceLock::new(),
213 }
214 }
215
216 pub fn new_unchecked(signed: TempoSignedAuthorization, authority: RecoveredAuthority) -> Self {
221 Self {
222 signed,
223 authority: {
224 let value = OnceLock::new();
225 #[allow(clippy::useless_conversion)]
226 let _ = value.set(authority.into());
227 value
228 },
229 }
230 }
231
232 pub fn recover(signed: TempoSignedAuthorization) -> Self {
236 let authority = signed
237 .recover_authority()
238 .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid);
239 Self::new_unchecked(signed, authority)
240 }
241
242 pub const fn signed(&self) -> &TempoSignedAuthorization {
244 &self.signed
245 }
246
247 pub const fn inner(&self) -> &Authorization {
249 self.signed.inner()
250 }
251
252 pub const fn signature(&self) -> &TempoSignature {
254 self.signed.signature()
255 }
256
257 pub fn authority(&self) -> Option<Address> {
261 match self.authority_status() {
262 RecoveredAuthority::Valid(addr) => Some(*addr),
263 RecoveredAuthority::Invalid => None,
264 }
265 }
266
267 pub fn authority_status(&self) -> &RecoveredAuthority {
271 #[allow(clippy::useless_conversion)]
272 self.authority.get_or_init(|| {
273 self.signed
274 .recover_authority()
275 .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid)
276 .into()
277 })
278 }
279
280 pub fn into_recovered_authorization(self) -> RecoveredAuthorization {
282 let authority = self.authority_status().clone();
283 RecoveredAuthorization::new_unchecked(self.signed.strip_signature(), authority)
284 }
285}
286
287impl PartialEq for RecoveredTempoAuthorization {
288 fn eq(&self, other: &Self) -> bool {
289 self.signed == other.signed
290 }
291}
292
293impl Eq for RecoveredTempoAuthorization {}
294
295impl core::hash::Hash for RecoveredTempoAuthorization {
296 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
297 self.signed.hash(state);
298 }
299}
300
301impl Deref for RecoveredTempoAuthorization {
302 type Target = Authorization;
303
304 fn deref(&self) -> &Self::Target {
305 self.signed.inner()
306 }
307}
308
309impl AuthorizationTr for RecoveredTempoAuthorization {
310 fn chain_id(&self) -> U256 {
311 self.chain_id
312 }
313 fn address(&self) -> Address {
314 self.address
315 }
316 fn nonce(&self) -> u64 {
317 self.nonce
318 }
319
320 fn authority(&self) -> Option<Address> {
321 self.authority()
322 }
323}
324
325#[cfg(test)]
326pub mod tests {
327 use super::*;
328 use crate::TempoSignature;
329 use alloy_primitives::{U256, address};
330 use alloy_signer::SignerSync;
331 use alloy_signer_local::PrivateKeySigner;
332
333 #[test]
334 fn test_aa_signed_auth_encode_decode_roundtrip() {
335 let auth = Authorization {
336 chain_id: U256::from(1),
337 address: address!("0000000000000000000000000000000000000006"),
338 nonce: 1,
339 };
340
341 let signature = TempoSignature::default(); let signed = TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
343
344 let mut buf = Vec::new();
345 signed.encode(&mut buf);
346
347 let decoded = TempoSignedAuthorization::decode(&mut buf.as_slice()).unwrap();
348 assert_eq!(buf.len(), signed.length());
349 assert_eq!(decoded, signed);
350
351 assert_eq!(signed.inner(), &auth);
353 assert_eq!(signed.signature(), &signature);
354 assert!(signed.size() > 0);
355
356 assert_eq!(signed.chain_id, auth.chain_id);
358 assert_eq!(signed.address, auth.address);
359 assert_eq!(signed.nonce, auth.nonce);
360
361 let stripped = signed.strip_signature();
363 assert_eq!(stripped, auth);
364 }
365
366 #[test]
367 fn test_signature_hash() {
368 let auth = Authorization {
369 chain_id: U256::from(1),
370 address: address!("0000000000000000000000000000000000000006"),
371 nonce: 1,
372 };
373
374 let signature = TempoSignature::default();
375 let signed = TempoSignedAuthorization::new_unchecked(auth.clone(), signature);
376
377 let expected_hash = {
379 let mut buf = Vec::new();
380 buf.push(MAGIC);
381 auth.encode(&mut buf);
382 keccak256(buf)
383 };
384
385 assert_eq!(signed.signature_hash(), expected_hash);
386 }
387
388 pub fn generate_secp256k1_keypair() -> (PrivateKeySigner, Address) {
389 let signer = PrivateKeySigner::random();
390 let address = signer.address();
391 (signer, address)
392 }
393
394 pub fn sign_hash(signer: &PrivateKeySigner, hash: &B256) -> TempoSignature {
395 let signature = signer.sign_hash_sync(hash).expect("signing failed");
396 TempoSignature::from(signature)
397 }
398
399 #[test]
400 fn test_recover_authority() {
401 let (signing_key, expected_address) = generate_secp256k1_keypair();
402
403 let auth = Authorization {
404 chain_id: U256::ONE,
405 address: Address::random(),
406 nonce: 1,
407 };
408
409 let placeholder_sig = TempoSignature::default();
411 let temp_signed = TempoSignedAuthorization::new_unchecked(auth.clone(), placeholder_sig);
412 let signature = sign_hash(&signing_key, &temp_signed.signature_hash());
413 let signed = TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
414
415 let recovered = signed.recover_authority();
417 assert!(recovered.is_ok());
418 assert_eq!(recovered.unwrap(), expected_address);
419
420 let signed_for_into =
422 TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
423 let std_recovered = signed_for_into.into_recovered();
424 assert_eq!(std_recovered.authority(), Some(expected_address));
425
426 let signed_for_lazy =
428 TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
429 let lazy_recovered = RecoveredTempoAuthorization::new(signed_for_lazy);
430 assert_eq!(lazy_recovered.authority(), Some(expected_address));
431 assert!(matches!(
432 lazy_recovered.authority_status(),
433 RecoveredAuthority::Valid(_)
434 ));
435
436 let signed_for_eager =
438 TempoSignedAuthorization::new_unchecked(auth.clone(), signature.clone());
439 let eager_recovered = RecoveredTempoAuthorization::recover(signed_for_eager);
440 assert_eq!(eager_recovered.authority(), Some(expected_address));
441
442 assert_eq!(eager_recovered.signed().inner(), &auth);
444 assert_eq!(eager_recovered.inner(), &auth);
445 assert_eq!(eager_recovered.signature(), &signature);
446
447 let signed_for_convert = TempoSignedAuthorization::new_unchecked(auth.clone(), signature);
449 let converted = RecoveredTempoAuthorization::new(signed_for_convert);
450 let std_auth = converted.into_recovered_authorization();
451 assert_eq!(std_auth.authority(), Some(expected_address));
452
453 let wrong_hash = B256::random();
455 let wrong_signature = sign_hash(&signing_key, &wrong_hash);
456 let bad_signed = TempoSignedAuthorization::new_unchecked(auth, wrong_signature);
457
458 let recovered = bad_signed.recover_authority();
460 assert!(recovered.is_ok());
461 assert_ne!(recovered.unwrap(), expected_address);
462
463 let bad_lazy = RecoveredTempoAuthorization::new(bad_signed);
465 assert!(bad_lazy.authority().is_some());
466 assert_ne!(bad_lazy.authority().unwrap(), expected_address);
467 }
468}