auths_verifier/tlog/
types.rs1use std::fmt;
4
5use base64::{Engine, engine::general_purpose::STANDARD};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use sha2::{Digest, Sha256};
8
9use super::error::TransparencyError;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
22pub struct MerkleHash([u8; 32]);
23
24impl MerkleHash {
25 pub const EMPTY: Self = Self([0u8; 32]);
27
28 pub fn from_bytes(bytes: [u8; 32]) -> Self {
30 Self(bytes)
31 }
32
33 pub fn from_hex(s: &str) -> Result<Self, TransparencyError> {
35 let bytes = hex::decode(s).map_err(|e| TransparencyError::InvalidProof(e.to_string()))?;
36 let arr: [u8; 32] = bytes
37 .try_into()
38 .map_err(|_| TransparencyError::InvalidProof("hash must be 32 bytes".into()))?;
39 Ok(Self(arr))
40 }
41
42 pub fn as_bytes(&self) -> &[u8; 32] {
44 &self.0
45 }
46
47 pub fn to_base64(&self) -> String {
49 STANDARD.encode(self.0)
50 }
51
52 pub fn from_base64(s: &str) -> Result<Self, TransparencyError> {
54 let bytes = STANDARD
55 .decode(s)
56 .map_err(|e| TransparencyError::InvalidProof(format!("base64 decode: {e}")))?;
57 let arr: [u8; 32] = bytes
58 .try_into()
59 .map_err(|_| TransparencyError::InvalidProof("hash must be 32 bytes".into()))?;
60 Ok(Self(arr))
61 }
62
63 pub fn sha256(data: &[u8]) -> Self {
65 let digest = Sha256::digest(data);
66 let mut out = [0u8; 32];
67 out.copy_from_slice(&digest);
68 Self(out)
69 }
70}
71
72impl fmt::Debug for MerkleHash {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 write!(f, "MerkleHash({})", self)
75 }
76}
77
78impl fmt::Display for MerkleHash {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 for b in &self.0 {
81 write!(f, "{b:02x}")?;
82 }
83 Ok(())
84 }
85}
86
87impl Serialize for MerkleHash {
88 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
89 serializer.serialize_str(&self.to_string())
90 }
91}
92
93impl<'de> Deserialize<'de> for MerkleHash {
94 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
95 let s = String::deserialize(deserializer)?;
96 Self::from_hex(&s).map_err(serde::de::Error::custom)
97 }
98}
99
100impl AsRef<[u8]> for MerkleHash {
101 fn as_ref(&self) -> &[u8] {
102 &self.0
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
118#[serde(try_from = "String", into = "String")]
119pub struct LogOrigin(String);
120
121impl LogOrigin {
122 pub fn new(s: &str) -> Result<Self, TransparencyError> {
124 if s.is_empty() {
125 return Err(TransparencyError::InvalidOrigin("must not be empty".into()));
126 }
127 if !s.is_ascii() {
128 return Err(TransparencyError::InvalidOrigin("must be ASCII".into()));
129 }
130 if s.bytes().any(|b| b < 0x20) {
131 return Err(TransparencyError::InvalidOrigin(
132 "must not contain control characters".into(),
133 ));
134 }
135 Ok(Self(s.to_string()))
136 }
137
138 #[allow(clippy::expect_used)] pub fn new_unchecked(s: &str) -> Self {
144 Self::new(s).expect("LogOrigin::new_unchecked called with invalid origin")
145 }
146
147 pub fn as_str(&self) -> &str {
149 &self.0
150 }
151}
152
153impl fmt::Display for LogOrigin {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 f.write_str(&self.0)
156 }
157}
158
159impl TryFrom<String> for LogOrigin {
160 type Error = TransparencyError;
161 fn try_from(s: String) -> Result<Self, Self::Error> {
162 Self::new(&s)
163 }
164}
165
166impl From<LogOrigin> for String {
167 fn from(o: LogOrigin) -> Self {
168 o.0
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn merkle_hash_hex_roundtrip() {
178 let bytes = [0xabu8; 32];
179 let h = MerkleHash::from_bytes(bytes);
180 let hex_str = h.to_string();
181 let h2 = MerkleHash::from_hex(&hex_str).unwrap();
182 assert_eq!(h, h2);
183 }
184
185 #[test]
186 fn merkle_hash_json_roundtrip() {
187 let h = MerkleHash::from_bytes([0x42u8; 32]);
188 let json = serde_json::to_string(&h).unwrap();
189 let h2: MerkleHash = serde_json::from_str(&json).unwrap();
190 assert_eq!(h, h2);
191 }
192
193 #[test]
194 fn log_origin_rejects_empty() {
195 assert!(LogOrigin::new("").is_err());
196 }
197
198 #[test]
199 fn log_origin_rejects_non_ascii() {
200 assert!(LogOrigin::new("日本語").is_err());
201 }
202
203 #[test]
204 fn log_origin_rejects_control_chars() {
205 assert!(LogOrigin::new("auths\x00log").is_err());
206 }
207
208 #[test]
209 fn log_origin_valid() {
210 let o = LogOrigin::new("auths.dev/log").unwrap();
211 assert_eq!(o.as_str(), "auths.dev/log");
212 }
213}