use crate::consts::TAG_ENTR;
use crate::error::{Error, Result};
const CODEX32_ALPHABET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Tag([u8; 4]);
impl Tag {
pub const ENTR: Tag = Tag(TAG_ENTR);
pub fn from_raw_bytes(b: [u8; 4]) -> Self {
Tag(b)
}
pub fn try_new(s: &str) -> Result<Self> {
let bytes = s.as_bytes();
if bytes.len() != 4 {
return Err(Error::TagInvalidAlphabet { got: [0; 4] });
}
let mut out = [0u8; 4];
for (i, b) in bytes.iter().enumerate() {
if !CODEX32_ALPHABET.contains(b) {
return Err(Error::TagInvalidAlphabet {
got: [bytes[0], bytes[1], bytes[2], bytes[3]],
});
}
out[i] = *b;
}
Ok(Tag(out))
}
pub fn as_bytes(&self) -> &[u8; 4] {
&self.0
}
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap_or("<non-utf8>")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entr_const_matches_string() {
assert_eq!(Tag::ENTR.as_str(), "entr");
}
#[test]
fn try_new_accepts_alphabet_chars() {
for s in ["entr", "seed", "xprv", "mnem", "prvk"] {
let t = Tag::try_new(s).expect(s);
assert_eq!(t.as_str(), s);
}
}
#[test]
fn try_new_rejects_uppercase() {
assert!(matches!(
Tag::try_new("ENTR"),
Err(Error::TagInvalidAlphabet { .. })
));
}
#[test]
fn try_new_rejects_out_of_alphabet_chars() {
for s in ["beer", "iron", "oboe"] {
assert!(
matches!(Tag::try_new(s), Err(Error::TagInvalidAlphabet { .. })),
"expected reject for {:?}",
s
);
}
}
#[test]
fn try_new_rejects_wrong_length() {
for s in ["", "a", "ab", "abc", "abcde"] {
assert!(
matches!(Tag::try_new(s), Err(Error::TagInvalidAlphabet { .. })),
"expected reject for {:?}",
s
);
}
}
#[test]
fn from_raw_bytes_skips_validation() {
let t = Tag::from_raw_bytes(*b"ENTR");
assert_eq!(t.as_bytes(), b"ENTR");
}
}