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