Skip to main content

ic_platform/principal/
mod.rs

1//! Platform-owned principal representation and non-cryptographic conversions.
2//!
3//! Adapted from ic_principal 0.1.5 under Apache-2.0; see NOTICE.md.
4//! No Candid marker handling or generic range stepping is implemented.
5//! Required Candid DTOs must use [ExternalPrincipal], converting at the boundary:
6//!
7//! ```compile_fail
8//! ic_platform::candid::contract::encode_one(ic_platform::Principal::anonymous()).unwrap();
9//! ```
10
11use serde::{Deserialize, Serialize};
12use std::convert::TryFrom;
13
14/// An error happened while encoding, decoding or serializing a [`Principal`].
15#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
16pub enum PrincipalError {
17    /// More than 29 significant bytes.
18    BytesTooLong(),
19
20    /// Invalid Base32 spelling or trailing bits.
21    InvalidBase32(),
22
23    /// Missing complete checksum.
24    TextTooShort(),
25
26    /// Input exceeds 63 text bytes or decodes to more than 29 identity bytes.
27    TextTooLong(),
28
29    /// CRC32 checksum mismatch.
30    CheckSequenceNotMatch(),
31
32    /// Correct identity with noncanonical dash placement.
33    AbnormalGrouped(Principal),
34}
35
36/// Generic ID on Internet Computer.
37///
38/// Principals are generic identifiers for canisters, users
39/// and possibly other concepts in the future.
40/// As far as most uses of the IC are concerned they are
41/// opaque binary blobs with a length between 0 and 29 bytes,
42/// and there is intentionally no mechanism to tell canister ids and user ids apart.
43///
44/// Note a principal is not necessarily tied with a public key-pair,
45/// yet we need at least a key-pair of a related principal to sign
46/// requests.
47///
48/// A Principal can be serialized to a byte array ([`Vec<u8>`]) or a text
49/// representation, but the inner structure of the byte representation
50/// is kept private.
51///
52/// Example of using a Principal object:
53/// ```
54/// # {
55/// use ic_platform::Principal;
56///
57/// let text = "aaaaa-aa";  // The management canister ID.
58/// let principal = Principal::from_text(text).expect("Could not decode the principal.");
59/// assert!(principal.as_slice().is_empty());
60/// assert_eq!(principal.to_text(), text);
61///
62/// # }
63/// ```
64///
65/// Serialization is always available. It supports serializing
66/// to a byte buffer for non-human readable serializer, and a string version for human
67/// readable serializers.
68///
69#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
70pub struct Principal {
71    /// Length.
72    len: u8,
73
74    /// The content buffer. When returning slices this should always be sized according to
75    /// `len`.
76    bytes: [u8; Self::MAX_LENGTH_IN_BYTES],
77}
78
79impl Principal {
80    /// Maximum significant identity length.
81    pub const MAX_LENGTH_IN_BYTES: usize = 29;
82    /// Maximum canonical text length, including dashes and checksum.
83    pub const MAX_LENGTH_IN_TEXT: usize = 63;
84    #[allow(dead_code)]
85    const CRC_LENGTH_IN_BYTES: usize = 4;
86
87    const ANONYMOUS_TAG: u8 = 4;
88
89    /// Construct a [`Principal`] of the IC management canister
90    pub const fn management_canister() -> Self {
91        Self {
92            len: 0,
93            bytes: [0; Self::MAX_LENGTH_IN_BYTES],
94        }
95    }
96
97    /// Construct a self-authenticating ID from public key
98    pub fn self_authenticating<P: AsRef<[u8]>>(public_key: P) -> Self {
99        ExternalPrincipal::self_authenticating(public_key).into()
100    }
101
102    /// Construct an anonymous ID.
103    pub const fn anonymous() -> Self {
104        let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES];
105        bytes[0] = Self::ANONYMOUS_TAG;
106        Self { len: 1, bytes }
107    }
108
109    /// Returns `None` if the slice exceeds the max length.
110    const fn from_slice_core(slice: &[u8]) -> Option<Self> {
111        match slice.len() {
112            len @ 0..=Self::MAX_LENGTH_IN_BYTES => {
113                let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES];
114                let mut i = 0;
115                while i < len {
116                    bytes[i] = slice[i];
117                    i += 1;
118                }
119                Some(Self {
120                    len: len as u8,
121                    bytes,
122                })
123            }
124            _ => None,
125        }
126    }
127
128    /// Construct a [`Principal`] from a slice of bytes.
129    ///
130    /// # Panics
131    ///
132    /// Panics if the slice is longer than 29 bytes.
133    pub const fn from_slice(slice: &[u8]) -> Self {
134        match Self::from_slice_core(slice) {
135            Some(principal) => principal,
136            _ => panic!("slice length exceeds capacity"),
137        }
138    }
139
140    /// Construct a [`Principal`] from a slice of bytes.
141    pub const fn try_from_slice(slice: &[u8]) -> Result<Self, PrincipalError> {
142        match Self::from_slice_core(slice) {
143            Some(principal) => Ok(principal),
144            None => Err(PrincipalError::BytesTooLong()),
145        }
146    }
147
148    /// Parse a [`Principal`] from text representation.
149    pub fn from_text<S: AsRef<str>>(text: S) -> Result<Self, PrincipalError> {
150        let input = text.as_ref();
151        if input.len() > Self::MAX_LENGTH_IN_TEXT {
152            return Err(PrincipalError::TextTooLong());
153        }
154        let mut encoded = [0u8; Self::MAX_LENGTH_IN_TEXT];
155        let mut len = 0;
156        for byte in input.bytes().filter(|&byte| byte != b'-') {
157            encoded[len] = byte.to_ascii_uppercase();
158            len += 1;
159        }
160        let encoding = data_encoding::BASE32_NOPAD;
161        let size = encoding
162            .decode_len(len)
163            .map_err(|_| PrincipalError::InvalidBase32())?;
164        let mut decoded = [0u8; 39]; // floor(63 * 5 / 8), before dash validation.
165        let size = encoding
166            .decode_mut(&encoded[..len], &mut decoded[..size])
167            .map_err(|_| PrincipalError::InvalidBase32())?;
168        if size < Self::CRC_LENGTH_IN_BYTES {
169            return Err(PrincipalError::TextTooShort());
170        }
171        let payload = &decoded[Self::CRC_LENGTH_IN_BYTES..size];
172        if payload.len() > Self::MAX_LENGTH_IN_BYTES {
173            return Err(PrincipalError::TextTooLong());
174        }
175        if crc32fast::hash(payload).to_be_bytes() != decoded[..Self::CRC_LENGTH_IN_BYTES] {
176            return Err(PrincipalError::CheckSequenceNotMatch());
177        }
178        let result = Self::from_slice(payload);
179        let mut output = [0u8; Self::MAX_LENGTH_IN_TEXT];
180        if !input.eq_ignore_ascii_case(result.write_text(&mut output)) {
181            return Err(PrincipalError::AbnormalGrouped(result));
182        }
183        Ok(result)
184    }
185
186    /// Convert [`Principal`] to text representation.
187    pub fn to_text(&self) -> String {
188        self.write_text(&mut [0u8; Self::MAX_LENGTH_IN_TEXT])
189            .to_owned()
190    }
191
192    /// Returns the [`Principal`]'s significant bytes as a slice: the first
193    /// [`len`](Self::len) bytes of the backing array, without the trailing
194    /// zero padding.
195    ///
196    /// Use [`as_fixed_bytes`](Self::as_fixed_bytes) to get the full,
197    /// fixed-size backing array instead.
198    #[inline]
199    pub const fn as_slice(&self) -> &[u8] {
200        self.bytes.split_at(self.len as usize).0
201    }
202
203    /// Returns the number of significant bytes in the [`Principal`], between
204    /// `0` and [`MAX_LENGTH_IN_BYTES`](Self::MAX_LENGTH_IN_BYTES) inclusive.
205    ///
206    /// This equals the length of the slice returned by
207    /// [`as_slice`](Self::as_slice).
208    #[inline]
209    #[allow(clippy::len_without_is_empty)]
210    pub const fn len(&self) -> u8 {
211        self.len
212    }
213
214    /// Returns a reference to the [`Principal`]'s fixed-size backing array.
215    ///
216    /// The array is always [`MAX_LENGTH_IN_BYTES`](Self::MAX_LENGTH_IN_BYTES)
217    /// long; bytes from index [`len`](Self::len) onwards are always zero.
218    /// Prefer [`as_slice`](Self::as_slice) for the significant bytes without
219    /// padding.
220    #[inline]
221    pub const fn as_fixed_bytes(&self) -> &[u8; Self::MAX_LENGTH_IN_BYTES] {
222        &self.bytes
223    }
224}
225
226impl std::fmt::Display for Principal {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        f.write_str(self.write_text(&mut [0u8; Self::MAX_LENGTH_IN_TEXT]))
229    }
230}
231
232impl std::str::FromStr for Principal {
233    type Err = PrincipalError;
234
235    fn from_str(s: &str) -> Result<Self, Self::Err> {
236        Principal::from_text(s)
237    }
238}
239
240impl TryFrom<&str> for Principal {
241    type Error = PrincipalError;
242
243    fn try_from(s: &str) -> Result<Self, Self::Error> {
244        Principal::from_text(s)
245    }
246}
247
248impl TryFrom<Vec<u8>> for Principal {
249    type Error = PrincipalError;
250
251    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
252        Self::try_from(bytes.as_slice())
253    }
254}
255
256impl TryFrom<&Vec<u8>> for Principal {
257    type Error = PrincipalError;
258
259    fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
260        Self::try_from(bytes.as_slice())
261    }
262}
263
264impl TryFrom<&[u8]> for Principal {
265    type Error = PrincipalError;
266
267    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
268        Self::try_from_slice(bytes)
269    }
270}
271
272impl AsRef<[u8]> for Principal {
273    fn as_ref(&self) -> &[u8] {
274        self.as_slice()
275    }
276}
277
278// Serialization
279impl serde::Serialize for Principal {
280    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
281        if serializer.is_human_readable() {
282            self.to_text().serialize(serializer)
283        } else {
284            serializer.serialize_bytes(self.as_slice())
285        }
286    }
287}
288
289// Deserialization
290mod deserialize {
291    use super::Principal;
292    use std::convert::TryFrom;
293
294    // Simple visitor for deserialization from bytes. We don't support other number types
295    // as there's no need for it.
296    pub(super) struct PrincipalVisitor;
297
298    impl serde::de::Visitor<'_> for PrincipalVisitor {
299        type Value = super::Principal;
300
301        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302            formatter.write_str("bytes or string")
303        }
304
305        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
306        where
307            E: serde::de::Error,
308        {
309            Principal::from_text(v).map_err(E::custom)
310        }
311
312        fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
313        where
314            E: serde::de::Error,
315        {
316            Principal::try_from(value).map_err(E::custom)
317        }
318        // Owned and borrowed binary bytes carry the same identity.
319        fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
320        where
321            E: serde::de::Error,
322        {
323            Principal::try_from_slice(&v).map_err(E::custom)
324        }
325    }
326}
327
328impl<'de> serde::Deserialize<'de> for Principal {
329    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Principal, D::Error> {
330        use serde::de::Error;
331        if deserializer.is_human_readable() {
332            deserializer
333                .deserialize_str(deserialize::PrincipalVisitor)
334                .map_err(D::Error::custom)
335        } else {
336            deserializer
337                .deserialize_bytes(deserialize::PrincipalVisitor)
338                .map_err(D::Error::custom)
339        }
340    }
341}
342
343/// Original registry identity required by external IC clients and DTOs.
344/// Convert explicitly at the boundary; owned application payloads use [Principal].
345pub use ic_principal::Principal as ExternalPrincipal;
346/// Original parse errors for the explicitly external principal identity.
347pub use ic_principal::PrincipalError as ExternalPrincipalError;
348
349impl From<ExternalPrincipal> for Principal {
350    fn from(value: ExternalPrincipal) -> Self {
351        Self::from_slice(value.as_slice())
352    }
353}
354impl From<Principal> for ExternalPrincipal {
355    fn from(value: Principal) -> Self {
356        Self::from_slice(value.as_slice())
357    }
358}
359impl std::fmt::Display for PrincipalError {
360    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361        match self {
362            Self::BytesTooLong() => f.write_str("Bytes is longer than 29 bytes."),
363            Self::InvalidBase32() => f.write_str("Text must be in valid Base32 encoding."),
364            Self::TextTooShort() => f.write_str("Text is too short."),
365            Self::TextTooLong() => f.write_str("Text is too long."),
366            Self::CheckSequenceNotMatch() => f.write_str(
367                "CRC32 check sequence doesn't match with calculated from Principal bytes.",
368            ),
369            Self::AbnormalGrouped(principal) => write!(
370                f,
371                "Text should be separated by - (dash) every 5 characters: expected \"{principal}\""
372            ),
373        }
374    }
375}
376impl std::error::Error for PrincipalError {}
377
378impl Principal {
379    fn write_text<'a>(&self, output: &'a mut [u8; Self::MAX_LENGTH_IN_TEXT]) -> &'a str {
380        let mut input = [0u8; 33];
381        input[..4].copy_from_slice(&crc32fast::hash(self.as_slice()).to_be_bytes());
382        input[4..4 + self.len as usize].copy_from_slice(self.as_slice());
383        let encoding = data_encoding::BASE32_NOPAD;
384        let mut encoded = [0u8; 53];
385        let count = encoding.encode_len(4 + self.len as usize);
386        encoding.encode_mut(&input[..4 + self.len as usize], &mut encoded[..count]);
387        let mut written = 0;
388        for (i, byte) in encoded[..count].iter().enumerate() {
389            if i > 0 && i % 5 == 0 {
390                output[written] = b'-';
391                written += 1;
392            }
393            output[written] = byte.to_ascii_lowercase();
394            written += 1;
395        }
396        std::str::from_utf8(&output[..written]).expect("Base32 is ASCII")
397    }
398}
399
400#[cfg(test)]
401mod tests;