miden_protocol/utils/
strings.rs1use alloc::fmt;
2use alloc::string::String;
3
4use crate::Felt;
5use crate::errors::ShortCapitalStringError;
6
7#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
18pub(crate) struct ShortCapitalString(String);
19
20impl ShortCapitalString {
21 pub const MAX_LENGTH: usize = 12;
23
24 pub fn from_ascii_uppercase(
31 string: impl Into<String>,
32 ) -> Result<Self, ShortCapitalStringError> {
33 let string = string.into();
34 let char_count = string.chars().count();
35 if char_count == 0 || char_count > Self::MAX_LENGTH {
36 return Err(ShortCapitalStringError::InvalidLength(char_count));
37 }
38 for character in string.chars() {
39 if !character.is_ascii_uppercase() {
40 return Err(ShortCapitalStringError::InvalidCharacter);
41 }
42 }
43 Ok(Self(string))
44 }
45
46 pub fn from_ascii_uppercase_and_underscore(
53 string: impl Into<String>,
54 ) -> Result<Self, ShortCapitalStringError> {
55 let string = string.into();
56 let char_count = string.chars().count();
57 if char_count == 0 || char_count > Self::MAX_LENGTH {
58 return Err(ShortCapitalStringError::InvalidLength(char_count));
59 }
60 for character in string.chars() {
61 if !character.is_ascii_uppercase() && character != '_' {
62 return Err(ShortCapitalStringError::InvalidCharacter);
63 }
64 }
65 Ok(Self(string))
66 }
67
68 pub fn as_element(&self, alphabet: &str) -> Result<Felt, ShortCapitalStringError> {
84 debug_assert!(
85 alphabet.is_ascii(),
86 "ShortCapitalString::as_element: alphabet must be ASCII-only"
87 );
88 let alphabet_len = alphabet.len() as u64;
89 let mut encoded_value: u64 = 0;
90
91 for character in self.0.chars() {
92 let digit = alphabet
93 .chars()
94 .position(|c| c == character)
95 .map(|pos| pos as u64)
96 .ok_or(ShortCapitalStringError::InvalidCharacter)?;
97
98 encoded_value = encoded_value * alphabet_len + digit;
99 }
100
101 let char_len = self.0.chars().count() as u64;
103 encoded_value = encoded_value * alphabet_len + char_len;
104 Ok(Felt::new_unchecked(encoded_value))
105 }
106
107 pub fn try_from_encoded_felt(
124 encoded_string: Felt,
125 alphabet: &str,
126 min_encoded_value: u64,
127 max_encoded_value: u64,
128 ) -> Result<Self, ShortCapitalStringError> {
129 let encoded_value = encoded_string.as_canonical_u64();
130 if encoded_value < min_encoded_value {
131 return Err(ShortCapitalStringError::ValueTooSmall(encoded_value));
132 }
133 if encoded_value > max_encoded_value {
134 return Err(ShortCapitalStringError::ValueTooLarge(encoded_value));
135 }
136
137 debug_assert!(
138 alphabet.is_ascii(),
139 "ShortCapitalString::try_from_encoded_felt: alphabet must be ASCII-only"
140 );
141 let alphabet_len = alphabet.len() as u64;
142 let mut remaining_value = encoded_value;
143 let string_len = (remaining_value % alphabet_len) as usize;
144 if string_len == 0 || string_len > Self::MAX_LENGTH {
145 return Err(ShortCapitalStringError::InvalidLength(string_len));
146 }
147 remaining_value /= alphabet_len;
148
149 let mut decoded = String::with_capacity(string_len);
150 for _ in 0..string_len {
151 let digit = (remaining_value % alphabet_len) as usize;
152 let character =
153 alphabet.chars().nth(digit).ok_or(ShortCapitalStringError::InvalidCharacter)?;
154 decoded.insert(0, character);
155 remaining_value /= alphabet_len;
156 }
157
158 if remaining_value != 0 {
159 return Err(ShortCapitalStringError::DataNotFullyDecoded);
160 }
161
162 Ok(Self(decoded))
163 }
164}
165
166impl fmt::Display for ShortCapitalString {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 f.write_str(&self.0)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use alloc::string::{String, ToString};
175
176 use assert_matches::assert_matches;
177
178 use super::{Felt, ShortCapitalString};
179 use crate::errors::ShortCapitalStringError;
180
181 #[test]
182 fn short_capital_string_encode_decode_roundtrip() {
183 let short_string = ShortCapitalString::from_ascii_uppercase("MIDEN").unwrap();
184 let encoded = short_string.as_element("ABCDEFGHIJKLMNOPQRSTUVWXYZ").unwrap();
185 let decoded = ShortCapitalString::try_from_encoded_felt(
186 encoded,
187 "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
188 1,
189 2481152873203736562,
190 )
191 .unwrap();
192 assert_eq!(decoded.to_string(), "MIDEN");
193
194 let name = String::from("MIDEN");
195 let from_name = ShortCapitalString::from_ascii_uppercase(name).unwrap();
196 assert_eq!(from_name.to_string(), "MIDEN");
197 }
198
199 #[test]
200 fn short_capital_string_rejects_invalid_values() {
201 assert_matches!(
202 ShortCapitalString::from_ascii_uppercase("").unwrap_err(),
203 ShortCapitalStringError::InvalidLength(0)
204 );
205 assert_matches!(
206 ShortCapitalString::from_ascii_uppercase("ABCDEFGHIJKLM").unwrap_err(),
207 ShortCapitalStringError::InvalidLength(13)
208 );
209 assert_matches!(
210 ShortCapitalString::from_ascii_uppercase("A_B").unwrap_err(),
211 ShortCapitalStringError::InvalidCharacter
212 );
213
214 assert_matches!(
215 ShortCapitalString::from_ascii_uppercase_and_underscore("MINTER-ADMIN").unwrap_err(),
216 ShortCapitalStringError::InvalidCharacter
217 );
218
219 let err = ShortCapitalString::try_from_encoded_felt(
220 Felt::ZERO,
221 "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
222 1,
223 2481152873203736562,
224 )
225 .unwrap_err();
226 assert_matches!(err, ShortCapitalStringError::ValueTooSmall(0));
227 }
228}