Skip to main content

dpp/identity/identity_public_key/
purpose.rs

1use crate::identity::Purpose::{
2    AUTHENTICATION, DECRYPTION, ENCRYPTION, OWNER, SYSTEM, TRANSFER, VOTING,
3};
4use anyhow::bail;
5use bincode::{Decode, DecodeUntrusted, Encode};
6#[cfg(feature = "cbor")]
7use ciborium::value::Value as CborValue;
8use serde_repr::{Deserialize_repr, Serialize_repr};
9use std::convert::TryFrom;
10
11#[repr(u8)]
12#[derive(
13    Debug,
14    PartialEq,
15    Eq,
16    Clone,
17    Copy,
18    Hash,
19    Serialize_repr,
20    Deserialize_repr,
21    Ord,
22    PartialOrd,
23    Encode,
24    Decode,
25    Default,
26    strum::EnumIter,
27    DecodeUntrusted,
28)]
29pub enum Purpose {
30    /// at least one authentication key must be registered for all security levels
31    #[default]
32    AUTHENTICATION = 0,
33    /// this key cannot be used for signing documents
34    ENCRYPTION = 1,
35    /// this key cannot be used for signing documents
36    DECRYPTION = 2,
37    /// this key is used to sign credit transfer and withdrawal state transitions
38    /// this key can also be used by identities for claims and transfers of tokens
39    TRANSFER = 3,
40    /// this key cannot be used for signing documents
41    SYSTEM = 4,
42    /// this key cannot be used for signing documents
43    VOTING = 5,
44    /// this key is used to prove ownership of a masternode or evonode
45    OWNER = 6,
46}
47
48impl From<Purpose> for [u8; 1] {
49    fn from(purpose: Purpose) -> Self {
50        [purpose as u8]
51    }
52}
53
54impl From<Purpose> for &'static [u8; 1] {
55    fn from(purpose: Purpose) -> Self {
56        match purpose {
57            AUTHENTICATION => &[0],
58            ENCRYPTION => &[1],
59            DECRYPTION => &[2],
60            TRANSFER => &[3],
61            SYSTEM => &[4],
62            VOTING => &[5],
63            OWNER => &[6],
64        }
65    }
66}
67
68impl TryFrom<u8> for Purpose {
69    type Error = anyhow::Error;
70    fn try_from(value: u8) -> Result<Self, Self::Error> {
71        match value {
72            0 => Ok(AUTHENTICATION),
73            1 => Ok(ENCRYPTION),
74            2 => Ok(DECRYPTION),
75            3 => Ok(TRANSFER),
76            4 => Ok(SYSTEM),
77            5 => Ok(VOTING),
78            6 => Ok(OWNER),
79            value => bail!("unrecognized purpose: {}", value),
80        }
81    }
82}
83
84impl TryFrom<i32> for Purpose {
85    type Error = anyhow::Error;
86    fn try_from(value: i32) -> Result<Self, Self::Error> {
87        match value {
88            0 => Ok(AUTHENTICATION),
89            1 => Ok(ENCRYPTION),
90            2 => Ok(DECRYPTION),
91            3 => Ok(TRANSFER),
92            4 => Ok(SYSTEM),
93            5 => Ok(VOTING),
94            6 => Ok(OWNER),
95            value => bail!("unrecognized purpose: {}", value),
96        }
97    }
98}
99
100#[cfg(feature = "cbor")]
101impl Into<CborValue> for Purpose {
102    fn into(self) -> CborValue {
103        CborValue::from(self as u128)
104    }
105}
106impl std::fmt::Display for Purpose {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, "{self:?}")
109    }
110}
111
112impl Purpose {
113    /// The full range of purposes
114    pub fn full_range() -> [Purpose; 6] {
115        [
116            AUTHENTICATION,
117            ENCRYPTION,
118            DECRYPTION,
119            TRANSFER,
120            VOTING,
121            OWNER,
122        ]
123    }
124    /// Just the authentication and withdraw purposes
125    pub fn searchable_purposes() -> [Purpose; 3] {
126        [AUTHENTICATION, TRANSFER, VOTING]
127    }
128    /// Just the encryption and decryption purposes
129    pub fn encryption_decryption() -> [Purpose; 2] {
130        [ENCRYPTION, DECRYPTION]
131    }
132
133    /// The name a schema keyword spells the purpose with: the variant's own name in lower case,
134    /// `"decryption"` for one.
135    pub fn wire_name(&self) -> &'static str {
136        match self {
137            AUTHENTICATION => "authentication",
138            ENCRYPTION => "encryption",
139            DECRYPTION => "decryption",
140            TRANSFER => "transfer",
141            SYSTEM => "system",
142            VOTING => "voting",
143            OWNER => "owner",
144        }
145    }
146
147    /// The purpose a wire name names, `None` for any other name.
148    pub fn from_wire_name(name: &str) -> Option<Self> {
149        match name {
150            "authentication" => Some(AUTHENTICATION),
151            "encryption" => Some(ENCRYPTION),
152            "decryption" => Some(DECRYPTION),
153            "transfer" => Some(TRANSFER),
154            "system" => Some(SYSTEM),
155            "voting" => Some(VOTING),
156            "owner" => Some(OWNER),
157            _ => None,
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    // -- TryFrom<u8> valid --
167    #[test]
168    fn test_purpose_try_from_u8_valid_all_variants() {
169        assert_eq!(Purpose::try_from(0u8).unwrap(), AUTHENTICATION);
170        assert_eq!(Purpose::try_from(1u8).unwrap(), ENCRYPTION);
171        assert_eq!(Purpose::try_from(2u8).unwrap(), DECRYPTION);
172        assert_eq!(Purpose::try_from(3u8).unwrap(), TRANSFER);
173        assert_eq!(Purpose::try_from(4u8).unwrap(), SYSTEM);
174        assert_eq!(Purpose::try_from(5u8).unwrap(), VOTING);
175        assert_eq!(Purpose::try_from(6u8).unwrap(), OWNER);
176    }
177
178    // -- TryFrom<u8> invalid --
179    #[test]
180    fn test_purpose_try_from_u8_invalid() {
181        assert!(Purpose::try_from(7u8).is_err());
182        assert!(Purpose::try_from(255u8).is_err());
183    }
184
185    // -- TryFrom<i32> valid + invalid --
186    #[test]
187    fn test_purpose_try_from_i32_valid_all_variants() {
188        assert_eq!(Purpose::try_from(0i32).unwrap(), AUTHENTICATION);
189        assert_eq!(Purpose::try_from(1i32).unwrap(), ENCRYPTION);
190        assert_eq!(Purpose::try_from(2i32).unwrap(), DECRYPTION);
191        assert_eq!(Purpose::try_from(3i32).unwrap(), TRANSFER);
192        assert_eq!(Purpose::try_from(4i32).unwrap(), SYSTEM);
193        assert_eq!(Purpose::try_from(5i32).unwrap(), VOTING);
194        assert_eq!(Purpose::try_from(6i32).unwrap(), OWNER);
195    }
196
197    #[test]
198    fn test_purpose_try_from_i32_invalid() {
199        assert!(Purpose::try_from(-1i32).is_err());
200        assert!(Purpose::try_from(7i32).is_err());
201        assert!(Purpose::try_from(1_000_000i32).is_err());
202    }
203
204    // -- From<Purpose> for [u8; 1] (by-value) --
205    #[test]
206    fn test_purpose_to_owned_byte_array() {
207        let arr: [u8; 1] = AUTHENTICATION.into();
208        assert_eq!(arr, [0]);
209        let arr: [u8; 1] = OWNER.into();
210        assert_eq!(arr, [6]);
211        let arr: [u8; 1] = SYSTEM.into();
212        assert_eq!(arr, [4]);
213    }
214
215    // -- From<Purpose> for &'static [u8; 1] --
216    #[test]
217    fn test_purpose_to_static_byte_ref_all_variants() {
218        let r: &'static [u8; 1] = AUTHENTICATION.into();
219        assert_eq!(r, &[0u8]);
220        let r: &'static [u8; 1] = ENCRYPTION.into();
221        assert_eq!(r, &[1u8]);
222        let r: &'static [u8; 1] = DECRYPTION.into();
223        assert_eq!(r, &[2u8]);
224        let r: &'static [u8; 1] = TRANSFER.into();
225        assert_eq!(r, &[3u8]);
226        let r: &'static [u8; 1] = SYSTEM.into();
227        assert_eq!(r, &[4u8]);
228        let r: &'static [u8; 1] = VOTING.into();
229        assert_eq!(r, &[5u8]);
230        let r: &'static [u8; 1] = OWNER.into();
231        assert_eq!(r, &[6u8]);
232    }
233
234    // -- Display (via Debug) --
235    #[test]
236    fn test_purpose_display_matches_debug_form() {
237        assert_eq!(format!("{}", AUTHENTICATION), "AUTHENTICATION");
238        assert_eq!(format!("{}", ENCRYPTION), "ENCRYPTION");
239        assert_eq!(format!("{}", DECRYPTION), "DECRYPTION");
240        assert_eq!(format!("{}", TRANSFER), "TRANSFER");
241        assert_eq!(format!("{}", SYSTEM), "SYSTEM");
242        assert_eq!(format!("{}", VOTING), "VOTING");
243        assert_eq!(format!("{}", OWNER), "OWNER");
244    }
245
246    // -- Default --
247    #[test]
248    fn test_purpose_default_is_authentication() {
249        assert_eq!(Purpose::default(), AUTHENTICATION);
250    }
251
252    // -- Range helpers --
253    #[test]
254    fn test_purpose_full_range_contents() {
255        // NOTE: full_range() intentionally excludes SYSTEM.
256        let full = Purpose::full_range();
257        assert_eq!(full.len(), 6);
258        assert!(full.contains(&AUTHENTICATION));
259        assert!(full.contains(&ENCRYPTION));
260        assert!(full.contains(&DECRYPTION));
261        assert!(full.contains(&TRANSFER));
262        assert!(full.contains(&VOTING));
263        assert!(full.contains(&OWNER));
264        assert!(!full.contains(&SYSTEM));
265    }
266
267    #[test]
268    fn test_purpose_searchable_purposes_contents() {
269        let searchable = Purpose::searchable_purposes();
270        assert_eq!(searchable.len(), 3);
271        assert_eq!(searchable, [AUTHENTICATION, TRANSFER, VOTING]);
272    }
273
274    #[test]
275    fn test_purpose_encryption_decryption_contents() {
276        let ed = Purpose::encryption_decryption();
277        assert_eq!(ed.len(), 2);
278        assert_eq!(ed, [ENCRYPTION, DECRYPTION]);
279    }
280
281    // -- round-trip: Purpose -> u8 -> Purpose --
282    #[test]
283    fn test_purpose_round_trip_u8() {
284        for val in 0u8..=6 {
285            let p = Purpose::try_from(val).unwrap();
286            assert_eq!(p as u8, val);
287        }
288    }
289
290    // -- wire names --
291    #[test]
292    fn should_round_trip_every_purpose_through_its_wire_name() {
293        for purpose in [
294            AUTHENTICATION,
295            ENCRYPTION,
296            DECRYPTION,
297            TRANSFER,
298            SYSTEM,
299            VOTING,
300            OWNER,
301        ] {
302            let name = purpose.wire_name();
303            assert_eq!(name, format!("{purpose:?}").to_lowercase());
304            assert_eq!(Purpose::from_wire_name(name), Some(purpose));
305        }
306        assert_eq!(Purpose::from_wire_name("AUTHENTICATION"), None);
307        assert_eq!(Purpose::from_wire_name("signing"), None);
308    }
309
310    // -- ordering --
311    #[test]
312    fn test_purpose_ordering_matches_discriminant() {
313        assert!(AUTHENTICATION < ENCRYPTION);
314        assert!(ENCRYPTION < DECRYPTION);
315        assert!(DECRYPTION < TRANSFER);
316        assert!(TRANSFER < SYSTEM);
317        assert!(SYSTEM < VOTING);
318        assert!(VOTING < OWNER);
319    }
320}