snarkvm_circuit_program/data/identifier/
mod.rs

1// Copyright 2024-2025 Aleo Network Foundation
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
16#[cfg(test)]
17use snarkvm_circuit_types::environment::assert_scope;
18
19mod equal;
20mod from_bits;
21mod from_field;
22mod size_in_bits;
23mod to_bits;
24mod to_field;
25
26use snarkvm_circuit_network::Aleo;
27use snarkvm_circuit_types::{Boolean, Field, U8, environment::prelude::*};
28use snarkvm_utilities::ToBits as TB;
29
30/// An identifier is an **immutable** UTF-8 string,
31/// represented as a **constant** field element in the circuit.
32///
33/// # Requirements
34/// The identifier must not be an empty string.
35/// The identifier must not start with a number.
36/// The identifier must be alphanumeric, and may include underscores.
37/// The identifier must not consist solely of underscores.
38/// The identifier must fit within the data capacity of a base field element.
39#[derive(Clone)]
40pub struct Identifier<A: Aleo>(Field<A>, u8); // Number of bytes in the identifier.
41
42#[cfg(feature = "console")]
43impl<A: Aleo> Inject for Identifier<A> {
44    type Primitive = console::Identifier<A::Network>;
45
46    /// Initializes a new identifier from a string.
47    /// Note: Identifiers are always `Mode::Constant`.
48    fn new(_: Mode, identifier: Self::Primitive) -> Self {
49        // Convert the identifier to a string to check its validity.
50        let identifier = identifier.to_string();
51
52        // Note: The string bytes themselves are **not** little-endian. Rather, they are order-preserving
53        // for reconstructing the string when recovering the field element back into bytes.
54        let field = Field::from_bits_le(&Vec::<Boolean<_>>::constant(identifier.to_bits_le()));
55
56        // Return the identifier.
57        Self(field, identifier.len() as u8)
58    }
59}
60
61#[cfg(feature = "console")]
62impl<A: Aleo> Eject for Identifier<A> {
63    type Primitive = console::Identifier<A::Network>;
64
65    /// Ejects the mode of the identifier.
66    fn eject_mode(&self) -> Mode {
67        debug_assert!(self.0.eject_mode() == Mode::Constant, "Identifier::eject_mode - Mode must be 'Constant'");
68        Mode::Constant
69    }
70
71    /// Ejects the identifier as a string.
72    fn eject_value(&self) -> Self::Primitive {
73        match console::FromField::from_field(&self.0.eject_value()) {
74            Ok(identifier) => identifier,
75            Err(error) => A::halt(format!("Failed to convert an identifier to a string: {error}")),
76        }
77    }
78}
79
80#[cfg(feature = "console")]
81impl<A: Aleo> Parser for Identifier<A> {
82    /// Parses a UTF-8 string into an identifier.
83    #[inline]
84    fn parse(string: &str) -> ParserResult<Self> {
85        // Parse the identifier from the string.
86        let (string, identifier) = console::Identifier::parse(string)?;
87
88        Ok((string, Identifier::constant(identifier)))
89    }
90}
91
92#[cfg(feature = "console")]
93impl<A: Aleo> FromStr for Identifier<A> {
94    type Err = Error;
95
96    /// Parses a UTF-8 string into an identifier.
97    #[inline]
98    fn from_str(string: &str) -> Result<Self> {
99        match Self::parse(string) {
100            Ok((remainder, object)) => {
101                // Ensure the remainder is empty.
102                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
103                // Return the object.
104                Ok(object)
105            }
106            Err(error) => bail!("Failed to parse string. {error}"),
107        }
108    }
109}
110
111#[cfg(feature = "console")]
112impl<A: Aleo> Debug for Identifier<A> {
113    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
114        Display::fmt(self, f)
115    }
116}
117
118#[cfg(feature = "console")]
119impl<A: Aleo> Display for Identifier<A> {
120    /// Prints the identifier as a string.
121    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
122        write!(f, "{}", self.eject_value())
123    }
124}
125
126impl<A: Aleo> Eq for Identifier<A> {}
127
128impl<A: Aleo> PartialEq for Identifier<A> {
129    /// Implements the `Eq` trait for the identifier.
130    fn eq(&self, other: &Self) -> bool {
131        self.0.eject_value() == other.0.eject_value()
132    }
133}
134
135impl<A: Aleo> core::hash::Hash for Identifier<A> {
136    /// Implements the `Hash` trait for the identifier.
137    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
138        self.0.eject_value().hash(state);
139    }
140}
141
142impl<A: Aleo> From<Identifier<A>> for LinearCombination<A::BaseField> {
143    /// Note: Identifier is always `Mode::Constant`.
144    fn from(identifier: Identifier<A>) -> Self {
145        From::from(&identifier)
146    }
147}
148
149impl<A: Aleo> From<&Identifier<A>> for LinearCombination<A::BaseField> {
150    /// Note: Identifier is always `Mode::Constant`.
151    fn from(identifier: &Identifier<A>) -> Self {
152        LinearCombination::from(&identifier.0)
153    }
154}
155
156#[cfg(all(test, feature = "console"))]
157pub(crate) mod tests {
158    use super::*;
159    use crate::Circuit;
160    use console::{Rng, TestRng};
161
162    use anyhow::{Result, bail};
163    use core::str::FromStr;
164    use rand::distributions::Alphanumeric;
165
166    /// Samples a random identifier.
167    pub(crate) fn sample_console_identifier<A: Aleo>() -> Result<console::Identifier<A::Network>> {
168        // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
169        let string = sample_console_identifier_as_string::<A>()?;
170        // Return the identifier.
171        console::Identifier::from_str(&string)
172    }
173
174    /// Samples a random identifier as a string.
175    pub(crate) fn sample_console_identifier_as_string<A: Aleo>() -> Result<String> {
176        // Initialize a test RNG.
177        let rng = &mut TestRng::default();
178        // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
179        let string = "a".to_string()
180            + &rng
181                .sample_iter(&Alphanumeric)
182                .take(A::BaseField::size_in_data_bits() / (8 * 2))
183                .map(char::from)
184                .collect::<String>();
185        // Ensure identifier fits within the data capacity of the base field.
186        let max_bytes = A::BaseField::size_in_data_bits() / 8; // Note: This intentionally rounds down.
187        match string.len() <= max_bytes {
188            // Return the identifier.
189            true => Ok(string),
190            false => bail!("Identifier exceeds the maximum capacity allowed"),
191        }
192    }
193
194    /// Samples a random identifier as a string.
195    pub(crate) fn sample_lowercase_console_identifier_as_string<A: Aleo>() -> Result<String> {
196        // Sample a random identifier.
197        let string = sample_console_identifier_as_string::<A>()?;
198        // Return the identifier as lowercase.
199        Ok(string.to_lowercase())
200    }
201
202    #[test]
203    fn test_identifier_parse() -> Result<()> {
204        let candidate = Identifier::<Circuit>::parse("foo_bar").unwrap();
205        assert_eq!("", candidate.0);
206        assert_eq!(Identifier::<Circuit>::constant("foo_bar".try_into()?).eject(), candidate.1.eject());
207        Ok(())
208    }
209
210    #[test]
211    fn test_identifier_parse_fails() -> Result<()> {
212        // Must be alphanumeric or underscore.
213        let identifier = Identifier::<Circuit>::parse("foo_bar~baz").unwrap();
214        assert_eq!(("~baz", Identifier::<Circuit>::from_str("foo_bar")?.eject()), (identifier.0, identifier.1.eject()));
215        let identifier = Identifier::<Circuit>::parse("foo_bar-baz").unwrap();
216        assert_eq!(("-baz", Identifier::<Circuit>::from_str("foo_bar")?.eject()), (identifier.0, identifier.1.eject()));
217
218        // Must not be solely underscores.
219        assert!(Identifier::<Circuit>::parse("_").is_err());
220        assert!(Identifier::<Circuit>::parse("__").is_err());
221        assert!(Identifier::<Circuit>::parse("___").is_err());
222        assert!(Identifier::<Circuit>::parse("____").is_err());
223
224        // Must not start with a number.
225        assert!(Identifier::<Circuit>::parse("1").is_err());
226        assert!(Identifier::<Circuit>::parse("2").is_err());
227        assert!(Identifier::<Circuit>::parse("3").is_err());
228        assert!(Identifier::<Circuit>::parse("1foo").is_err());
229        assert!(Identifier::<Circuit>::parse("12").is_err());
230        assert!(Identifier::<Circuit>::parse("111").is_err());
231
232        // Must fit within the data capacity of a base field element.
233        let identifier =
234            Identifier::<Circuit>::parse("foo_bar_baz_qux_quux_quuz_corge_grault_garply_waldo_fred_plugh_xyzzy");
235        assert!(identifier.is_err());
236        Ok(())
237    }
238
239    #[test]
240    fn test_identifier_display() -> Result<()> {
241        let identifier = Identifier::<Circuit>::from_str("foo_bar")?;
242        assert_eq!("foo_bar", format!("{identifier}"));
243        Ok(())
244    }
245
246    #[test]
247    fn test_identifier_bits() -> Result<()> {
248        let identifier = Identifier::<Circuit>::from_str("foo_bar")?;
249        assert_eq!(
250            identifier.to_bits_le().eject(),
251            Identifier::from_bits_le(&identifier.to_bits_le()).to_bits_le().eject()
252        );
253        assert_eq!(
254            identifier.to_bits_be().eject(),
255            Identifier::from_bits_be(&identifier.to_bits_be()).to_bits_be().eject()
256        );
257        Ok(())
258    }
259}