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
| 1 | use crate::sha256; |
| 2 | |
| 3 | use oxedyne_fe2o3_core::{ |
| 4 | prelude::*, |
| 5 | alt::{ |
| 6 | Alt, |
| 7 | DefAlt, |
| 8 | Override, |
| 9 | Gnomon, |
| 10 | }, |
| 11 | }; |
| 12 | use oxedyne_fe2o3_iop_hash::api::{ |
| 13 | Hash, |
| 14 | HashForm, |
| 15 | Hasher, |
| 16 | }; |
| 17 | use oxedyne_fe2o3_namex::{ |
| 18 | id::{ |
| 19 | LocalId, |
| 20 | InNamex, |
| 21 | NamexId, |
| 22 | }, |
| 23 | }; |
| 24 | |
| 25 | use std::{ |
| 26 | fmt, |
| 27 | hash::Hasher as _, |
| 28 | str, |
| 29 | }; |
| 30 | |
| 31 | use tiny_keccak::{ |
| 32 | self as keccak, |
| 33 | Hasher as _, |
| 34 | }; |
| 35 | |
| 36 | #[derive(Clone)] |
| 37 | pub 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 | |
| 46 | impl 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 | |
| 56 | impl 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 | |
| 103 | impl 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 | |
| 147 | impl 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 | |
| 162 | impl 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 | |
| 170 | impl 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 | |
| 185 | impl 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)] |
| 207 | pub struct HasherDefAlt< |
| 208 | D: Hasher, |
| 209 | G: Hasher, |
| 210 | >( |
| 211 | pub DefAlt<D, G> |
| 212 | ); |
| 213 | |
| 214 | impl< |
| 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 | |
| 224 | impl< |
| 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 | |
| 237 | impl< |
| 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 | |
| 250 | impl< |
| 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 | |
| 261 | impl< |
| 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 | |
| 309 | impl< |
| 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 | |
| 340 | impl< |
| 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)] |
| 409 | mod 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 | } |