Skip to content

Commit 768ecd5

Browse files
apskhemstevenjbkioshn
authored
feat(rust/catalyst-types): Add a new Rust crate catalyst-types (#138)
* feat: initial commit * ci: register crate * feat(rust): Add uniform problem report type (#140) * feat(rust): Add uniform problem report type * fix(docs): spelling * fix(rust): catalyst-types ci build * fix(rust): doc tests * feat: hash and coversion * feat: uuid * refactor: rename * chore: fmtfix * chore: fmtfix again * test: integration * fix: uuid type * feat(rust/catalyst-types): Add duplicate data error report (#141) * feat(catalyst-types): add duplicate data error report Signed-off-by: bkioshn <[email protected]> * fix(catalyst-types): format Signed-off-by: bkioshn <[email protected]> * fix(catalyst-types): add duplicate field description and change name Signed-off-by: bkioshn <[email protected]> * Update rust/catalyst-types/src/problem_report.rs --------- Signed-off-by: bkioshn <[email protected]> Co-authored-by: Steven Johnson <[email protected]> * chore: fmtfix * chore: rename module fixing lint * fix: module name * chore: lintfix rename * feat: kid * chore: fmtfix * feat: improve type errors * chore: fmtfix * fix: cspell * fix: error coverage * fix: lint * fix: test * fix: minor * feat: lintfix * fix: comment * fix: comment * chore: fmtfix * Update rust/catalyst-types/src/conversion.rs Co-authored-by: bkioshn <[email protected]> * docs: update * chore: kiduri * chore: displaydoc * fix: add displaydoc to .dic --------- Signed-off-by: bkioshn <[email protected]> Co-authored-by: Steven Johnson <[email protected]> Co-authored-by: Steven Johnson <[email protected]> Co-authored-by: bkioshn <[email protected]>
1 parent 3c5d97a commit 768ecd5

File tree

18 files changed

+1452
-2
lines changed

18 files changed

+1452
-2
lines changed

.config/dictionaries/project.dic

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ dbsync
5858
dcbor
5959
decompressor
6060
delegators
61+
displaydoc
6162
dleq
6263
dlog
6364
dockerhub
@@ -254,6 +255,7 @@ Traceback
254255
txmonitor
255256
txns
256257
typenum
258+
uncategorized
257259
unfinalized
258260
unixfs
259261
unlinkat

.github/workflows/semantic_pull_request.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818
rust
1919
rust/c509-certificate
2020
rust/cardano-chain-follower
21+
rust/catalyst-types
2122
rust/catalyst-voting
2223
rust/immutable-ledger
2324
rust/vote-tx-v1

rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ members = [
1010
"cbork-cddl-parser",
1111
"cbork-utils",
1212
"catalyst-voting",
13-
"catalyst-voting",
13+
"catalyst-types",
1414
"immutable-ledger",
1515
"vote-tx-v1",
1616
"vote-tx-v2",

rust/Earthfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ COPY_SRC:
99
Cargo.toml clippy.toml deny.toml rustfmt.toml \
1010
.cargo .config \
1111
c509-certificate \
12+
catalyst-types \
1213
cardano-blockchain-types \
1314
cardano-chain-follower \
1415
catalyst-voting vote-tx-v1 vote-tx-v2 \
@@ -55,7 +56,7 @@ build:
5556
DO rust-ci+EXECUTE \
5657
--cmd="/scripts/std_build.py" \
5758
--args1="--libs=c509-certificate --libs=cardano-blockchain-types --libs=cardano-chain-follower --libs=hermes-ipfs" \
58-
--args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser --libs=cbork-utils" \
59+
--args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser --libs=cbork-utils --libs=catalyst-types" \
5960
--args3="--libs=catalyst-voting --libs=immutable-ledger --libs=vote-tx-v1 --libs=vote-tx-v2" \
6061
--args4="--bins=cbork/cbork --libs=rbac-registration --libs=signed_doc" \
6162
--args5="--cov_report=$HOME/build/coverage-report.info" \

rust/catalyst-types/Cargo.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[package]
2+
name = "catalyst-types"
3+
version = "0.0.1"
4+
edition.workspace = true
5+
license.workspace = true
6+
authors.workspace = true
7+
homepage.workspace = true
8+
repository.workspace = true
9+
10+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11+
12+
[lints]
13+
workspace = true
14+
15+
[lib]
16+
name = "catalyst_types"
17+
18+
[dependencies]
19+
blake2b_simd = "1.0.2"
20+
coset = "0.3.8"
21+
displaydoc = "0.2.5"
22+
ed25519-dalek = "2.1.1"
23+
fluent-uri = "0.3.2"
24+
hex = "0.4.3"
25+
minicbor = { version = "0.25.1", features = ["std"] }
26+
num-traits = "0.2.19"
27+
orx-concurrent-vec = "3.1.0"
28+
pallas-crypto = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
29+
serde = { version = "1.0.217", features = ["derive"] }
30+
thiserror = "2.0.9"
31+
base64-url = "3.0.0"
32+
uuid = { version = "1.11.0", features = ["v4", "v7", "serde"] }
33+
34+
[dev-dependencies]
35+
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
36+
rand = "0.8.5"

rust/catalyst-types/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Catalyst Types
2+
3+
This library is designed to streamline the organization and sharing of types across multiple crates.
4+
It provides a centralized location for reusable, enhanced types that are not specific to a particular domain, such as Cardano.
5+
6+
## Purpose
7+
8+
* To enhance types that can be utilized across different libraries or projects.
9+
* To provide utility functions related to types and conversion between types.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Conversion functions
2+
3+
use displaydoc::Display;
4+
use thiserror::Error;
5+
6+
/// Errors that can occur when converting bytes to an Ed25519 verifying key.
7+
#[derive(Display, Debug, Error)]
8+
pub enum VKeyFromBytesError {
9+
/// Invalid byte length: expected {expected} bytes, got {actual}
10+
InvalidLength {
11+
/// The expected number of bytes (must be 32).
12+
expected: usize,
13+
/// The actual number of bytes in the provided input.
14+
actual: usize,
15+
},
16+
/// Failed to parse Ed25519 public key: {source}
17+
ParseError {
18+
/// The underlying error from `ed25519_dalek`.
19+
#[from]
20+
source: ed25519_dalek::SignatureError,
21+
},
22+
}
23+
24+
/// Convert an `<T>` to `<R>` (saturate if out of range).
25+
/// Note can convert any int to float, or f32 to f64 as well.
26+
/// can not convert from float to int, or f64 to f32.
27+
pub fn from_saturating<
28+
R: Copy + num_traits::identities::Zero + num_traits::Bounded,
29+
T: Copy
30+
+ TryInto<R>
31+
+ std::ops::Sub<Output = T>
32+
+ std::cmp::PartialOrd<T>
33+
+ num_traits::identities::Zero,
34+
>(
35+
value: T,
36+
) -> R {
37+
match value.try_into() {
38+
Ok(value) => value,
39+
Err(_) => {
40+
// If we couldn't convert, its out of range for the destination type.
41+
if value > T::zero() {
42+
// If the number is positive, its out of range in the positive direction.
43+
R::max_value()
44+
} else {
45+
// Otherwise its out of range in the negative direction.
46+
R::min_value()
47+
}
48+
},
49+
}
50+
}
51+
52+
/// Try and convert a byte array into an Ed25519 verifying key.
53+
///
54+
/// # Errors
55+
///
56+
/// Fails if the bytes are not a valid ED25519 Public Key
57+
pub fn vkey_from_bytes(bytes: &[u8]) -> Result<ed25519_dalek::VerifyingKey, VKeyFromBytesError> {
58+
if bytes.len() != ed25519_dalek::PUBLIC_KEY_LENGTH {
59+
return Err(VKeyFromBytesError::InvalidLength {
60+
expected: ed25519_dalek::PUBLIC_KEY_LENGTH,
61+
actual: bytes.len(),
62+
});
63+
}
64+
65+
let mut ed25519 = [0u8; ed25519_dalek::PUBLIC_KEY_LENGTH];
66+
ed25519.copy_from_slice(bytes); // Can't panic because we already validated its size.
67+
68+
ed25519_dalek::VerifyingKey::from_bytes(&ed25519)
69+
.map_err(|source| VKeyFromBytesError::ParseError { source })
70+
}

rust/catalyst-types/src/hashes.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
//! Cardano hashing functions
2+
3+
use std::{fmt, str::FromStr};
4+
5+
use blake2b_simd::Params;
6+
use displaydoc::Display;
7+
use pallas_crypto::hash::Hash;
8+
use thiserror::Error;
9+
10+
/// Number of bytes in a blake2b 224 hash.
11+
pub const BLAKE_2B224_SIZE: usize = 224 / 8;
12+
13+
/// `Blake2B` 224bit Hash.
14+
pub type Blake2b224Hash = Blake2bHash<BLAKE_2B224_SIZE>;
15+
16+
/// Number of bytes in a blake2b 256 hash.
17+
pub const BLAKE_2B256_SIZE: usize = 256 / 8;
18+
19+
/// `Blake2B` 256bit Hash
20+
pub type Blake2b256Hash = Blake2bHash<BLAKE_2B256_SIZE>;
21+
22+
/// Number of bytes in a blake2b 128 hash.
23+
pub const BLAKE_2B128_SIZE: usize = 128 / 8;
24+
25+
/// `Blake2B` 128bit Hash
26+
pub type Blake2b128Hash = Blake2bHash<BLAKE_2B128_SIZE>;
27+
28+
/// Errors that can occur when converting to a `Blake2bHash`.
29+
#[derive(Display, Debug, Error)]
30+
pub enum Blake2bHashError {
31+
/// Invalid length: expected {expected} bytes, got {actual}
32+
InvalidLength {
33+
/// The expected number of bytes (must be 32 or 28).
34+
expected: usize,
35+
/// The actual number of bytes in the provided input.
36+
actual: usize,
37+
},
38+
/// Invalid hex string: {source}
39+
InvalidHex {
40+
/// The underlying error from `hex`.
41+
#[from]
42+
source: hex::FromHexError,
43+
},
44+
}
45+
46+
/// data that is a blake2b [`struct@Hash`] of `BYTES` long.
47+
///
48+
/// Possible values with Cardano are 32 bytes long (block hash or transaction
49+
/// hash). Or 28 bytes long (as used in addresses)
50+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
51+
pub struct Blake2bHash<const BYTES: usize>(Hash<BYTES>);
52+
53+
impl<const BYTES: usize> Blake2bHash<BYTES> {
54+
/// Create a new `Blake2bHash` from a slice of bytes by hashing them.
55+
#[must_use]
56+
pub fn new(input_bytes: &[u8]) -> Self {
57+
let mut bytes: [u8; BYTES] = [0u8; BYTES];
58+
59+
// Generate a unique hash of the data.
60+
let mut hasher = Params::new().hash_length(BYTES).to_state();
61+
62+
hasher.update(input_bytes);
63+
let hash = hasher.finalize();
64+
65+
// Create a new array containing the first BYTES elements from the original array
66+
bytes.copy_from_slice(hash.as_bytes());
67+
68+
bytes.into()
69+
}
70+
}
71+
72+
impl<const BYTES: usize> From<[u8; BYTES]> for Blake2bHash<BYTES> {
73+
#[inline]
74+
fn from(bytes: [u8; BYTES]) -> Self {
75+
let hash: Hash<BYTES> = bytes.into();
76+
hash.into()
77+
}
78+
}
79+
80+
impl<const BYTES: usize> From<Hash<BYTES>> for Blake2bHash<BYTES> {
81+
#[inline]
82+
fn from(bytes: Hash<BYTES>) -> Self {
83+
Self(bytes)
84+
}
85+
}
86+
87+
impl<const BYTES: usize> From<Blake2bHash<BYTES>> for Vec<u8> {
88+
fn from(val: Blake2bHash<BYTES>) -> Self {
89+
val.0.to_vec()
90+
}
91+
}
92+
93+
/// Convert hash in a form of byte array into the `Blake2bHash` type.
94+
impl<const BYTES: usize> TryFrom<&[u8]> for Blake2bHash<BYTES> {
95+
type Error = Blake2bHashError;
96+
97+
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
98+
if value.len() < BYTES {
99+
return Err(Blake2bHashError::InvalidLength {
100+
expected: BYTES,
101+
actual: value.len(),
102+
});
103+
}
104+
105+
let mut hash = [0; BYTES];
106+
hash.copy_from_slice(value);
107+
let hash: Hash<BYTES> = hash.into();
108+
Ok(hash.into())
109+
}
110+
}
111+
112+
impl<const BYTES: usize> TryFrom<&Vec<u8>> for Blake2bHash<BYTES> {
113+
type Error = Blake2bHashError;
114+
115+
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
116+
value.as_slice().try_into()
117+
}
118+
}
119+
120+
impl<const BYTES: usize> TryFrom<Vec<u8>> for Blake2bHash<BYTES> {
121+
type Error = Blake2bHashError;
122+
123+
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
124+
value.as_slice().try_into()
125+
}
126+
}
127+
128+
impl<const BYTES: usize> fmt::Debug for Blake2bHash<BYTES> {
129+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130+
f.write_str(&format!("{:?}", self.0))
131+
}
132+
}
133+
134+
impl<const BYTES: usize> fmt::Display for Blake2bHash<BYTES> {
135+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136+
f.write_str(&format!("{}", self.0))
137+
}
138+
}
139+
140+
impl<const BYTES: usize> FromStr for Blake2bHash<BYTES> {
141+
type Err = Blake2bHashError;
142+
143+
fn from_str(s: &str) -> Result<Self, Self::Err> {
144+
let hash: Hash<BYTES> = s.parse().map_err(Blake2bHashError::from)?;
145+
Ok(hash.into())
146+
}
147+
}
148+
149+
impl<C, const BYTES: usize> minicbor::Encode<C> for Blake2bHash<BYTES> {
150+
fn encode<W: minicbor::encode::Write>(
151+
&self, e: &mut minicbor::Encoder<W>, _ctx: &mut C,
152+
) -> Result<(), minicbor::encode::Error<W::Error>> {
153+
e.bytes(self.0.as_ref())?.ok()
154+
}
155+
}
156+
157+
impl<'a, C, const BYTES: usize> minicbor::Decode<'a, C> for Blake2bHash<BYTES> {
158+
fn decode(
159+
d: &mut minicbor::Decoder<'a>, _ctx: &mut C,
160+
) -> Result<Self, minicbor::decode::Error> {
161+
let bytes = d.bytes()?;
162+
bytes.try_into().map_err(|_| {
163+
minicbor::decode::Error::message("Invalid hash size for Blake2bHash cbor decode")
164+
})
165+
}
166+
}
167+
168+
#[cfg(test)]
169+
mod tests {
170+
use super::*;
171+
172+
#[test]
173+
fn test_blake2b_hash_init() {
174+
let data = b"Cardano";
175+
let hash_224 = Blake2b224Hash::new(data);
176+
let hash_256 = Blake2b256Hash::new(data);
177+
let hash_128 = Blake2b128Hash::new(data);
178+
179+
assert_eq!(hash_224.0.as_ref().len(), BLAKE_2B224_SIZE);
180+
assert_eq!(hash_256.0.as_ref().len(), BLAKE_2B256_SIZE);
181+
assert_eq!(hash_128.0.as_ref().len(), BLAKE_2B128_SIZE);
182+
}
183+
184+
#[test]
185+
fn test_blake2b_hash_conversion() {
186+
let data = b"Cardano";
187+
let hash = Blake2b224Hash::new(data);
188+
189+
let as_vec: Vec<u8> = hash.into();
190+
let from_vec = Blake2b224Hash::try_from(&as_vec).unwrap();
191+
assert_eq!(hash, from_vec);
192+
193+
let from_slice = Blake2b224Hash::try_from(as_vec.as_slice()).unwrap();
194+
assert_eq!(hash, from_slice);
195+
}
196+
197+
#[test]
198+
fn test_blake2b_hash_invalid_length() {
199+
let invalid_data = vec![0u8; 10];
200+
let result = Blake2b224Hash::try_from(&invalid_data);
201+
assert!(result.is_err());
202+
203+
if let Err(Blake2bHashError::InvalidLength { expected, actual }) = result {
204+
assert_eq!(expected, BLAKE_2B224_SIZE);
205+
assert_eq!(actual, invalid_data.len());
206+
} else {
207+
panic!("Expected InvalidLength error");
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)