Skip to main content

snarkvm_circuit_types_string/
lib.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#![forbid(unsafe_code)]
17#![cfg_attr(test, allow(clippy::assertions_on_result_states))]
18
19extern crate snarkvm_console_types_string as console;
20
21pub mod identifier_literal;
22pub use identifier_literal::IdentifierLiteral;
23
24mod equal;
25mod helpers;
26
27#[cfg(test)]
28use console::TestRng;
29#[cfg(test)]
30use snarkvm_circuit_environment::assert_scope;
31
32use snarkvm_circuit_environment::prelude::*;
33use snarkvm_circuit_types_boolean::Boolean;
34use snarkvm_circuit_types_field::Field;
35use snarkvm_circuit_types_integers::U8;
36
37#[derive(Clone)]
38pub struct StringType<E: Environment> {
39    mode: Mode,
40    bytes: Vec<U8<E>>,
41    size_in_bytes: Field<E>,
42}
43
44impl<E: Environment> StringTrait for StringType<E> {}
45
46impl<E: Environment> Inject for StringType<E> {
47    type Primitive = console::StringType<E::Network>;
48
49    /// Initializes a new instance of a string.
50    fn new(mode: Mode, string: Self::Primitive) -> Self {
51        // Cast the number of bytes in the 'string' as a field element.
52        let num_bytes =
53            console::Field::from_u32(u32::try_from(string.len()).unwrap_or_else(|error| E::halt(error.to_string())));
54
55        // "Load-bearing witness allocation - Please do not optimize me." - Pratyush :)
56
57        // Inject the number of bytes as a constant.
58        let expected_size_in_bytes = Field::constant(num_bytes);
59        // Inject the number of bytes as a witness.
60        let size_in_bytes = match mode.is_constant() {
61            true => expected_size_in_bytes.clone(),
62            false => Field::new(Mode::Private, num_bytes),
63        };
64        // Ensure the witness matches the constant.
65        E::assert_eq(&expected_size_in_bytes, &size_in_bytes)
66            .expect("String size witness does not match expected size");
67
68        Self {
69            mode,
70            bytes: string.as_bytes().iter().map(|byte| U8::new(mode, console::Integer::new(*byte))).collect(),
71            size_in_bytes,
72        }
73    }
74}
75
76impl<E: Environment> Eject for StringType<E> {
77    type Primitive = console::StringType<E::Network>;
78
79    /// Ejects the mode of the string.
80    fn eject_mode(&self) -> Mode {
81        match self.bytes.is_empty() {
82            true => self.mode,
83            false => self.bytes.eject_mode(),
84        }
85    }
86
87    /// Ejects the string as a string literal.
88    fn eject_value(&self) -> Self::Primitive {
89        // Ensure the string is within the allowed capacity.
90        let num_bytes = self.bytes.len();
91        match num_bytes <= E::MAX_STRING_BYTES as usize {
92            true => console::StringType::new(
93                &String::from_utf8(self.bytes.eject_value().into_iter().map(|byte| *byte).collect())
94                    .unwrap_or_else(|error| E::halt(format!("Failed to eject a string value: {error}"))),
95            ),
96            false => E::halt(format!("Attempted to eject a string of size {num_bytes}")),
97        }
98    }
99}
100
101impl<E: Environment> Parser for StringType<E> {
102    /// Parses a string into a string circuit.
103    #[inline]
104    fn parse(string: &str) -> ParserResult<Self> {
105        // Parse the content from the string.
106        let (string, content) = console::StringType::parse(string)?;
107        // Parse the mode from the string.
108        let (string, mode) = opt(pair(tag("."), Mode::parse))(string)?;
109
110        match mode {
111            Some((_, mode)) => Ok((string, StringType::new(mode, content))),
112            None => Ok((string, StringType::new(Mode::Constant, content))),
113        }
114    }
115}
116
117impl<E: Environment> FromStr for StringType<E> {
118    type Err = Error;
119
120    /// Parses a string into a string circuit.
121    #[inline]
122    fn from_str(string: &str) -> Result<Self> {
123        match Self::parse(string) {
124            Ok((remainder, object)) => {
125                // Ensure the remainder is empty.
126                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
127                // Return the object.
128                Ok(object)
129            }
130            Err(error) => bail!("Failed to parse string. {error}"),
131        }
132    }
133}
134
135impl<E: Environment> TypeName for StringType<E> {
136    /// Returns the type name of the circuit as a string.
137    #[inline]
138    fn type_name() -> &'static str {
139        console::StringType::<E::Network>::type_name()
140    }
141}
142
143impl<E: Environment> Debug for StringType<E> {
144    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145        Display::fmt(self, f)
146    }
147}
148
149impl<E: Environment> Display for StringType<E> {
150    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151        write!(f, "{}.{}", self.eject_value(), self.eject_mode())
152    }
153}