dcl_crypto/
chain.rs

1use std::ops::Deref;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::Error as SerdeError;
5
6use crate::{account::{Address, EIP1271Signature, PersonalSignature, EphemeralPayload, PERSONAL_SIGNATURE_SIZE, DecodeHexError}};
7
8
9static SIGNER: &str = "SIGNER";
10static ECDSA_EPHEMERAL: &str = "ECDSA_EPHEMERAL";
11static ECDSA_SIGNED_ENTITY: &str = "ECDSA_SIGNED_ENTITY";
12static ECDSA_EIP_1654_EPHEMERAL: &str = "ECDSA_EIP_1654_EPHEMERAL";
13static ECDSA_EIP_1654_SIGNED_ENTITY: &str = "ECDSA_EIP_1654_SIGNED_ENTITY";
14
15/// Representation of each link on an auth chain
16///
17/// ```rust
18/// use dcl_crypto::account::Address;
19/// use dcl_crypto::chain::AuthLink;
20///
21/// let signer = AuthLink::parse(r#"{"type": "SIGNER","payload": "0x3f17f1962b36e491b30a40b2405849e597ba5fb5","signature": ""}"#).unwrap();
22/// let expected = AuthLink::signer(Address::try_from("0x3f17f1962b36e491b30a40b2405849e597ba5fb5").unwrap());
23/// assert_eq!(signer, expected);
24/// ```
25///
26/// ```rust
27/// use dcl_crypto::account::{Address, PersonalSignature, EphemeralPayload};
28/// use dcl_crypto::chain::AuthLink;
29///
30/// let personal_ephemeral = AuthLink::parse(r#"{"type":"ECDSA_EPHEMERAL","payload":"Decentraland Login\nEphemeral address: 0x612f2657CE738799056051aB09926cE806CcDa0E\nExpiration: 2023-05-02T23:06:21.135Z","signature":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5"}"#).unwrap();
31/// let expected = AuthLink::EcdsaPersonalEphemeral{
32///     payload: EphemeralPayload::try_from("Decentraland Login\nEphemeral address: 0x612f2657CE738799056051aB09926cE806CcDa0E\nExpiration: 2023-05-02T23:06:21.135Z").unwrap(),
33///     signature: PersonalSignature::try_from("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5").unwrap(),
34/// };
35/// assert_eq!(personal_ephemeral, expected);
36/// ```
37#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
38#[serde(tag = "type")]
39pub enum AuthLink {
40    #[serde(rename = "SIGNER")]
41    Signer{
42        payload: Address,
43        signature: String,
44    },
45
46    #[serde(rename = "ECDSA_EPHEMERAL")]
47    EcdsaPersonalEphemeral {
48        payload: EphemeralPayload,
49        signature: PersonalSignature,
50    },
51
52    #[serde(rename = "ECDSA_SIGNED_ENTITY")]
53    EcdsaPersonalSignedEntity {
54        payload: String,
55        signature: PersonalSignature,
56    },
57
58    /// See <https://github.com/ethereum/EIPs/issues/1654>
59    /// See <https://eips.ethereum.org/EIPS/eip-1271>
60    #[serde(rename = "ECDSA_EIP_1654_EPHEMERAL")]
61    EcdsaEip1654Ephemeral {
62        payload: EphemeralPayload,
63        signature: EIP1271Signature,
64    },
65
66    /// See <https://github.com/ethereum/EIPs/issues/1654>
67    /// See <https://eips.ethereum.org/EIPS/eip-1271>
68    #[serde(rename = "ECDSA_EIP_1654_SIGNED_ENTITY")]
69    EcdsaEip1654SignedEntity {
70        payload: String,
71        signature: EIP1271Signature,
72    },
73}
74
75impl AuthLink {
76    pub fn kind(&self) -> &str {
77        match self {
78            AuthLink::Signer{ .. } => SIGNER,
79            AuthLink::EcdsaPersonalEphemeral { .. } => ECDSA_EPHEMERAL,
80            AuthLink::EcdsaPersonalSignedEntity { .. } => ECDSA_SIGNED_ENTITY,
81            AuthLink::EcdsaEip1654Ephemeral { .. } => ECDSA_EIP_1654_EPHEMERAL,
82            AuthLink::EcdsaEip1654SignedEntity { .. } => ECDSA_EIP_1654_SIGNED_ENTITY,
83        }
84    }
85
86    pub fn parse(value: &str) -> Result<AuthLink, SerdeError> {
87        serde_json::from_str::<AuthLink>(value)
88    }
89
90    pub fn signer(payload: Address) -> Self {
91        AuthLink::Signer{ payload, signature: String::with_capacity(0) }
92    }
93}
94
95#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
96#[serde(transparent)]
97pub struct AuthChain(Vec<AuthLink>);
98
99impl Deref for AuthChain {
100    type Target = Vec<AuthLink>;
101
102    fn deref(&self) -> &Self::Target {
103        &self.0
104    }
105}
106
107impl From<Vec<AuthLink>> for AuthChain {
108    fn from(links: Vec<AuthLink>) -> Self {
109        AuthChain(links)
110    }
111}
112
113impl AuthChain {
114
115
116    /// Returns the original owner of the chain
117    ///
118    /// ```rust
119    /// use dcl_crypto::chain::AuthChain;
120    /// use dcl_crypto::account::Address;
121    ///
122    /// let address = Address::try_from("0x4A1b9FD363dE915145008C41FA217377B2C223F2").unwrap();
123    /// let payload = "QmUsqJaHc5HQaBrojhBdjF4fr5MQc6CqhwZjqwhVRftNAo";
124    /// let signature = "0xb962b57accc8e12083769339888f82752d13f280012b2c7b2aa2722eae103aea7a623dc88605bf7036ec8c23b0bb8f036b52f5e4e30ee913f6f2a077d5e5e3e01b";
125    ///
126    /// let chain = AuthChain::simple(address, payload, signature).unwrap();
127    /// let owner = chain.owner().unwrap();
128    /// assert_eq!(owner, &Address::try_from("0x4A1b9FD363dE915145008C41FA217377B2C223F2").unwrap());
129    /// ```
130    ///
131    /// ```rust
132    /// use dcl_crypto::chain::AuthChain;
133    /// use dcl_crypto::account::Address;
134    ///
135    /// let address = Address::try_from("0x8C889222833F961FC991B31d15e25738c6732930").unwrap();
136    /// let payload = "QmUsqJaHc5HQaBrojhBdjF4fr5MQc6CqhwZjqwhVRftNAo";
137    /// let signature = "0x00050203596af90cecdbf9a768886e771178fd5561dd27ab005d000100018d7c77aaeb3a8529951423128fa5b807192c27c4ce5af76bbf73ffc84eecd48c4a004c4b4db03cf04b269ad0f1fcf26c31b859cb0a693eeb3b8efd89dc9e3bea1b020101c50adeadb7fe15bee45dcb820610cdedcd314eb0030102640dccefda3685e6c0dbeb70c1cf8018c27077eb00020af67e80c0f311fddd79d3163c8ee840863a5a1eb3d236b10b8b6972164164236b32e13443e3cfe1be591534cb93607cf6e49e48e51f012e806c158819fa7b471c020103d9e87370ededc599df3bf9dd0e48586005f1a1bb";
138    ///
139    /// let chain = AuthChain::simple(address, payload, signature).unwrap();
140    /// let owner = chain.owner().unwrap();
141    /// assert_eq!(owner, &Address::try_from("0x8C889222833F961FC991B31d15e25738c6732930").unwrap());
142    /// ```
143    pub fn simple<P, T>(signer: Address, payload: P, signature: T) -> Result<Self, DecodeHexError> where P: AsRef<str>, T: AsRef<str> {
144        let signature = signature.as_ref();
145        let payload = payload.as_ref().to_string();
146        let entity = if signature.len() == (PERSONAL_SIGNATURE_SIZE * 2) + 2 {
147            let signature = PersonalSignature::try_from(signature)?;
148            AuthLink::EcdsaPersonalSignedEntity { payload, signature }
149        } else {
150            let signature = EIP1271Signature::try_from(signature)?;
151            AuthLink::EcdsaEip1654SignedEntity { payload, signature }
152        };
153
154        Ok(AuthChain::from(vec![
155            AuthLink::signer(signer),
156            entity
157        ]))
158    }
159
160    /// Parse a json string and returns an AuthChain
161    ///
162    /// ```rust
163    /// use dcl_crypto::chain::AuthChain;
164    /// use dcl_crypto::account::Address;
165    ///
166    /// let chain = AuthChain::from_json(r#"[
167    ///       {
168    ///        "type": "SIGNER",
169    ///        "payload": "0x3f17f1962b36e491b30a40b2405849e597ba5fb5",
170    ///        "signature": ""
171    ///      },
172    ///      {
173    ///        "type": "ECDSA_EPHEMERAL",
174    ///        "payload": "Decentraland Login\nEphemeral address: 0x612f2657CE738799056051aB09926cE806CcDa0E\nExpiration: 2023-05-02T23:06:21.135Z",
175    ///        "signature": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5"
176    ///      },
177    ///      {
178    ///        "type": "ECDSA_SIGNED_ENTITY",
179    ///        "payload": "signed message",
180    ///        "signature": "0x6168f285b5f905510de86c042c08fea79a66fff86abdf9ba4d374d0a6680ffc52ac251e36430a208d2369692797b3b049164b4b68d7519f50c8e5022e100837c1c"
181    ///      }
182    ///    ]"#).unwrap();
183    /// ```
184    ///
185    /// let owner = chain.owner().unwrap();
186    /// assert_eq!(owner, &Address::try_from("0x3f17f1962b36e491b30a40b2405849e597ba5fb5").unwrap());
187    pub fn from_json<V>(value: V) -> Result<AuthChain, SerdeError> where V: AsRef<str> {
188        serde_json::from_str::<AuthChain>(value.as_ref())
189    }
190
191    /// Parse a list of json strings and returns an AuthChain
192    ///
193    /// ```rust
194    /// use dcl_crypto::account::Address;
195    /// use dcl_crypto::chain::AuthChain;
196    ///
197    /// let chain = AuthChain::from_json_links(vec![
198    ///      r#"{
199    ///        "type": "SIGNER",
200    ///        "payload": "0x3f17f1962b36e491b30a40b2405849e597ba5fb5",
201    ///        "signature": ""
202    ///      }"#,
203    ///     r#"{
204    ///        "type": "ECDSA_EPHEMERAL",
205    ///        "payload": "Decentraland Login\nEphemeral address: 0x612f2657CE738799056051aB09926cE806CcDa0E\nExpiration: 2023-05-02T23:06:21.135Z",
206    ///        "signature": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5"
207    ///      }"#,
208    ///     r#"{
209    ///        "type": "ECDSA_SIGNED_ENTITY",
210    ///        "payload": "signed message",
211    ///        "signature": "0x6168f285b5f905510de86c042c08fea79a66fff86abdf9ba4d374d0a6680ffc52ac251e36430a208d2369692797b3b049164b4b68d7519f50c8e5022e100837c1c"
212    ///      }"#]).unwrap();
213    ///
214    /// let owner = chain.owner().unwrap();
215    /// assert_eq!(owner, &Address::try_from("0x3f17f1962b36e491b30a40b2405849e597ba5fb5").unwrap());
216    /// ```
217    pub fn from_json_links(value: Vec<&str>) -> Result<AuthChain, SerdeError> {
218        let links = value
219            .iter()
220            .map(|link| {
221                let link = serde_json::from_str::<AuthLink>(link)?;
222                Ok(link)
223            })
224            .collect::<Result<Vec<AuthLink>, SerdeError>>()?;
225
226        Ok(AuthChain::from(links))
227    }
228
229    /// Returns the original owner of the chain
230    ///
231    /// ```rust
232    /// use dcl_crypto::chain::AuthChain;
233    /// use dcl_crypto::account::Address;
234    ///
235    /// let chain = AuthChain::from_json(r#"[
236    ///     { "type": "SIGNER", "payload": "0x84452bbfa4ca14b7828e2f3bbd106a2bd495cd34", "signature": ""},
237    ///     { "type": "ECDSA_SIGNED_ENTITY", "payload": "signed message", "signature": "0x013e0b0b75bd8404d70a37d96bb893596814d8f29f517e383d9d1421111f83c32d4ca0d6e399349c7badd54261feaa39895d027880d28d806c01089677400b7c1b"}
238    /// ]"#).unwrap();
239    ///
240    /// let owner = chain.owner().unwrap();
241    /// assert_eq!(owner, &Address::try_from("0x84452bbfa4ca14b7828e2f3bbd106a2bd495cd34").unwrap());
242    /// ```
243    pub fn owner(&self) -> Option<&Address> {
244        match (*self).first() {
245            Some(AuthLink::Signer{ payload, .. }) => Some(payload),
246            _ => None,
247        }
248    }
249
250    pub fn is_expired(&self) -> bool {
251        let now = &Utc::now();
252        self.iter().any(|link| match link {
253            AuthLink::EcdsaPersonalEphemeral { payload, .. } => payload.is_expired_at(now),
254            AuthLink::EcdsaEip1654Ephemeral { payload, .. } => payload.is_expired_at(now),
255            _ => false,
256        })
257    }
258
259    pub fn is_expired_at(&self, time: &DateTime<Utc>) -> bool {
260        self.iter().any(|link| match link {
261            AuthLink::EcdsaPersonalEphemeral { payload, .. } => payload.is_expired_at(time),
262            AuthLink::EcdsaEip1654Ephemeral { payload, .. } => payload.is_expired_at(time),
263            _ => false,
264        })
265    }
266}