#![cfg(feature = "fips")]
use multi_codec::Codec;
use multi_hash::{Builder, FIPS_CODECS, HASH_CODECS, Multihash, SAFE_FIPS_CODECS};
use multi_util::CodecInfo;
const RESTRICTED_FIPS_CODECS: [Codec; 5] = [
Codec::Sha1,
Codec::Sha2224,
Codec::Sha2512224,
Codec::Sha2512256,
Codec::Sha3224,
];
const KAT_MESSAGE: &[u8] = b"multihash";
const SHA1_KAT: [u8; 20] = [
0x88, 0xc2, 0xf1, 0x1f, 0xb2, 0xce, 0x39, 0x2a, 0xcb, 0x5b, 0x29, 0x86, 0xe6, 0x40, 0x21, 0x1c,
0x46, 0x90, 0x07, 0x3e,
];
const SHA2_256_KAT: [u8; 32] = [
0x9c, 0xbc, 0x07, 0xc3, 0xf9, 0x91, 0x72, 0x58, 0x36, 0xa3, 0xaa, 0x2a, 0x58, 0x1c, 0xa2, 0x02,
0x91, 0x98, 0xaa, 0x42, 0x0b, 0x9d, 0x99, 0xbc, 0x0e, 0x13, 0x1d, 0x9f, 0x3e, 0x2c, 0xbe, 0x47,
];
const SHA3_256_KAT: [u8; 32] = [
0x08, 0xc3, 0x79, 0x2b, 0x2a, 0x4d, 0xee, 0xd1, 0xbd, 0x7e, 0xa2, 0x32, 0x8f, 0xb5, 0xde, 0x55,
0x31, 0xec, 0xcf, 0x0f, 0xbf, 0xa0, 0x4a, 0x7d, 0x80, 0x0c, 0xdc, 0x26, 0x71, 0x37, 0xc6, 0x35,
];
const SHAKE128_KAT: [u8; 32] = [
0xd3, 0x70, 0x45, 0x66, 0x3a, 0x07, 0xfb, 0x35, 0xec, 0x57, 0x1d, 0x8f, 0x6e, 0xf9, 0x83, 0x00,
0xa2, 0xda, 0xa5, 0xa8, 0x2d, 0x9d, 0x05, 0x5e, 0x68, 0x4b, 0xc2, 0x92, 0xe9, 0x8a, 0x02, 0xa3,
];
struct KatVector {
codec: Codec,
expected: &'static [u8],
}
const KAT_VECTORS: [KatVector; 4] = [
KatVector {
codec: Codec::Sha1,
expected: &SHA1_KAT,
},
KatVector {
codec: Codec::Sha2256,
expected: &SHA2_256_KAT,
},
KatVector {
codec: Codec::Sha3256,
expected: &SHA3_256_KAT,
},
KatVector {
codec: Codec::Shake128,
expected: &SHAKE128_KAT,
},
];
const fn xof_output_len(codec: Codec) -> Option<usize> {
match codec {
Codec::Shake128 => Some(32),
Codec::Shake256 => Some(64),
_ => None,
}
}
const fn expected_digest_len(codec: Codec) -> Option<usize> {
match codec {
Codec::Sha1 => Some(20),
Codec::Sha2224 | Codec::Sha2512224 | Codec::Sha3224 => Some(28),
Codec::Sha2256 | Codec::Sha2512256 | Codec::Sha3256 | Codec::Shake128 => Some(32),
Codec::Sha2384 | Codec::Sha3384 => Some(48),
Codec::Sha2512 | Codec::Sha3512 | Codec::Shake256 => Some(64),
_ => None,
}
}
fn streamed_multihash(codec: Codec, message: &[u8]) -> Multihash {
let mut builder = Builder::new(codec).unwrap();
for chunk in message.chunks(3) {
builder.update(chunk);
}
if let Some(output_len) = xof_output_len(codec) {
builder.output_len(output_len);
}
builder.try_build().unwrap()
}
#[test]
fn test_fips_constant_lengths() {
assert_eq!(FIPS_CODECS.len(), 13);
assert_eq!(SAFE_FIPS_CODECS.len(), 8);
for (index, codec) in FIPS_CODECS.iter().enumerate() {
assert!(
!FIPS_CODECS[..index].contains(codec),
"{codec:?} appears more than once in FIPS_CODECS"
);
}
for (index, codec) in SAFE_FIPS_CODECS.iter().enumerate() {
assert!(
!SAFE_FIPS_CODECS[..index].contains(codec),
"{codec:?} appears more than once in SAFE_FIPS_CODECS"
);
}
}
#[test]
fn test_fips_codecs_are_supported_hash_codecs() {
for &codec in &FIPS_CODECS {
assert!(
HASH_CODECS.contains(&codec),
"{codec:?} is missing from HASH_CODECS"
);
}
}
#[test]
fn test_safe_fips_codecs_are_fips_codecs() {
for &codec in &SAFE_FIPS_CODECS {
assert!(
FIPS_CODECS.contains(&codec),
"{codec:?} is missing from FIPS_CODECS"
);
}
}
#[test]
fn test_safe_list_excludes_restricted_and_deprecated_codecs() {
for &codec in &RESTRICTED_FIPS_CODECS {
assert!(
FIPS_CODECS.contains(&codec),
"{codec:?} is missing from FIPS_CODECS"
);
assert!(
!SAFE_FIPS_CODECS.contains(&codec),
"{codec:?} must not appear in SAFE_FIPS_CODECS"
);
}
for &codec in &FIPS_CODECS {
assert_eq!(
SAFE_FIPS_CODECS.contains(&codec),
!RESTRICTED_FIPS_CODECS.contains(&codec),
"{codec:?} membership disagrees between the two FIPS lists"
);
}
}
#[test]
fn test_each_fips_codec_builds_with_expected_digest_length() {
let message: &[u8] = b"every FIPS approved codec hashes through the streaming builder";
for &codec in &FIPS_CODECS {
let mh = streamed_multihash(codec, message);
let expected_len =
expected_digest_len(codec).expect("FIPS codec has an expected digest length");
assert_eq!(mh.codec(), codec, "codec of the built {codec:?} multihash");
assert_eq!(mh.as_ref().len(), expected_len, "{codec:?} digest length");
}
}
#[test]
fn test_known_answer_digests() {
for vector in &KAT_VECTORS {
let mh = streamed_multihash(vector.codec, KAT_MESSAGE);
assert_eq!(
mh.as_ref(),
vector.expected,
"{:?} digest of the known-answer message",
vector.codec
);
}
}