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