dpp/identity/
identity_factory.rs

1use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
2#[cfg(all(feature = "state-transitions", feature = "client"))]
3use crate::identity::state_transition::asset_lock_proof::AssetLockProof;
4use crate::identity::state_transition::asset_lock_proof::InstantAssetLockProof;
5#[cfg(all(feature = "state-transitions", feature = "client"))]
6use crate::identity::state_transition::AssetLockProved;
7#[cfg(all(feature = "state-transitions", feature = "client"))]
8use crate::identity::IdentityV0;
9
10use crate::identity::{Identity, IdentityPublicKey, KeyID};
11
12use crate::ProtocolError;
13
14use dashcore::{InstantLock, Transaction};
15use platform_value::Identifier;
16use std::collections::BTreeMap;
17
18#[cfg(all(feature = "identity-serialization", feature = "client"))]
19use crate::consensus::basic::decode::SerializedObjectParsingError;
20#[cfg(all(feature = "identity-serialization", feature = "client"))]
21use crate::consensus::basic::BasicError;
22#[cfg(all(feature = "identity-serialization", feature = "client"))]
23use crate::consensus::ConsensusError;
24#[cfg(all(feature = "state-transitions", feature = "client"))]
25use crate::identity::accessors::IdentityGettersV0;
26#[cfg(all(feature = "state-transitions", feature = "client"))]
27use crate::identity::core_script::CoreScript;
28#[cfg(all(feature = "state-transitions", feature = "client"))]
29use crate::prelude::IdentityNonce;
30#[cfg(all(feature = "identity-serialization", feature = "client"))]
31use crate::serialization::PlatformDeserializable;
32#[cfg(all(feature = "state-transitions", feature = "client"))]
33use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
34#[cfg(all(feature = "state-transitions", feature = "client"))]
35use crate::state_transition::identity_create_transition::IdentityCreateTransition;
36#[cfg(all(feature = "state-transitions", feature = "client"))]
37use crate::state_transition::identity_credit_transfer_transition::v0::IdentityCreditTransferTransitionV0;
38#[cfg(all(feature = "state-transitions", feature = "client"))]
39use crate::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition;
40#[cfg(all(feature = "state-transitions", feature = "client"))]
41use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
42#[cfg(all(feature = "state-transitions", feature = "client"))]
43use crate::state_transition::identity_credit_withdrawal_transition::v1::IdentityCreditWithdrawalTransitionV1;
44#[cfg(all(feature = "state-transitions", feature = "client"))]
45use crate::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition;
46#[cfg(all(feature = "state-transitions", feature = "client"))]
47use crate::state_transition::identity_topup_transition::accessors::IdentityTopUpTransitionAccessorsV0;
48#[cfg(all(feature = "state-transitions", feature = "client"))]
49use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
50#[cfg(all(feature = "state-transitions", feature = "client"))]
51use crate::state_transition::identity_topup_transition::IdentityTopUpTransition;
52#[cfg(all(feature = "state-transitions", feature = "client"))]
53use crate::state_transition::identity_update_transition::accessors::IdentityUpdateTransitionAccessorsV0;
54#[cfg(all(feature = "state-transitions", feature = "client"))]
55use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
56#[cfg(all(feature = "state-transitions", feature = "client"))]
57use crate::state_transition::identity_update_transition::IdentityUpdateTransition;
58#[cfg(all(feature = "state-transitions", feature = "client"))]
59use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation;
60use crate::version::PlatformVersion;
61#[cfg(all(feature = "state-transitions", feature = "client"))]
62use crate::withdrawal::Pooling;
63
64pub const IDENTITY_PROTOCOL_VERSION: u32 = 1;
65
66#[derive(Clone)]
67pub struct IdentityFactory {
68    protocol_version: u32,
69}
70
71impl IdentityFactory {
72    pub fn new(protocol_version: u32) -> Self {
73        IdentityFactory { protocol_version }
74    }
75
76    pub fn create(
77        &self,
78        id: Identifier,
79        public_keys: BTreeMap<KeyID, IdentityPublicKey>,
80    ) -> Result<Identity, ProtocolError> {
81        Identity::new_with_id_and_keys(
82            id,
83            public_keys,
84            PlatformVersion::get(self.protocol_version)?,
85        )
86    }
87
88    #[cfg(all(feature = "identity-serialization", feature = "client"))]
89    pub fn create_from_buffer(
90        &self,
91        buffer: Vec<u8>,
92        #[cfg(feature = "validation")] skip_validation: bool,
93    ) -> Result<Identity, ProtocolError> {
94        let identity: Identity =
95            Identity::deserialize_from_bytes_no_limit(&buffer).map_err(|e| {
96                ConsensusError::BasicError(BasicError::SerializedObjectParsingError(
97                    SerializedObjectParsingError::new(format!("Decode protocol entity: {:#?}", e)),
98                ))
99            })?;
100
101        #[cfg(feature = "validation")]
102        if !skip_validation {
103            // todo: validate identity
104        }
105
106        Ok(identity)
107    }
108
109    pub fn create_instant_lock_proof(
110        instant_lock: InstantLock,
111        asset_lock_transaction: Transaction,
112        output_index: u32,
113    ) -> InstantAssetLockProof {
114        InstantAssetLockProof::new(instant_lock, asset_lock_transaction, output_index)
115    }
116
117    pub fn create_chain_asset_lock_proof(
118        core_chain_locked_height: u32,
119        out_point: [u8; 36],
120    ) -> ChainAssetLockProof {
121        ChainAssetLockProof::new(core_chain_locked_height, out_point)
122    }
123
124    #[cfg(all(feature = "state-transitions", feature = "client"))]
125    pub fn create_identity_create_transition(
126        &self,
127        identity: &Identity,
128        asset_lock_proof: AssetLockProof,
129    ) -> Result<IdentityCreateTransition, ProtocolError> {
130        let transition =
131            IdentityCreateTransitionV0::try_from_identity_v0(identity, asset_lock_proof)?;
132
133        Ok(IdentityCreateTransition::V0(transition))
134    }
135
136    #[cfg(all(feature = "state-transitions", feature = "client"))]
137    pub fn create_identity_with_create_transition(
138        &self,
139        public_keys: BTreeMap<KeyID, IdentityPublicKey>,
140        asset_lock_proof: AssetLockProof,
141    ) -> Result<(Identity, IdentityCreateTransition), ProtocolError> {
142        let identifier = asset_lock_proof.create_identifier()?;
143        let identity = Identity::V0(IdentityV0 {
144            id: identifier,
145            public_keys: public_keys.clone(),
146            balance: 0,
147            revision: 0,
148        });
149
150        let identity_create_transition = IdentityCreateTransition::V0(
151            IdentityCreateTransitionV0::try_from_identity_v0(&identity, asset_lock_proof)?,
152        );
153        Ok((identity, identity_create_transition))
154    }
155
156    #[cfg(all(feature = "state-transitions", feature = "client"))]
157    pub fn create_identity_topup_transition(
158        &self,
159        identity_id: Identifier,
160        asset_lock_proof: AssetLockProof,
161    ) -> Result<IdentityTopUpTransition, ProtocolError> {
162        let mut identity_topup_transition = IdentityTopUpTransitionV0::default();
163
164        identity_topup_transition.set_identity_id(identity_id);
165        identity_topup_transition.set_asset_lock_proof(asset_lock_proof)?;
166
167        Ok(IdentityTopUpTransition::V0(identity_topup_transition))
168    }
169
170    #[cfg(all(feature = "state-transitions", feature = "client"))]
171    pub fn create_identity_credit_transfer_transition(
172        &self,
173        identity: &Identity,
174        recipient_id: Identifier,
175        amount: u64,
176        identity_nonce: IdentityNonce,
177    ) -> Result<IdentityCreditTransferTransition, ProtocolError> {
178        let identity_credit_transfer_transition = IdentityCreditTransferTransitionV0 {
179            identity_id: identity.id(),
180            recipient_id,
181            amount,
182            nonce: identity_nonce,
183            ..Default::default()
184        };
185
186        Ok(IdentityCreditTransferTransition::from(
187            identity_credit_transfer_transition,
188        ))
189    }
190
191    #[cfg(all(feature = "state-transitions", feature = "client"))]
192    pub fn create_identity_credit_withdrawal_transition(
193        &self,
194        identity_id: Identifier,
195        amount: u64,
196        core_fee_per_byte: u32,
197        pooling: Pooling,
198        output_script: Option<CoreScript>,
199        identity_nonce: IdentityNonce,
200    ) -> Result<IdentityCreditWithdrawalTransition, ProtocolError> {
201        let platform_version = PlatformVersion::get(self.protocol_version)?;
202
203        let identity_credit_withdrawal_transition = match platform_version
204            .dpp
205            .state_transitions
206            .identities
207            .credit_withdrawal
208            .default_constructor
209        {
210            0 => {
211                let output_script = output_script.ok_or_else(|| {
212                    ProtocolError::Generic(
213                        "Output script is required for IdentityCreditWithdrawalTransitionV0"
214                            .to_string(),
215                    )
216                })?;
217
218                let transition = IdentityCreditWithdrawalTransitionV0 {
219                    identity_id,
220                    amount,
221                    core_fee_per_byte,
222                    pooling,
223                    output_script,
224                    nonce: identity_nonce,
225                    ..Default::default()
226                };
227
228                IdentityCreditWithdrawalTransition::from(transition)
229            }
230            1 => {
231                let transition = IdentityCreditWithdrawalTransitionV1 {
232                    identity_id,
233                    amount,
234                    core_fee_per_byte,
235                    pooling,
236                    output_script,
237                    nonce: identity_nonce,
238                    ..Default::default()
239                };
240
241                IdentityCreditWithdrawalTransition::from(transition)
242            }
243            version => {
244                return Err(ProtocolError::UnknownVersionMismatch {
245                    method: "create_identity_credit_withdrawal_transition".to_string(),
246                    known_versions: vec![0, 1],
247                    received: version,
248                });
249            }
250        };
251
252        Ok(identity_credit_withdrawal_transition)
253    }
254
255    #[cfg(all(feature = "state-transitions", feature = "client"))]
256    pub fn create_identity_update_transition(
257        &self,
258        identity: Identity,
259        identity_nonce: u64,
260        add_public_keys: Option<Vec<IdentityPublicKeyInCreation>>,
261        public_key_ids_to_disable: Option<Vec<KeyID>>,
262    ) -> Result<IdentityUpdateTransition, ProtocolError> {
263        let mut identity_update_transition = IdentityUpdateTransitionV0::default();
264        identity_update_transition.set_identity_id(identity.id().to_owned());
265        identity_update_transition.set_revision(identity.revision() + 1);
266        identity_update_transition.set_nonce(identity_nonce);
267
268        if let Some(add_public_keys) = add_public_keys {
269            identity_update_transition.set_public_keys_to_add(add_public_keys);
270        }
271
272        if let Some(public_key_ids_to_disable) = public_key_ids_to_disable {
273            identity_update_transition.set_public_key_ids_to_disable(public_key_ids_to_disable);
274        }
275
276        Ok(IdentityUpdateTransition::V0(identity_update_transition))
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use crate::identity::accessors::IdentityGettersV0;
284    use platform_value::Identifier;
285    use std::collections::BTreeMap;
286
287    #[test]
288    fn factory_new_sets_protocol_version() {
289        let factory = IdentityFactory::new(1);
290        assert_eq!(factory.protocol_version, 1);
291    }
292
293    #[test]
294    fn factory_clone() {
295        let factory = IdentityFactory::new(1);
296        let cloned = factory.clone();
297        assert_eq!(cloned.protocol_version, 1);
298    }
299
300    #[test]
301    fn create_identity_with_empty_keys() {
302        let factory = IdentityFactory::new(1);
303        let id = Identifier::random();
304        let public_keys = BTreeMap::new();
305
306        let identity = factory.create(id, public_keys).unwrap();
307
308        assert_eq!(identity.id(), id);
309    }
310
311    #[test]
312    fn create_identity_with_invalid_version() {
313        let factory = IdentityFactory::new(u32::MAX);
314        let id = Identifier::random();
315        let public_keys = BTreeMap::new();
316
317        let result = factory.create(id, public_keys);
318        assert!(result.is_err());
319    }
320
321    #[test]
322    fn create_identity_preserves_id() {
323        let factory = IdentityFactory::new(1);
324        let id = Identifier::random();
325        let public_keys = BTreeMap::new();
326
327        let identity = factory.create(id, public_keys).unwrap();
328        assert_eq!(identity.id(), id);
329    }
330
331    #[test]
332    fn create_identity_has_zero_balance() {
333        let factory = IdentityFactory::new(1);
334        let id = Identifier::random();
335        let public_keys = BTreeMap::new();
336
337        let identity = factory.create(id, public_keys).unwrap();
338        assert_eq!(identity.balance(), 0);
339    }
340
341    #[test]
342    fn create_identity_has_zero_revision() {
343        let factory = IdentityFactory::new(1);
344        let id = Identifier::random();
345        let public_keys = BTreeMap::new();
346
347        let identity = factory.create(id, public_keys).unwrap();
348        assert_eq!(identity.revision(), 0);
349    }
350
351    #[test]
352    fn create_identity_with_multiple_versions() {
353        // Version 1 should work
354        let factory_v1 = IdentityFactory::new(1);
355        let id = Identifier::random();
356        let result = factory_v1.create(id, BTreeMap::new());
357        assert!(result.is_ok());
358    }
359
360    #[test]
361    fn create_chain_asset_lock_proof_test() {
362        let out_point = [0u8; 36];
363        let proof = IdentityFactory::create_chain_asset_lock_proof(100, out_point);
364
365        assert_eq!(proof.core_chain_locked_height, 100);
366    }
367
368    #[test]
369    fn create_chain_asset_lock_proof_different_heights() {
370        let out_point = [1u8; 36];
371        let proof = IdentityFactory::create_chain_asset_lock_proof(500, out_point);
372        assert_eq!(proof.core_chain_locked_height, 500);
373
374        let proof2 = IdentityFactory::create_chain_asset_lock_proof(0, out_point);
375        assert_eq!(proof2.core_chain_locked_height, 0);
376
377        let proof3 = IdentityFactory::create_chain_asset_lock_proof(u32::MAX, out_point);
378        assert_eq!(proof3.core_chain_locked_height, u32::MAX);
379    }
380
381    #[test]
382    fn create_two_identities_have_different_ids() {
383        let factory = IdentityFactory::new(1);
384        let id1 = Identifier::random();
385        let id2 = Identifier::random();
386
387        let identity1 = factory.create(id1, BTreeMap::new()).unwrap();
388        let identity2 = factory.create(id2, BTreeMap::new()).unwrap();
389
390        assert_ne!(identity1.id(), identity2.id());
391    }
392
393    #[test]
394    fn create_identity_with_public_keys() {
395        use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
396        use crate::identity::{KeyType, Purpose, SecurityLevel};
397
398        let factory = IdentityFactory::new(1);
399        let id = Identifier::random();
400
401        let key = IdentityPublicKey::V0(IdentityPublicKeyV0 {
402            id: 0,
403            purpose: Purpose::AUTHENTICATION,
404            security_level: SecurityLevel::MASTER,
405            contract_bounds: None,
406            key_type: KeyType::ECDSA_SECP256K1,
407            read_only: false,
408            data: vec![0u8; 33].into(),
409            disabled_at: None,
410        });
411
412        let mut public_keys = BTreeMap::new();
413        public_keys.insert(0u32, key);
414
415        let identity = factory.create(id, public_keys).unwrap();
416        assert_eq!(identity.public_keys().len(), 1);
417    }
418}