exocore_core/sec/
hash.rs

1use std::{fs::File, io::Read, path::Path};
2
3pub use multihash_codetable::{Code, Sha3_256, Sha3_512};
4pub use multihash_derive::{Hasher, Multihash, MultihashDigest};
5
6use crate::framing;
7
8const MULTIHASH_CODE_SIZE: usize = 2;
9
10/// Multihash digest extension.
11pub trait MultihashDigestExt<const S: usize>: Hasher + Default {
12    fn size() -> usize;
13
14    fn to_multihash(&mut self) -> Multihash<S>;
15
16    fn input_signed_frame<I: framing::FrameReader>(
17        &mut self,
18        frame: &framing::MultihashFrame<S, Self, I>,
19    ) {
20        self.update(frame.multihash_bytes());
21    }
22
23    fn multihash_size() -> usize {
24        MULTIHASH_CODE_SIZE + Self::size()
25    }
26
27    fn update_from_reader<R: Read>(&mut self, mut read: R) -> Result<Multihash<S>, std::io::Error> {
28        let mut bytes = Vec::new();
29        read.read_to_end(&mut bytes)?;
30
31        self.update(&bytes);
32        Ok(self.to_multihash())
33    }
34}
35
36impl MultihashDigestExt<32> for Sha3_256 {
37    fn to_multihash(&mut self) -> Multihash<32> {
38        let digest = self.finalize();
39        Multihash::wrap(0x16, digest).unwrap() // TODO: FIXME
40    }
41
42    fn size() -> usize {
43        32
44    }
45}
46
47impl MultihashDigestExt<64> for Sha3_512 {
48    fn to_multihash(&mut self) -> Multihash<64> {
49        let digest = self.finalize();
50        Multihash::wrap(0x14, digest).unwrap() // TODO: FIXME
51    }
52
53    fn size() -> usize {
54        64
55    }
56}
57
58pub trait MultihashExt {
59    fn encode_bs58(&self) -> String;
60}
61
62impl<const S: usize> MultihashExt for Multihash<S> {
63    fn encode_bs58(&self) -> String {
64        bs58::encode(self.to_bytes()).into_string()
65    }
66}
67
68pub fn multihash_decode_bs58<const S: usize>(str: &str) -> Result<Multihash<S>, HashError> {
69    let bytes = bs58::decode(str).into_vec()?;
70    let mh = Multihash::from_bytes(&bytes)?;
71    Ok(mh)
72}
73
74pub fn multihash_sha3_256_file<P: AsRef<Path>>(path: P) -> Result<Multihash<32>, HashError> {
75    let file = File::open(path.as_ref())?;
76    multihash_sha3_256(file)
77}
78
79pub fn multihash_sha3_256<R: Read>(reader: R) -> Result<Multihash<32>, HashError> {
80    let mut digest = Sha3_256::default();
81    let mh = digest.update_from_reader(reader)?;
82    Ok(mh)
83}
84
85#[derive(thiserror::Error, Debug)]
86pub enum HashError {
87    #[error("Base58 decoding error: {0}")]
88    Bs58(#[from] bs58::decode::Error),
89    #[error("Multihash error: {0}")]
90    Multihash(#[from] multihash::Error),
91    #[error("IO error: {0}")]
92    Io(#[from] std::io::Error),
93}
94
95#[cfg(test)]
96mod tests {
97    use std::io::{Seek, Write};
98
99    use tempfile::tempdir;
100
101    use super::*;
102
103    #[test]
104    fn test_to_multihash() {
105        let stateless = Code::Sha3_256.digest(b"Hello world");
106
107        let mut hasher = Sha3_256::default();
108        hasher.update(b"Hello world");
109        let stateful = hasher.to_multihash();
110
111        assert_eq!(stateful, stateless);
112    }
113
114    #[test]
115    fn multihash_from_reader() {
116        let stateless = Code::Sha3_256.digest(b"Hello world");
117
118        let mut file = tempfile::tempfile().unwrap();
119        file.write_all(b"Hello world").unwrap();
120        file.rewind().unwrap();
121
122        let mut hasher = Sha3_256::default();
123        hasher.update_from_reader(file).unwrap();
124        let file_hash = hasher.to_multihash();
125
126        assert_eq!(stateless, file_hash);
127    }
128
129    #[test]
130    fn bs58_encode_decode() {
131        let mh_init = Code::Sha3_256.digest(b"Hello world");
132
133        let bs58 = mh_init.encode_bs58();
134        let mh_decoded = multihash_decode_bs58::<32>(&bs58).unwrap();
135        assert_eq!(mh_init, mh_decoded);
136    }
137
138    #[test]
139    fn multihash_file() {
140        let dir = tempdir().unwrap();
141        let file_path = dir.path().join("file.txt");
142
143        {
144            let mut file = File::create(&file_path).unwrap();
145            file.write_all(b"Hello world").unwrap();
146        }
147
148        let file_hash = multihash_sha3_256_file(file_path).unwrap();
149        let hw_hash = Code::Sha3_256.digest(b"Hello world");
150
151        assert_eq!(hw_hash, file_hash);
152    }
153}