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