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}