dcl_crypto/
identity.rs

1use serde::{Serialize, Deserialize};
2use crate::{AuthLink, AuthChain};
3use crate::account::{Account, Expiration, Signer, EphemeralPayload, PersonalSignature};
4
5/// An `Identity` is an abstraction where an Account that you don't control delegates the
6/// ability to sign messages to a new address (encapsulated in the `Identity`) for a limited
7/// amount of time using a signature.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Identity {
11    ephemeral_identity: Account,
12    expiration: Expiration,
13    auth_chain: Vec<AuthLink>,
14}
15
16/// Implements `Signer` trait for `Identity`
17impl Signer for Identity {
18
19    /// Returns the address of the ephemeral identity
20    fn address(&self) -> crate::Address {
21        self.ephemeral_identity.address()
22    }
23
24    /// Signs the given message with the ephemeral identity
25    fn sign<M: AsRef<[u8]>>(&self, message: M) -> crate::account::PersonalSignature {
26        self.ephemeral_identity.sign(message)
27    }
28}
29
30impl Identity {
31
32    /// Creates a new Identity from the given JSON
33    ///
34    /// ```rust
35    /// use dcl_crypto::Identity;
36    ///
37    /// let identity = Identity::from_json(r#"{
38    ///   "ephemeralIdentity": {
39    ///     "address": "0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34",
40    ///     "publicKey": "0x0420c548d960b06dac035d1daf826472eded46b8b9d123294f1199c56fa235c89f2515158b1e3be0874bfb15b42d1551db8c276787a654d0b8d7b4d4356e70fe42",
41    ///     "privateKey": "0xbc453a92d9baeb3d10294cbc1d48ef6738f718fd31b4eb8085efe7b311299399"
42    ///   },
43    ///   "expiration": "3021-10-16T22:32:29.626Z",
44    ///   "authChain": [
45    ///     {
46    ///       "type": "SIGNER",
47    ///       "payload": "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5",
48    ///       "signature": ""
49    ///     },
50    ///     {
51    ///       "type": "ECDSA_EPHEMERAL",
52    ///       "payload": "Decentraland Login\nEphemeral address: 0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34\nExpiration: 3021-10-16T22:32:29.626Z",
53    ///       "signature": "0x39dd4ddf131ad2435d56c81c994c4417daef5cf5998258027ef8a1401470876a1365a6b79810dc0c4a2e9352befb63a9e4701d67b38007d83ffc4cd2b7a38ad51b"
54    ///     }
55    ///   ]
56    ///  }"#);
57    ///
58    /// assert!(identity.is_ok());
59    /// ```
60    pub fn from_json<J: AsRef<str>>(json: J) -> Result<Self, serde_json::Error> {
61        serde_json::from_str::<Identity>(json.as_ref())
62    }
63
64    /// Creates a new Identity from the given Signer
65    ///
66    /// ```rust
67    /// use dcl_crypto::{Identity, Account, Signer, Expiration};
68    ///
69    /// let signer = Account::random();
70    /// let identity = Identity::from_signer(&signer, Expiration::try_from("3021-10-16T22:32:29.626Z").unwrap());
71    /// let chain = identity.sign_payload("Hello World");
72    ///
73    /// assert_eq!(chain.owner(), Some(&signer.address()));
74    /// assert_eq!(chain.len(), 3);
75    /// ```
76    pub fn from_signer<S: Signer, E: Into<Expiration>>(signer: &S, exp: E) -> Self {
77        let ephemeral_identity = Account::random();
78        let expiration = exp.into();
79
80        let payload = EphemeralPayload::new(ephemeral_identity.address(), expiration);
81        let signature = signer.sign(payload.to_string());
82
83        Self {
84            ephemeral_identity,
85            expiration,
86            auth_chain: vec![
87                AuthLink::signer(signer.address()),
88                AuthLink::EcdsaPersonalEphemeral{ payload, signature }
89            ],
90        }
91    }
92
93    /// Creates a new Identity extended from a given Identity
94    ///
95    /// ```rust
96    /// use dcl_crypto::{Identity, Account, Signer, Expiration};
97    ///
98    /// let signer = Account::random();
99    /// let identity1 = Identity::from_signer(&signer, Expiration::try_from("3021-10-16T22:32:29.626Z").unwrap());
100    /// let identity2 = Identity::from_identity(&identity1, Expiration::try_from("3021-10-16T22:32:29.626Z").unwrap());
101    /// let chain = identity2.sign_payload("Hello World");
102    ///
103    /// assert_eq!(chain.owner(), Some(&signer.address()));
104    /// assert_eq!(chain.len(), 4);
105    /// ```
106    pub fn from_identity<E: Into<Expiration>>(identity: &Identity, exp: E) -> Self {
107        let ephemeral_identity = Account::random();
108        let mut expiration = exp.into();
109        if identity.expiration < expiration {
110            expiration = identity.expiration;
111        }
112
113        let payload = EphemeralPayload::new(ephemeral_identity.address(), expiration);
114        let signature = identity.sign(payload.to_string());
115        let mut auth_chain: Vec<AuthLink> = identity.auth_chain.clone();
116        auth_chain.push(AuthLink::EcdsaPersonalEphemeral{ payload, signature });
117
118        Self {
119            ephemeral_identity,
120            expiration,
121            auth_chain
122        }
123    }
124
125    /// Creates a PersonalSignature for the given payload
126    ///
127    /// ```rust
128    /// use dcl_crypto::Identity;
129    ///
130    /// let identity = Identity::from_json(r#"{
131    ///   "ephemeralIdentity": {
132    ///     "address": "0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34",
133    ///     "publicKey": "0x0420c548d960b06dac035d1daf826472eded46b8b9d123294f1199c56fa235c89f2515158b1e3be0874bfb15b42d1551db8c276787a654d0b8d7b4d4356e70fe42",
134    ///     "privateKey": "0xbc453a92d9baeb3d10294cbc1d48ef6738f718fd31b4eb8085efe7b311299399"
135    ///   },
136    ///   "expiration": "3021-10-16T22:32:29.626Z",
137    ///   "authChain": [
138    ///     {
139    ///       "type": "SIGNER",
140    ///       "payload": "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5",
141    ///       "signature": ""
142    ///     },
143    ///     {
144    ///       "type": "ECDSA_EPHEMERAL",
145    ///       "payload": "Decentraland Login\nEphemeral address: 0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34\nExpiration: 3021-10-16T22:32:29.626Z",
146    ///       "signature": "0x39dd4ddf131ad2435d56c81c994c4417daef5cf5998258027ef8a1401470876a1365a6b79810dc0c4a2e9352befb63a9e4701d67b38007d83ffc4cd2b7a38ad51b"
147    ///     }
148    ///   ]
149    ///  }"#).unwrap();
150    ///
151    /// assert_eq!(
152    ///     identity.create_signature("Hello World!").to_string(),
153    ///     "0x33e6b0f71b69d9dca7e25fa279cd70e2e9a44d0c11ab973a936a5b67ba3dbb6554ec2ea5cb4112b2f051dd95b47874c40b8bb7443a0bc4f16c67e2017e0dcb0c1c"
154    /// );
155    /// ```
156    pub fn create_signature<M: AsRef<str>>(&self, payload: M) -> PersonalSignature {
157        self.ephemeral_identity.sign(payload.as_ref())
158    }
159
160    /// Creates an AuthChain signing the the given payload
161    ///
162    /// ```rust
163    /// use dcl_crypto::{Identity, Authenticator, Signer, Account, Expiration};
164    ///
165    /// # tokio_test::block_on(async {
166    ///     let signer = Account::random();
167    ///     let identity = Identity::from_signer(&signer, Expiration::try_from("3021-10-16T22:32:29.626Z").unwrap());
168    ///     let chain = identity.sign_payload("Hello World!");
169    ///     let address = Authenticator::new().verify_signature(&chain, "Hello World!").await.unwrap();
170    ///     assert_eq!(address, &signer.address());
171    /// })
172    /// ```
173    ///
174    /// ```rust
175    /// use dcl_crypto::{Identity, Authenticator, Signer, AuthChain};
176    ///
177    /// let identity = Identity::from_json(r#"{
178    ///   "ephemeralIdentity": {
179    ///     "address": "0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34",
180    ///     "publicKey": "0x0420c548d960b06dac035d1daf826472eded46b8b9d123294f1199c56fa235c89f2515158b1e3be0874bfb15b42d1551db8c276787a654d0b8d7b4d4356e70fe42",
181    ///     "privateKey": "0xbc453a92d9baeb3d10294cbc1d48ef6738f718fd31b4eb8085efe7b311299399"
182    ///   },
183    ///   "expiration": "3021-10-16T22:32:29.626Z",
184    ///   "authChain": [
185    ///     {
186    ///       "type": "SIGNER",
187    ///       "payload": "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5",
188    ///       "signature": ""
189    ///     },
190    ///     {
191    ///       "type": "ECDSA_EPHEMERAL",
192    ///       "payload": "Decentraland Login\nEphemeral address: 0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34\nExpiration: 3021-10-16T22:32:29.626Z",
193    ///       "signature": "0x39dd4ddf131ad2435d56c81c994c4417daef5cf5998258027ef8a1401470876a1365a6b79810dc0c4a2e9352befb63a9e4701d67b38007d83ffc4cd2b7a38ad51b"
194    ///     }
195    ///   ]
196    ///  }"#).unwrap();
197    ///
198    ///
199    /// # tokio_test::block_on(async {
200    ///     let chain = identity.sign_payload("Hello World!");
201    ///     let address = Authenticator::new().verify_signature(&chain, "Hello World!").await.unwrap();
202    ///     assert_eq!(identity.address().to_string(), "0x84452bbfa4ca14b7828e2f3bbd106a2bd495cd34");
203    ///     assert_eq!(address.to_string(), "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5");
204    ///     assert_eq!(
205    ///         chain,
206    ///         AuthChain::from_json(r#"[
207    ///             {
208    ///               "type": "SIGNER",
209    ///               "payload": "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5",
210    ///               "signature": ""
211    ///             },
212    ///             {
213    ///               "type": "ECDSA_EPHEMERAL",
214    ///               "payload": "Decentraland Login\nEphemeral address: 0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34\nExpiration: 3021-10-16T22:32:29.626Z",
215    ///               "signature": "0x39dd4ddf131ad2435d56c81c994c4417daef5cf5998258027ef8a1401470876a1365a6b79810dc0c4a2e9352befb63a9e4701d67b38007d83ffc4cd2b7a38ad51b"
216    ///             },
217    ///             {
218    ///               "type": "ECDSA_SIGNED_ENTITY",
219    ///               "payload": "Hello World!",
220    ///               "signature": "0x33e6b0f71b69d9dca7e25fa279cd70e2e9a44d0c11ab973a936a5b67ba3dbb6554ec2ea5cb4112b2f051dd95b47874c40b8bb7443a0bc4f16c67e2017e0dcb0c1c"
221    ///             }
222    ///         ]"#).unwrap()
223    ///     );
224    /// })
225    /// ```
226    pub fn sign_payload<M: AsRef<str>>(&self, payload: M) -> AuthChain {
227        let entity = AuthLink::EcdsaPersonalSignedEntity {
228            payload: payload.as_ref().to_string(),
229            signature: self.create_signature(payload.as_ref())
230        };
231
232        let mut links = self.auth_chain.clone();
233        links.push(entity);
234        AuthChain::from(links)
235    }
236
237}