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 = "value-conversion")]
8use crate::serialization::ValueConvertible;
9
10#[cfg(feature = "identity-hashing")]
11use crate::serialization::PlatformSerializable;
12#[cfg(feature = "identity-hashing")]
13use crate::util::hash;
14use crate::version::PlatformVersion;
15
16use crate::ProtocolError;
17#[cfg(feature = "identity-serialization")]
18use bincode::{Decode, Encode};
19use derive_more::From;
20#[cfg(feature = "identity-serialization")]
21use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
22use platform_value::Identifier;
23
24use crate::fee::Credits;
25use std::collections::{BTreeMap, BTreeSet};
26
27#[derive(Debug, Clone, PartialEq, From)]
31#[cfg_attr(
32 any( feature = "serde-conversion" ,feature = "serde-conversion",),
33 derive(serde::Serialize, serde::Deserialize),
34 serde(tag = "$formatVersion"),
35 )]
37#[cfg_attr(
38 feature = "identity-serialization",
39 derive(Encode, Decode, PlatformDeserialize, PlatformSerialize),
40 platform_serialize(limit = 15000, unversioned)
41)]
42#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
43pub enum Identity {
44 #[cfg_attr(
45 any(feature = "serde-conversion", feature = "serde-conversion"),
46 serde(rename = "0")
47 )]
48 V0(IdentityV0),
49}
50
51#[cfg_attr(feature = "json-conversion", json_safe_fields)]
53#[derive(Debug, Clone, Eq, PartialEq)]
54#[cfg_attr(
55 any(feature = "serde-conversion", feature = "serde-conversion",),
56 derive(serde::Serialize, serde::Deserialize),
57 serde(rename_all = "camelCase")
58)]
59pub struct PartialIdentity {
60 pub id: Identifier,
61 pub loaded_public_keys: BTreeMap<KeyID, IdentityPublicKey>,
62 pub balance: Option<Credits>,
63 pub revision: Option<Revision>,
64 pub not_found_public_keys: BTreeSet<KeyID>,
66}
67
68impl Identity {
69 #[cfg(feature = "identity-hashing")]
70 pub fn hash(&self) -> Result<Vec<u8>, ProtocolError> {
72 Ok(hash::hash_double_to_vec(
73 PlatformSerializable::serialize_to_bytes(self)?,
74 ))
75 }
76
77 pub fn default_versioned(
78 platform_version: &PlatformVersion,
79 ) -> Result<Identity, ProtocolError> {
80 match platform_version
81 .dpp
82 .identity_versions
83 .identity_structure_version
84 {
85 0 => Ok(Identity::V0(IdentityV0::default())),
86 version => Err(ProtocolError::UnknownVersionMismatch {
87 method: "Identity::default_versioned".to_string(),
88 known_versions: vec![0],
89 received: version,
90 }),
91 }
92 }
93
94 pub fn new_with_id_and_keys(
96 id: Identifier,
97 public_keys: BTreeMap<KeyID, IdentityPublicKey>,
98 platform_version: &PlatformVersion,
99 ) -> Result<Identity, ProtocolError> {
100 match platform_version
101 .dpp
102 .identity_versions
103 .identity_structure_version
104 {
105 0 => {
106 let identity_v0 = IdentityV0 {
107 id,
108 public_keys,
109 balance: 0,
110 revision: 0,
111 };
112 Ok(identity_v0.into())
113 }
114 version => Err(ProtocolError::UnknownVersionMismatch {
115 method: "Identity::new_with_id_and_keys".to_string(),
116 known_versions: vec![0],
117 received: version,
118 }),
119 }
120 }
121
122 #[cfg(feature = "state-transitions")]
138 pub fn new_with_input_addresses_and_keys(
139 inputs: &BTreeMap<PlatformAddress, (AddressNonce, Credits)>,
140 public_keys: BTreeMap<KeyID, IdentityPublicKey>,
141 platform_version: &PlatformVersion,
142 ) -> Result<Identity, ProtocolError> {
143 use crate::state_transition::identity_id_from_input_addresses;
144
145 let identity_id = identity_id_from_input_addresses(inputs)?;
146 Self::new_with_id_and_keys(identity_id, public_keys, platform_version)
147 }
148
149 pub fn into_partial_identity_info(self) -> PartialIdentity {
151 match self {
152 Identity::V0(v0) => v0.into_partial_identity_info(),
153 }
154 }
155
156 pub fn into_partial_identity_info_no_balance(self) -> PartialIdentity {
158 match self {
159 Identity::V0(v0) => v0.into_partial_identity_info_no_balance(),
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use crate::identity::accessors::IdentityGettersV0;
168 use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
169 use crate::identity::{KeyType, Purpose, SecurityLevel};
170 use platform_value::{BinaryData, Identifier};
171 use platform_version::version::LATEST_PLATFORM_VERSION;
172 use std::collections::BTreeMap;
173
174 fn sample_key(id: u32) -> IdentityPublicKey {
175 IdentityPublicKey::V0(IdentityPublicKeyV0 {
176 id,
177 purpose: Purpose::AUTHENTICATION,
178 security_level: SecurityLevel::MASTER,
179 contract_bounds: None,
180 key_type: KeyType::ECDSA_SECP256K1,
181 read_only: false,
182 data: BinaryData::new(vec![0x42; 33]),
183 disabled_at: None,
184 })
185 }
186
187 #[test]
188 fn default_versioned_returns_default_v0() {
189 let identity =
190 Identity::default_versioned(LATEST_PLATFORM_VERSION).expect("default should succeed");
191 assert_eq!(identity.id(), Identifier::default());
192 assert_eq!(identity.balance(), 0);
193 assert_eq!(identity.revision(), 0);
194 assert!(identity.public_keys().is_empty());
195 }
196
197 #[test]
198 fn new_with_id_and_keys_preserves_inputs() {
199 let id = Identifier::from([4u8; 32]);
200 let mut keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
201 keys.insert(0, sample_key(0));
202 keys.insert(1, sample_key(1));
203
204 let identity = Identity::new_with_id_and_keys(id, keys.clone(), LATEST_PLATFORM_VERSION)
205 .expect("new_with_id_and_keys");
206 assert_eq!(identity.id(), id);
207 assert_eq!(identity.balance(), 0);
208 assert_eq!(identity.revision(), 0);
209 assert_eq!(identity.public_keys().len(), 2);
210 }
211
212 #[test]
213 fn into_partial_identity_info_preserves_balance_and_revision() {
214 let mut keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
215 keys.insert(0, sample_key(0));
216 let v0 = IdentityV0 {
217 id: Identifier::from([5u8; 32]),
218 public_keys: keys,
219 balance: 123,
220 revision: 7,
221 };
222 let identity: Identity = v0.clone().into();
223 let partial = identity.into_partial_identity_info();
224 assert_eq!(partial.id, v0.id);
225 assert_eq!(partial.balance, Some(123));
226 assert_eq!(partial.revision, Some(7));
227 assert_eq!(partial.loaded_public_keys.len(), 1);
228 assert!(partial.not_found_public_keys.is_empty());
229 }
230
231 #[test]
232 fn into_partial_identity_info_no_balance_drops_balance() {
233 let v0 = IdentityV0 {
234 id: Identifier::from([6u8; 32]),
235 public_keys: BTreeMap::new(),
236 balance: 999,
237 revision: 2,
238 };
239 let identity: Identity = v0.into();
240 let partial = identity.into_partial_identity_info_no_balance();
241 assert!(partial.balance.is_none());
242 assert_eq!(partial.revision, Some(2));
243 }
244
245 #[test]
246 fn from_v0_conversion_works() {
247 let v0 = IdentityV0 {
248 id: Identifier::from([1u8; 32]),
249 public_keys: BTreeMap::new(),
250 balance: 1,
251 revision: 1,
252 };
253 let identity: Identity = v0.clone().into();
254 match identity {
255 Identity::V0(inner) => assert_eq!(inner, v0),
256 }
257 }
258
259 #[test]
260 fn clone_and_equality() {
261 let id = Identifier::from([3u8; 32]);
262 let identity =
263 Identity::new_with_id_and_keys(id, BTreeMap::new(), LATEST_PLATFORM_VERSION).unwrap();
264 let clone = identity.clone();
265 assert_eq!(identity, clone);
266 }
267
268 #[cfg(feature = "identity-hashing")]
269 #[test]
270 fn hash_is_stable_for_same_identity() {
271 let id = Identifier::from([8u8; 32]);
272 let identity =
273 Identity::new_with_id_and_keys(id, BTreeMap::new(), LATEST_PLATFORM_VERSION).unwrap();
274 let h1 = identity.hash().unwrap();
275 let h2 = identity.hash().unwrap();
276 assert_eq!(h1, h2);
277 assert_eq!(h1.len(), 32);
279 }
280
281 #[cfg(feature = "identity-hashing")]
282 #[test]
283 fn hash_differs_for_different_identities() {
284 let a = Identity::new_with_id_and_keys(
285 Identifier::from([0u8; 32]),
286 BTreeMap::new(),
287 LATEST_PLATFORM_VERSION,
288 )
289 .unwrap();
290 let b = Identity::new_with_id_and_keys(
291 Identifier::from([1u8; 32]),
292 BTreeMap::new(),
293 LATEST_PLATFORM_VERSION,
294 )
295 .unwrap();
296 assert_ne!(a.hash().unwrap(), b.hash().unwrap());
297 }
298
299 #[cfg(feature = "state-transitions")]
300 #[test]
301 fn new_with_input_addresses_and_keys_is_deterministic() {
302 use crate::address_funds::PlatformAddress;
303
304 let mut inputs: BTreeMap<PlatformAddress, (u32, u64)> = BTreeMap::new();
305 inputs.insert(PlatformAddress::P2pkh([0x11; 20]), (1, 0));
306 inputs.insert(PlatformAddress::P2pkh([0x22; 20]), (2, 0));
307
308 let keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
309
310 let a = Identity::new_with_input_addresses_and_keys(
311 &inputs,
312 keys.clone(),
313 LATEST_PLATFORM_VERSION,
314 )
315 .unwrap();
316 let b = Identity::new_with_input_addresses_and_keys(
317 &inputs,
318 keys.clone(),
319 LATEST_PLATFORM_VERSION,
320 )
321 .unwrap();
322 assert_eq!(a.id(), b.id());
324 }
325
326 #[cfg(feature = "state-transitions")]
327 #[test]
328 fn new_with_input_addresses_and_keys_fails_on_empty_inputs() {
329 use crate::address_funds::PlatformAddress;
330 let inputs: BTreeMap<PlatformAddress, (u32, u64)> = BTreeMap::new();
331 let keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
332
333 let result =
334 Identity::new_with_input_addresses_and_keys(&inputs, keys, LATEST_PLATFORM_VERSION);
335 assert!(result.is_err());
336 }
337}