Skip to content

Commit

Permalink
Move the cryptographic hash to newly created pallas-crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasDP committed Jan 17, 2022
1 parent cea75d6 commit ad2e0d1
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 71 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -8,6 +8,7 @@ members = [
"pallas-chainsync",
"pallas-txsubmission",
"pallas-localstate",
"pallas-crypto",
"pallas-alonzo",
"pallas",
]
Expand Down
6 changes: 1 addition & 5 deletions pallas-alonzo/Cargo.toml
Expand Up @@ -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" }
69 changes: 10 additions & 59 deletions pallas-alonzo/src/crypto.rs
@@ -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,
}

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
}
}

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(())
}
}

// 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_block_header(data: &Header) -> Hash<32> {
Hasher::<256>::hash_cbor(data)
}

pub fn hash_auxiliary_data(data: &AuxiliaryData) -> Result<Hash32, Error> {
hash_cbor_encodable(data)
pub fn hash_auxiliary_data(data: &AuxiliaryData) -> Hash<32> {
Hasher::<256>::hash_cbor(data)
}

pub fn hash_transaction(data: &TransactionBody) -> Result<Hash32, Error> {
hash_cbor_encodable(data)
pub fn hash_transaction(data: &TransactionBody) -> Hash<32> {
Hasher::<256>::hash_cbor(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)]
Expand Down Expand Up @@ -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)
}
Expand Down
1 change: 0 additions & 1 deletion pallas-alonzo/src/lib.rs
Expand Up @@ -7,5 +7,4 @@ mod utils;
pub use framework::*;
pub use model::*;

#[cfg(feature = "crypto")]
pub mod crypto;
2 changes: 1 addition & 1 deletion pallas-chainsync/Cargo.toml
Expand Up @@ -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/" }
4 changes: 2 additions & 2 deletions pallas-chainsync/examples/blocks.rs
Expand Up @@ -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()))
}
}

Expand Down
4 changes: 2 additions & 2 deletions pallas-chainsync/examples/headers.rs
Expand Up @@ -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()))
}
}

Expand Down
19 changes: 19 additions & 0 deletions pallas-crypto/Cargo.toml
@@ -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 changes: 14 additions & 0 deletions pallas-crypto/README.md
@@ -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

96 changes: 96 additions & 0 deletions pallas-crypto/src/hash/hash.rs
@@ -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();
}
}

0 comments on commit ad2e0d1

Please sign in to comment.