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}