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 cea75d6626
commit ad2e0d1bb3
No known key found for this signature in database
GPG key ID: CE351471B788435F
15 changed files with 309 additions and 71 deletions

View file

@ -8,6 +8,7 @@ members = [
"pallas-chainsync",
"pallas-txsubmission",
"pallas-localstate",
"pallas-crypto",
"pallas-alonzo",
"pallas",
]

View file

@ -12,13 +12,9 @@ authors = [
"Santiago Carmuega <santiago@carmuega.me>"
]
[features]
crypto = ["cryptoxide"]
[dependencies]
minicbor = { version = "0.12", features = ["std"] }
minicbor-derive = "0.8.0"
hex = "0.4.3"
log = "0.4.14"
cryptoxide = { version = "0.3.6", optional = true }
pallas-crypto = { version = "0.3", path = "../pallas-crypto" }

View file

@ -1,66 +1,20 @@
use crate::{AuxiliaryData, Header, PlutusData, TransactionBody};
use cryptoxide::blake2b::Blake2b;
use minicbor::Encode;
use pallas_crypto::hash::{Hash, Hasher};
pub type Hash32 = [u8; 32];
pub type Error = Box<dyn std::error::Error>;
struct Hasher<const N: usize> {
inner: Blake2b,
pub fn hash_block_header(data: &Header) -> Hash<32> {
Hasher::<256>::hash_cbor(data)
}
impl Hasher<256> {
#[inline]
fn new() -> Self {
Self {
inner: Blake2b::new(32),
}
}
#[inline]
fn result(mut self) -> Hash32 {
use cryptoxide::digest::Digest as _;
let mut hash = [0; 32];
self.inner.result(&mut hash);
hash
}
pub fn hash_auxiliary_data(data: &AuxiliaryData) -> Hash<32> {
Hasher::<256>::hash_cbor(data)
}
impl<'a, const N: usize> minicbor::encode::write::Write for &'a mut Hasher<N> {
type Error = std::convert::Infallible;
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
use cryptoxide::digest::Digest as _;
self.inner.input(buf);
Ok(())
}
pub fn hash_transaction(data: &TransactionBody) -> Hash<32> {
Hasher::<256>::hash_cbor(data)
}
// TODO: think if we should turn this into a blanket implementation of a new
// trait
fn hash_cbor_encodable(data: &impl Encode) -> Result<Hash32, Error> {
let mut hasher = Hasher::<256>::new();
let () = minicbor::encode(data, &mut hasher)?;
Ok(hasher.result())
}
pub fn hash_block_header(data: &Header) -> Result<Hash32, Error> {
hash_cbor_encodable(data)
}
pub fn hash_auxiliary_data(data: &AuxiliaryData) -> Result<Hash32, Error> {
hash_cbor_encodable(data)
}
pub fn hash_transaction(data: &TransactionBody) -> Result<Hash32, Error> {
hash_cbor_encodable(data)
}
pub fn hash_plutus_data(data: &PlutusData) -> Result<Hash32, Error> {
hash_cbor_encodable(data)
pub fn hash_plutus_data(data: &PlutusData) -> Hash<32> {
Hasher::<256>::hash_cbor(data)
}
#[cfg(test)]
@ -88,10 +42,7 @@ mod tests {
];
for (tx_idx, tx) in block_model.1.transaction_bodies.iter().enumerate() {
let computed_hash = hash_transaction(tx).expect(&format!(
"error hashing tx {} from block {}",
tx_idx, block_idx
));
let computed_hash = hash_transaction(tx);
let known_hash = valid_hashes[tx_idx];
assert_eq!(hex::encode(computed_hash), known_hash)
}

View file

@ -7,5 +7,4 @@ mod utils;
pub use framework::*;
pub use model::*;
#[cfg(feature = "crypto")]
pub mod crypto;

View file

@ -25,4 +25,4 @@ cryptoxide = "0.3.6"
env_logger = "0.9.0"
pallas-handshake = { version = "0.3.0", path = "../pallas-handshake/" }
pallas-txsubmission = { version = "0.3.0", path = "../pallas-txsubmission/" }
pallas-alonzo = { version = "0.3.0", path = "../pallas-alonzo/", features = ["crypto"] }
pallas-alonzo = { version = "0.3.0", path = "../pallas-alonzo/" }

View file

@ -29,8 +29,8 @@ impl DecodePayload for Content {
impl BlockLike for Content {
fn block_point(&self) -> Result<Point, Box<dyn std::error::Error>> {
let hash = crypto::hash_block_header(&self.0.header)?;
Ok(Point(self.0.header.header_body.slot, Vec::from(hash)))
let hash = crypto::hash_block_header(&self.0.header);
Ok(Point(self.0.header.header_body.slot, hash.to_vec()))
}
}

View file

@ -37,8 +37,8 @@ impl DecodePayload for Content {
impl BlockLike for Content {
fn block_point(&self) -> Result<Point, Box<dyn std::error::Error>> {
let hash = crypto::hash_block_header(&self.1)?;
Ok(Point(self.1.header_body.slot, Vec::from(hash)))
let hash = crypto::hash_block_header(&self.1);
Ok(Point(self.1.header_body.slot, hash.to_vec()))
}
}

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;

View file

@ -20,4 +20,5 @@ pallas-chainsync = { version = "0.3.5", path = "../pallas-chainsync/" }
pallas-blockfetch = { version = "0.3.4", path = "../pallas-blockfetch/" }
pallas-localstate = { version = "0.3.5", path = "../pallas-localstate/" }
pallas-txsubmission = { version = "0.3.5", path = "../pallas-txsubmission/" }
pallas-alonzo = { version = "0.3.7", path = "../pallas-alonzo/", features=["crypto"] }
pallas-alonzo = { version = "0.3.7", path = "../pallas-alonzo/" }
pallas-crypto = { version = "0.3.0", path = "../pallas-crypto/" }

View file

@ -12,3 +12,4 @@
pub mod ouroboros;
pub mod ledger;
pub use pallas_crypto as crypto;