Skip to main content

snarkvm_circuit_program/data/identifier/
mod.rs

1// Copyright (c) 2019-2026 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
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_console_program::ToField as TF;
29use snarkvm_utilities::ToBits as TB;
30
31/// An identifier is an **immutable** UTF-8 string,
32/// represented as a **constant** field element in the circuit.
33///
34/// # Requirements
35/// The identifier must not be an empty string.
36/// The identifier must not start with a number.
37/// The identifier must be alphanumeric, and may include underscores.
38/// The identifier must not consist solely of underscores.
39/// The identifier must fit within the data capacity of a base field element.
40#[derive(Clone)]
41pub struct Identifier<A: Aleo>(Field<A>, u8); // Number of bytes in the identifier.
42
43impl<A: Aleo> Identifier<A> {
44    /// Returns a constant identifier.
45    pub fn constant(identifier: console::Identifier<A::Network>) -> Self {
46        // Convert the identifier to a string to obtain the byte-level string length.
47        // The `Identifier` struct stores this length as its second field (used during
48        // serialization/deserialization), but `console::Identifier::to_field()` does not
49        // expose it directly, so a String round-trip is the canonical way to recover it.
50        let identifier_string = 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_string.to_bits_le()));
55
56        // Return the identifier.
57        Self(field, identifier_string.len() as u8)
58    }
59
60    /// Returns a public identifier.
61    /// Note: This method should be used cautiously since identifiers typically should be constant.
62    /// Public mode is used when the target of a dynamic call is witnessed as a public variable,
63    /// allowing both the prover and verifier to agree on the callee at verification time.
64    pub fn public(identifier: console::Identifier<A::Network>) -> Self {
65        // Get the identifier string length.
66        let identifier_string = identifier.to_string();
67
68        // Initialize the field as public variable.
69        let field = match identifier.to_field() {
70            Ok(field) => Field::new(Mode::Public, field),
71            Err(error) => A::halt(format!("Failed to convert an identifier to a field: {error}")),
72        };
73
74        Self(field, identifier_string.len() as u8)
75    }
76}
77
78impl<A: Aleo> Eject for Identifier<A> {
79    type Primitive = console::Identifier<A::Network>;
80
81    /// Ejects the mode of the identifier.
82    fn eject_mode(&self) -> Mode {
83        // Eject the mode.
84        let mode = self.0.eject_mode();
85
86        debug_assert!(mode != Mode::Private, "Identifier::eject_mode - Mode cannot be 'Private'");
87        mode
88    }
89
90    /// Ejects the identifier as a string.
91    fn eject_value(&self) -> Self::Primitive {
92        match console::FromField::from_field(&self.0.eject_value()) {
93            Ok(identifier) => identifier,
94            Err(error) => A::halt(format!("Failed to convert an identifier to a string: {error}")),
95        }
96    }
97}
98
99impl<A: Aleo> Parser for Identifier<A> {
100    /// Parses a UTF-8 string into an identifier.
101    #[inline]
102    fn parse(string: &str) -> ParserResult<Self> {
103        // Parse the identifier from the string.
104        let (string, identifier) = console::Identifier::parse(string)?;
105
106        Ok((string, Identifier::constant(identifier)))
107    }
108}
109
110impl<A: Aleo> FromStr for Identifier<A> {
111    type Err = Error;
112
113    /// Parses a UTF-8 string into an identifier.
114    #[inline]
115    fn from_str(string: &str) -> Result<Self> {
116        match Self::parse(string) {
117            Ok((remainder, object)) => {
118                // Ensure the remainder is empty.
119                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
120                // Return the object.
121                Ok(object)
122            }
123            Err(error) => bail!("Failed to parse string. {error}"),
124        }
125    }
126}
127
128impl<A: Aleo> Debug for Identifier<A> {
129    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
130        Display::fmt(self, f)
131    }
132}
133
134impl<A: Aleo> Display for Identifier<A> {
135    /// Prints the identifier as a string.
136    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
137        write!(f, "{}", self.eject_value())
138    }
139}
140
141impl<A: Aleo> Eq for Identifier<A> {}
142
143impl<A: Aleo> PartialEq for Identifier<A> {
144    /// Implements the `Eq` trait for the identifier.
145    fn eq(&self, other: &Self) -> bool {
146        self.0.eject_value() == other.0.eject_value()
147    }
148}
149
150impl<A: Aleo> core::hash::Hash for Identifier<A> {
151    /// Implements the `Hash` trait for the identifier.
152    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
153        self.0.eject_value().hash(state);
154    }
155}
156
157impl<A: Aleo> From<Identifier<A>> for LinearCombination<A::BaseField> {
158    /// Note: Identifier is always `Mode::Constant`.
159    fn from(identifier: Identifier<A>) -> Self {
160        From::from(&identifier)
161    }
162}
163
164impl<A: Aleo> From<&Identifier<A>> for LinearCombination<A::BaseField> {
165    /// Note: Identifier is always `Mode::Constant`.
166    fn from(identifier: &Identifier<A>) -> Self {
167        LinearCombination::from(&identifier.0)
168    }
169}
170
171#[cfg(test)]
172pub(crate) mod tests {
173    use super::*;
174    use crate::Circuit;
175    use console::{Rng, TestRng};
176
177    use anyhow::{Result, bail};
178    use core::str::FromStr;
179    use rand::distributions::Alphanumeric;
180
181    /// Samples a random identifier.
182    pub(crate) fn sample_console_identifier<A: Aleo>() -> Result<console::Identifier<A::Network>> {
183        // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
184        let string = sample_console_identifier_as_string::<A>()?;
185        // Return the identifier.
186        console::Identifier::from_str(&string)
187    }
188
189    /// Samples a random identifier as a string.
190    pub(crate) fn sample_console_identifier_as_string<A: Aleo>() -> Result<String> {
191        // Initialize a test RNG.
192        let rng = &mut TestRng::default();
193        // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
194        let string = "a".to_string()
195            + &rng
196                .sample_iter(&Alphanumeric)
197                .take(A::BaseField::size_in_data_bits() / (8 * 2))
198                .map(char::from)
199                .collect::<String>();
200        // Ensure identifier fits within the data capacity of the base field.
201        let max_bytes = A::BaseField::size_in_data_bits() / 8; // Note: This intentionally rounds down.
202        match string.len() <= max_bytes {
203            // Return the identifier.
204            true => Ok(string),
205            false => bail!("Identifier exceeds the maximum capacity allowed"),
206        }
207    }
208
209    /// Samples a random identifier as a string.
210    pub(crate) fn sample_lowercase_console_identifier_as_string<A: Aleo>() -> Result<String> {
211        // Sample a random identifier.
212        let string = sample_console_identifier_as_string::<A>()?;
213        // Return the identifier as lowercase.
214        Ok(string.to_lowercase())
215    }
216
217    #[test]
218    fn test_identifier_parse() -> Result<()> {
219        let candidate = Identifier::<Circuit>::parse("foo_bar").unwrap();
220        assert_eq!("", candidate.0);
221        assert_eq!(Identifier::<Circuit>::constant("foo_bar".try_into()?).eject(), candidate.1.eject());
222        Ok(())
223    }
224
225    #[test]
226    fn test_identifier_parse_fails() -> Result<()> {
227        // Must be alphanumeric or underscore.
228        let identifier = Identifier::<Circuit>::parse("foo_bar~baz").unwrap();
229        assert_eq!(("~baz", Identifier::<Circuit>::from_str("foo_bar")?.eject()), (identifier.0, identifier.1.eject()));
230        let identifier = Identifier::<Circuit>::parse("foo_bar-baz").unwrap();
231        assert_eq!(("-baz", Identifier::<Circuit>::from_str("foo_bar")?.eject()), (identifier.0, identifier.1.eject()));
232
233        // Must not be solely underscores.
234        assert!(Identifier::<Circuit>::parse("_").is_err());
235        assert!(Identifier::<Circuit>::parse("__").is_err());
236        assert!(Identifier::<Circuit>::parse("___").is_err());
237        assert!(Identifier::<Circuit>::parse("____").is_err());
238
239        // Must not start with a number.
240        assert!(Identifier::<Circuit>::parse("1").is_err());
241        assert!(Identifier::<Circuit>::parse("2").is_err());
242        assert!(Identifier::<Circuit>::parse("3").is_err());
243        assert!(Identifier::<Circuit>::parse("1foo").is_err());
244        assert!(Identifier::<Circuit>::parse("12").is_err());
245        assert!(Identifier::<Circuit>::parse("111").is_err());
246
247        // Must fit within the data capacity of a base field element.
248        let identifier =
249            Identifier::<Circuit>::parse("foo_bar_baz_qux_quux_quuz_corge_grault_garply_waldo_fred_plugh_xyzzy");
250        assert!(identifier.is_err());
251        Ok(())
252    }
253
254    #[test]
255    fn test_identifier_display() -> Result<()> {
256        let identifier = Identifier::<Circuit>::from_str("foo_bar")?;
257        assert_eq!("foo_bar", format!("{identifier}"));
258        Ok(())
259    }
260
261    #[test]
262    fn test_identifier_bits() -> Result<()> {
263        let identifier = Identifier::<Circuit>::from_str("foo_bar")?;
264        assert_eq!(
265            identifier.to_bits_le().eject(),
266            Identifier::from_bits_le(&identifier.to_bits_le()).to_bits_le().eject()
267        );
268        assert_eq!(
269            identifier.to_bits_be().eject(),
270            Identifier::from_bits_be(&identifier.to_bits_be()).to_bits_be().eject()
271        );
272        Ok(())
273    }
274}