referrerpolicy=no-referrer-when-downgrade

sc_network_types/
multihash.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! [`Multihash`] implemenattion used by substrate. Currently it's a wrapper over
20//! multihash used by litep2p, but it can be switched to other implementation if needed.
21
22use litep2p::types::multihash::{Code as LiteP2pCode, MultihashDigest as _};
23use multihash::Multihash as LiteP2pMultihash;
24use std::fmt::{self, Debug};
25
26/// Identity multihash code from the [multicodec table][multicodec].
27///
28/// Defined locally because `multihash-codetable` enum only covers cryptographic
29/// hashes and intentionally omits identity.
30///
31/// [multicodec]: https://github.com/multiformats/multicodec/blob/master/table.csv
32const MULTIHASH_IDENTITY_CODE: u64 = 0x00;
33
34/// Default [`Multihash`] implementations. Only hashes used by substrate are defined.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum Code {
37	/// Identity hasher.
38	Identity,
39	/// SHA-256 (32-byte hash size).
40	Sha2_256,
41}
42
43impl Code {
44	/// Calculate digest using this [`Code`]'s hashing algorithm.
45	pub fn digest(&self, input: &[u8]) -> Multihash {
46		match self {
47			Code::Identity => LiteP2pMultihash::<64>::wrap(MULTIHASH_IDENTITY_CODE, input)
48				.expect("identity digest fits in Multihash<64>; qed")
49				.into(),
50			Code::Sha2_256 => LiteP2pCode::Sha2_256.digest(input).into(),
51		}
52	}
53}
54
55/// Error generated when converting to [`Code`].
56#[derive(Debug, thiserror::Error)]
57pub enum Error {
58	/// Invalid multihash size.
59	#[error("invalid multihash size '{0}'")]
60	InvalidSize(u64),
61	/// The multihash code is not supported.
62	#[error("unsupported multihash code '{0:x}'")]
63	UnsupportedCode(u64),
64	/// Catch-all for other errors emitted when converting `u64` code to enum or parsing multihash
65	/// from bytes.
66	#[error("other error: {0}")]
67	Other(Box<dyn std::error::Error + Send + Sync>),
68}
69
70impl From<multihash::Error> for Error {
71	fn from(error: multihash::Error) -> Self {
72		// `multihash::Error` is opaque in multihash 0.19, so we can no longer pattern-match
73		// on its variants. Wrap it as `Other` to preserve the error chain.
74		Self::Other(Box::new(error))
75	}
76}
77
78impl TryFrom<u64> for Code {
79	type Error = Error;
80
81	fn try_from(code: u64) -> Result<Self, Self::Error> {
82		if code == MULTIHASH_IDENTITY_CODE {
83			Ok(Code::Identity)
84		} else if code == u64::from(LiteP2pCode::Sha2_256) {
85			Ok(Code::Sha2_256)
86		} else {
87			Err(Error::UnsupportedCode(code))
88		}
89	}
90}
91
92impl From<Code> for u64 {
93	fn from(code: Code) -> Self {
94		match code {
95			Code::Identity => MULTIHASH_IDENTITY_CODE,
96			Code::Sha2_256 => u64::from(LiteP2pCode::Sha2_256),
97		}
98	}
99}
100
101#[derive(Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd)]
102pub struct Multihash {
103	multihash: LiteP2pMultihash<64>,
104}
105
106impl Multihash {
107	/// Multihash code.
108	pub fn code(&self) -> u64 {
109		self.multihash.code()
110	}
111
112	/// Multihash digest.
113	pub fn digest(&self) -> &[u8] {
114		self.multihash.digest()
115	}
116
117	/// Wraps the digest in a multihash.
118	pub fn wrap(code: u64, input_digest: &[u8]) -> Result<Self, Error> {
119		LiteP2pMultihash::<64>::wrap(code, input_digest)
120			.map(Into::into)
121			.map_err(Into::into)
122	}
123
124	/// Parses a multihash from bytes.
125	///
126	/// You need to make sure the passed in bytes have the length of 64.
127	pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
128		LiteP2pMultihash::<64>::from_bytes(bytes).map(Into::into).map_err(Into::into)
129	}
130
131	/// Returns the bytes of a multihash.
132	pub fn to_bytes(&self) -> Vec<u8> {
133		self.multihash.to_bytes()
134	}
135}
136
137/// Remove extra layer of nestedness by deferring to the wrapped value's [`Debug`].
138impl Debug for Multihash {
139	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140		Debug::fmt(&self.multihash, f)
141	}
142}
143
144impl From<LiteP2pMultihash<64>> for Multihash {
145	fn from(multihash: LiteP2pMultihash<64>) -> Self {
146		Multihash { multihash }
147	}
148}
149
150impl From<Multihash> for LiteP2pMultihash<64> {
151	fn from(multihash: Multihash) -> Self {
152		multihash.multihash
153	}
154}
155
156#[cfg(test)]
157mod tests {
158	use super::*;
159
160	#[test]
161	fn code_from_u64() {
162		assert_eq!(Code::try_from(0x00).unwrap(), Code::Identity);
163		assert_eq!(Code::try_from(0x12).unwrap(), Code::Sha2_256);
164		assert!(matches!(Code::try_from(0x01).unwrap_err(), Error::UnsupportedCode(0x01)));
165	}
166
167	#[test]
168	fn code_into_u64() {
169		assert_eq!(u64::from(Code::Identity), 0x00);
170		assert_eq!(u64::from(Code::Sha2_256), 0x12);
171	}
172}