Move the cryptographic hash to newly created pallas-crypto

This commit is contained in:
Nicolas Di Prima 2022-01-17 23:16:13 +00:00
parent 9c4d41e711
commit 57d6791096
15 changed files with 309 additions and 71 deletions

19
pallas-crypto/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "pallas-crypto"
description = "Cryptographic primitives for Cardano"
version = "0.3.0"
edition = "2021"
repository = "https://github.com/txpipe/pallas"
homepage = "https://github.com/txpipe/pallas"
documentation = "https://docs.rs/pallas-crypto"
license = "Apache-2.0"
readme = "README.md"
authors = [
"Nicolas Di Prima <nicolas@primetype.co.uk>"
]
[dependencies]
minicbor = { version = "0.12" }
hex = "0.4"
cryptoxide = { version = "0.3.6" }

14
pallas-crypto/README.md Normal file
View file

@ -0,0 +1,14 @@
# Pallas Crypto
Crate with all the cryptographic material to support Cardano protocol:
- [x] Blake2b 256
- [x] Blake2b 224
- [ ] Ed25519 asymmetric key pair and ECDSA
- [ ] Ed25519 Extended asymmetric key pair
- [ ] Bip32-Ed25519 key derivation
- [ ] BIP39 mnemonics
- [ ] VRF
- [ ] KES
- [ ] SECP256k1

View file

@ -0,0 +1,96 @@
use std::{fmt, ops::Deref, str::FromStr};
/// data that is a cryptographic [`struct@Hash`] of `BYTES` long.
///
/// Possible values with Cardano are 32 bytes long (block hash or transaction
/// hash). Or 28 bytes long (as used in addresses)
///
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Hash<const BYTES: usize>([u8; BYTES]);
impl<const BYTES: usize> Hash<BYTES> {
#[inline]
pub const fn new(bytes: [u8; BYTES]) -> Self {
Self(bytes)
}
}
impl<const BYTES: usize> From<[u8; BYTES]> for Hash<BYTES> {
#[inline]
fn from(bytes: [u8; BYTES]) -> Self {
Self::new(bytes)
}
}
impl<const BYTES: usize> AsRef<[u8]> for Hash<BYTES> {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const BYTES: usize> Deref for Hash<BYTES> {
type Target = [u8; BYTES];
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const BYTES: usize> PartialEq<[u8]> for Hash<BYTES> {
fn eq(&self, other: &[u8]) -> bool {
self.0.eq(other)
}
}
impl<const BYTES: usize> fmt::Debug for Hash<BYTES> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple(&format!("Hash<{size}>", size = BYTES))
.field(&hex::encode(self))
.finish()
}
}
impl<const BYTES: usize> fmt::Display for Hash<BYTES> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::encode(self))
}
}
impl<const BYTES: usize> FromStr for Hash<BYTES> {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0; BYTES];
hex::decode_to_slice(s, &mut bytes)?;
Ok(Self::new(bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_str() {
let _digest: Hash<28> = "276fd18711931e2c0e21430192dbeac0e458093cd9d1fcd7210f64b3"
.parse()
.unwrap();
let _digest: Hash<32> = "0d8d00cdd4657ac84d82f0a56067634a7adfdf43da41cb534bcaa45060973d21"
.parse()
.unwrap();
}
#[test]
#[should_panic]
fn from_str_fail_1() {
let _digest: Hash<28> = "27".parse().unwrap();
}
#[test]
#[should_panic]
fn from_str_fail_2() {
let _digest: Hash<32> = "0d8d00cdd465".parse().unwrap();
}
}

View file

@ -0,0 +1,125 @@
use crate::hash::Hash;
use cryptoxide::blake2b::Blake2b;
use minicbor::encode::Write;
/// handy method to create a hash of given `SIZE` bit size.
///
/// The hash algorithm is `Blake2b` and the constant parameter is
/// the number of bits to generate. Good values are `256` or `224` for
/// Cardano.
///
/// # Generate a cryptographic hash with Blake2b 256
///
/// The following will generate a 32 bytes digest output
///
/// ```
/// # use pallas_crypto::hash::Hasher;
///
/// let mut hasher = Hasher::<256>::new();
/// hasher.input(b"My transaction");
///
/// let digest = hasher.finalize();
/// # assert_eq!(
/// # "0d8d00cdd4657ac84d82f0a56067634a7adfdf43da41cb534bcaa45060973d21",
/// # hex::encode(digest)
/// # );
/// ```
///
/// # Generate a cryptographic hash with Blake2b 224
///
/// The following will generate a 28 bytes digest output. This is used
/// to generate the hash of public keys for addresses.
///
/// ```
/// # use pallas_crypto::hash::Hasher;
///
/// let digest = Hasher::<224>::hash(b"My Public Key");
/// # assert_eq!(
/// # "c123c9bc0e9e31a20a4aa23518836ec5fb54bdc85735c56b38eb79a5",
/// # hex::encode(digest)
/// # );
/// ```
pub struct Hasher<const BITS: usize>(Blake2b);
impl<const BITS: usize> Hasher<BITS> {
/// update the [`Hasher`] with the given inputs
#[inline]
pub fn input(&mut self, bytes: &[u8]) {
use cryptoxide::digest::Digest as _;
self.0.input(bytes);
}
}
macro_rules! common_hasher {
($size:literal) => {
impl Hasher<$size> {
/// create a new [`Hasher`]
#[inline]
pub fn new() -> Self {
Self(Blake2b::new($size / 8))
}
/// convenient function to directly generate the hash
/// of the given bytes without creating the intermediary
/// types [`Hasher`] and calling [`Hasher::input`].
#[inline]
pub fn hash(bytes: &[u8]) -> Hash<{ $size / 8 }> {
let mut hasher = Self::new();
hasher.input(bytes);
hasher.finalize()
}
/// convenient function to directly generate the hash
/// of the given [minicbor::Encode] data object
#[inline]
pub fn hash_cbor(data: &impl minicbor::Encode) -> Hash<{ $size / 8 }> {
let mut hasher = Self::new();
let () = minicbor::encode(data, &mut hasher).expect("Infallible");
hasher.finalize()
}
/// consume the [`Hasher`] and returns the computed digest
pub fn finalize(mut self) -> Hash<{ $size / 8 }> {
use cryptoxide::digest::Digest as _;
let mut hash = [0; $size / 8];
self.0.result(&mut hash);
Hash::new(hash)
}
}
impl Default for Hasher<$size> {
fn default() -> Self {
Self::new()
}
}
};
}
common_hasher!(224);
common_hasher!(256);
/*
TODO: somehow the `minicbor::Write` does not allow to implement this
version of the trait and to automatically have the impl of the
other one automatically derived by default.
impl<const BITS: usize> Write for Hasher<BITS> {
type Error = std::convert::Infallible;
#[inline]
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.input(buf);
Ok(())
}
}
*/
impl<'a, const BITS: usize> Write for &'a mut Hasher<BITS> {
type Error = std::convert::Infallible;
#[inline]
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.input(buf);
Ok(())
}
}

View file

@ -0,0 +1,34 @@
//! Cryptographic Hash for Cardano
//!
//! we expose two helper objects:
//!
//! * [`Hasher`] to help streaming objects or bytes into a hasher
//! and computing a hash without allocating extra memory due to
//! the required **CBOR** encoding for everything by the cardano
//! protocol
//! * [`struct@Hash`] a conveniently strongly typed byte array
//!
//! The algorithm exposed here is `Blake2b`. We currently support two
//! digest size for the algorithm: 224 bits and 256 bits. They are the
//! only two required to use with the Cardano protocol
//!
//! # Example
//!
//! ```
//! use pallas_crypto::hash::Hasher;
//!
//! let mut hasher = Hasher::<224>::new();
//! hasher.input(b"my key");
//!
//! let digest = hasher.finalize();
//! # assert_eq!(
//! # "276fd18711931e2c0e21430192dbeac0e458093cd9d1fcd7210f64b3",
//! # hex::encode(digest)
//! # );
//! ```
#[allow(clippy::module_inception)]
mod hash;
mod hasher;
pub use self::{hash::Hash, hasher::Hasher};

1
pallas-crypto/src/lib.rs Normal file
View file

@ -0,0 +1 @@
pub mod hash;