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, 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)]
28pub enum Purpose {
29    /// at least one authentication key must be registered for all security levels
30    #[default]
31    AUTHENTICATION = 0,
32    /// this key cannot be used for signing documents
33    ENCRYPTION = 1,
34    /// this key cannot be used for signing documents
35    DECRYPTION = 2,
36    /// this key is used to sign credit transfer and withdrawal state transitions
37    /// this key can also be used by identities for claims and transfers of tokens
38    TRANSFER = 3,
39    /// this key cannot be used for signing documents
40    SYSTEM = 4,
41    /// this key cannot be used for signing documents
42    VOTING = 5,
43    /// this key is used to prove ownership of a masternode or evonode
44    OWNER = 6,
45}
46
47impl From<Purpose> for [u8; 1] {
48    fn from(purpose: Purpose) -> Self {
49        [purpose as u8]
50    }
51}
52
53impl From<Purpose> for &'static [u8; 1] {
54    fn from(purpose: Purpose) -> Self {
55        match purpose {
56            AUTHENTICATION => &[0],
57            ENCRYPTION => &[1],
58            DECRYPTION => &[2],
59            TRANSFER => &[3],
60            SYSTEM => &[4],
61            VOTING => &[5],
62            OWNER => &[6],
63        }
64    }
65}
66
67impl TryFrom<u8> for Purpose {
68    type Error = anyhow::Error;
69    fn try_from(value: u8) -> Result<Self, Self::Error> {
70        match value {
71            0 => Ok(AUTHENTICATION),
72            1 => Ok(ENCRYPTION),
73            2 => Ok(DECRYPTION),
74            3 => Ok(TRANSFER),
75            4 => Ok(SYSTEM),
76            5 => Ok(VOTING),
77            6 => Ok(OWNER),
78            value => bail!("unrecognized purpose: {}", value),
79        }
80    }
81}
82
83impl TryFrom<i32> for Purpose {
84    type Error = anyhow::Error;
85    fn try_from(value: i32) -> Result<Self, Self::Error> {
86        match value {
87            0 => Ok(AUTHENTICATION),
88            1 => Ok(ENCRYPTION),
89            2 => Ok(DECRYPTION),
90            3 => Ok(TRANSFER),
91            4 => Ok(SYSTEM),
92            5 => Ok(VOTING),
93            6 => Ok(OWNER),
94            value => bail!("unrecognized purpose: {}", value),
95        }
96    }
97}
98
99#[cfg(feature = "cbor")]
100impl Into<CborValue> for Purpose {
101    fn into(self) -> CborValue {
102        CborValue::from(self as u128)
103    }
104}
105impl std::fmt::Display for Purpose {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        write!(f, "{self:?}")
108    }
109}
110
111impl Purpose {
112    /// The full range of purposes
113    pub fn full_range() -> [Purpose; 6] {
114        [
115            AUTHENTICATION,
116            ENCRYPTION,
117            DECRYPTION,
118            TRANSFER,
119            VOTING,
120            OWNER,
121        ]
122    }
123    /// Just the authentication and withdraw purposes
124    pub fn searchable_purposes() -> [Purpose; 3] {
125        [AUTHENTICATION, TRANSFER, VOTING]
126    }
127    /// Just the encryption and decryption purposes
128    pub fn encryption_decryption() -> [Purpose; 2] {
129        [ENCRYPTION, DECRYPTION]
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    // -- TryFrom<u8> valid --
138    #[test]
139    fn test_purpose_try_from_u8_valid_all_variants() {
140        assert_eq!(Purpose::try_from(0u8).unwrap(), AUTHENTICATION);
141        assert_eq!(Purpose::try_from(1u8).unwrap(), ENCRYPTION);
142        assert_eq!(Purpose::try_from(2u8).unwrap(), DECRYPTION);
143        assert_eq!(Purpose::try_from(3u8).unwrap(), TRANSFER);
144        assert_eq!(Purpose::try_from(4u8).unwrap(), SYSTEM);
145        assert_eq!(Purpose::try_from(5u8).unwrap(), VOTING);
146        assert_eq!(Purpose::try_from(6u8).unwrap(), OWNER);
147    }
148
149    // -- TryFrom<u8> invalid --
150    #[test]
151    fn test_purpose_try_from_u8_invalid() {
152        assert!(Purpose::try_from(7u8).is_err());
153        assert!(Purpose::try_from(255u8).is_err());
154    }
155
156    // -- TryFrom<i32> valid + invalid --
157    #[test]
158    fn test_purpose_try_from_i32_valid_all_variants() {
159        assert_eq!(Purpose::try_from(0i32).unwrap(), AUTHENTICATION);
160        assert_eq!(Purpose::try_from(1i32).unwrap(), ENCRYPTION);
161        assert_eq!(Purpose::try_from(2i32).unwrap(), DECRYPTION);
162        assert_eq!(Purpose::try_from(3i32).unwrap(), TRANSFER);
163        assert_eq!(Purpose::try_from(4i32).unwrap(), SYSTEM);
164        assert_eq!(Purpose::try_from(5i32).unwrap(), VOTING);
165        assert_eq!(Purpose::try_from(6i32).unwrap(), OWNER);
166    }
167
168    #[test]
169    fn test_purpose_try_from_i32_invalid() {
170        assert!(Purpose::try_from(-1i32).is_err());
171        assert!(Purpose::try_from(7i32).is_err());
172        assert!(Purpose::try_from(1_000_000i32).is_err());
173    }
174
175    // -- From<Purpose> for [u8; 1] (by-value) --
176    #[test]
177    fn test_purpose_to_owned_byte_array() {
178        let arr: [u8; 1] = AUTHENTICATION.into();
179        assert_eq!(arr, [0]);
180        let arr: [u8; 1] = OWNER.into();
181        assert_eq!(arr, [6]);
182        let arr: [u8; 1] = SYSTEM.into();
183        assert_eq!(arr, [4]);
184    }
185
186    // -- From<Purpose> for &'static [u8; 1] --
187    #[test]
188    fn test_purpose_to_static_byte_ref_all_variants() {
189        let r: &'static [u8; 1] = AUTHENTICATION.into();
190        assert_eq!(r, &[0u8]);
191        let r: &'static [u8; 1] = ENCRYPTION.into();
192        assert_eq!(r, &[1u8]);
193        let r: &'static [u8; 1] = DECRYPTION.into();
194        assert_eq!(r, &[2u8]);
195        let r: &'static [u8; 1] = TRANSFER.into();
196        assert_eq!(r, &[3u8]);
197        let r: &'static [u8; 1] = SYSTEM.into();
198        assert_eq!(r, &[4u8]);
199        let r: &'static [u8; 1] = VOTING.into();
200        assert_eq!(r, &[5u8]);
201        let r: &'static [u8; 1] = OWNER.into();
202        assert_eq!(r, &[6u8]);
203    }
204
205    // -- Display (via Debug) --
206    #[test]
207    fn test_purpose_display_matches_debug_form() {
208        assert_eq!(format!("{}", AUTHENTICATION), "AUTHENTICATION");
209        assert_eq!(format!("{}", ENCRYPTION), "ENCRYPTION");
210        assert_eq!(format!("{}", DECRYPTION), "DECRYPTION");
211        assert_eq!(format!("{}", TRANSFER), "TRANSFER");
212        assert_eq!(format!("{}", SYSTEM), "SYSTEM");
213        assert_eq!(format!("{}", VOTING), "VOTING");
214        assert_eq!(format!("{}", OWNER), "OWNER");
215    }
216
217    // -- Default --
218    #[test]
219    fn test_purpose_default_is_authentication() {
220        assert_eq!(Purpose::default(), AUTHENTICATION);
221    }
222
223    // -- Range helpers --
224    #[test]
225    fn test_purpose_full_range_contents() {
226        // NOTE: full_range() intentionally excludes SYSTEM.
227        let full = Purpose::full_range();
228        assert_eq!(full.len(), 6);
229        assert!(full.contains(&AUTHENTICATION));
230        assert!(full.contains(&ENCRYPTION));
231        assert!(full.contains(&DECRYPTION));
232        assert!(full.contains(&TRANSFER));
233        assert!(full.contains(&VOTING));
234        assert!(full.contains(&OWNER));
235        assert!(!full.contains(&SYSTEM));
236    }
237
238    #[test]
239    fn test_purpose_searchable_purposes_contents() {
240        let searchable = Purpose::searchable_purposes();
241        assert_eq!(searchable.len(), 3);
242        assert_eq!(searchable, [AUTHENTICATION, TRANSFER, VOTING]);
243    }
244
245    #[test]
246    fn test_purpose_encryption_decryption_contents() {
247        let ed = Purpose::encryption_decryption();
248        assert_eq!(ed.len(), 2);
249        assert_eq!(ed, [ENCRYPTION, DECRYPTION]);
250    }
251
252    // -- round-trip: Purpose -> u8 -> Purpose --
253    #[test]
254    fn test_purpose_round_trip_u8() {
255        for val in 0u8..=6 {
256            let p = Purpose::try_from(val).unwrap();
257            assert_eq!(p as u8, val);
258        }
259    }
260
261    // -- ordering --
262    #[test]
263    fn test_purpose_ordering_matches_discriminant() {
264        assert!(AUTHENTICATION < ENCRYPTION);
265        assert!(ENCRYPTION < DECRYPTION);
266        assert!(DECRYPTION < TRANSFER);
267        assert!(TRANSFER < SYSTEM);
268        assert!(SYSTEM < VOTING);
269        assert!(VOTING < OWNER);
270    }
271}