1use crate::hazmat::dstu4145::curve163::Point;
23use crate::hazmat::dstu4145::gf2m163::FieldElement;
24use crate::hazmat::dstu4145::signature::verify as dstu4145_verify;
25use crate::hazmat::kalyna::Kalyna128_128;
26use crate::hazmat::kupyna::Kupyna256;
27use crate::hazmat::strumok::Strumok256;
28use std::fmt;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum Primitive {
33 Kalyna,
34 Kupyna,
35 Strumok,
36 Dstu4145,
37}
38
39impl fmt::Display for Primitive {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.write_str(match self {
42 Primitive::Kalyna => "Kalyna",
43 Primitive::Kupyna => "Kupyna",
44 Primitive::Strumok => "Strumok",
45 Primitive::Dstu4145 => "DSTU 4145",
46 })
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum FailureKind {
53 Mismatch,
56 MalformedEmbeddedVector,
61}
62
63impl fmt::Display for FailureKind {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.write_str(match self {
66 FailureKind::Mismatch => "output did not match the official vector",
67 FailureKind::MalformedEmbeddedVector => "embedded vector data could not be parsed",
68 })
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct Failure {
75 pub primitive: Primitive,
76 pub kind: FailureKind,
77}
78
79impl fmt::Display for Failure {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 write!(f, "{}: {}", self.primitive, self.kind)
82 }
83}
84
85impl std::error::Error for Failure {}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct Report {
91 pub failures: Vec<Failure>,
92}
93
94impl fmt::Display for Report {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "dstu_core self-test failed:")?;
97 for failure in &self.failures {
98 write!(f, " [{failure}]")?;
99 }
100 Ok(())
101 }
102}
103
104impl std::error::Error for Report {}
105
106pub fn run() -> Result<(), Report> {
115 type Check = (Primitive, fn() -> Result<(), FailureKind>);
116 let checks: [Check; 4] = [
117 (Primitive::Kalyna, check_kalyna),
118 (Primitive::Kupyna, check_kupyna),
119 (Primitive::Strumok, check_strumok),
120 (Primitive::Dstu4145, check_dstu4145),
121 ];
122
123 let failures: Vec<Failure> = checks
124 .into_iter()
125 .filter_map(|(primitive, check)| check().err().map(|kind| Failure { primitive, kind }))
126 .collect();
127
128 if failures.is_empty() {
129 Ok(())
130 } else {
131 Err(Report { failures })
132 }
133}
134
135fn check_equal(actual: &[u8], expected: &[u8]) -> Result<(), FailureKind> {
136 if actual == expected {
137 Ok(())
138 } else {
139 Err(FailureKind::Mismatch)
140 }
141}
142
143fn find_str_value<'a>(json: &'a str, key: &str, start: usize) -> Option<(&'a str, usize)> {
148 let pattern = std::format!("\"{key}\": \"");
149 let haystack = json.get(start..)?;
150 let rel_start = haystack.find(pattern.as_str())?;
151 let after = haystack.get(rel_start + pattern.len()..)?;
152 let end = after.find('"')?;
153 let value = after.get(..end)?;
154 let abs_end = start + rel_start + pattern.len() + end + 1;
155 Some((value, abs_end))
156}
157
158fn decode_hex(hex: &str) -> Option<Vec<u8>> {
163 let owned;
164 let hex = if hex.len().is_multiple_of(2) {
165 hex
166 } else {
167 owned = std::format!("0{hex}");
168 &owned
169 };
170 let mut out = Vec::with_capacity(hex.len() / 2);
171 let mut i = 0;
172 while i < hex.len() {
173 out.push(u8::from_str_radix(hex.get(i..i + 2)?, 16).ok()?);
174 i += 2;
175 }
176 Some(out)
177}
178
179fn decode_hex_fixed<const N: usize>(hex: &str) -> Option<[u8; N]> {
180 let bytes = decode_hex(hex)?;
181 if bytes.len() != N {
182 return None;
183 }
184 let mut out = [0u8; N];
185 out.copy_from_slice(&bytes);
186 Some(out)
187}
188
189fn decode_hex_padded<const N: usize>(hex: &str) -> Option<[u8; N]> {
193 let bytes = decode_hex(hex)?;
194 if bytes.len() > N {
195 return None;
196 }
197 let mut out = [0u8; N];
198 out[N - bytes.len()..].copy_from_slice(&bytes);
199 Some(out)
200}
201
202fn check_kalyna() -> Result<(), FailureKind> {
203 const JSON: &str = include_str!("../tests/vectors/kalyna/128-128.json");
204 let (key_hex, at) =
205 find_str_value(JSON, "key_hex", 0).ok_or(FailureKind::MalformedEmbeddedVector)?;
206 let (pt_hex, at) =
207 find_str_value(JSON, "plaintext_hex", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
208 let (ct_hex, _) =
209 find_str_value(JSON, "ciphertext_hex", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
210
211 let key: [u8; 16] = decode_hex_fixed(key_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
212 let plaintext: [u8; 16] =
213 decode_hex_fixed(pt_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
214 let ciphertext: [u8; 16] =
215 decode_hex_fixed(ct_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
216
217 check_equal(&Kalyna128_128::encrypt(&key, &plaintext), &ciphertext)?;
218 check_equal(&Kalyna128_128::decrypt(&key, &ciphertext), &plaintext)
219}
220
221fn check_kupyna() -> Result<(), FailureKind> {
222 const JSON: &str = include_str!("../tests/vectors/kupyna/kupyna-256.json");
223 let (msg_hex, at) =
224 find_str_value(JSON, "message_hex", 0).ok_or(FailureKind::MalformedEmbeddedVector)?;
225 let (hash_hex, _) =
226 find_str_value(JSON, "hash_hex", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
227
228 let message = decode_hex(msg_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
229 let expected: [u8; 32] =
230 decode_hex_fixed(hash_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
231
232 check_equal(&Kupyna256::digest(&message), &expected)
233}
234
235fn check_strumok() -> Result<(), FailureKind> {
236 const JSON: &str = include_str!("../tests/vectors/strumok/keystream-256.json");
237 let (key_hex, at) =
238 find_str_value(JSON, "key_hex", 0).ok_or(FailureKind::MalformedEmbeddedVector)?;
239 let (iv_hex, at) =
240 find_str_value(JSON, "iv_hex", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
241 let (ks_hex, _) =
242 find_str_value(JSON, "keystream_hex", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
243
244 let key: [u8; 32] = decode_hex_fixed(key_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
245 let iv: [u8; 32] = decode_hex_fixed(iv_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
246 let expected = decode_hex(ks_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
247
248 let mut actual = std::vec![0u8; expected.len()];
249 Strumok256::new(&key, &iv).apply_keystream(&mut actual);
250
251 check_equal(&actual, &expected)
252}
253
254#[allow(clippy::similar_names)]
259fn check_dstu4145() -> Result<(), FailureKind> {
260 const JSON: &str = include_str!("../tests/vectors/dstu4145/gf2m163.json");
261 let (_bp_x, at) = find_str_value(JSON, "x", 0).ok_or(FailureKind::MalformedEmbeddedVector)?;
263 let (_bp_y, at) = find_str_value(JSON, "y", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
264 let (qx_hex, at) = find_str_value(JSON, "x", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
265 let (qy_hex, at) = find_str_value(JSON, "y", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
266 let (hash_hex, at) =
267 find_str_value(JSON, "hash_h_of_t", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
268 let (r_hex, at) = find_str_value(JSON, "r", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
269 let (s_hex, _) = find_str_value(JSON, "s", at).ok_or(FailureKind::MalformedEmbeddedVector)?;
270
271 let qx = decode_hex(qx_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
272 let qy = decode_hex(qy_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
273 let hash = decode_hex(hash_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
274 let r: [u8; 21] = decode_hex_padded(r_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
275 let s: [u8; 21] = decode_hex_padded(s_hex).ok_or(FailureKind::MalformedEmbeddedVector)?;
276
277 let q = Point::Affine(
278 FieldElement::from_be_bytes(&qx),
279 FieldElement::from_be_bytes(&qy),
280 );
281 let g = Point::generator();
282
283 if dstu4145_verify(&hash, &r, &s, q, g) {
284 Ok(())
285 } else {
286 Err(FailureKind::Mismatch)
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::{
293 check_equal, decode_hex, decode_hex_fixed, decode_hex_padded, find_str_value, Failure,
294 FailureKind, Primitive, Report,
295 };
296
297 #[test]
298 fn check_equal_detects_a_real_mismatch() {
299 assert_eq!(check_equal(b"abc", b"abc"), Ok(()));
300 assert_eq!(check_equal(b"abc", b"abd"), Err(FailureKind::Mismatch));
301 }
302
303 #[test]
304 fn find_str_value_locates_a_key_and_reports_none_when_absent() {
305 let json = r#"{"foo": "bar", "foo": "baz"}"#;
306 let Some((first, at)) = find_str_value(json, "foo", 0) else {
307 panic!("first occurrence must be found");
308 };
309 assert_eq!(first, "bar");
310 let Some((second, _)) = find_str_value(json, "foo", at) else {
311 panic!("second occurrence must be found");
312 };
313 assert_eq!(second, "baz");
314 assert_eq!(find_str_value(json, "missing", 0), None);
315 }
316
317 #[test]
318 fn decode_hex_pads_odd_length_and_rejects_non_hex_digits() {
319 assert_eq!(decode_hex("00ff"), Some(std::vec![0x00, 0xff]));
320 assert_eq!(
321 decode_hex("f"),
322 Some(std::vec![0x0f]),
323 "odd-length hex must be treated as a missing leading zero nibble, not rejected"
324 );
325 assert_eq!(decode_hex("zz"), None, "non-hex digits must be rejected");
326 }
327
328 #[test]
329 fn decode_hex_fixed_rejects_wrong_length() {
330 assert_eq!(decode_hex_fixed::<2>("00ff"), Some([0x00, 0xff]));
331 assert_eq!(decode_hex_fixed::<3>("00ff"), None);
332 }
333
334 #[test]
335 fn decode_hex_padded_left_pads_a_short_scalar() {
336 assert_eq!(
337 decode_hex_padded::<4>("ff"),
338 Some([0x00, 0x00, 0x00, 0xff]),
339 "a short hex string must be treated as missing leading zero bytes, not misaligned ones"
340 );
341 }
342
343 #[test]
344 fn report_display_lists_every_failed_primitive() {
345 let report = Report {
346 failures: std::vec![
347 Failure {
348 primitive: Primitive::Kalyna,
349 kind: FailureKind::Mismatch,
350 },
351 Failure {
352 primitive: Primitive::Dstu4145,
353 kind: FailureKind::MalformedEmbeddedVector,
354 },
355 ],
356 };
357 let text = report.to_string();
358 assert!(text.contains("Kalyna"));
359 assert!(text.contains("DSTU 4145"));
360 }
361}