Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_hash/src/hash.rs

16.5 KiB, 52 runs

created by r1870400018:351, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::sha256;
2
3use oxedyne_fe2o3_core::{
4 prelude::*,
5 alt::{
6 Alt,
7 DefAlt,
8 Override,
9 Gnomon,
10 },
11};
12use oxedyne_fe2o3_iop_hash::api::{
13 Hash,
14 HashForm,
15 Hasher,
16};
17use oxedyne_fe2o3_namex::{
18 id::{
19 LocalId,
20 InNamex,
21 NamexId,
22 },
23};
24
25use std::{
26 fmt,
27 hash::Hasher as _,
28 str,
29};
30
31use tiny_keccak::{
32 self as keccak,
33 Hasher as _,
34};
35
36#[derive(Clone)]
37pub enum HashScheme {
38 // Crypto
39 SHA3_256(keccak::Sha3),
40 #[allow(non_camel_case_types)]
41 SHA_256(sha256::Sha256),
42 // Non-crypto
43 Seahash(seahash::SeaHasher),
44}
45
46impl fmt::Debug for HashScheme {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::SHA3_256(..) => write!(f, "SHA3_256"),
50 Self::SHA_256(..) => write!(f, "SHA_256"),
51 Self::Seahash(..) => write!(f, "Seahash"),
52 }
53 }
54}
55
56impl InNamex for HashScheme {
57
58 fn name_id(&self) -> Outcome<NamexId> {
59 Ok(match self {
60 Self::SHA3_256(..) =>
61 res!(NamexId::try_from("VybbHNWeNXeTqTrXj66TzZScbSTsEFVy0W79QnbroFA=")),
62 Self::SHA_256(..) =>
63 res!(NamexId::try_from("kCnQluCVX4v62XUObBIPJhg+VZaXjXHQOLoNDVrOZso=")),
64 Self::Seahash(..) =>
65 res!(NamexId::try_from("O/3zgxf8/f6mjc0RBau1MMtkfNi9B1eeFB5Q9f6ZfAM=")),
66 })
67 }
68
69 fn local_id(&self) -> LocalId {
70 match self {
71 Self::SHA3_256(..) => LocalId(1),
72 Self::SHA_256(..) => LocalId(3),
73 Self::Seahash(..) => LocalId(2),
74 }
75 }
76
77 fn assoc_names_base64(
78 gname: &'static str,
79 )
80 -> Outcome<Option<Vec<(
81 &'static str,
82 &'static str,
83 )>>>
84 {
85 let ids = match gname {
86 "schemes" => [
87 ("SHA3_256", "VybbHNWeNXeTqTrXj66TzZScbSTsEFVy0W79QnbroFA="),
88 ("SHA_256", "kCnQluCVX4v62XUObBIPJhg+VZaXjXHQOLoNDVrOZso="),
89 ("Seahash", "O/3zgxf8/f6mjc0RBau1MMtkfNi9B1eeFB5Q9f6ZfAM="),
90 ],
91 _ => return Err(err!(
92 "The Namex group name '{}' is not recognised for HashScheme.", gname;
93 Invalid, Input)),
94 };
95 Ok(if ids.len() == 0 {
96 None
97 } else {
98 Some(ids.to_vec())
99 })
100 }
101}
102
103impl Hasher for HashScheme {
104
105 /// Absorbs the input slices in order and the salt last, so that the digest is `H(input ‖
106 /// salt)`. This ordering is fixed, and pinned by test; changing it changes every digest.
107 fn hash<const S: usize>(self, input: &[&[u8]], salt: [u8; S]) -> Hash<S> {
108 match self {
109 Self::SHA3_256(mut hasher) => {
110 for slice in input {
111 hasher.update(slice);
112 }
113 hasher.update(&salt);
114 let mut hash = [0u8; 32];
115 hasher.finalize(&mut hash);
116 Hash::new(HashForm::Bytes32(hash), salt)
117 },
118 Self::SHA_256(mut hasher) => {
119 for slice in input {
120 hasher.update(slice);
121 }
122 hasher.update(&salt);
123 Hash::new(HashForm::Bytes32(hasher.finish()), salt)
124 },
125 Self::Seahash(mut hasher) => {
126 for slice in input {
127 hasher.write(slice);
128 }
129 hasher.write(&salt);
130 let h = hasher.finish();
131 Hash::new(HashForm::U64(h), salt)
132 }
133 }
134 }
135
136 fn hash_length(&self) -> Gnomon<usize> {
137 match self {
138 Self::SHA3_256(..) => Gnomon::Known(Self::SHA3_256_BYTE_LEN),
139 Self::SHA_256(..) => Gnomon::Known(Self::SHA_256_BYTE_LEN),
140 Self::Seahash(..) => Gnomon::Known(Self::SEAHASH_BYTE_LEN),
141 }
142 }
143
144 fn is_identity(&self) -> bool { false }
145}
146
147impl str::FromStr for HashScheme {
148 type Err = Error<ErrTag>;
149
150 fn from_str(name: &str) -> std::result::Result<Self, Self::Err> {
151 match name {
152 "SHA3_256" => Ok(Self::new_sha3_256()),
153 "SHA_256" => Ok(Self::new_sha256()),
154 "Seahash" => Ok(Self::new_seahash()),
155 _ => Err(err!(
156 "The hash scheme '{}' is not recognised.", name;
157 Invalid, Input)),
158 }
159 }
160}
161
162impl TryFrom<&str> for HashScheme {
163 type Error = Error<ErrTag>;
164
165 fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
166 Self::from_str(s)
167 }
168}
169
170impl TryFrom<LocalId> for HashScheme {
171 type Error = Error<ErrTag>;
172
173 fn try_from(n: LocalId) -> std::result::Result<Self, Self::Error> {
174 match n {
175 LocalId(1) => Ok(Self::new_sha3_256()),
176 LocalId(2) => Ok(Self::new_seahash()),
177 LocalId(3) => Ok(Self::new_sha256()),
178 _ => Err(err!(
179 "The hash scheme with local id {} is not recognised.", n;
180 Invalid, Input)),
181 }
182 }
183}
184
185impl HashScheme {
186
187 pub const SEAHASH_BYTE_LEN: usize = 8;
188 pub const SHA3_256_BYTE_LEN: usize = 32;
189 pub const SHA_256_BYTE_LEN: usize = 32;
190
191 pub fn new_sha3_256() -> Self {
192 Self::SHA3_256(keccak::Sha3::v256())
193 }
194
195 /// Creates a SHA-256 hasher, the digest of choice when the other party is a web browser using
196 /// the Web Crypto API, which offers no SHA3.
197 pub fn new_sha256() -> Self {
198 Self::SHA_256(sha256::Sha256::new())
199 }
200
201 pub fn new_seahash() -> Self {
202 Self::Seahash(seahash::SeaHasher::new())
203 }
204}
205
206#[derive(Clone, Debug, Default)]
207pub struct HasherDefAlt<
208 D: Hasher,
209 G: Hasher,
210>(
211 pub DefAlt<D, G>
212);
213
214impl<
215 D: Hasher,
216 G: Hasher,
217>
218 std::ops::Deref for HasherDefAlt<D, G>
219{
220 type Target = DefAlt<D, G>;
221 fn deref(&self) -> &Self::Target { &self.0 }
222}
223
224impl<
225 D: Hasher,
226 G: Hasher,
227>
228 From<Option<G>> for HasherDefAlt<D, G>
229{
230 fn from(opt: Option<G>) -> Self {
231 Self(
232 DefAlt::from(opt),
233 )
234 }
235}
236
237impl<
238 D: Hasher,
239 G: Hasher,
240>
241 From<Alt<G>> for HasherDefAlt<D, G>
242{
243 fn from(alt: Alt<G>) -> Self {
244 Self(
245 DefAlt::from(alt),
246 )
247 }
248}
249
250impl<
251 D: Hasher,
252 G: Hasher,
253>
254 fmt::Display for HasherDefAlt<D, G>
255{
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "{:?}", self)
258 }
259}
260
261impl<
262 D: Hasher + InNamex,
263 G: Hasher + InNamex,
264>
265 InNamex for HasherDefAlt<D, G>
266{
267 fn name_id(&self) -> Outcome<NamexId> {
268 match &self.0 {
269 DefAlt::Default(inner) => inner.name_id(),
270 DefAlt::Given(inner) => inner.name_id(),
271 DefAlt::None => Err(err!(
272 "No Namex id can be specified for DefAlt::None.";
273 Missing, Bug)),
274 }
275 }
276
277 fn local_id(&self) -> LocalId {
278 match &self.0 {
279 DefAlt::Default(inner) => inner.local_id(),
280 DefAlt::Given(inner) => inner.local_id(),
281 DefAlt::None => LocalId::default(),
282 }
283 }
284
285 fn assoc_names_base64(
286 gname: &'static str,
287 )
288 -> Outcome<Option<Vec<(
289 &'static str,
290 &'static str,
291 )>>>
292 {
293 match res!(D::assoc_names_base64(gname)) {
294 Some(mut vd) => match res!(G::assoc_names_base64(gname)) {
295 Some(vg) => {
296 vd.extend(vg);
297 Ok(Some(vd))
298 },
299 None => Ok(Some(vd)),
300 },
301 None => match res!(G::assoc_names_base64(gname)) {
302 Some(vg) => Ok(Some(vg)),
303 None => Ok(None),
304 },
305 }
306 }
307}
308
309impl<
310 D: Hasher,
311 G: Hasher,
312>
313 Hasher for HasherDefAlt<D, G>
314{
315 fn hash<const S: usize>(self, input: &[&[u8]], salt: [u8; S]) -> Hash<S> {
316 match self.0 {
317 DefAlt::Default(inner) => inner.hash(input, salt),
318 DefAlt::Given(inner) => inner.hash(input, salt),
319 DefAlt::None => ().hash(input, salt),
320 }
321 }
322
323 fn hash_length(&self) -> Gnomon<usize> {
324 match &self.0 {
325 DefAlt::Default(inner) => inner.hash_length(),
326 DefAlt::Given(inner) => inner.hash_length(),
327 DefAlt::None => ().hash_length(),
328 }
329 }
330
331 fn is_identity(&self) -> bool {
332 match &self.0 {
333 DefAlt::Default(inner) => inner.is_identity(),
334 DefAlt::Given(inner) => inner.is_identity(),
335 DefAlt::None => true,
336 }
337 }
338}
339
340impl<
341 D: Hasher,
342 G: Hasher,
343>
344 HasherDefAlt<D, G>
345{
346 pub const HASHER_MISSING_MSG: &'static str = "Hasher not specified.";
347
348 /// Possibly override the hasher in `HasherDefAlt`.
349 pub fn or_hash<
350 const S: usize,
351 >(
352 &self,
353 input: &[&[u8]],
354 salt: [u8; S],
355 or: Option<&Override<D, G>>,
356 )
357 -> Hash<S>
358 {
359 match or {
360 None | Some(Override::PassThrough) => self.clone().hash(input, salt),
361 Some(Override::Default(inner)) => inner.clone().hash(input, salt),
362 Some(Override::Given(inner)) => inner.clone().hash(input, salt),
363 Some(Override::None) => ().hash(input, salt),
364 }
365 }
366
367 pub fn or_hash_length(
368 &self,
369 or: Option<&Override<D, G>>,
370 )
371 -> Gnomon<usize>
372 {
373 match or {
374 None | Some(Override::PassThrough) => self.hash_length(),
375 Some(Override::Default(inner)) => inner.hash_length(),
376 Some(Override::Given(inner)) => inner.hash_length(),
377 Some(Override::None) => ().hash_length(),
378 }
379 }
380
381 pub fn or_is_identity(
382 &self,
383 or: Option<&Override<D, G>>,
384 )
385 -> bool
386 {
387 match or {
388 None | Some(Override::PassThrough) => self.is_identity(),
389 Some(Override::Default(inner)) => inner.is_identity(),
390 Some(Override::Given(inner)) => inner.is_identity(),
391 Some(Override::None) => true,
392 }
393 }
394
395 pub fn or_debug(
396 &self,
397 or: Option<&Override<D, G>>,
398 )
399 -> String
400 {
401 match or {
402 None | Some(Override::PassThrough) => fmt!("{:?}", self),
403 _ => fmt!("{:?}", or),
404 }
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 use oxedyne_fe2o3_iop_hash::api::HashForm;
413
414 /// The known answer for SHA3-256("hello world").
415 const HELLO_WORLD: [u8; 32] = [
416 0x64, 0x4b, 0xcc, 0x7e, 0x56, 0x43, 0x73, 0x04,
417 0x09, 0x99, 0xaa, 0xc8, 0x9e, 0x76, 0x22, 0xf3,
418 0xca, 0x71, 0xfb, 0xa1, 0xd9, 0x72, 0xfd, 0x94,
419 0xa3, 0x1c, 0x3b, 0xfb, 0xf2, 0x4e, 0x39, 0x38,
420 ];
421
422 /// Unwraps the digest of a SHA3-256 hash, which is always 32 bytes.
423 fn digest<const S: usize>(hash: Hash<S>) -> Outcome<[u8; 32]> {
424 match hash.as_hashform() {
425 HashForm::Bytes32(a32) => Ok(a32),
426 other => Err(err!(
427 "Expected SHA3-256 to produce a HashForm::Bytes32, found {:?}.", other;
428 Test, Mismatch)),
429 }
430 }
431
432 /// SHA3-256 short message vectors from the NIST Cryptographic Algorithm Validation Programme,
433 /// SHA3_256ShortMsg.rsp, plus the widely published digest of "abc".
434 #[test]
435 fn test_sha3_256_nist_cavp_short_msg() -> Outcome<()> {
436 let vectors: [(&[u8], [u8; 32]); 5] = [
437 // Len = 0.
438 (&[], [
439 0xa7, 0xff, 0xc6, 0xf8, 0xbf, 0x1e, 0xd7, 0x66,
440 0x51, 0xc1, 0x47, 0x56, 0xa0, 0x61, 0xd6, 0x62,
441 0xf5, 0x80, 0xff, 0x4d, 0xe4, 0x3b, 0x49, 0xfa,
442 0x82, 0xd8, 0x0a, 0x4b, 0x80, 0xf8, 0x43, 0x4a,
443 ]),
444 // Len = 8.
445 (&[0xe9], [
446 0xf0, 0xd0, 0x4d, 0xd1, 0xe6, 0xcf, 0xc2, 0x9a,
447 0x44, 0x60, 0xd5, 0x21, 0x79, 0x68, 0x52, 0xf2,
448 0x5d, 0x9e, 0xf8, 0xd2, 0x8b, 0x44, 0xee, 0x91,
449 0xff, 0x5b, 0x75, 0x9d, 0x72, 0xc1, 0xe6, 0xd6,
450 ]),
451 // Len = 16.
452 (&[0xd4, 0x77], [
453 0x94, 0x27, 0x9e, 0x8f, 0x5c, 0xcd, 0xf6, 0xe1,
454 0x7f, 0x29, 0x2b, 0x59, 0x69, 0x8a, 0xb4, 0xe6,
455 0x14, 0xdf, 0xe6, 0x96, 0xa4, 0x6c, 0x46, 0xda,
456 0x78, 0x30, 0x5f, 0xc6, 0xa3, 0x14, 0x6a, 0xb7,
457 ]),
458 // Len = 32.
459 (&[0xb0, 0x53, 0xfa, 0x1d], [
460 0xbb, 0x86, 0x2f, 0x25, 0xe1, 0x0d, 0x09, 0x3f,
461 0xae, 0x21, 0xea, 0xd5, 0xb4, 0xa2, 0xb3, 0xc5,
462 0x4a, 0x41, 0x10, 0x40, 0x51, 0x09, 0x34, 0x82,
463 0xf0, 0x15, 0x90, 0xb2, 0xea, 0x36, 0xd2, 0x3a,
464 ]),
465 // The published digest of "abc".
466 (b"abc", [
467 0x3a, 0x98, 0x5d, 0xa7, 0x4f, 0xe2, 0x25, 0xb2,
468 0x04, 0x5c, 0x17, 0x2d, 0x6b, 0xd3, 0x90, 0xbd,
469 0x85, 0x5f, 0x08, 0x6e, 0x3e, 0x9d, 0x52, 0x5b,
470 0x46, 0xbf, 0xe2, 0x45, 0x11, 0x43, 0x15, 0x32,
471 ]),
472 ];
473 for (msg, expected) in vectors {
474 // `Hasher::hash` takes `self` by value, and `req!` renders its arguments a second time
475 // when it fails, so the digest is taken once and compared as a binding.
476 let hasher = HashScheme::new_sha3_256();
477 let hash = res!(digest(hasher.hash(&[msg], [])));
478 req!(hash, expected, "SHA3-256 of {:02x?}", msg);
479 }
480 Ok(())
481 }
482
483 /// The input slices must be absorbed in order, as one message.
484 #[test]
485 fn test_sha3_256_absorbs_input_slices_in_order() -> Outcome<()> {
486 let hasher = HashScheme::new_sha3_256();
487 let hash = res!(digest(hasher.hash(&[b"hello", b" ", b"world"], [])));
488 req!(hash, HELLO_WORLD);
489 Ok(())
490 }
491
492 /// The salt is absorbed after the input, giving `H(input ‖ salt)`. Prepending the salt
493 /// instead would produce a different digest, so this pins the convention against a published
494 /// value rather than against ourselves.
495 #[test]
496 fn test_sha3_256_salt_follows_input() -> Outcome<()> {
497 let hasher = HashScheme::new_sha3_256();
498 // "hello" with the salt " world" must digest as SHA3-256("hello world").
499 let hash = res!(digest(hasher.hash(&[b"hello"], *b" world")));
500 req!(hash, HELLO_WORLD);
501 Ok(())
502 }
503
504 #[test]
505 fn test_hash_lengths() -> Outcome<()> {
506 let sha3 = HashScheme::new_sha3_256();
507 req!(*res!(sha3.hash_length().required("SHA3-256 hash length")), 32);
508 let len = res!(digest(sha3.hash(&[b"this is a test"], []))).len();
509 req!(len, 32);
510 let seahash = HashScheme::new_seahash();
511 req!(*res!(seahash.hash_length().required("Seahash hash length")), 8);
512 let sha256 = HashScheme::new_sha256();
513 req!(*res!(sha256.hash_length().required("SHA-256 hash length")), 32);
514 req!(HashScheme::SHA_256_BYTE_LEN, 32);
515 Ok(())
516 }
517
518 /// The scheme must reach the same digest through `Hasher` as the module does directly, and
519 /// must agree with the published digest of "abc".
520 #[test]
521 fn test_sha_256_via_hash_scheme() -> Outcome<()> {
522 let expected = crate::sha256::digest(b"abc");
523 let hasher = HashScheme::new_sha256();
524 let hash = res!(digest(hasher.hash(&[b"abc"], [])));
525 req!(hash, expected);
526 Ok(())
527 }
528
529 /// The salt follows the input for SHA-256 too, giving `H(input ‖ salt)`.
530 #[test]
531 fn test_sha_256_salt_follows_input() -> Outcome<()> {
532 let expected = crate::sha256::digest(b"hello world");
533 let hasher = HashScheme::new_sha256();
534 let hash = res!(digest(hasher.hash(&[b"hello"], *b" world")));
535 req!(hash, expected);
536 Ok(())
537 }
538
539 /// SHA-256 must survive the round trip through both the string name and the local id, since
540 /// those are how a scheme is recorded and recovered.
541 #[test]
542 fn test_sha_256_round_trips() -> Outcome<()> {
543 req!(fmt!("{:?}", HashScheme::new_sha256()), "SHA_256".to_string());
544
545 let from_name = res!(HashScheme::try_from("SHA_256"));
546 req!(fmt!("{:?}", from_name), "SHA_256".to_string());
547
548 let id = HashScheme::new_sha256().local_id();
549 req!(id, LocalId(3));
550 let from_id = res!(HashScheme::try_from(id));
551 req!(fmt!("{:?}", from_id), "SHA_256".to_string());
552
553 // The Namex id must be distinct from that of the other schemes.
554 let sha256_id = res!(HashScheme::new_sha256().name_id());
555 let sha3_id = res!(HashScheme::new_sha3_256().name_id());
556 let ids_differ = sha256_id != sha3_id;
557 req!(ids_differ, true, "SHA-256 and SHA3-256 Namex ids must differ");
558 Ok(())
559 }
560}