snarkvm_circuit_types_string/
lib.rs

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