alloy_eip7702/
auth_list.rs

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