mnem_core/id/
multihash.rs1use core::fmt;
9
10use serde::{Deserialize, Serialize};
11
12use crate::error::IdError;
13
14pub use multihash::Error as MultihashError;
17
18pub const HASH_SHA2_256: u64 = 0x12;
20
21pub const HASH_BLAKE3_256: u64 = 0x1e;
27
28#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
40#[serde(transparent)]
41pub struct Multihash(multihash::Multihash<64>);
42
43impl Multihash {
44 #[must_use]
48 pub fn sha2_256(bytes: &[u8]) -> Self {
49 use multihash_codetable::{Code, MultihashDigest};
50 Self(Code::Sha2_256.digest(bytes))
51 }
52
53 #[must_use]
59 pub fn blake3_256(bytes: &[u8]) -> Self {
60 use multihash_codetable::{Code, MultihashDigest};
61 Self(Code::Blake3_256.digest(bytes))
62 }
63
64 pub fn wrap(code: u64, digest: &[u8]) -> Result<Self, IdError> {
71 multihash::Multihash::<64>::wrap(code, digest)
72 .map(Self)
73 .map_err(|source| IdError::Multihash { source })
74 }
75
76 #[must_use]
78 pub const fn code(&self) -> u64 {
79 self.0.code()
80 }
81
82 #[must_use]
84 pub const fn size(&self) -> u8 {
85 self.0.size()
86 }
87
88 #[must_use]
90 pub fn digest(&self) -> &[u8] {
91 self.0.digest()
92 }
93
94 #[must_use]
96 pub fn to_bytes(&self) -> Vec<u8> {
97 self.0.to_bytes()
98 }
99
100 pub fn from_bytes(bytes: &[u8]) -> Result<Self, IdError> {
106 multihash::Multihash::<64>::from_bytes(bytes)
107 .map(Self)
108 .map_err(|source| IdError::Multihash { source })
109 }
110
111 #[must_use]
114 pub(crate) const fn into_inner(self) -> multihash::Multihash<64> {
115 self.0
116 }
117}
118
119impl fmt::Debug for Multihash {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(
122 f,
123 "multihash(code=0x{:x}, size={}, digest=",
124 self.code(),
125 self.size()
126 )?;
127 for b in self.digest().iter().take(4) {
128 write!(f, "{b:02x}")?;
129 }
130 f.write_str("…)")
131 }
132}
133
134impl From<multihash::Multihash<64>> for Multihash {
135 fn from(m: multihash::Multihash<64>) -> Self {
136 Self(m)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn sha2_256_deterministic() {
146 let a = Multihash::sha2_256(b"hello");
147 let b = Multihash::sha2_256(b"hello");
148 assert_eq!(a, b);
149 assert_eq!(a.code(), HASH_SHA2_256);
150 assert_eq!(a.size(), 32);
151 assert_eq!(a.digest().len(), 32);
152 }
153
154 #[test]
155 fn different_inputs_different_hashes() {
156 let a = Multihash::sha2_256(b"hello");
157 let b = Multihash::sha2_256(b"world");
158 assert_ne!(a, b);
159 }
160
161 #[test]
162 fn different_algos_distinct_even_on_empty() {
163 let sha = Multihash::sha2_256(&[]);
164 let blake = Multihash::blake3_256(&[]);
165 assert_ne!(
166 sha, blake,
167 "sha2-256 and blake3 of empty must not compare equal"
168 );
169 assert_ne!(sha.code(), blake.code());
170 }
171
172 #[test]
173 fn wire_round_trip() {
174 let original = Multihash::sha2_256(b"round-trip me");
175 let bytes = original.to_bytes();
176 let decoded = Multihash::from_bytes(&bytes).expect("decode");
177 assert_eq!(original, decoded);
178 assert_eq!(bytes[0], 0x12);
180 assert_eq!(bytes[1], 0x20);
181 assert_eq!(bytes.len(), 34);
182 }
183
184 #[test]
185 fn wrap_roundtrip() {
186 let digest = [0xabu8; 32];
187 let m = Multihash::wrap(HASH_SHA2_256, &digest).expect("wrap");
188 assert_eq!(m.code(), HASH_SHA2_256);
189 assert_eq!(m.digest(), &digest);
190 }
191}