dpp/identity/identity_public_key/v1/
mod.rs1mod accessors;
2mod methods;
3
4use bincode::{Decode, DecodeUntrusted, Encode};
5
6use crate::fee::Credits;
7use crate::identity::identity_public_key::contract_bounds::ContractBounds;
8use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
9use crate::identity::{KeyID, KeyType, Purpose, SecurityLevel, TimestampMillis};
10#[cfg(feature = "json-conversion")]
11use crate::serialization::json_safe_fields;
12#[cfg(feature = "state-transitions")]
13use crate::state_transition::public_key_in_creation::v1::IdentityPublicKeyInCreationV1;
14use platform_value::BinaryData;
15use serde::{Deserialize, Serialize};
16
17#[cfg_attr(feature = "json-conversion", json_safe_fields)]
23#[derive(
24 Default,
25 Debug,
26 Serialize,
27 Deserialize,
28 Encode,
29 Decode,
30 Clone,
31 PartialEq,
32 Eq,
33 Ord,
34 PartialOrd,
35 Hash,
36 DecodeUntrusted,
37)]
38#[serde(rename_all = "camelCase")]
39pub struct IdentityPublicKeyV1 {
40 pub id: KeyID,
41 pub purpose: Purpose,
42 pub security_level: SecurityLevel,
43 pub contract_bounds: Option<ContractBounds>,
44 #[serde(rename = "type")]
45 pub key_type: KeyType,
46 pub read_only: bool,
47 pub data: BinaryData,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub disabled_at: Option<TimestampMillis>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub total_budget: Option<Credits>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub expires_at: Option<TimestampMillis>,
58}
59
60impl IdentityPublicKeyV1 {
61 pub fn from_v0_with_limits(
63 key: IdentityPublicKeyV0,
64 total_budget: Option<Credits>,
65 expires_at: Option<TimestampMillis>,
66 ) -> Self {
67 let IdentityPublicKeyV0 {
68 id,
69 purpose,
70 security_level,
71 contract_bounds,
72 key_type,
73 read_only,
74 data,
75 disabled_at,
76 } = key;
77 IdentityPublicKeyV1 {
78 id,
79 purpose,
80 security_level,
81 contract_bounds,
82 key_type,
83 read_only,
84 data,
85 disabled_at,
86 total_budget,
87 expires_at,
88 }
89 }
90}
91
92#[cfg(feature = "state-transitions")]
93impl From<&IdentityPublicKeyV1> for IdentityPublicKeyInCreationV1 {
94 fn from(key: &IdentityPublicKeyV1) -> Self {
95 IdentityPublicKeyInCreationV1 {
96 id: key.id,
97 key_type: key.key_type,
98 purpose: key.purpose,
99 security_level: key.security_level,
100 contract_bounds: key.contract_bounds.clone(),
101 read_only: key.read_only,
102 data: key.data.clone(),
103 total_budget: key.total_budget,
104 expires_at: key.expires_at,
105 signature: BinaryData::default(),
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
114 use crate::identity::identity_public_key::accessors::v1::IdentityPublicKeyGettersV1;
115 use crate::identity::IdentityPublicKey;
116 use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
117
118 fn key_v0() -> IdentityPublicKeyV0 {
119 IdentityPublicKeyV0 {
120 id: 7,
121 purpose: Purpose::AUTHENTICATION,
122 security_level: SecurityLevel::HIGH,
123 contract_bounds: None,
124 key_type: KeyType::ECDSA_SECP256K1,
125 read_only: false,
126 data: BinaryData::new(vec![3; 33]),
127 disabled_at: None,
128 }
129 }
130
131 #[test]
132 fn should_round_trip_a_limited_key_through_platform_serialization() {
133 let key: IdentityPublicKey =
134 IdentityPublicKeyV1::from_v0_with_limits(key_v0(), Some(5_000_000), Some(1_800_000))
135 .into();
136 let bytes = key.serialize_to_bytes().expect("expected to serialize");
137 let decoded = IdentityPublicKey::deserialize_from_bytes_untrusted(&bytes)
138 .expect("expected to deserialize");
139 assert_eq!(decoded, key);
140 assert_eq!(decoded.total_budget(), Some(5_000_000));
141 assert_eq!(decoded.expires_at(), Some(1_800_000));
142 }
143
144 #[test]
145 fn should_leave_the_version_0_encoding_untouched() {
146 let key: IdentityPublicKey = key_v0().into();
149 let bytes = key.serialize_to_bytes().expect("expected to serialize");
150 assert_eq!(bytes[0], 0, "the variant index of a version 0 key");
151 let decoded = IdentityPublicKey::deserialize_from_bytes_untrusted(&bytes)
152 .expect("expected to deserialize");
153 assert!(matches!(decoded, IdentityPublicKey::V0(_)));
154 assert!(!decoded.has_limits());
155
156 let limited: IdentityPublicKey =
158 IdentityPublicKeyV1::from_v0_with_limits(key_v0(), None, None).into();
159 let limited_bytes = limited.serialize_to_bytes().expect("expected to serialize");
160 assert_eq!(limited_bytes[0], 1);
161 assert_eq!(limited_bytes[1..bytes.len()], bytes[1..]);
162 assert_eq!(limited_bytes[bytes.len()..], [0, 0]);
163 }
164
165 #[test]
166 fn should_upgrade_a_version_0_key_when_limits_are_added() {
167 let key: IdentityPublicKey = key_v0().into();
168 let limited = key.clone().with_limits(Some(10), None);
169 assert!(matches!(limited, IdentityPublicKey::V1(_)));
170 assert_eq!(limited.id(), key.id());
171 assert_eq!(limited.data(), key.data());
172 assert_eq!(limited.total_budget(), Some(10));
173 assert_eq!(limited.expires_at(), None);
174
175 let relimited = limited.with_limits(None, Some(20));
176 assert_eq!(relimited.total_budget(), None);
177 assert_eq!(relimited.expires_at(), Some(20));
178 }
179
180 #[test]
181 fn should_be_expired_from_the_expiry_instant_on() {
182 let key = IdentityPublicKeyV1::from_v0_with_limits(key_v0(), None, Some(1_000));
183 assert!(!key.is_expired_at(999));
184 assert!(key.is_expired_at(1_000));
185 assert!(key.is_expired_at(1_001));
186
187 let never = IdentityPublicKeyV1::from_v0_with_limits(key_v0(), Some(1), None);
188 assert!(!never.is_expired_at(u64::MAX));
189 }
190
191 #[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
192 #[test]
193 fn should_tag_the_json_shape_with_format_version_1() {
194 use crate::serialization::JsonConvertible;
195 use serde_json::json;
196
197 let key: IdentityPublicKey =
198 IdentityPublicKeyV1::from_v0_with_limits(key_v0(), Some(5_000), Some(1_800_000)).into();
199 let json = key.to_json().expect("to_json");
200 assert_eq!(json["$formatVersion"], json!("1"));
201 assert_eq!(json["totalBudget"], json!(5_000));
202 assert_eq!(json["expiresAt"], json!(1_800_000));
203 assert_eq!(IdentityPublicKey::from_json(json).expect("from_json"), key);
204
205 let unlimited: IdentityPublicKey =
207 IdentityPublicKeyV1::from_v0_with_limits(key_v0(), None, None).into();
208 let json = unlimited.to_json().expect("to_json");
209 assert!(json.get("totalBudget").is_none());
210 assert!(json.get("expiresAt").is_none());
211 assert_eq!(
212 IdentityPublicKey::from_json(json).expect("from_json"),
213 unlimited
214 );
215 }
216}