radix_engine/blueprints/identity/
package.rs

1use crate::blueprints::util::{PresecurifiedRoleAssignment, SecurifiedRoleAssignment};
2use crate::errors::{ApplicationError, RuntimeError};
3use crate::internal_prelude::*;
4use crate::roles_template;
5use radix_blueprint_schema_init::{
6    BlueprintEventSchemaInit, BlueprintFunctionsSchemaInit, FunctionSchemaInit, ReceiverInfo,
7    TypeRef,
8};
9use radix_blueprint_schema_init::{BlueprintSchemaInit, BlueprintStateSchemaInit};
10use radix_engine_interface::api::{AttachedModuleId, SystemApi};
11use radix_engine_interface::blueprints::hooks::{OnVirtualizeInput, OnVirtualizeOutput};
12use radix_engine_interface::blueprints::identity::*;
13use radix_engine_interface::blueprints::package::{
14    AuthConfig, BlueprintDefinitionInit, BlueprintType, FunctionAuth, MethodAuthTemplate,
15    PackageDefinition,
16};
17use radix_engine_interface::blueprints::resource::*;
18use radix_engine_interface::metadata_init;
19use radix_engine_interface::object_modules::metadata::*;
20use radix_native_sdk::modules::metadata::Metadata;
21use radix_native_sdk::modules::role_assignment::RoleAssignment;
22use radix_native_sdk::modules::royalty::ComponentRoyalty;
23use radix_native_sdk::runtime::Runtime;
24
25pub const IDENTITY_ON_VIRTUALIZE_EXPORT_NAME: &str = "on_virtualize";
26
27pub const IDENTITY_CREATE_PREALLOCATED_SECP256K1_ID: u8 = 0u8;
28pub const IDENTITY_CREATE_PREALLOCATED_ED25519_ID: u8 = 1u8;
29
30pub struct IdentityNativePackage;
31
32impl IdentityNativePackage {
33    pub fn definition() -> PackageDefinition {
34        let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
35
36        let fields = Vec::new();
37
38        let mut functions = index_map_new();
39        functions.insert(
40            IDENTITY_CREATE_ADVANCED_IDENT.to_string(),
41            FunctionSchemaInit {
42                receiver: None,
43                input: TypeRef::Static(
44                    aggregator.add_child_type_and_descendents::<IdentityCreateAdvancedInput>(),
45                ),
46                output: TypeRef::Static(
47                    aggregator.add_child_type_and_descendents::<IdentityCreateAdvancedOutput>(),
48                ),
49                export: IDENTITY_CREATE_ADVANCED_IDENT.to_string(),
50            },
51        );
52        functions.insert(
53            IDENTITY_CREATE_IDENT.to_string(),
54            FunctionSchemaInit {
55                receiver: None,
56                input: TypeRef::Static(
57                    aggregator.add_child_type_and_descendents::<IdentityCreateInput>(),
58                ),
59                output: TypeRef::Static(
60                    aggregator.add_child_type_and_descendents::<IdentityCreateOutput>(),
61                ),
62                export: IDENTITY_CREATE_IDENT.to_string(),
63            },
64        );
65        functions.insert(
66            IDENTITY_SECURIFY_IDENT.to_string(),
67            FunctionSchemaInit {
68                receiver: Some(ReceiverInfo::normal_ref_mut()),
69                input: TypeRef::Static(
70                    aggregator
71                        .add_child_type_and_descendents::<IdentitySecurifyToSingleBadgeInput>(),
72                ),
73                output: TypeRef::Static(
74                    aggregator
75                        .add_child_type_and_descendents::<IdentitySecurifyToSingleBadgeOutput>(),
76                ),
77                export: IDENTITY_SECURIFY_IDENT.to_string(),
78            },
79        );
80
81        let schema = generate_full_schema(aggregator);
82        let blueprints = indexmap!(
83            IDENTITY_BLUEPRINT.to_string() => BlueprintDefinitionInit {
84                blueprint_type: BlueprintType::default(),
85                is_transient: false,
86                feature_set: indexset!(),
87                dependencies: indexset!(
88                    SECP256K1_SIGNATURE_RESOURCE.into(),
89                    ED25519_SIGNATURE_RESOURCE.into(),
90                    IDENTITY_OWNER_BADGE.into(),
91                    PACKAGE_OF_DIRECT_CALLER_RESOURCE.into(),
92                ),
93                schema: BlueprintSchemaInit {
94                    generics: vec![],
95                    schema,
96                    state: BlueprintStateSchemaInit {
97                        fields,
98                        collections: vec![],
99                    },
100                    events: BlueprintEventSchemaInit::default(),
101                    types: BlueprintTypeSchemaInit::default(),
102                    functions: BlueprintFunctionsSchemaInit {
103                        functions,
104                    },
105                    hooks: BlueprintHooksInit {
106                        hooks: indexmap!(BlueprintHook::OnVirtualize => IDENTITY_ON_VIRTUALIZE_EXPORT_NAME.to_string())
107                    }
108                },
109                royalty_config: PackageRoyaltyConfig::default(),
110                auth_config: AuthConfig {
111                    function_auth: FunctionAuth::AllowAll,
112                    method_auth: MethodAuthTemplate::StaticRoleDefinition(roles_template! {
113                        roles {
114                            SECURIFY_ROLE => updaters: [SELF_ROLE];
115                        },
116                        methods {
117                            IDENTITY_SECURIFY_IDENT => [SECURIFY_ROLE];
118                        }
119                    }),
120                },
121            }
122        );
123
124        PackageDefinition { blueprints }
125    }
126
127    pub fn invoke_export<Y: SystemApi<RuntimeError>>(
128        export_name: &str,
129        input: &IndexedScryptoValue,
130        version: IdentityV1MinorVersion,
131        api: &mut Y,
132    ) -> Result<IndexedScryptoValue, RuntimeError> {
133        match export_name {
134            IDENTITY_CREATE_ADVANCED_IDENT => {
135                let input: IdentityCreateAdvancedInput = input.as_typed().map_err(|e| {
136                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
137                })?;
138
139                let rtn = IdentityBlueprint::create_advanced(input.owner_role, version, api)?;
140
141                Ok(IndexedScryptoValue::from_typed(&rtn))
142            }
143            IDENTITY_CREATE_IDENT => {
144                let _input: IdentityCreateInput = input.as_typed().map_err(|e| {
145                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
146                })?;
147
148                let rtn = IdentityBlueprint::create(version, api)?;
149
150                Ok(IndexedScryptoValue::from_typed(&rtn))
151            }
152            IDENTITY_SECURIFY_IDENT => {
153                let _input: IdentitySecurifyToSingleBadgeInput = input.as_typed().map_err(|e| {
154                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
155                })?;
156
157                let rtn = IdentityBlueprint::securify(api)?;
158
159                Ok(IndexedScryptoValue::from_typed(&rtn))
160            }
161            IDENTITY_ON_VIRTUALIZE_EXPORT_NAME => {
162                let input: OnVirtualizeInput = input.as_typed().map_err(|e| {
163                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
164                })?;
165
166                let rtn = IdentityBlueprint::on_virtualize(input, version, api)?;
167
168                Ok(IndexedScryptoValue::from_typed(&rtn))
169            }
170            _ => Err(RuntimeError::ApplicationError(
171                ApplicationError::ExportDoesNotExist(export_name.to_string()),
172            )),
173        }
174    }
175}
176
177const SECURIFY_ROLE: &'static str = "securify";
178
179struct SecurifiedIdentity;
180
181impl SecurifiedRoleAssignment for SecurifiedIdentity {
182    type OwnerBadgeNonFungibleData = IdentityOwnerBadgeData;
183    const OWNER_BADGE: ResourceAddress = IDENTITY_OWNER_BADGE;
184    const SECURIFY_ROLE: Option<&'static str> = Some(SECURIFY_ROLE);
185}
186
187impl PresecurifiedRoleAssignment for SecurifiedIdentity {}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Sbor)]
190pub enum IdentityV1MinorVersion {
191    Zero,
192    One,
193}
194
195pub struct IdentityBlueprint;
196
197impl IdentityBlueprint {
198    pub fn create_advanced<Y: SystemApi<RuntimeError>>(
199        owner_role: OwnerRole,
200        version: IdentityV1MinorVersion,
201        api: &mut Y,
202    ) -> Result<GlobalAddress, RuntimeError> {
203        let role_assignment = SecurifiedIdentity::create_advanced(owner_role, api)?;
204
205        let (node_id, modules) = Self::create_object(
206            role_assignment,
207            metadata_init!(
208                "owner_badge" => EMPTY, locked;
209            ),
210            version,
211            api,
212        )?;
213        let modules = modules.into_iter().map(|(id, own)| (id, own.0)).collect();
214        let address = api.globalize(node_id, modules, None)?;
215        Ok(address)
216    }
217
218    pub fn create<Y: SystemApi<RuntimeError>>(
219        version: IdentityV1MinorVersion,
220        api: &mut Y,
221    ) -> Result<(GlobalAddress, Bucket), RuntimeError> {
222        let (address_reservation, address) = api.allocate_global_address(BlueprintId {
223            package_address: IDENTITY_PACKAGE,
224            blueprint_name: IDENTITY_BLUEPRINT.to_string(),
225        })?;
226        let (role_assignment, bucket) = SecurifiedIdentity::create_securified(
227            IdentityOwnerBadgeData {
228                name: "Identity Owner Badge".to_string(),
229                identity: address.try_into().expect("Impossible Case"),
230            },
231            Some(NonFungibleLocalId::bytes(address.as_node_id().0).unwrap()),
232            api,
233        )?;
234
235        let (node_id, modules) = Self::create_object(
236            role_assignment,
237            metadata_init! {
238                "owner_badge" => NonFungibleLocalId::bytes(address.as_node_id().0).unwrap(), locked;
239            },
240            version,
241            api,
242        )?;
243        let modules = modules.into_iter().map(|(id, own)| (id, own.0)).collect();
244        let address = api.globalize(node_id, modules, Some(address_reservation))?;
245        Ok((address, bucket))
246    }
247
248    pub fn on_virtualize<Y: SystemApi<RuntimeError>>(
249        input: OnVirtualizeInput,
250        version: IdentityV1MinorVersion,
251        api: &mut Y,
252    ) -> Result<OnVirtualizeOutput, RuntimeError> {
253        match input.variant_id {
254            IDENTITY_CREATE_PREALLOCATED_SECP256K1_ID => {
255                let public_key_hash = PublicKeyHash::Secp256k1(Secp256k1PublicKeyHash(input.rid));
256                Self::create_virtual(public_key_hash, input.address_reservation, version, api)
257            }
258            IDENTITY_CREATE_PREALLOCATED_ED25519_ID => {
259                let public_key_hash = PublicKeyHash::Ed25519(Ed25519PublicKeyHash(input.rid));
260                Self::create_virtual(public_key_hash, input.address_reservation, version, api)
261            }
262            x => Err(RuntimeError::ApplicationError(
263                ApplicationError::PanicMessage(format!("Unexpected variant id: {:?}", x)),
264            )),
265        }
266    }
267
268    fn create_virtual<Y: SystemApi<RuntimeError>>(
269        public_key_hash: PublicKeyHash,
270        address_reservation: GlobalAddressReservation,
271        version: IdentityV1MinorVersion,
272        api: &mut Y,
273    ) -> Result<(), RuntimeError> {
274        let owner_badge = {
275            let bytes = public_key_hash.get_hash_bytes();
276            let entity_type = match public_key_hash {
277                PublicKeyHash::Ed25519(..) => EntityType::GlobalPreallocatedEd25519Identity,
278                PublicKeyHash::Secp256k1(..) => EntityType::GlobalPreallocatedSecp256k1Identity,
279            };
280
281            let mut id_bytes = vec![entity_type as u8];
282            id_bytes.extend(bytes);
283
284            NonFungibleLocalId::bytes(id_bytes).unwrap()
285        };
286
287        let owner_id = NonFungibleGlobalId::from_public_key_hash(public_key_hash);
288        let role_assignment = SecurifiedIdentity::create_presecurified(owner_id, api)?;
289
290        let (node_id, modules) = Self::create_object(
291            role_assignment,
292            metadata_init! {
293                // NOTE:
294                // This is the owner key for ROLA. We choose to set this explicitly to simplify the
295                // security-critical logic off-ledger. In particular, we want an owner to be able to
296                // explicitly delete the owner keys. If we went with a "no metadata = assume default
297                // public key hash", then this could cause unexpected security-critical behavior if
298                // a user expected that deleting the metadata removed the owner keys.
299                "owner_keys" => vec![public_key_hash], updatable;
300                "owner_badge" => owner_badge, locked;
301            },
302            version,
303            api,
304        )?;
305
306        api.globalize(
307            node_id,
308            modules.into_iter().map(|(k, v)| (k, v.0)).collect(),
309            Some(address_reservation),
310        )?;
311        Ok(())
312    }
313
314    fn securify<Y: SystemApi<RuntimeError>>(api: &mut Y) -> Result<Bucket, RuntimeError> {
315        let receiver = Runtime::get_node_id(api)?;
316        let owner_badge_data = IdentityOwnerBadgeData {
317            name: "Identity Owner Badge".into(),
318            identity: ComponentAddress::new_or_panic(receiver.0),
319        };
320        let bucket = SecurifiedIdentity::securify(
321            &receiver,
322            owner_badge_data,
323            Some(NonFungibleLocalId::bytes(receiver.0).unwrap()),
324            api,
325        )?;
326        Ok(bucket.into())
327    }
328
329    fn create_object<Y: SystemApi<RuntimeError>>(
330        role_assignment: RoleAssignment,
331        metadata_init: MetadataInit,
332        version: IdentityV1MinorVersion,
333        api: &mut Y,
334    ) -> Result<(NodeId, IndexMap<AttachedModuleId, Own>), RuntimeError> {
335        match version {
336            IdentityV1MinorVersion::Zero => {
337                let metadata = Metadata::create_with_data(metadata_init, api)?;
338                let royalty = ComponentRoyalty::create(ComponentRoyaltyConfig::default(), api)?;
339                let object_id = api.new_simple_object(IDENTITY_BLUEPRINT, indexmap!())?;
340                let modules = indexmap!(
341                    AttachedModuleId::RoleAssignment => role_assignment.0,
342                    AttachedModuleId::Metadata => metadata,
343                    AttachedModuleId::Royalty => royalty,
344                );
345                Ok((object_id, modules))
346            }
347            IdentityV1MinorVersion::One => {
348                let metadata = Metadata::create_with_data(metadata_init, api)?;
349                let object_id = api.new_simple_object(IDENTITY_BLUEPRINT, indexmap!())?;
350                let modules = indexmap!(
351                    AttachedModuleId::RoleAssignment => role_assignment.0,
352                    AttachedModuleId::Metadata => metadata,
353                );
354                Ok((object_id, modules))
355            }
356        }
357    }
358}
359
360#[derive(ScryptoSbor)]
361pub struct IdentityOwnerBadgeData {
362    pub name: String,
363    pub identity: ComponentAddress,
364}
365
366impl NonFungibleData for IdentityOwnerBadgeData {
367    const MUTABLE_FIELDS: &'static [&'static str] = &[];
368}