Skip to main content

ai_hash/
encode.rs

1use crate::assert_length;
2use crate::error::AiHashError;
3use crate::AiHash;
4use crate::HashType;
5use crate::PrimitiveHashType;
6use crate::AI_HASH_CORE_LEN;
7use crate::AI_HASH_FULL_LEN;
8use crate::AI_HASH_PREFIX_LEN;
9use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
10use std::convert::TryFrom;
11use std::convert::TryInto;
12
13impl<P: PrimitiveHashType> TryFrom<&str> for AiHash<P> {
14    type Error = AiHashError;
15    fn try_from(s: &str) -> Result<Self, AiHashError> {
16        let hash_type = P::new();
17        AiHash::from_raw_39(ai_hash_decode(hash_type.get_prefix(), s)?)
18    }
19}
20
21impl<P: PrimitiveHashType> TryFrom<&String> for AiHash<P> {
22    type Error = AiHashError;
23    fn try_from(s: &String) -> Result<Self, AiHashError> {
24        Self::try_from(s as &str)
25    }
26}
27
28impl<P: PrimitiveHashType> TryFrom<String> for AiHash<P> {
29    type Error = AiHashError;
30    fn try_from(s: String) -> Result<Self, AiHashError> {
31        Self::try_from(&s)
32    }
33}
34
35impl<T: HashType> std::fmt::Display for AiHash<T> {
36    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
37        write!(f, "{}", ai_hash_encode(self.get_raw_39()))
38    }
39}
40
41/// internal REPR for ai hash
42pub fn ai_hash_encode(data: &[u8]) -> String {
43    format!("u{}", URL_SAFE_NO_PAD.encode(data))
44}
45
46/// internal PARSE for ai hash REPR
47pub fn ai_hash_decode_unchecked(s: &str) -> Result<Vec<u8>, AiHashError> {
48    if &s[..1] != "u" {
49        return Err(AiHashError::NoU);
50    }
51    let s = match URL_SAFE_NO_PAD.decode(&s[1..]) {
52        Err(_) => return Err(AiHashError::BadBase64),
53        Ok(s) => s,
54    };
55    if s.len() != AI_HASH_FULL_LEN {
56        return Err(AiHashError::BadSize);
57    }
58    let loc_bytes =
59        ai_sgd_location_bytes(&s[AI_HASH_PREFIX_LEN..AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN]);
60    let loc_bytes: &[u8] = &loc_bytes;
61    if loc_bytes != &s[AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN..] {
62        return Err(AiHashError::BadChecksum);
63    }
64    assert_length!(AI_HASH_FULL_LEN, &s);
65    Ok(s.to_vec())
66}
67
68/// internal PARSE for ai hash REPR
69pub fn ai_hash_decode(prefix: &[u8], s: &str) -> Result<Vec<u8>, AiHashError> {
70    if &s[..1] != "u" {
71        return Err(AiHashError::NoU);
72    }
73    let s = match URL_SAFE_NO_PAD.decode(&s[1..]) {
74        Err(_) => return Err(AiHashError::BadBase64),
75        Ok(s) => s,
76    };
77    if s.len() != AI_HASH_FULL_LEN {
78        return Err(AiHashError::BadSize);
79    }
80    let actual_prefix: [u8; AI_HASH_PREFIX_LEN] = s[..AI_HASH_PREFIX_LEN].try_into().unwrap();
81    if actual_prefix != prefix {
82        return Err(AiHashError::BadPrefix(
83            format!("{:?}", prefix),
84            actual_prefix,
85        ));
86    }
87    let loc_bytes =
88        ai_sgd_location_bytes(&s[AI_HASH_PREFIX_LEN..AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN]);
89    let loc_bytes: &[u8] = &loc_bytes;
90    if loc_bytes != &s[AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN..] {
91        return Err(AiHashError::BadChecksum);
92    }
93    assert_length!(AI_HASH_FULL_LEN, &s);
94    Ok(s.to_vec())
95}
96
97/// internal compute the ai sgd location u32
98pub fn ai_sgd_location_bytes(data: &[u8]) -> Vec<u8> {
99    // Assert the data size is relatively small so we are
100    // comfortable executing this synchronously / blocking tokio thread.
101    assert_eq!(32, data.len(), "only 32 byte hashes supported");
102
103    let hash = blake2b_128(data);
104    let mut out = vec![hash[0], hash[1], hash[2], hash[3]];
105    for i in (4..16).step_by(4) {
106        out[0] ^= hash[i];
107        out[1] ^= hash[i + 1];
108        out[2] ^= hash[i + 2];
109        out[3] ^= hash[i + 3];
110    }
111    out
112}
113
114/// internal compute a 32 byte blake2b hash
115pub fn blake2b_256(data: &[u8]) -> Vec<u8> {
116    let hash = blake2b_simd::Params::new().hash_length(32).hash(data);
117    hash.as_bytes().to_vec()
118}
119
120/// internal compute a 16 byte blake2b hash
121pub fn blake2b_128(data: &[u8]) -> Vec<u8> {
122    let hash = blake2b_simd::Params::new().hash_length(16).hash(data);
123    hash.as_bytes().to_vec()
124}