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))]
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#[cfg(feature = "serde")]
300impl<'de> serde::Deserialize<'de> for SignedAuthorization {
301    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
302    where
303        D: serde::de::Deserializer<'de>,
304    {
305        #[derive(serde::Deserialize)]
306        struct Helper {
307            #[cfg_attr(feature = "serde", serde(flatten))]
308            inner: Authorization,
309            r: U256,
310            s: U256,
311            #[serde(rename = "yParity")]
312            y_parity: Option<U8>,
313            v: Option<U8>,
314        }
315
316        let Helper { inner, r, s, y_parity, v } = Helper::deserialize(deserializer)?;
317
318        // Attempt to deserialize `yParity` or `v` value, preferring the former.
319        let y_parity =
320            y_parity.or(v).ok_or_else(|| serde::de::Error::custom("missing `yParity` or `v`"))?;
321
322        Ok(Self { inner, r, s, y_parity })
323    }
324}
325
326/// A recovered authorization.
327#[derive(Debug, Clone, Hash, Eq, PartialEq)]
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329pub struct RecoveredAuthorization {
330    #[cfg_attr(feature = "serde", serde(flatten))]
331    inner: Authorization,
332    /// The result of the authority recovery process, which can either be a valid address or
333    /// indicate a failure.
334    authority: RecoveredAuthority,
335}
336
337impl RecoveredAuthorization {
338    /// Instantiate without performing recovery. This should be used carefully.
339    pub const fn new_unchecked(inner: Authorization, authority: RecoveredAuthority) -> Self {
340        Self { inner, authority }
341    }
342
343    /// Returns an optional address based on the current state of the authority.
344    pub const fn authority(&self) -> Option<Address> {
345        self.authority.address()
346    }
347
348    /// Splits the authorization into parts.
349    pub const fn into_parts(self) -> (Authorization, RecoveredAuthority) {
350        (self.inner, self.authority)
351    }
352}
353
354#[cfg(feature = "k256")]
355impl From<SignedAuthorization> for RecoveredAuthority {
356    fn from(value: SignedAuthorization) -> Self {
357        value.into_recovered().authority
358    }
359}
360
361#[cfg(feature = "k256")]
362impl From<SignedAuthorization> for RecoveredAuthorization {
363    fn from(value: SignedAuthorization) -> Self {
364        value.into_recovered()
365    }
366}
367impl Deref for RecoveredAuthorization {
368    type Target = Authorization;
369
370    fn deref(&self) -> &Self::Target {
371        &self.inner
372    }
373}
374
375#[cfg(feature = "serde")]
376mod quantity {
377    use alloy_primitives::U64;
378    use serde::{Deserialize, Deserializer, Serialize, Serializer};
379
380    /// Serializes a primitive number as a "quantity" hex string.
381    pub(crate) fn serialize<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
382    where
383        S: Serializer,
384    {
385        U64::from(*value).serialize(serializer)
386    }
387
388    /// Deserializes a primitive number from a "quantity" hex string.
389    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
390    where
391        D: Deserializer<'de>,
392    {
393        U64::deserialize(deserializer).map(|value| value.to())
394    }
395}
396
397/// Bincode-compatible [`SignedAuthorization`] serde implementation.
398#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
399pub(super) mod serde_bincode_compat {
400    use crate::Authorization;
401    use alloc::borrow::Cow;
402    use alloy_primitives::{U256, U8};
403    use serde::{Deserialize, Deserializer, Serialize, Serializer};
404    use serde_with::{DeserializeAs, SerializeAs};
405
406    /// Bincode-compatible [`super::SignedAuthorization`] serde implementation.
407    ///
408    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
409    /// ```rust
410    /// use alloy_eip7702::{serde_bincode_compat, SignedAuthorization};
411    /// use serde::{Deserialize, Serialize};
412    /// use serde_with::serde_as;
413    ///
414    /// #[serde_as]
415    /// #[derive(Serialize, Deserialize)]
416    /// struct Data {
417    ///     #[serde_as(as = "serde_bincode_compat::SignedAuthorization")]
418    ///     authorization: SignedAuthorization,
419    /// }
420    /// ```
421    #[derive(Debug, Serialize, Deserialize)]
422    pub struct SignedAuthorization<'a> {
423        inner: Cow<'a, Authorization>,
424        #[serde(rename = "yParity")]
425        y_parity: U8,
426        r: U256,
427        s: U256,
428    }
429
430    impl<'a> From<&'a super::SignedAuthorization> for SignedAuthorization<'a> {
431        fn from(value: &'a super::SignedAuthorization) -> Self {
432            Self {
433                inner: Cow::Borrowed(&value.inner),
434                y_parity: value.y_parity,
435                r: value.r,
436                s: value.s,
437            }
438        }
439    }
440
441    impl<'a> From<SignedAuthorization<'a>> for super::SignedAuthorization {
442        fn from(value: SignedAuthorization<'a>) -> Self {
443            Self {
444                inner: value.inner.into_owned(),
445                y_parity: value.y_parity,
446                r: value.r,
447                s: value.s,
448            }
449        }
450    }
451
452    impl SerializeAs<super::SignedAuthorization> for SignedAuthorization<'_> {
453        fn serialize_as<S>(
454            source: &super::SignedAuthorization,
455            serializer: S,
456        ) -> Result<S::Ok, S::Error>
457        where
458            S: Serializer,
459        {
460            SignedAuthorization::from(source).serialize(serializer)
461        }
462    }
463
464    impl<'de> DeserializeAs<'de, super::SignedAuthorization> for SignedAuthorization<'de> {
465        fn deserialize_as<D>(deserializer: D) -> Result<super::SignedAuthorization, D::Error>
466        where
467            D: Deserializer<'de>,
468        {
469            SignedAuthorization::deserialize(deserializer).map(Into::into)
470        }
471    }
472
473    #[cfg(all(test, feature = "k256"))]
474    mod tests {
475        use arbitrary::Arbitrary;
476        use rand::Rng;
477        use serde::{Deserialize, Serialize};
478        use serde_with::serde_as;
479
480        use super::super::{serde_bincode_compat, SignedAuthorization};
481
482        #[test]
483        fn test_signed_authorization_bincode_roundtrip() {
484            #[serde_as]
485            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
486            struct Data {
487                #[serde_as(as = "serde_bincode_compat::SignedAuthorization")]
488                authorization: SignedAuthorization,
489            }
490
491            let mut bytes = [0u8; 1024];
492            rand::thread_rng().fill(bytes.as_mut_slice());
493            let data = Data {
494                authorization: SignedAuthorization::arbitrary(&mut arbitrary::Unstructured::new(
495                    &bytes,
496                ))
497                .unwrap(),
498            };
499
500            let encoded = bincode::serialize(&data).unwrap();
501            let decoded: Data = bincode::deserialize(&encoded).unwrap();
502            assert_eq!(decoded, data);
503        }
504    }
505}
506
507#[cfg(test)]
508mod tests {
509    use super::*;
510    use alloy_primitives::hex;
511    use core::str::FromStr;
512
513    fn test_encode_decode_roundtrip(auth: Authorization) {
514        let mut buf = Vec::new();
515        auth.encode(&mut buf);
516        let decoded = Authorization::decode(&mut buf.as_ref()).unwrap();
517        assert_eq!(buf.len(), auth.length());
518        assert_eq!(decoded, auth);
519    }
520
521    #[test]
522    fn test_encode_decode_auth() {
523        // fully filled
524        test_encode_decode_roundtrip(Authorization {
525            chain_id: U256::from(1),
526            address: Address::left_padding_from(&[6]),
527            nonce: 1,
528        });
529    }
530
531    #[test]
532    fn test_encode_decode_signed_auth() {
533        let auth = Authorization {
534            chain_id: U256::from(1),
535            address: Address::left_padding_from(&[6]),
536            nonce: 1,
537        };
538
539        let auth = auth.into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap());
540        let mut buf = Vec::new();
541        auth.encode(&mut buf);
542
543        let expected = "f85a019400000000000000000000000000000000000000060180a048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804";
544        assert_eq!(hex::encode(&buf), expected);
545
546        let decoded = SignedAuthorization::decode(&mut buf.as_ref()).unwrap();
547        assert_eq!(buf.len(), auth.length());
548        assert_eq!(decoded, auth);
549    }
550
551    #[cfg(feature = "serde")]
552    #[test]
553    fn test_auth_json() {
554        let sig = r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","yParity":"0x1"}"#;
555        let auth = Authorization {
556            chain_id: U256::from(1),
557            address: Address::left_padding_from(&[6]),
558            nonce: 1,
559        }
560        .into_signed(serde_json::from_str(sig).unwrap());
561        let val = serde_json::to_string(&auth).unwrap();
562        let s = r#"{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","yParity":"0x1","r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05"}"#;
563        assert_eq!(val, s);
564    }
565
566    #[cfg(all(feature = "arbitrary", feature = "k256"))]
567    #[test]
568    fn test_arbitrary_auth() {
569        use arbitrary::Arbitrary;
570        let mut unstructured = arbitrary::Unstructured::new(b"unstructured auth");
571        // try this multiple times
572        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
573        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
574        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
575        let _auth = SignedAuthorization::arbitrary(&mut unstructured).unwrap();
576    }
577
578    #[test]
579    #[cfg(feature = "serde")]
580    fn deserde_signed_auth_with_duplicate_fields() {
581        let s = r#"{
582                    "chainId": "0x2105",
583                    "address": "0x000000004F43C49e93C970E84001853a70923B03",
584                    "nonce": "0x0",
585                    "r": "0xb3fdb76993ec6787313ab8b54129200032dfb9ce683fa9f7693129421e6a3185",
586                    "s": "0x210b3350107a5687b532a346a90e7cc9a799b995743e2b79698bedba7bd779ae",
587                    "v": "0x1b",
588                    "yParity": "0x0"
589                }"#;
590
591        let auth: SignedAuthorization = serde_json::from_str(s).unwrap();
592        assert!(auth.y_parity.is_zero());
593    }
594}