1use crate::address_funds::PlatformAddress;
2use crate::identity::v0::IdentityV0;
3use crate::identity::{IdentityPublicKey, KeyID};
4use crate::prelude::{AddressNonce, Revision};
5#[cfg(feature = "json-conversion")]
6use crate::serialization::json_safe_fields;
7#[cfg(feature = "json-conversion")]
8use crate::serialization::JsonConvertible;
9#[cfg(feature = "value-conversion")]
10use crate::serialization::ValueConvertible;
11
12#[cfg(feature = "identity-hashing")]
13use crate::serialization::PlatformSerializable;
14#[cfg(feature = "identity-hashing")]
15use crate::util::hash;
16use crate::version::PlatformVersion;
17
18use crate::ProtocolError;
19#[cfg(feature = "identity-serialization")]
20use bincode::{Decode, Encode};
21use derive_more::From;
22#[cfg(feature = "identity-serialization")]
23use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
24use platform_value::Identifier;
25
26use crate::fee::Credits;
27use std::collections::{BTreeMap, BTreeSet};
28
29#[derive(Debug, Clone, PartialEq, From)]
33#[cfg_attr(
34 feature = "serde-conversion",
35 derive(serde::Serialize, serde::Deserialize),
36 serde(tag = "$formatVersion"),
37 )]
39#[cfg_attr(
40 feature = "identity-serialization",
41 derive(Encode, Decode, PlatformDeserialize, PlatformSerialize),
42 platform_serialize(limit = 15000, unversioned)
43)]
44#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
45pub enum Identity {
46 #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
47 V0(IdentityV0),
48}
49
50#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
51impl JsonConvertible for Identity {}
52
53#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
54impl JsonConvertible for PartialIdentity {}
55
56#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
57impl ValueConvertible for PartialIdentity {}
58
59#[cfg(all(
60 test,
61 feature = "json-conversion",
62 feature = "value-conversion",
63 feature = "serde-conversion"
64))]
65mod json_convertible_tests {
66 use super::*;
67 use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
68 use crate::identity::{KeyType, Purpose, SecurityLevel};
69 use platform_value::{platform_value, BinaryData, Value};
70 use serde_json::json;
71
72 fn fixture_pubkey(id: u32, byte: u8) -> IdentityPublicKey {
73 IdentityPublicKey::V0(IdentityPublicKeyV0 {
74 id,
75 key_type: KeyType::ECDSA_SECP256K1,
76 purpose: Purpose::AUTHENTICATION,
77 security_level: SecurityLevel::MASTER,
78 contract_bounds: None,
79 read_only: false,
80 data: BinaryData::new(vec![byte; 33]),
81 disabled_at: None,
82 })
83 }
84
85 fn fixture() -> Identity {
86 let mut public_keys = BTreeMap::new();
87 public_keys.insert(0, fixture_pubkey(0, 0xa0));
88 public_keys.insert(1, fixture_pubkey(1, 0xb1));
89 Identity::V0(IdentityV0 {
90 id: Identifier::new([0x42; 32]),
91 public_keys,
92 balance: 1_000_000,
93 revision: 7,
94 })
95 }
96
97 #[test]
98 fn json_round_trip_with_full_wire_shape() {
99 use crate::serialization::JsonConvertible;
100 let original = fixture();
101 let json = original.to_json().expect("to_json");
102 assert_eq!(
117 json,
118 json!({
119 "$formatVersion": "0",
120 "id": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
121 "publicKeys": [
125 {
126 "$formatVersion": "0",
127 "id": 0,
128 "purpose": 0,
129 "securityLevel": 0,
130 "contractBounds": serde_json::Value::Null,
131 "type": 0,
132 "readOnly": false,
133 "data": "oKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg",
134 },
135 {
136 "$formatVersion": "0",
137 "id": 1,
138 "purpose": 0,
139 "securityLevel": 0,
140 "contractBounds": serde_json::Value::Null,
141 "type": 0,
142 "readOnly": false,
143 "data": "sbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx",
144 },
145 ],
146 "balance": 1_000_000u64,
147 "revision": 7,
148 })
149 );
150 let recovered = Identity::from_json(json).expect("from_json");
151 assert_eq!(original, recovered);
152 }
153
154 #[test]
155 fn value_round_trip_with_full_wire_shape() {
156 use crate::serialization::ValueConvertible;
157 let original = fixture();
158 let value = original.to_object().expect("to_object");
159 let id = Identifier::new([0x42; 32]);
164 assert_eq!(
165 value,
166 platform_value!({
167 "$formatVersion": "0",
168 "id": id,
169 "publicKeys": [
172 {
173 "$formatVersion": "0",
174 "id": 0u32,
175 "purpose": 0u8,
176 "securityLevel": 0u8,
177 "contractBounds": Value::Null,
178 "type": 0u8,
179 "readOnly": false,
180 "data": Value::Bytes(vec![0xa0; 33]),
181 },
182 {
183 "$formatVersion": "0",
184 "id": 1u32,
185 "purpose": 0u8,
186 "securityLevel": 0u8,
187 "contractBounds": Value::Null,
188 "type": 0u8,
189 "readOnly": false,
190 "data": Value::Bytes(vec![0xb1; 33]),
191 },
192 ],
193 "balance": 1_000_000u64,
194 "revision": 7u64,
195 })
196 );
197 let recovered = Identity::from_object(value).expect("from_object");
198 assert_eq!(original, recovered);
199 }
200}
201
202#[cfg_attr(feature = "json-conversion", json_safe_fields)]
204#[derive(Debug, Clone, Eq, PartialEq)]
205#[cfg_attr(
206 feature = "serde-conversion",
207 derive(serde::Serialize, serde::Deserialize),
208 serde(rename_all = "camelCase")
209)]
210pub struct PartialIdentity {
211 pub id: Identifier,
212 pub loaded_public_keys: BTreeMap<KeyID, IdentityPublicKey>,
213 pub balance: Option<Credits>,
214 pub revision: Option<Revision>,
215 pub not_found_public_keys: BTreeSet<KeyID>,
217}
218
219impl Identity {
220 #[cfg(feature = "identity-hashing")]
221 pub fn hash(&self) -> Result<Vec<u8>, ProtocolError> {
223 Ok(hash::hash_double_to_vec(
224 PlatformSerializable::serialize_to_bytes(self)?,
225 ))
226 }
227
228 pub fn default_versioned(
229 platform_version: &PlatformVersion,
230 ) -> Result<Identity, ProtocolError> {
231 match platform_version
232 .dpp
233 .identity_versions
234 .identity_structure_version
235 {
236 0 => Ok(Identity::V0(IdentityV0::default())),
237 version => Err(ProtocolError::UnknownVersionMismatch {
238 method: "Identity::default_versioned".to_string(),
239 known_versions: vec![0],
240 received: version,
241 }),
242 }
243 }
244
245 pub fn new_with_id_and_keys(
247 id: Identifier,
248 public_keys: BTreeMap<KeyID, IdentityPublicKey>,
249 platform_version: &PlatformVersion,
250 ) -> Result<Identity, ProtocolError> {
251 match platform_version
252 .dpp
253 .identity_versions
254 .identity_structure_version
255 {
256 0 => {
257 let identity_v0 = IdentityV0 {
258 id,
259 public_keys,
260 balance: 0,
261 revision: 0,
262 };
263 Ok(identity_v0.into())
264 }
265 version => Err(ProtocolError::UnknownVersionMismatch {
266 method: "Identity::new_with_id_and_keys".to_string(),
267 known_versions: vec![0],
268 received: version,
269 }),
270 }
271 }
272
273 #[cfg(feature = "state-transitions")]
289 pub fn new_with_input_addresses_and_keys(
290 inputs: &BTreeMap<PlatformAddress, (AddressNonce, Credits)>,
291 public_keys: BTreeMap<KeyID, IdentityPublicKey>,
292 platform_version: &PlatformVersion,
293 ) -> Result<Identity, ProtocolError> {
294 use crate::state_transition::identity_id_from_input_addresses;
295
296 let identity_id = identity_id_from_input_addresses(inputs)?;
297 Self::new_with_id_and_keys(identity_id, public_keys, platform_version)
298 }
299
300 pub fn into_partial_identity_info(self) -> PartialIdentity {
302 match self {
303 Identity::V0(v0) => v0.into_partial_identity_info(),
304 }
305 }
306
307 pub fn into_partial_identity_info_no_balance(self) -> PartialIdentity {
309 match self {
310 Identity::V0(v0) => v0.into_partial_identity_info_no_balance(),
311 }
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::identity::accessors::IdentityGettersV0;
319 use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
320 use crate::identity::{KeyType, Purpose, SecurityLevel};
321 use platform_value::{BinaryData, Identifier};
322 use platform_version::version::LATEST_PLATFORM_VERSION;
323 use std::collections::BTreeMap;
324
325 fn sample_key(id: u32) -> IdentityPublicKey {
326 IdentityPublicKey::V0(IdentityPublicKeyV0 {
327 id,
328 purpose: Purpose::AUTHENTICATION,
329 security_level: SecurityLevel::MASTER,
330 contract_bounds: None,
331 key_type: KeyType::ECDSA_SECP256K1,
332 read_only: false,
333 data: BinaryData::new(vec![0x42; 33]),
334 disabled_at: None,
335 })
336 }
337
338 #[test]
339 fn default_versioned_returns_default_v0() {
340 let identity =
341 Identity::default_versioned(LATEST_PLATFORM_VERSION).expect("default should succeed");
342 assert_eq!(identity.id(), Identifier::default());
343 assert_eq!(identity.balance(), 0);
344 assert_eq!(identity.revision(), 0);
345 assert!(identity.public_keys().is_empty());
346 }
347
348 #[test]
349 fn new_with_id_and_keys_preserves_inputs() {
350 let id = Identifier::from([4u8; 32]);
351 let mut keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
352 keys.insert(0, sample_key(0));
353 keys.insert(1, sample_key(1));
354
355 let identity = Identity::new_with_id_and_keys(id, keys.clone(), LATEST_PLATFORM_VERSION)
356 .expect("new_with_id_and_keys");
357 assert_eq!(identity.id(), id);
358 assert_eq!(identity.balance(), 0);
359 assert_eq!(identity.revision(), 0);
360 assert_eq!(identity.public_keys().len(), 2);
361 }
362
363 #[test]
364 fn into_partial_identity_info_preserves_balance_and_revision() {
365 let mut keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
366 keys.insert(0, sample_key(0));
367 let v0 = IdentityV0 {
368 id: Identifier::from([5u8; 32]),
369 public_keys: keys,
370 balance: 123,
371 revision: 7,
372 };
373 let identity: Identity = v0.clone().into();
374 let partial = identity.into_partial_identity_info();
375 assert_eq!(partial.id, v0.id);
376 assert_eq!(partial.balance, Some(123));
377 assert_eq!(partial.revision, Some(7));
378 assert_eq!(partial.loaded_public_keys.len(), 1);
379 assert!(partial.not_found_public_keys.is_empty());
380 }
381
382 #[test]
383 fn into_partial_identity_info_no_balance_drops_balance() {
384 let v0 = IdentityV0 {
385 id: Identifier::from([6u8; 32]),
386 public_keys: BTreeMap::new(),
387 balance: 999,
388 revision: 2,
389 };
390 let identity: Identity = v0.into();
391 let partial = identity.into_partial_identity_info_no_balance();
392 assert!(partial.balance.is_none());
393 assert_eq!(partial.revision, Some(2));
394 }
395
396 #[test]
397 fn from_v0_conversion_works() {
398 let v0 = IdentityV0 {
399 id: Identifier::from([1u8; 32]),
400 public_keys: BTreeMap::new(),
401 balance: 1,
402 revision: 1,
403 };
404 let identity: Identity = v0.clone().into();
405 match identity {
406 Identity::V0(inner) => assert_eq!(inner, v0),
407 }
408 }
409
410 #[test]
411 fn clone_and_equality() {
412 let id = Identifier::from([3u8; 32]);
413 let identity =
414 Identity::new_with_id_and_keys(id, BTreeMap::new(), LATEST_PLATFORM_VERSION).unwrap();
415 let clone = identity.clone();
416 assert_eq!(identity, clone);
417 }
418
419 #[cfg(feature = "identity-hashing")]
420 #[test]
421 fn hash_is_stable_for_same_identity() {
422 let id = Identifier::from([8u8; 32]);
423 let identity =
424 Identity::new_with_id_and_keys(id, BTreeMap::new(), LATEST_PLATFORM_VERSION).unwrap();
425 let h1 = identity.hash().unwrap();
426 let h2 = identity.hash().unwrap();
427 assert_eq!(h1, h2);
428 assert_eq!(h1.len(), 32);
430 }
431
432 #[cfg(feature = "identity-hashing")]
433 #[test]
434 fn hash_differs_for_different_identities() {
435 let a = Identity::new_with_id_and_keys(
436 Identifier::from([0u8; 32]),
437 BTreeMap::new(),
438 LATEST_PLATFORM_VERSION,
439 )
440 .unwrap();
441 let b = Identity::new_with_id_and_keys(
442 Identifier::from([1u8; 32]),
443 BTreeMap::new(),
444 LATEST_PLATFORM_VERSION,
445 )
446 .unwrap();
447 assert_ne!(a.hash().unwrap(), b.hash().unwrap());
448 }
449
450 #[cfg(feature = "state-transitions")]
451 #[test]
452 fn new_with_input_addresses_and_keys_is_deterministic() {
453 use crate::address_funds::PlatformAddress;
454
455 let mut inputs: BTreeMap<PlatformAddress, (u32, u64)> = BTreeMap::new();
456 inputs.insert(PlatformAddress::P2pkh([0x11; 20]), (1, 0));
457 inputs.insert(PlatformAddress::P2pkh([0x22; 20]), (2, 0));
458
459 let keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
460
461 let a = Identity::new_with_input_addresses_and_keys(
462 &inputs,
463 keys.clone(),
464 LATEST_PLATFORM_VERSION,
465 )
466 .unwrap();
467 let b = Identity::new_with_input_addresses_and_keys(
468 &inputs,
469 keys.clone(),
470 LATEST_PLATFORM_VERSION,
471 )
472 .unwrap();
473 assert_eq!(a.id(), b.id());
475 }
476
477 #[cfg(feature = "state-transitions")]
478 #[test]
479 fn new_with_input_addresses_and_keys_fails_on_empty_inputs() {
480 use crate::address_funds::PlatformAddress;
481 let inputs: BTreeMap<PlatformAddress, (u32, u64)> = BTreeMap::new();
482 let keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
483
484 let result =
485 Identity::new_with_input_addresses_and_keys(&inputs, keys, LATEST_PLATFORM_VERSION);
486 assert!(result.is_err());
487 }
488}