alloy_eip7702/
auth_list.rs

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