Skip to content

Commit 8b0607e

Browse files
committed
feat: hash and coversion
1 parent 05bce83 commit 8b0607e

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed

rust/catalyst-types/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,12 @@ workspace = true
1616
name = "catalyst_types"
1717

1818
[dependencies]
19+
anyhow = "1.0.95"
20+
blake2b_simd = "1.0.2"
21+
ed25519-dalek = "2.1.1"
22+
hex = "0.4.3"
23+
minicbor = { version = "0.25.1", features = ["std"] }
24+
num-traits = "0.2.19"
1925
orx-concurrent-vec = "3.1.0"
26+
pallas-crypto = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
2027
serde = { version = "1.0.217", features = ["derive"] }
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Conversion functions
2+
3+
use anyhow::bail;
4+
5+
/// Convert an `<T>` to `<R>` (saturate if out of range).
6+
/// Note can convert any int to float, or f32 to f64 as well.
7+
/// can not convert from float to int, or f64 to f32.
8+
pub fn from_saturating<
9+
R: Copy + num_traits::identities::Zero + num_traits::Bounded,
10+
T: Copy
11+
+ TryInto<R>
12+
+ std::ops::Sub<Output = T>
13+
+ std::cmp::PartialOrd<T>
14+
+ num_traits::identities::Zero,
15+
>(
16+
value: T,
17+
) -> R {
18+
match value.try_into() {
19+
Ok(value) => value,
20+
Err(_) => {
21+
// If we couldn't convert, its out of range for the destination type.
22+
if value > T::zero() {
23+
// If the number is positive, its out of range in the positive direction.
24+
R::max_value()
25+
} else {
26+
// Otherwise its out of range in the negative direction.
27+
R::min_value()
28+
}
29+
},
30+
}
31+
}
32+
33+
/// Try and convert a byte array into an ed25519 verifying key.
34+
///
35+
/// # Errors
36+
///
37+
/// Fails if the bytes are not a valid ED25519 Public Key
38+
pub fn vkey_from_bytes(bytes: &[u8]) -> anyhow::Result<ed25519_dalek::VerifyingKey> {
39+
if bytes.len() != ed25519_dalek::PUBLIC_KEY_LENGTH {
40+
bail!(
41+
"Failed to convert bytes to ED25519 public key. Expected {} bytes, got {}",
42+
ed25519_dalek::PUBLIC_KEY_LENGTH,
43+
bytes.len()
44+
);
45+
}
46+
47+
let mut ed25519 = [0u8; ed25519_dalek::PUBLIC_KEY_LENGTH];
48+
ed25519.copy_from_slice(bytes); // Can't panic because we already validated its size.
49+
50+
let pubkey = match ed25519_dalek::VerifyingKey::from_bytes(&ed25519) {
51+
Ok(key) => key,
52+
Err(err) => bail!("Failed to convert Ed25519 public key in SimplePublicKeyType {err}"),
53+
};
54+
Ok(pubkey)
55+
}

rust/catalyst-types/src/hashes.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//! Cardano hashing functions
2+
3+
use std::{fmt, str::FromStr};
4+
5+
use anyhow::bail;
6+
use blake2b_simd::Params;
7+
use pallas_crypto::hash::Hash;
8+
9+
/// Number of bytes in a blake2b 224 hash.
10+
pub const BLAKE_2B224_SIZE: usize = 224 / 8;
11+
12+
/// `Blake2B` 224bit Hash.
13+
pub type Blake2b224Hash = Blake2bHash<BLAKE_2B224_SIZE>;
14+
15+
/// Number of bytes in a blake2b 256 hash.
16+
pub const BLAKE_2B256_SIZE: usize = 256 / 8;
17+
18+
/// `Blake2B` 256bit Hash
19+
pub type Blake2b256Hash = Blake2bHash<BLAKE_2B256_SIZE>;
20+
21+
/// Number of bytes in a blake2b 128 hash.
22+
pub const BLAKE_2B128_SIZE: usize = 128 / 8;
23+
24+
/// `Blake2B` 128bit Hash
25+
pub type Blake2b128Hash = Blake2bHash<BLAKE_2B128_SIZE>;
26+
27+
/// data that is a blake2b [`struct@Hash`] of `BYTES` long.
28+
///
29+
/// Possible values with Cardano are 32 bytes long (block hash or transaction
30+
/// hash). Or 28 bytes long (as used in addresses)
31+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32+
pub struct Blake2bHash<const BYTES: usize>(Hash<BYTES>);
33+
34+
impl<const BYTES: usize> Blake2bHash<BYTES> {
35+
/// Create a new `Blake2bHash` from a slice of bytes by hashing them.
36+
#[must_use]
37+
pub fn new(input_bytes: &[u8]) -> Self {
38+
let mut bytes: [u8; BYTES] = [0u8; BYTES];
39+
40+
// Generate a unique hash of the data.
41+
let mut hasher = Params::new().hash_length(BYTES).to_state();
42+
43+
hasher.update(input_bytes);
44+
let hash = hasher.finalize();
45+
46+
// Create a new array containing the first BYTES elements from the original array
47+
bytes.copy_from_slice(hash.as_bytes());
48+
49+
bytes.into()
50+
}
51+
}
52+
53+
impl<const BYTES: usize> From<[u8; BYTES]> for Blake2bHash<BYTES> {
54+
#[inline]
55+
fn from(bytes: [u8; BYTES]) -> Self {
56+
let hash: Hash<BYTES> = bytes.into();
57+
hash.into()
58+
}
59+
}
60+
61+
impl<const BYTES: usize> From<Hash<BYTES>> for Blake2bHash<BYTES> {
62+
#[inline]
63+
fn from(bytes: Hash<BYTES>) -> Self {
64+
Self(bytes)
65+
}
66+
}
67+
68+
impl<const BYTES: usize> From<Blake2bHash<BYTES>> for Vec<u8> {
69+
fn from(val: Blake2bHash<BYTES>) -> Self {
70+
val.0.to_vec()
71+
}
72+
}
73+
74+
/// Convert hash in a form of byte array into the `Blake2bHash` type.
75+
impl<const BYTES: usize> TryFrom<&[u8]> for Blake2bHash<BYTES> {
76+
type Error = anyhow::Error;
77+
78+
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
79+
if value.len() < BYTES {
80+
bail!("Invalid input length");
81+
}
82+
83+
let mut hash = [0; BYTES];
84+
hash.copy_from_slice(value);
85+
let hash: Hash<BYTES> = hash.into();
86+
Ok(hash.into())
87+
}
88+
}
89+
90+
impl<const BYTES: usize> TryFrom<&Vec<u8>> for Blake2bHash<BYTES> {
91+
type Error = anyhow::Error;
92+
93+
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
94+
value.as_slice().try_into()
95+
}
96+
}
97+
98+
impl<const BYTES: usize> TryFrom<Vec<u8>> for Blake2bHash<BYTES> {
99+
type Error = anyhow::Error;
100+
101+
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
102+
value.as_slice().try_into()
103+
}
104+
}
105+
106+
impl<const BYTES: usize> fmt::Debug for Blake2bHash<BYTES> {
107+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108+
f.write_str(&format!("{:?}", self.0))
109+
}
110+
}
111+
112+
impl<const BYTES: usize> fmt::Display for Blake2bHash<BYTES> {
113+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114+
f.write_str(&format!("{}", self.0))
115+
}
116+
}
117+
118+
impl<const BYTES: usize> FromStr for Blake2bHash<BYTES> {
119+
type Err = hex::FromHexError;
120+
121+
fn from_str(s: &str) -> Result<Self, Self::Err> {
122+
let hash: Hash<BYTES> = s.parse()?;
123+
Ok(hash.into())
124+
}
125+
}
126+
127+
impl<C, const BYTES: usize> minicbor::Encode<C> for Blake2bHash<BYTES> {
128+
fn encode<W: minicbor::encode::Write>(
129+
&self, e: &mut minicbor::Encoder<W>, _ctx: &mut C,
130+
) -> Result<(), minicbor::encode::Error<W::Error>> {
131+
e.bytes(self.0.as_ref())?.ok()
132+
}
133+
}
134+
135+
impl<'a, C, const BYTES: usize> minicbor::Decode<'a, C> for Blake2bHash<BYTES> {
136+
fn decode(
137+
d: &mut minicbor::Decoder<'a>, _ctx: &mut C,
138+
) -> Result<Self, minicbor::decode::Error> {
139+
let bytes = d.bytes()?;
140+
bytes.try_into().map_err(|_| {
141+
minicbor::decode::Error::message("Invalid hash size for Blake2bHash cbor decode")
142+
})
143+
}
144+
}

rust/catalyst-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
//! Catalyst Generic Types
22
3+
pub mod conversion;
4+
pub mod hashes;
35
pub mod problem_report;

0 commit comments

Comments
 (0)