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
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    // -- TryFrom<u8> valid --
139    #[test]
140    fn test_purpose_try_from_u8_valid_all_variants() {
141        assert_eq!(Purpose::try_from(0u8).unwrap(), AUTHENTICATION);
142        assert_eq!(Purpose::try_from(1u8).unwrap(), ENCRYPTION);
143        assert_eq!(Purpose::try_from(2u8).unwrap(), DECRYPTION);
144        assert_eq!(Purpose::try_from(3u8).unwrap(), TRANSFER);
145        assert_eq!(Purpose::try_from(4u8).unwrap(), SYSTEM);
146        assert_eq!(Purpose::try_from(5u8).unwrap(), VOTING);
147        assert_eq!(Purpose::try_from(6u8).unwrap(), OWNER);
148    }
149
150    // -- TryFrom<u8> invalid --
151    #[test]
152    fn test_purpose_try_from_u8_invalid() {
153        assert!(Purpose::try_from(7u8).is_err());
154        assert!(Purpose::try_from(255u8).is_err());
155    }
156
157    // -- TryFrom<i32> valid + invalid --
158    #[test]
159    fn test_purpose_try_from_i32_valid_all_variants() {
160        assert_eq!(Purpose::try_from(0i32).unwrap(), AUTHENTICATION);
161        assert_eq!(Purpose::try_from(1i32).unwrap(), ENCRYPTION);
162        assert_eq!(Purpose::try_from(2i32).unwrap(), DECRYPTION);
163        assert_eq!(Purpose::try_from(3i32).unwrap(), TRANSFER);
164        assert_eq!(Purpose::try_from(4i32).unwrap(), SYSTEM);
165        assert_eq!(Purpose::try_from(5i32).unwrap(), VOTING);
166        assert_eq!(Purpose::try_from(6i32).unwrap(), OWNER);
167    }
168
169    #[test]
170    fn test_purpose_try_from_i32_invalid() {
171        assert!(Purpose::try_from(-1i32).is_err());
172        assert!(Purpose::try_from(7i32).is_err());
173        assert!(Purpose::try_from(1_000_000i32).is_err());
174    }
175
176    // -- From<Purpose> for [u8; 1] (by-value) --
177    #[test]
178    fn test_purpose_to_owned_byte_array() {
179        let arr: [u8; 1] = AUTHENTICATION.into();
180        assert_eq!(arr, [0]);
181        let arr: [u8; 1] = OWNER.into();
182        assert_eq!(arr, [6]);
183        let arr: [u8; 1] = SYSTEM.into();
184        assert_eq!(arr, [4]);
185    }
186
187    // -- From<Purpose> for &'static [u8; 1] --
188    #[test]
189    fn test_purpose_to_static_byte_ref_all_variants() {
190        let r: &'static [u8; 1] = AUTHENTICATION.into();
191        assert_eq!(r, &[0u8]);
192        let r: &'static [u8; 1] = ENCRYPTION.into();
193        assert_eq!(r, &[1u8]);
194        let r: &'static [u8; 1] = DECRYPTION.into();
195        assert_eq!(r, &[2u8]);
196        let r: &'static [u8; 1] = TRANSFER.into();
197        assert_eq!(r, &[3u8]);
198        let r: &'static [u8; 1] = SYSTEM.into();
199        assert_eq!(r, &[4u8]);
200        let r: &'static [u8; 1] = VOTING.into();
201        assert_eq!(r, &[5u8]);
202        let r: &'static [u8; 1] = OWNER.into();
203        assert_eq!(r, &[6u8]);
204    }
205
206    // -- Display (via Debug) --
207    #[test]
208    fn test_purpose_display_matches_debug_form() {
209        assert_eq!(format!("{}", AUTHENTICATION), "AUTHENTICATION");
210        assert_eq!(format!("{}", ENCRYPTION), "ENCRYPTION");
211        assert_eq!(format!("{}", DECRYPTION), "DECRYPTION");
212        assert_eq!(format!("{}", TRANSFER), "TRANSFER");
213        assert_eq!(format!("{}", SYSTEM), "SYSTEM");
214        assert_eq!(format!("{}", VOTING), "VOTING");
215        assert_eq!(format!("{}", OWNER), "OWNER");
216    }
217
218    // -- Default --
219    #[test]
220    fn test_purpose_default_is_authentication() {
221        assert_eq!(Purpose::default(), AUTHENTICATION);
222    }
223
224    // -- Range helpers --
225    #[test]
226    fn test_purpose_full_range_contents() {
227        // NOTE: full_range() intentionally excludes SYSTEM.
228        let full = Purpose::full_range();
229        assert_eq!(full.len(), 6);
230        assert!(full.contains(&AUTHENTICATION));
231        assert!(full.contains(&ENCRYPTION));
232        assert!(full.contains(&DECRYPTION));
233        assert!(full.contains(&TRANSFER));
234        assert!(full.contains(&VOTING));
235        assert!(full.contains(&OWNER));
236        assert!(!full.contains(&SYSTEM));
237    }
238
239    #[test]
240    fn test_purpose_searchable_purposes_contents() {
241        let searchable = Purpose::searchable_purposes();
242        assert_eq!(searchable.len(), 3);
243        assert_eq!(searchable, [AUTHENTICATION, TRANSFER, VOTING]);
244    }
245
246    #[test]
247    fn test_purpose_encryption_decryption_contents() {
248        let ed = Purpose::encryption_decryption();
249        assert_eq!(ed.len(), 2);
250        assert_eq!(ed, [ENCRYPTION, DECRYPTION]);
251    }
252
253    // -- round-trip: Purpose -> u8 -> Purpose --
254    #[test]
255    fn test_purpose_round_trip_u8() {
256        for val in 0u8..=6 {
257            let p = Purpose::try_from(val).unwrap();
258            assert_eq!(p as u8, val);
259        }
260    }
261
262    // -- ordering --
263    #[test]
264    fn test_purpose_ordering_matches_discriminant() {
265        assert!(AUTHENTICATION < ENCRYPTION);
266        assert!(ENCRYPTION < DECRYPTION);
267        assert!(DECRYPTION < TRANSFER);
268        assert!(TRANSFER < SYSTEM);
269        assert!(SYSTEM < VOTING);
270        assert!(VOTING < OWNER);
271    }
272}