Move the cryptographic hash to newly created pallas-crypto
This commit is contained in:
parent
cea75d6626
commit
ad2e0d1bb3
15 changed files with 309 additions and 71 deletions
|
|
@ -8,6 +8,7 @@ members = [
|
|||
"pallas-chainsync",
|
||||
"pallas-txsubmission",
|
||||
"pallas-localstate",
|
||||
"pallas-crypto",
|
||||
"pallas-alonzo",
|
||||
"pallas",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,5 +7,4 @@ mod utils;
|
|||
pub use framework::*;
|
||||
pub use model::*;
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
pub mod crypto;
|
||||
|
|
|
|||
|
|
@ -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/" }
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
19
pallas-crypto/Cargo.toml
Normal 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
14
pallas-crypto/README.md
Normal 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
|
||||
|
||||
96
pallas-crypto/src/hash/hash.rs
Normal file
96
pallas-crypto/src/hash/hash.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
125
pallas-crypto/src/hash/hasher.rs
Normal file
125
pallas-crypto/src/hash/hasher.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
34
pallas-crypto/src/hash/mod.rs
Normal file
34
pallas-crypto/src/hash/mod.rs
Normal 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
1
pallas-crypto/src/lib.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod hash;
|
||||
|
|
@ -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/" }
|
||||
|
|
|
|||
|
|
@ -12,3 +12,4 @@
|
|||
pub mod ouroboros;
|
||||
|
||||
pub mod ledger;
|
||||
pub use pallas_crypto as crypto;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue