miden-crypto/src/utils/mod.rs
src/utils/mod.rsRaw
1//! Utilities used in this crate which can also be generally useful downstream.
2
3use alloc::string::String;
4use core::fmt::{self, Display, Write};
5
6use super::Word;
7
8mod kv_map;
9
10// RE-EXPORTS
11// ================================================================================================
12
13pub use winter_utils::{
14 uninit_vector, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
15 SliceReader,
16};
17
18pub mod collections {
19 pub use super::kv_map::*;
20}
21
22// UTILITY FUNCTIONS
23// ================================================================================================
24
25/// Converts a [Word] into hex.
26pub fn word_to_hex(w: &Word) -> Result<String, fmt::Error> {
27 let mut s = String::new();
28
29 for byte in w.iter().flat_map(|e| e.to_bytes()) {
30 write!(s, "{byte:02x}")?;
31 }
32
33 Ok(s)
34}
35
36/// Renders an array of bytes as hex into a String.
37pub fn bytes_to_hex_string<const N: usize>(data: [u8; N]) -> String {
38 let mut s = String::with_capacity(N + 2);
39
40 s.push_str("0x");
41 for byte in data.iter() {
42 write!(s, "{byte:02x}").expect("formatting hex failed");
43 }
44
45 s
46}
47
48/// Defines errors which can occur during parsing of hexadecimal strings.
49#[derive(Debug)]
50pub enum HexParseError {
51 InvalidLength { expected: usize, actual: usize },
52 MissingPrefix,
53 InvalidChar,
54 OutOfRange,
55}
56
57impl Display for HexParseError {
58 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59 match self {
60 HexParseError::InvalidLength { expected, actual } => {
61 write!(f, "Hex encoded RpoDigest must have length 66, including the 0x prefix. expected {expected} got {actual}")
62 }
63 HexParseError::MissingPrefix => {
64 write!(f, "Hex encoded RpoDigest must start with 0x prefix")
65 }
66 HexParseError::InvalidChar => {
67 write!(f, "Hex encoded RpoDigest must contain characters [a-zA-Z0-9]")
68 }
69 HexParseError::OutOfRange => {
70 write!(f, "Hex encoded values of an RpoDigest must be inside the field modulus")
71 }
72 }
73 }
74}
75
76#[cfg(feature = "std")]
77impl std::error::Error for HexParseError {}
78
79/// Parses a hex string into an array of bytes of known size.
80pub fn hex_to_bytes<const N: usize>(value: &str) -> Result<[u8; N], HexParseError> {
81 let expected: usize = (N * 2) + 2;
82 if value.len() != expected {
83 return Err(HexParseError::InvalidLength { expected, actual: value.len() });
84 }
85
86 if !value.starts_with("0x") {
87 return Err(HexParseError::MissingPrefix);
88 }
89
90 let mut data = value.bytes().skip(2).map(|v| match v {
91 b'0'..=b'9' => Ok(v - b'0'),
92 b'a'..=b'f' => Ok(v - b'a' + 10),
93 b'A'..=b'F' => Ok(v - b'A' + 10),
94 _ => Err(HexParseError::InvalidChar),
95 });
96
97 let mut decoded = [0u8; N];
98 for byte in decoded.iter_mut() {
99 // These `unwrap` calls are okay because the length was checked above
100 let high: u8 = data.next().unwrap()?;
101 let low: u8 = data.next().unwrap()?;
102 *byte = (high << 4) + low;
103 }
104
105 Ok(decoded)
106}