snarkvm_console_program/data/identifier/
mod.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16mod bytes;
17mod equal;
18mod from_bits;
19mod from_field;
20mod parse;
21mod serialize;
22mod size_in_bits;
23mod to_bits;
24mod to_field;
25
26use snarkvm_console_network::Network;
27use snarkvm_console_types::{Field, prelude::*};
28
29/// An identifier is an **immutable** UTF-8 string,
30/// represented as a **constant** field element in the CurrentNetwork.
31///
32/// # Requirements
33/// The identifier must not be an empty string.
34/// The identifier must not start with a number.
35/// The identifier must be alphanumeric, and may include underscores.
36/// The identifier must not consist solely of underscores.
37/// The identifier must fit within the data capacity of a base field element.
38#[derive(Copy, Clone)]
39pub struct Identifier<N: Network>(Field<N>, u8); // Number of bytes in the identifier.
40
41impl<N: Network> From<&Identifier<N>> for Identifier<N> {
42    /// Returns a copy of the identifier.
43    fn from(identifier: &Identifier<N>) -> Self {
44        *identifier
45    }
46}
47
48impl<N: Network> TryFrom<String> for Identifier<N> {
49    type Error = Error;
50
51    /// Initializes an identifier from a string.
52    fn try_from(identifier: String) -> Result<Self> {
53        Self::from_str(&identifier)
54    }
55}
56
57impl<N: Network> TryFrom<&String> for Identifier<N> {
58    type Error = Error;
59
60    /// Initializes an identifier from a string.
61    fn try_from(identifier: &String) -> Result<Self> {
62        Self::from_str(identifier)
63    }
64}
65
66impl<N: Network> TryFrom<&str> for Identifier<N> {
67    type Error = Error;
68
69    /// Initializes an identifier from a string.
70    fn try_from(identifier: &str) -> Result<Self> {
71        Self::from_str(identifier)
72    }
73}
74
75#[cfg(test)]
76pub(crate) mod tests {
77    use super::*;
78    use snarkvm_console_network::MainnetV0;
79
80    type CurrentNetwork = MainnetV0;
81
82    const ITERATIONS: usize = 100;
83
84    /// Samples a random identifier.
85    pub(crate) fn sample_identifier<N: Network>(rng: &mut TestRng) -> Result<Identifier<N>> {
86        // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
87        let string = sample_identifier_as_string::<N>(rng)?;
88        // Recover the field element from the bits.
89        let field = Field::<N>::from_bits_le(&string.as_bytes().to_bits_le())?;
90        // Return the identifier.
91        Ok(Identifier(field, u8::try_from(string.len()).or_halt_with::<CurrentNetwork>("Invalid identifier length")))
92    }
93
94    /// Samples a random identifier as a string.
95    pub(crate) fn sample_identifier_as_string<N: Network>(rng: &mut TestRng) -> Result<String> {
96        // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
97        let string = "a".to_string()
98            + &rng
99                .sample_iter(&Alphanumeric)
100                .take(Field::<N>::size_in_data_bits() / (8 * 2))
101                .map(char::from)
102                .collect::<String>();
103        // Ensure identifier fits within the data capacity of the base field.
104        let max_bytes = Field::<N>::size_in_data_bits() / 8; // Note: This intentionally rounds down.
105        match string.len() <= max_bytes {
106            // Return the identifier.
107            true => Ok(string),
108            false => bail!("Identifier exceeds the maximum capacity allowed"),
109        }
110    }
111
112    /// Samples a random lowercase identifier as a string.
113    pub(crate) fn sample_lowercase_identifier_as_string<N: Network>(rng: &mut TestRng) -> Result<String> {
114        // Sample a random identifier.
115        let string = sample_identifier_as_string::<N>(rng)?;
116        // Return the identifier as lowercase.
117        Ok(string.to_lowercase())
118    }
119
120    #[test]
121    fn test_try_from() -> Result<()> {
122        let mut rng = TestRng::default();
123
124        for _ in 0..ITERATIONS {
125            // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
126            let expected_string = sample_identifier_as_string::<CurrentNetwork>(&mut rng)?;
127            // Recover the field element from the bits.
128            let expected_field = Field::<CurrentNetwork>::from_bits_le(&expected_string.as_bytes().to_bits_le())?;
129
130            // Try to initialize an identifier from the string.
131            let candidate = Identifier::<CurrentNetwork>::try_from(expected_string.as_str())?;
132            assert_eq!(expected_field, candidate.0);
133            assert_eq!(expected_string.len(), candidate.1 as usize);
134        }
135        Ok(())
136    }
137
138    #[test]
139    fn test_identifier_try_from_illegal() {
140        assert!(Identifier::<CurrentNetwork>::try_from("123").is_err());
141        assert!(Identifier::<CurrentNetwork>::try_from("abc\x08def").is_err());
142        assert!(Identifier::<CurrentNetwork>::try_from("abc\u{202a}def").is_err());
143    }
144}