use ms_codec::consts::{
PREIMAGE_PREFIX, RESERVED_ID_BLOCKLIST, TAG_HASH, VALID_PREIMAGE_STR_LENGTHS,
};
use ms_codec::{decode, encode, Error, Payload, PayloadKind, Tag};
use zeroize::Zeroizing;
fn preimage(byte: u8) -> Payload {
Payload::Preimage(Zeroizing::new([byte; 32]))
}
#[test]
fn constants_are_the_specs() {
assert_eq!(PREIMAGE_PREFIX, 0x03);
assert_eq!(TAG_HASH, *b"hash");
assert_eq!(VALID_PREIMAGE_STR_LENGTHS, &[75]);
assert_eq!(Tag::HASH.as_bytes(), b"hash");
assert_eq!(RESERVED_ID_BLOCKLIST.len(), 6);
assert!(RESERVED_ID_BLOCKLIST.contains(b"hash"));
}
#[test]
fn single_tag_by_kind() {
assert_eq!(PayloadKind::Entr.single_tag(), Tag::ENTR);
assert_eq!(PayloadKind::Mnem.single_tag(), Tag::ENTR);
assert_eq!(PayloadKind::Preimage.single_tag(), Tag::HASH);
}
#[test]
fn a_hash_single_round_trips_and_is_75_chars() {
let s = encode(Tag::HASH, &preimage(0xab)).expect("encode");
assert_eq!(s.len(), 75, "{s}");
assert!(s.starts_with("ms10hashsq"), "{s}");
let (tag, p) = decode(&s).expect("decode");
assert_eq!(tag, Tag::HASH);
assert_eq!(p.kind(), PayloadKind::Preimage);
assert_eq!(p.as_bytes(), &[0xab; 32]);
}
#[test]
fn the_entr32_and_preimage_pair_are_adjacent_rows() {
let e = encode(Tag::ENTR, &Payload::Entr(vec![0xab; 32])).unwrap();
let h = encode(Tag::HASH, &preimage(0xab)).unwrap();
assert_eq!(e.len(), 75);
assert_eq!(h.len(), 75);
assert!(e.starts_with("ms10entrsq"), "{e}");
assert!(h.starts_with("ms10hashsq"), "{h}");
}
#[test]
fn id_and_prefix_must_agree_both_directions() {
let err = encode(Tag::HASH, &Payload::Entr(vec![0; 32])).unwrap_err();
assert!(
matches!(err, Error::TagKindMismatch { tag, prefix: 0x00 } if tag == *b"hash"),
"{err:?}"
);
let err = encode(Tag::ENTR, &preimage(0)).unwrap_err();
assert!(
matches!(err, Error::TagKindMismatch { tag, prefix: 0x03 } if tag == *b"entr"),
"{err:?}"
);
let forged_hash_over_seed = forge("hash", &{
let mut v = vec![0x00u8];
v.extend_from_slice(&[0xab; 32]);
v
});
let err = decode(&forged_hash_over_seed).unwrap_err();
assert!(
matches!(err, Error::TagKindMismatch { prefix: 0x00, .. }),
"{err:?}"
);
let forged_entr_over_preimage = forge("entr", &{
let mut v = vec![PREIMAGE_PREFIX];
v.extend_from_slice(&[0xab; 32]);
v
});
let err = decode(&forged_entr_over_preimage).unwrap_err();
assert!(
matches!(err, Error::TagKindMismatch { prefix: 0x03, .. }),
"{err:?}"
);
}
fn forge(id: &str, payload: &[u8]) -> String {
ms_codec::codex32::Codex32String::from_seed("ms", 0, id, ms_codec::codex32::Fe::S, payload)
.expect("from_seed")
.to_string()
}
#[test]
fn preimage_length_rows_through_decode_name_their_error() {
for n in [17usize, 18, 21, 22, 25, 26, 29, 30, 34] {
let mut payload = vec![PREIMAGE_PREFIX];
payload.extend(std::iter::repeat(0xab).take(n - 1));
let s = forge("hash", &payload);
let err = decode(&s).unwrap_err();
assert!(
matches!(err, Error::PreimageLengthMismatch { got } if got == n - 1),
"payload {n} bytes ({} chars): {err:?}",
s.len()
);
}
}
#[test]
fn preimage_prefix_is_refused_by_length_not_prefix() {
let mut payload = vec![PREIMAGE_PREFIX];
payload.extend_from_slice(&[0xab; 16]);
let s = forge("hash", &payload);
let err = decode(&s).unwrap_err();
assert!(
matches!(err, Error::PreimageLengthMismatch { got: 16 }),
"{err:?}"
);
}
#[test]
fn preimage_length_rows_refused_by_the_string_gate_first() {
for (n, chars) in [(16usize, 48usize), (32, 74), (44, 93)] {
let mut payload = vec![PREIMAGE_PREFIX];
payload.extend(std::iter::repeat(0xab).take(n - 1));
let s = forge("hash", &payload);
assert_eq!(s.len(), chars);
let err = decode(&s).unwrap_err();
assert!(
matches!(err, Error::UnexpectedStringLength { got, .. } if got == chars),
"{n}: {err:?}"
);
}
}
#[test]
fn preimage_length_rows_through_combine_shares() {
for n in [16usize, 32, 44] {
let mut secret = vec![PREIMAGE_PREFIX];
secret.extend(std::iter::repeat(0xab).take(n - 1));
let shares = forge_shares(&secret, 2, 2);
let err = ms_codec::combine_shares(&shares).unwrap_err();
assert!(
matches!(err, Error::PreimageLengthMismatch { got } if got == n - 1),
"payload {n} bytes via combine: {err:?}"
);
}
}
#[test]
fn a_46_byte_payload_is_unconstructible() {
let mut payload = vec![PREIMAGE_PREFIX];
payload.extend(std::iter::repeat(0xab).take(45));
let s = ms_codec::codex32::Codex32String::from_seed(
"ms",
0,
"hash",
ms_codec::codex32::Fe::S,
&payload,
)
.expect("from_seed")
.to_string();
assert_eq!(s.len(), 96);
assert!(
ms_codec::codex32::Codex32String::from_string(s).is_err(),
"96 chars is outside both brackets"
);
}
#[test]
fn preimage_share_round_trip() {
let secret = preimage(0x5a);
let shares =
ms_codec::encode_shares(Tag::HASH, ms_codec::Threshold::new(2).unwrap(), 3, &secret)
.unwrap();
for pair in [[0, 1], [0, 2], [1, 2]] {
let (_tag, p) =
ms_codec::combine_shares(&[shares[pair[0]].clone(), shares[pair[1]].clone()]).unwrap();
assert_eq!(p, secret);
}
}
#[test]
fn preimage_field_is_zeroizing() {
let p = preimage(0x42);
if let Payload::Preimage(z) = &p {
let _: &Zeroizing<[u8; 32]> = z;
assert_eq!(z.len(), 32);
} else {
panic!("not a preimage");
}
}
#[test]
fn inspect_reports_the_kind() {
let s = encode(Tag::HASH, &preimage(0x11)).unwrap();
let r = ms_codec::inspect(&s).unwrap();
assert_eq!(r.kind, ms_codec::InspectKind::Preimage);
assert_eq!(r.prefix_byte, PREIMAGE_PREFIX);
assert_eq!(r.tag, Tag::HASH);
}
#[test]
fn codeword_distance_between_entr_and_hash_ids_exceeds_the_correction_bound() {
let payload = {
let mut v = vec![PREIMAGE_PREFIX];
v.extend_from_slice(&[0xab; 32]);
v
};
let a = forge("entr", &payload);
let b = forge("hash", &payload);
let distance = a.bytes().zip(b.bytes()).filter(|(x, y)| x != y).count();
println!("codeword distance entr/hash = {distance}");
assert!(
distance > 8,
"distance {distance} is within twice the correction bound"
);
}
fn forge_shares(secret: &[u8], k: usize, n: usize) -> Vec<String> {
use ms_codec::codex32::{Codex32String, Fe};
assert_eq!(k, 2, "this forger builds 2-of-N sets");
let s = Codex32String::from_seed("ms", k, "zzzz", Fe::S, secret).expect("secret at S");
let mut rnd = vec![0u8; secret.len()];
for (i, b) in rnd.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(37).wrapping_add(11);
}
let a = Codex32String::from_seed("ms", k, "zzzz", Fe::A, &rnd).expect("share at A");
let mut out = vec![a.to_string()];
for target in [Fe::C, Fe::D].iter().take(n - 1) {
out.push(
Codex32String::interpolate_at(&[s.clone(), a.clone()], *target)
.expect("interpolate")
.to_string(),
);
}
out
}