alloy_eip7702/
auth_list.rs

1use core::ops::Deref;
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5use alloy_primitives::{keccak256, Address, Signature, SignatureError, B256, U256, U8};
6use alloy_rlp::{
7    length_of_length, BufMut, Decodable, Encodable, Header, Result as RlpResult, RlpDecodable,
8    RlpEncodable,
9};
10use core::hash::{Hash, Hasher};
11
12/// Represents the outcome of an attempt to recover the authority from an authorization.
13/// It can either be valid (containing an [`Address`]) or invalid (indicating recovery failure).
14#[derive(Debug, Clone, Hash, Eq, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum RecoveredAuthority {
17    /// Indicates a successfully recovered authority address.
18    Valid(Address),
19    /// Indicates a failed recovery attempt where no valid address could be recovered.
20    Invalid,
21}
22
23impl RecoveredAuthority {
24    /// Returns an optional address if valid.
25    pub const fn address(&self) -> Option<Address> {
26        match *self {
27            Self::Valid(address) => Some(address),
28            Self::Invalid => None,
29        }
30    }
31
32    /// Returns true if the authority is valid.
33    pub const fn is_valid(&self) -> bool {
34        matches!(self, Self::Valid(_))
35    }
36
37    /// Returns true if the authority is invalid.
38    pub const fn is_invalid(&self) -> bool {
39        matches!(self, Self::Invalid)
40    }
41}
42
43/// An unsigned EIP-7702 authorization.
44#[derive(Debug, Clone, Hash, RlpEncodable, RlpDecodable, Eq, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
47#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
48pub struct Authorization {
49    /// The chain ID of the authorization.
50    pub chain_id: U256,
51    /// The address of the authorization.
52    pub address: Address,
53    /// The nonce for the authorization.
54    #[cfg_attr(feature = "serde", serde(with = "quantity"))]
55    pub nonce: u64,
56}
57
58impl Authorization {
59    /// Get the `chain_id` for the authorization.
60    ///
61    /// # Note
62    ///
63    /// Implementers should check that this matches the current `chain_id` *or* is 0.
64    pub const fn chain_id(&self) -> &U256 {
65        &self.chain_id
66    }
67
68    /// Get the `address` for the authorization.
69    pub const fn address(&self) -> &Address {
70        &self.address
71    }
72
73    /// Get the `nonce` for the authorization.
74    pub const fn nonce(&self) -> u64 {
75        self.nonce
76    }
77
78    /// Computes the signature hash used to sign the authorization, or recover the authority from a
79    /// signed authorization list item.
80    ///
81    /// The signature hash is `keccak(MAGIC || rlp([chain_id, address, nonce]))`
82    #[inline]
83    pub fn signature_hash(&self) -> B256 {
84        use super::constants::MAGIC;
85
86        let mut buf = Vec::new();
87        buf.put_u8(MAGIC);
88        self.encode(&mut buf);
89
90        keccak256(buf)
91    }
92
93    /// Convert to a signed authorization by adding a signature.
94    pub fn into_signed(self, signature: Signature) -> SignedAuthorization {
95        SignedAuthorization {
96            inner: self,
97            r: signature.r(),
98            s: signature.s(),
99            y_parity: U8::from(signature.v()),
100        }
101    }
102}
103
104/// A signed EIP-7702 authorization.
105#[derive(Debug, Clone, Eq, PartialEq)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107pub struct SignedAuthorization {
108    /// Inner authorization.
109    #[cfg_attr(feature = "serde", serde(flatten))]
110    inner: Authorization,
111    /// Signature parity value. We allow any [`U8`] here, however, the only valid values are `0`
112    /// and `1` and anything else will result in error during recovery.
113    #[cfg_attr(feature = "serde", serde(rename = "yParity", alias = "v"))]
114    y_parity: U8,
115    /// Signature `r` value.
116    r: U256,
117    /// Signature `s` value.
118    s: U256,
119}
120
121impl SignedAuthorization {
122    /// Creates a new signed authorization from raw signature values.
123    pub fn new_unchecked(inner: Authorization, y_parity: u8, r: U256, s: U256) -> Self {
124        Self { inner, y_parity: U8::from(y_parity), r, s }
125    }
126
127    /// Gets the `signature` for the authorization. Returns [`SignatureError`] if signature could
128    /// not be constructed from vrs values.
129    ///
130    /// Note that this signature might still be invalid for recovery as it might have `s` value
131    /// greater than [secp256k1n/2](crate::constants::SECP256K1N_HALF).
132    pub fn signature(&self) -> Result<Signature, SignatureError> {
133        if self.y_parity() <= 1 {
134            Ok(Signature::new(self.r, self.s, self.y_parity() == 1))
135        } else {
136            Err(SignatureError::InvalidParity(self.y_parity() as u64))
137        }
138    }
139
140    /// Returns the inner [`Authorization`].
141    pub const fn strip_signature(self) -> Authorization {
142        self.inner
143    }
144
145    /// Returns the inner [`Authorization`].
146    pub const fn inner(&self) -> &Authorization {
147        &self.inner
148    }
149
150    /// Returns the signature parity value.
151    pub fn y_parity(&self) -> u8 {
152        self.y_parity.to()
153    }
154
155    /// Returns the signature `r` value.
156    pub const fn r(&self) -> U256 {
157        self.r
158    }
159
160    /// Returns the signature `s` value.
161    pub const fn s(&self) -> U256 {
162        self.s
163    }
164
165    /// Decodes the transaction from RLP bytes, including the signature.
166    fn decode_fields(buf: &mut &[u8]) -> RlpResult<Self> {
167        Ok(Self {
168            inner: Authorization {
169                chain_id: Decodable::decode(buf)?,
170                address: Decodable::decode(buf)?,
171                nonce: Decodable::decode(buf)?,
172            },
173            y_parity: Decodable::decode(buf)?,
174            r: Decodable::decode(buf)?,
175            s: Decodable::decode(buf)?,
176        })
177    }
178
179    /// Outputs the length of the transaction's fields, without a RLP header.
180    fn fields_len(&self) -> usize {
181        self.inner.chain_id.length()
182            + self.inner.address.length()
183            + self.inner.nonce.length()
184            + self.y_parity.length()
185            + self.r.length()
186            + self.s.length()
187    }
188}
189
190impl Hash for SignedAuthorization {
191    fn hash<H: Hasher>(&self, state: &mut H) {
192        self.inner.hash(state);
193        self.r.hash(state);
194        self.s.hash(state);
195        self.y_parity.hash(state);
196    }
197}
198
199impl Decodable for SignedAuthorization {
200    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
201        let header = Header::decode(buf)?;
202        if !header.list {
203            return Err(alloy_rlp::Error::UnexpectedString);
204        }
205        let started_len = buf.len();
206
207        let this = Self::decode_fields(buf)?;
208
209        let consumed = started_len - buf.len();
210        if consumed != header.payload_length {
211            return Err(alloy_rlp::Error::ListLengthMismatch {
212                expected: header.payload_length,
213                got: consumed,
214            });
215        }
216
217        Ok(this)
218    }
219}
220
221impl Encodable for SignedAuthorization {
222    fn encode(&self, buf: &mut dyn BufMut) {
223        Header { list: true, payload_length: self.fields_len() }.encode(buf);
224        self.inner.chain_id.encode(buf);
225        self.inner.address.encode(buf);
226        self.inner.nonce.encode(buf);
227        self.y_parity.encode(buf);
228        self.r.encode(buf);
229        self.s.encode(buf);
230    }
231
232    fn length(&self) -> usize {
233        let len = self.fields_len();
234        len + length_of_length(len)
235    }
236}
237
238#[cfg(feature = "k256")]
239impl SignedAuthorization {
240    /// Recover the authority for the authorization.
241    ///
242    /// # Note
243    ///
244    /// Implementers should check that the authority has no code.
245    pub fn recover_authority(&self) -> Result<Address, crate::error::Eip7702Error> {
246        let signature = self.signature()?;
247
248        if signature.s() > crate::constants::SECP256K1N_HALF {
249            return Err(crate::error::Eip7702Error::InvalidSValue(signature.s()));
250        }
251
252        Ok(signature.recover_address_from_prehash(&self.inner.signature_hash())?)
253    }
254
255    /// Recover the authority and transform the signed authorization into a
256    /// [`RecoveredAuthorization`].
257    pub fn into_recovered(self) -> RecoveredAuthorization {
258        let authority_result = self.recover_authority();
259        let authority =
260            authority_result.map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid);
261
262        RecoveredAuthorization { inner: self.inner, authority }
263    }
264}
265
266impl Deref for SignedAuthorization {
267    type Target = Authorization;
268
269    fn deref(&self) -> &Self::Target {
270        &self.inner
271    }
272}
273
274#[cfg(all(any(test, feature = "arbitrary"), feature = "k256"))]
275impl<'a> arbitrary::Arbitrary<'a> for SignedAuthorization {
276    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
277        use k256::{
278            ecdsa::{signature::hazmat::PrehashSigner, SigningKey},
279            NonZeroScalar,
280        };
281        use rand::{rngs::StdRng, SeedableRng};
282
283        let rng_seed = u.arbitrary::<[u8; 32]>()?;
284        let mut rand_gen = StdRng::from_seed(rng_seed);
285        let signing_key: SigningKey = NonZeroScalar::random(&mut rand_gen).into();
286
287        let inner = u.arbitrary::<Authorization>()?;
288        let signature_hash = inner.signature_hash();
289
290        let (recoverable_sig, recovery_id) =
291            signing_key.sign_prehash(signature_hash.as_ref()).unwrap();
292        let signature =
293            Signature::from_signature_and_parity(recoverable_sig, recovery_id.is_y_odd());
294
295        Ok(inner.into_signed(signature))
296    }
297}
298
299/// A recovered authorization.
300#[derive(Debug, Clone, Hash, Eq, PartialEq)]
301#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
302pub struct RecoveredAuthorization {
303    #[cfg_attr(feature = "serde", serde(flatten))]
304    inner: Authorization,
305    /// The result of the authority recovery process, which can either be a valid address or
306    /// indicate a failure.
307    authority: RecoveredAuthority,
308}
309
310impl RecoveredAuthorization {
311    /// Instantiate without performing recovery. This should be used carefully.
312    pub const fn new_unchecked(inner: Authorization, authority: RecoveredAuthority) -> Self {
313        Self { inner, authority }
314    }
315
316    /// Returns an optional address based on the current state of the authority.
317    pub const fn authority(&self) -> Option<Address> {
318        self.authority.address()
319    }
320
321    /// Splits the authorization into parts.
322    pub const fn into_parts(self) -> (Authorization, RecoveredAuthority) {
323        (self.inner, self.authority)
324    }
325}
326
327#[cfg(feature = "k256")]
328impl From<SignedAuthorization> for RecoveredAuthority {
329    fn from(value: SignedAuthorization) -> Self {
330        value.into_recovered().authority
331    }
332}
333
334#[cfg(feature = "k256")]
335impl From<SignedAuthorization> for RecoveredAuthorization {
336    fn from(value: SignedAuthorization) -> Self {
337        value.into_recovered()
338    }
339}
340impl Deref for RecoveredAuthorization {
341    type Target = Authorization;
342
343    fn deref(&self) -> &Self::Target {
344        &self.inner
345    }
346}
347
348#[cfg(feature = "serde")]
349mod quantity {
350    use alloy_primitives::U64;
351    use serde::{Deserialize, Deserializer, Serialize, Serializer};
352
353    /// Serializes a primitive number as a "quantity" hex string.
354    pub(crate) fn serialize<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
355    where
356        S: Serializer,
357    {
358        U64::from(*value).serialize(serializer)
359    }
360
361    /// Deserializes a primitive number from a "quantity" hex string.
362    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
363    where
364        D: Deserializer<'de>,
365    {
366        U64::deserialize(deserializer).map(|value| value.to())
367    }
368}
369
370/// Bincode-compatible [`SignedAuthorization`] serde implementation.
371#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
372pub(super) mod serde_bincode_compat {
373    use crate::Authorization;
374    use alloc::borrow::Cow;
375    use alloy_primitives::{U256, U8};
376    use serde::{Deserialize, Deserializer, Serialize, Serializer};
377    use serde_with::{DeserializeAs, SerializeAs};
378
379    /// Bincode-compatible [`super::SignedAuthorization`] serde implementation.
380    ///
381    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
382    /// ```rust
383    /// use alloy_eip7702::{serde_bincode_compat, SignedAuthorization};
384    /// use serde::{Deserialize, Serialize};
385    /// use serde_with::serde_as;
386    ///
387    /// #[serde_as]
388    /// #[derive(Serialize, Deserialize)]
389    /// struct Data {
390    ///     #[serde_as(as = "serde_bincode_compat::SignedAuthorization")]
391    ///     authorization: SignedAuthorization,
392    /// }
393    /// ```
394    #[derive(Debug, Serialize, Deserialize)]
395    pub struct SignedAuthorization<'a> {
396        inner: Cow<'a, Authorization>,
397        #[serde(rename = "yParity")]
398        y_parity: U8,
399        r: U256,
400        s: U256,
401    }
402
403    impl<'a> From<&'a super::SignedAuthorization> for SignedAuthorization<'a> {
404        fn from(value: &'a super::SignedAuthorization) -> Self {
405            Self {
406                inner: Cow::Borrowed(&value.inner),
407                y_parity: value.y_parity,
408                r: value.r,
409                s: value.s,
410            }
411        }
412    }
413
414    impl<'a> From<SignedAuthorization<'a>> for super::SignedAuthorization {
415        fn from(value: SignedAuthorization<'a>) -> Self {
416            Self {
417                inner: value.inner.into_owned(),
418                y_parity: value.y_parity,
419                r: value.r,
420                s: value.s,
421            }
422        }
423    }
424
425    impl SerializeAs<super::SignedAuthorization> for SignedAuthorization<'_> {
426        fn serialize_as<S>(
427            source: &super::SignedAuthorization,
428            serializer: S,
429        ) -> Result<S::Ok, S::Error>
430        where
431            S: Serializer,
432        {
433            SignedAuthorization::from(source).serialize(serializer)
434        }
435    }
436
437    impl<'de> DeserializeAs<'de, super::SignedAuthorization> for SignedAuthorization<'de> {
438        fn deserialize_as<D>(deserializer: D) -> Result<super::SignedAuthorization, D::Error>
439        where
440            D: Deserializer<'de>,
441        {
442            SignedAuthorization::deserialize(deserializer).map(Into::into)
443        }
444    }
445
446    #[cfg(all(test, feature = "k256"))]
447    mod tests {
448        use arbitrary::Arbitrary;
449        use rand::Rng;
450        use serde::{Deserialize, Serialize};
451        use serde_with::serde_as;
452
453        use super::super::{serde_bincode_compat, SignedAuthorization};
454
455        #[test]
456        fn test_signed_authorization_bincode_roundtrip() {
457            #[serde_as]
458            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
459            struct Data {
460                #[serde_as(as = "serde_bincode_compat::SignedAuthorization")]
461                authorization: SignedAuthorization,
462            }
463
464            let mut bytes = [0u8; 1024];
465            rand::thread_rng().fill(bytes.as_mut_slice());
466            let data = Data {
467                authorization: SignedAuthorization::arbitrary(&mut arbitrary::Unstructured::new(
468                    &bytes,
469                ))
470                .unwrap(),
471            };
472
473            let encoded = bincode::serialize(&data).unwrap();
474            let decoded: Data = bincode::deserialize(&encoded).unwrap();
475            assert_eq!(decoded, data);
476        }
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483    use alloy_primitives::hex;
484    use core::str::FromStr;
485
486    fn test_encode_decode_roundtrip(auth: Authorization) {
487        let mut buf = Vec::new();
488        auth.encode(&mut buf);
489        let decoded = Authorization::decode(&mut buf.as_ref()).unwrap();
490        assert_eq!(buf.len(), auth.length());
491        assert_eq!(decoded, auth);
492    }
493
494    #[test]
495    fn test_encode_decode_auth() {
496        // fully filled
497        test_encode_decode_roundtrip(Authorization {
498            chain_id: U256::from(1),
499            address: Address::left_padding_from(&[6]),
500            nonce: 1,
501        });
502    }
503
504    #[test]
505    fn test_encode_decode_signed_auth() {
506        let auth = Authorization {
507            chain_id: U256::from(1),
508            address: Address::left_padding_from(&[6]),
509            nonce: 1,
510        };
511
512        let auth = auth.into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap());
513        let mut buf = Vec::new();
514        auth.encode(&mut buf);
515
516        let expected = "f85a019400000000000000000000000000000000000000060180a048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804";
517        assert_eq!(hex::encode(&buf), expected);
518
519        let decoded = SignedAuthorization::decode(&mut buf.as_ref()).unwrap();
520        assert_eq!(buf.len(), auth.length());
521        assert_eq!(decoded, auth);
522    }
523
524    #[cfg(feature = "serde")]
525    #[test]
526    fn test_auth_json() {
527        let sig = r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","yParity":"0x1"}"#;
528        let auth = Authorization {
529            chain_id: U256::from(1),
530            address: Address::left_padding_from(&[6]),
531            nonce: 1,
532        }
533        .into_signed(serde_json::from_str(sig).unwrap());
534        let val = serde_json::to_string(&auth).unwrap();
535        let s = r#"{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","yParity":"0x1","r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05"}"#;
536        assert_eq!(val, s);
537    }
538
539    #[cfg(all(feature = "arbitrary", feature = "k256"))]
540    #[test]
541    fn test_arbitrary_auth() {
542        use arbitrary::Arbitrary;
543        let mut unstructured = arbitrary::Unstructured::new(b"unstructured auth");
544        // try this multiple times
545        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
546        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
547        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
548        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
549    }
550}