Skip to main content

dpp/identity/
identity.rs

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/// The identity is not stored inside of drive, because of this, the serialization is mainly for
28/// transport, the serialization of the identity will include the version, so no passthrough or
29/// untagged is needed here
30#[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    // platform_version_path("dpp.identity_versions.identity_structure_version")
36)]
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/// An identity struct that represent partially set/loaded identity data.
52#[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    /// These are keys that were requested but didn't exist
65    pub not_found_public_keys: BTreeSet<KeyID>,
66}
67
68impl Identity {
69    #[cfg(feature = "identity-hashing")]
70    /// Computes the hash of an identity
71    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    /// Created a new identity based on asset locks and keys
95    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    /// Create a new identity using input [PlatformAddress]es.
123    ///
124    /// This function derives the identity ID from the provided input addresses.
125    ///
126    /// ## Arguments
127    ///
128    /// * `inputs` - A map of `PlatformAddress` to `(AddressNonce, Credits)`.
129    ///   The identity id is derived from the addresses and nonces (credits are ignored for the id derivation).
130    ///   The nonces should represent state after creation of the identity (e.g. be incremented by 1).
131    /// * `public_keys` - A map of KeyID to IdentityPublicKey tuples representing the public keys for the identity.
132    /// * `platform_version` - The platform version to use for identity creation.
133    ///
134    /// ## Returns
135    ///
136    /// * `Result<Identity, ProtocolError>` - Returns the newly created Identity or a ProtocolError if the operation fails.
137    #[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    /// Convenience method to get Partial Identity Info
150    pub fn into_partial_identity_info(self) -> PartialIdentity {
151        match self {
152            Identity::V0(v0) => v0.into_partial_identity_info(),
153        }
154    }
155
156    /// Convenience method to get Partial Identity Info
157    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        // The hash is a fixed-size SHA256-double, 32 bytes.
278        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        // Deterministic derivation: same inputs -> same id.
323        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}